From 4a4303afefdae2f23e40d784b005b540f37dc28a Mon Sep 17 00:00:00 2001 From: Nazar Kanaev Date: Tue, 16 Mar 2021 14:36:00 +0000 Subject: [PATCH] router package --- src/router/context.go | 33 +++++++++++++++++ src/router/example.go | 35 ++++++++++++++++++ src/router/router.go | 66 ++++++++++++++++++++++++++++++++++ src/router/router_test.go | 76 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 210 insertions(+) create mode 100644 src/router/context.go create mode 100644 src/router/example.go create mode 100644 src/router/router.go create mode 100644 src/router/router_test.go diff --git a/src/router/context.go b/src/router/context.go new file mode 100644 index 0000000..d893fc0 --- /dev/null +++ b/src/router/context.go @@ -0,0 +1,33 @@ +package router + +import ( + "net/http" +) + +type Context struct { + Req *http.Request + Out http.ResponseWriter + + Vars map[string]string + + chain []Handler + index int +} + +func (c *Context) Next() { + c.index++ + c.handlers[c.index](c) +} + +func (c *Context) JSON(status int, data interface{}) { + reply, err := json.Marshal(data) + if err != nil { + log.Fatal(err) + c.Out.WriteHeader(http.StatusInternalServerError) + return + } + c.Out.WriteHeader(status) + c.Out.Header().Set("Content-Type", "application/json; charset=utf-8") + c.Out.Write(reply) + c.Out.Write([]byte("\n")) +} diff --git a/src/router/example.go b/src/router/example.go new file mode 100644 index 0000000..7976824 --- /dev/null +++ b/src/router/example.go @@ -0,0 +1,35 @@ +package router +/* +func do() { + server := NewServer(db, worker) + + router := NewRouter() + + router.Use(AuthMiddleware()) + router.Use(CorsMiddleware()) + + router.For("/", server.index) + router.For("/static/*path", server.static) + router.For("/api/status", server.status) + router.For("/api/folders", server.folderlist) + router.For("/api/folders/:id", server.folder) + router.For("/api/feeds", server.feedlist) + router.For("/api/feeds/refresh", server.feedsRefresh) + router.For("/api/feeds/errors", server.feedsErrors) + router.For("/api/feeds/:id/icon", server.feedsIcons) + router.For("/api/feeds/:id", server.feed) + router.For("/api/items", server.itemlist) + router.For("/api/items/:id", server.item) + router.For("/api/settings", server.settings) + router.For("/opml/import", server.opmlImport) + router.For("/opml/export", server.opmlExport) + router.For("/page", server.pagecrawl) + router.For("/logout", server.logout) + + httpserver := &http.Server{Addr: server.Addr(), Handler: router} + httpserver.ListenAndServe() +} + +func (h Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { +} +*/ diff --git a/src/router/router.go b/src/router/router.go new file mode 100644 index 0000000..149853d --- /dev/null +++ b/src/router/router.go @@ -0,0 +1,66 @@ +package router + +import ( + "regexp" +) + +type Handler func(*Context) + +type Router struct { + middle []Handler + routes []Route +} + +type Route struct { + regex *regexp.Regexp + chain []Handler +} + +func NewRouter() *Router { + router := &Router{} + router.middle = make([]Handler, 0) + router.routes = make([]Route, 0) + return router +} + +func (r *Router) Use(h Handler) { + r.middle = append(r.middle, h) +} + +func (r *Router) For(path string, handler Handler) { + x := Route{} + x.regex = routeRegexp(path) + x.chain = append(r.middle, handler) + + r.routes = append(r.routes, x) +} + +func (r *Router) resolve(path string) *route { + for _, r := range r.routes { + if r.regex.MatchString(path) { + return &r + } + } + return nil +} + +func regexGroups(input string, regex *regexp.Regexp) map[string]string { + groups := make(map[string]string) + matches := regex.FindStringSubmatchIndex(input) + for i, key := range regex.SubexpNames()[1:] { + groups[key] = input[matches[i*2+2]:matches[i*2+3]] + } + return groups +} + +func routeRegexp(route string) *regexp.Regexp { + chunks := regexp.MustCompile(`[\*\:]\w+`) + output := chunks.ReplaceAllStringFunc(route, func(m string) string { + if m[0:1] == `*` { + return "(?P<" + m[1:] + ">.+)" + } + return "(?P<" + m[1:] + ">[^/]+)" + }) + output = "^" + output + "$" + return regexp.MustCompile(output) +} diff --git a/src/router/router_test.go b/src/router/router_test.go new file mode 100644 index 0000000..3f7ad81 --- /dev/null +++ b/src/router/router_test.go @@ -0,0 +1,76 @@ +package router + +import ( + "reflect" + "testing" +) + +func TestRouteRegexpPart(t *testing.T) { + in := "/hello/:world" + re := routeRegexp(in) + + pos := []string{ + "/hello/world", + "/hello/1234", + "/hello/bbc1", + } + for _, c := range pos { + if !re.MatchString(c) { + t.Errorf("%v must match %v", in, c) + } + } + + neg := []string{ + "/hello", + "/hello/world/", + "/sub/hello/123", + "//hello/123", + "/hello/123/hello/", + } + for _, c := range neg { + if re.MatchString(c) { + t.Errorf("%q must not match %q", in, c) + } + } +} + +func TestRouteRegexpStar(t *testing.T) { + in := "/hello/*world" + re := routeRegexp(in) + + pos := []string{"/hello/world", "/hello/world/test"} + for _, c := range pos { + if !re.MatchString(c) { + t.Errorf("%q must match %q", in, c) + } + } + + neg := []string{"/hello/", "/hello"} + for _, c := range neg { + if re.MatchString(c) { + t.Errorf("%v must not match %v", in, c) + } + } +} + +func TestRegexGroupsPart(t *testing.T) { + re := routeRegexp("/foo/:bar/1/:baz") + + expect := map[string]string{"bar": "one", "baz": "two"} + actual := regexGroups("/foo/one/1/two", re) + + if !reflect.DeepEqual(expect, actual) { + t.Errorf("expected: %q, actual: %q", expect, actual) + } +} + +func TestRegexGroupsStar(t *testing.T) { + re := routeRegexp("/foo/*bar") + + expect := map[string]string{"bar": "bar/baz/"} + actual := regexGroups("/foo/bar/baz/", re) + + if !reflect.DeepEqual(expect, actual) { + t.Errorf("expected: %q, actual: %q", expect, actual) + } +}