mirror of
https://github.com/1f349/violet.git
synced 2024-11-09 22:22:50 +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
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
github.com/MrMelon54/trie v0.0.2
|
||||||
github.com/stretchr/testify v1.8.2
|
github.com/stretchr/testify v1.8.2
|
||||||
github.com/yousuf64/shift v0.4.0
|
|
||||||
golang.org/x/net v0.9.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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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=
|
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.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 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
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 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
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
|
package target
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Redirect struct {
|
type Redirect struct {
|
||||||
url.URL
|
Pre bool
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
Path string
|
||||||
|
Abs bool
|
||||||
Code int
|
Code int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Redirect) Handler() http.Handler {
|
func (r Redirect) FullHost() string {
|
||||||
return http.RedirectHandler(r.URL.String(), r.Code)
|
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