mirror of
https://github.com/1f349/violet.git
synced 2024-11-21 10:51:40 +00:00
Add base routing system and add redirects
This commit is contained in:
parent
68bbd67c01
commit
77d570ac1e
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
3
go.mod
3
go.mod
@ -3,9 +3,8 @@ module github.com/MrMelon54/violet
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/MrMelon54/trie v0.0.2
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/yousuf64/shift v0.4.0
|
||||
golang.org/x/net v0.9.0
|
||||
)
|
||||
|
||||
|
6
go.sum
6
go.sum
@ -1,8 +1,8 @@
|
||||
github.com/MrMelon54/trie v0.0.2 h1:ZXWcX5ij62O9K4I/anuHmVg8L3tF0UGdlPceAASwKEY=
|
||||
github.com/MrMelon54/trie v0.0.2/go.mod h1:sGCGOcqb+DxSxvHgSOpbpkmA7mFZR47YDExy9OCbVZI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@ -12,8 +12,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/yousuf64/shift v0.4.0 h1:jiuYPa9KGyTShM08GEcSRSaZeO2jPEIpdqaplsRtk8g=
|
||||
github.com/yousuf64/shift v0.4.0/go.mod h1:D9b+mj37s3goL48EGX2oCrYHW4rg4c3Il2w6fukjxas=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
|
102
router/router.go
Normal file
102
router/router.go
Normal file
@ -0,0 +1,102 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/MrMelon54/trie"
|
||||
"github.com/MrMelon54/violet/target"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
route map[string]*trie.Trie[target.Route]
|
||||
redirect map[string]*trie.Trie[target.Redirect]
|
||||
notFound http.Handler
|
||||
proxy *httputil.ReverseProxy
|
||||
}
|
||||
|
||||
func New() *Router {
|
||||
return &Router{
|
||||
route: make(map[string]*trie.Trie[target.Route]),
|
||||
redirect: make(map[string]*trie.Trie[target.Redirect]),
|
||||
notFound: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
_, _ = fmt.Fprintf(rw, "%d %s\n", http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) hostRoute(host string) *trie.Trie[target.Route] {
|
||||
h := r.route[host]
|
||||
if h == nil {
|
||||
h = &trie.Trie[target.Route]{}
|
||||
r.route[host] = h
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (r *Router) hostRedirect(host string) *trie.Trie[target.Redirect] {
|
||||
h := r.redirect[host]
|
||||
if h == nil {
|
||||
h = &trie.Trie[target.Redirect]{}
|
||||
r.redirect[host] = h
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (r *Router) AddService(host string, t target.Route) {
|
||||
r.AddRoute(host, "", t)
|
||||
}
|
||||
|
||||
func (r *Router) AddRoute(host string, path string, t target.Route) {
|
||||
r.hostRoute(host).PutString(path, t)
|
||||
}
|
||||
|
||||
func (r *Router) AddRedirect(host, path string, t target.Redirect) {
|
||||
r.hostRedirect(host).PutString(path, t)
|
||||
}
|
||||
|
||||
func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
host := req.Host
|
||||
if r.serveRedirectHTTP(rw, req, host) {
|
||||
return
|
||||
}
|
||||
if r.serveRouteHTTP(rw, req, host) {
|
||||
return
|
||||
}
|
||||
|
||||
parentHostDot := strings.IndexByte(host, '.')
|
||||
if parentHostDot == -1 {
|
||||
r.notFound.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
wildcardHost := "*." + host[parentHostDot:]
|
||||
|
||||
if r.serveRedirectHTTP(rw, req, wildcardHost) {
|
||||
return
|
||||
}
|
||||
if r.serveRouteHTTP(rw, req, wildcardHost) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) serveRouteHTTP(rw http.ResponseWriter, req *http.Request, host string) bool {
|
||||
//fmt.Printf("Router::serveRouteHTTP(%#v, %#v, %s)\n", rw, req, host)
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Router) serveRedirectHTTP(rw http.ResponseWriter, req *http.Request, host string) bool {
|
||||
h := r.redirect[host]
|
||||
if h != nil {
|
||||
pairs := h.GetAllKeyValues([]byte(req.URL.Path))
|
||||
for i := len(pairs) - 1; i >= 0; i-- {
|
||||
if pairs[i].Value.Pre || pairs[i].Key == req.URL.Path {
|
||||
req.URL.Path = strings.TrimPrefix(req.URL.Path, pairs[i].Key)
|
||||
pairs[i].Value.ServeHTTP(rw, req)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
140
router/router_test.go
Normal file
140
router/router_test.go
Normal file
@ -0,0 +1,140 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/MrMelon54/violet/target"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type redirectTestBase struct {
|
||||
path string
|
||||
dst target.Redirect
|
||||
tests map[string]string
|
||||
}
|
||||
|
||||
type mss map[string]string
|
||||
|
||||
var redirectTests = []redirectTestBase{
|
||||
{"/", target.Redirect{}, mss{
|
||||
"/": "/",
|
||||
"/hello": "",
|
||||
}},
|
||||
{"/", target.Redirect{Path: "world"}, mss{
|
||||
"/": "/world",
|
||||
"/hello": "",
|
||||
}},
|
||||
{"/", target.Redirect{Abs: true}, mss{
|
||||
"/": "/",
|
||||
"/hello": "",
|
||||
}},
|
||||
{"/", target.Redirect{Abs: true, Path: "world"}, mss{
|
||||
"/": "/world",
|
||||
"/hello": "",
|
||||
}},
|
||||
{"/", target.Redirect{Pre: true}, mss{
|
||||
"/": "/",
|
||||
"/hello": "/hello",
|
||||
}},
|
||||
{"/", target.Redirect{Pre: true, Path: "world"}, mss{
|
||||
"/": "/world",
|
||||
"/hello": "/world/hello",
|
||||
}},
|
||||
{"/", target.Redirect{Pre: true, Abs: true}, mss{
|
||||
"/": "/",
|
||||
"/hello": "/",
|
||||
}},
|
||||
{"/", target.Redirect{Pre: true, Abs: true, Path: "world"}, mss{
|
||||
"/": "/world",
|
||||
"/hello": "/world",
|
||||
}},
|
||||
{"/hello", target.Redirect{}, mss{
|
||||
"/": "",
|
||||
"/hello": "/",
|
||||
"/hello/hi": "",
|
||||
}},
|
||||
{"/hello", target.Redirect{Path: "world"}, mss{
|
||||
"/": "",
|
||||
"/hello": "/world",
|
||||
"/hello/hi": "",
|
||||
}},
|
||||
{"/hello", target.Redirect{Abs: true}, mss{
|
||||
"/": "",
|
||||
"/hello": "/",
|
||||
"/hello/hi": "",
|
||||
}},
|
||||
{"/hello", target.Redirect{Abs: true, Path: "world"}, mss{
|
||||
"/": "",
|
||||
"/hello": "/world",
|
||||
"/hello/hi": "",
|
||||
}},
|
||||
{"/hello", target.Redirect{Pre: true}, mss{
|
||||
"/": "",
|
||||
"/hello": "/",
|
||||
"/hello/hi": "/hi",
|
||||
}},
|
||||
{"/hello", target.Redirect{Pre: true, Path: "world"}, mss{
|
||||
"/": "",
|
||||
"/hello": "/world",
|
||||
"/hello/hi": "/world/hi",
|
||||
}},
|
||||
{"/hello", target.Redirect{Pre: true, Abs: true}, mss{
|
||||
"/": "",
|
||||
"/hello": "/",
|
||||
"/hello/hi": "/",
|
||||
}},
|
||||
{"/hello", target.Redirect{Pre: true, Abs: true, Path: "world"}, mss{
|
||||
"/": "",
|
||||
"/hello": "/world",
|
||||
"/hello/hi": "/world",
|
||||
}},
|
||||
}
|
||||
|
||||
func assertHttpRedirect(t *testing.T, r *Router, code int, target, method, start string) {
|
||||
res := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(method, start, nil)
|
||||
r.ServeHTTP(res, req)
|
||||
l := res.Header().Get("Location")
|
||||
if target == "" {
|
||||
if code == res.Code || "" != l {
|
||||
t.Logf("Test URL: %#v\n", req.URL)
|
||||
t.Log(r.redirect["www.example.com"].String())
|
||||
t.Fatalf("%s => %s\n", start, target)
|
||||
}
|
||||
} else {
|
||||
if code != res.Code || target != l {
|
||||
t.Logf("Test URL: %#v\n", req.URL)
|
||||
t.Log(r.redirect["www.example.com"].String())
|
||||
t.Fatalf("\nexpected %s => %s\n got %s => %s\n", start, target, start, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter_AddRedirect(t *testing.T) {
|
||||
for _, i := range redirectTests {
|
||||
r := New()
|
||||
dst := i.dst
|
||||
dst.Host = "example.com"
|
||||
dst.Code = http.StatusFound
|
||||
t.Logf("Running tests for %#v\n", dst)
|
||||
r.AddRedirect("www.example.com", i.path, dst)
|
||||
for k, v := range i.tests {
|
||||
u1 := &url.URL{Scheme: "https", Host: "example.com", Path: v}
|
||||
if v == "" {
|
||||
u1 = nil
|
||||
} else if v == "/" {
|
||||
u1.Path = ""
|
||||
}
|
||||
u2 := &url.URL{Scheme: "https", Host: "www.example.com", Path: k}
|
||||
assertHttpRedirect(t, r, http.StatusFound, outputUrl(u1), http.MethodGet, outputUrl(u2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func outputUrl(u *url.URL) string {
|
||||
if u == nil {
|
||||
return ""
|
||||
}
|
||||
return u.String()
|
||||
}
|
@ -1,15 +1,44 @@
|
||||
package target
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
|
||||
type Redirect struct {
|
||||
url.URL
|
||||
Pre bool
|
||||
Host string
|
||||
Port int
|
||||
Path string
|
||||
Abs bool
|
||||
Code int
|
||||
}
|
||||
|
||||
func (r Redirect) Handler() http.Handler {
|
||||
return http.RedirectHandler(r.URL.String(), r.Code)
|
||||
func (r Redirect) FullHost() string {
|
||||
if r.Port == 0 {
|
||||
return r.Host
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", r.Host, r.Port)
|
||||
}
|
||||
|
||||
func (r Redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
p := r.Path
|
||||
if !r.Abs {
|
||||
p = path.Join(r.Path, req.URL.Path)
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: req.URL.Scheme,
|
||||
Host: r.FullHost(),
|
||||
Path: p,
|
||||
}
|
||||
if u.Path == "/" {
|
||||
u.Path = ""
|
||||
}
|
||||
http.Redirect(rw, req, u.String(), r.Code)
|
||||
}
|
||||
|
||||
func (r Redirect) String() string {
|
||||
return fmt.Sprintf("%#v", r)
|
||||
}
|
||||
|
9
target/route.go
Normal file
9
target/route.go
Normal file
@ -0,0 +1,9 @@
|
||||
package target
|
||||
|
||||
type Route struct {
|
||||
Pre bool
|
||||
Host string
|
||||
Port int
|
||||
Path string
|
||||
Abs bool
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package target
|
||||
|
||||
type Service struct {
|
||||
Host string
|
||||
Port int
|
||||
Absolute bool
|
||||
}
|
Loading…
Reference in New Issue
Block a user