-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add HTTPRedirector and OpenGraph packages with tests * Refactor code * Add dependencies and update styles and types * Update code formatting and fix minor bugs * Update Dockerfile and UI code * Add UI assets and fix file path in UIHandler * Add UI assets and fix file path in UIHandler
- Loading branch information
Showing
52 changed files
with
1,915 additions
and
1,477 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package httpredirector | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
|
||
"bridge/opengraph" | ||
) | ||
|
||
const DefaultRedirectURL = "https://alileza.me/" | ||
|
||
// HTTPRedirector is a struct that holds the routes and their destinations | ||
type HTTPRedirector struct { | ||
BaseURL string | ||
EnableOpengraph bool | ||
Storage Storage | ||
} | ||
|
||
type Route struct { | ||
Preview string `json:"preview"` | ||
Key string `json:"key"` | ||
URL string `json:"url"` | ||
} | ||
|
||
type Storage interface { | ||
Store(key any, value any) | ||
Load(key any) (value any, ok bool) | ||
Delete(key any) | ||
Range(f func(key any, value any) bool) | ||
} | ||
|
||
// LoadRoutes loads a map of routes into the redirector | ||
func (rdr *HTTPRedirector) LoadRoutes(routes map[string]string) { | ||
for k, v := range routes { | ||
rdr.Storage.Store(k, v) | ||
} | ||
} | ||
|
||
func (rdr *HTTPRedirector) ListRoutes() []Route { | ||
var routes []Route | ||
|
||
rdr.Storage.Range(func(k, v interface{}) bool { | ||
i, _ := opengraph.GenerateBarcode(rdr.BaseURL + k.(string)) | ||
routes = append(routes, Route{ | ||
Preview: "data:image/png;base64," + opengraph.EncodeImageToBase64(i), | ||
Key: k.(string), | ||
URL: v.(string), | ||
}) | ||
return true | ||
}) | ||
return routes | ||
} | ||
|
||
// AddRoute adds a route to the redirector it can be path or full with hostname without scheme | ||
func (rdr *HTTPRedirector) SetRoute(key string, destURL string) error { | ||
_, err := url.ParseRequestURI(destURL) | ||
if err != nil { | ||
return fmt.Errorf("invalid destination URL: %w", err) | ||
} | ||
|
||
if !strings.Contains(key, "/") && !strings.Contains(key, ".") { | ||
key = "/" + key | ||
} | ||
|
||
rdr.Storage.Store(key, destURL) | ||
return nil | ||
} | ||
|
||
// RemoveRoute removes a route from the redirector | ||
func (rdr *HTTPRedirector) RemoveRoute(key string) error { | ||
if _, ok := rdr.Storage.Load(key); !ok { | ||
return fmt.Errorf("route not found: %s", key) | ||
} | ||
|
||
rdr.Storage.Delete(key) | ||
return nil | ||
} | ||
|
||
// GetRedirectURL returns the destination for a given route | ||
func (rdr *HTTPRedirector) GetRedirectURL(key *url.URL) string { | ||
keyWithHost := key.Host + key.Path | ||
dest, ok := rdr.Storage.Load(keyWithHost) | ||
if ok { | ||
return dest.(string) | ||
} | ||
|
||
dest, ok = rdr.Storage.Load(key.Path) | ||
if ok { | ||
return dest.(string) | ||
} | ||
|
||
return DefaultRedirectURL | ||
} | ||
|
||
func (rdr *HTTPRedirector) Handler(w http.ResponseWriter, r *http.Request) { | ||
destinationURL := rdr.GetRedirectURL(r.URL) | ||
|
||
if !rdr.EnableOpengraph { | ||
http.Redirect(w, r, destinationURL, http.StatusFound) | ||
return | ||
} | ||
|
||
headElements, err := opengraph.FetchMetaTags(r.Context(), destinationURL) | ||
if err != nil { | ||
http.Redirect(w, r, destinationURL, http.StatusFound) | ||
return | ||
} | ||
|
||
w.Header().Set("Content-Type", "text/html") | ||
w.Write([]byte(fmt.Sprintf(responseBody, headElements, destinationURL, destinationURL, destinationURL))) | ||
} | ||
|
||
const responseBody = ` | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
%s | ||
<title>Bridge - github.com/alileza/bridge</title> | ||
</head> | ||
<body> | ||
<noscript> | ||
<a href="%s">Click here to continue to: %s</a> | ||
</noscript> | ||
<script> | ||
window.location.replace("%s"); | ||
</script> | ||
</body> | ||
</html> | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package httpredirector | ||
|
||
import ( | ||
"net/url" | ||
"sync" | ||
"testing" | ||
) | ||
|
||
// Test loading multiple routes at once | ||
func TestLoadRoutes(t *testing.T) { | ||
rdr := &HTTPRedirector{routes: sync.Map{}} | ||
routes := map[string]string{ | ||
"/key1": "http://destination1.com", | ||
"/key2": "http://destination2.com", | ||
} | ||
rdr.LoadRoutes(routes) | ||
|
||
for k, v := range routes { | ||
dest, ok := rdr.routes.Load(k) | ||
if !ok { | ||
t.Fatalf("expected to find route for key %s, but did not", k) | ||
} | ||
if dest != v { | ||
t.Fatalf("expected destination %s, got %s", v, dest) | ||
} | ||
} | ||
} | ||
|
||
// Test the GetRedirectURL function | ||
func TestGetRedirectURL(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
setupActions func(rdr *HTTPRedirector) | ||
key string | ||
expectedDest string | ||
expectedFound bool | ||
}{ | ||
{ | ||
name: "find route with path only", | ||
setupActions: func(rdr *HTTPRedirector) { | ||
rdr.AddRoute("/path-only", "http://destination-path-only.com") | ||
}, | ||
key: "/path-only", | ||
expectedDest: "http://destination-path-only.com", | ||
expectedFound: true, | ||
}, | ||
{ | ||
name: "find route with domain and path", | ||
setupActions: func(rdr *HTTPRedirector) { | ||
rdr.AddRoute("example.com/path", "http://destination-domain-path.com") | ||
}, | ||
key: "http://example.com/path", | ||
expectedDest: "http://destination-domain-path.com", | ||
expectedFound: true, | ||
}, | ||
{ | ||
name: "fallback to default for non-existing route", | ||
setupActions: func(rdr *HTTPRedirector) {}, | ||
key: "http://nonexisting.com/path", | ||
expectedDest: "https://alileza.me/", | ||
expectedFound: false, | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
t.Run(tc.name, func(t *testing.T) { | ||
rdr := &HTTPRedirector{routes: sync.Map{}} | ||
tc.setupActions(rdr) | ||
|
||
parsedKey, _ := url.Parse(tc.key) | ||
dest := rdr.GetRedirectURL(parsedKey) | ||
if dest != tc.expectedDest { | ||
t.Fatalf("expected destination %s, got %s for key %s", tc.expectedDest, dest, tc.key) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.