From 893a53c482161f6fc71cb0b76237074d109309f1 Mon Sep 17 00:00:00 2001 From: surdeus Date: Wed, 10 Jan 2024 20:11:02 +0300 Subject: [PATCH] feat: the new architucture basing on the context. --- api.go | 107 ++++++---------------------------------------- cmd/hook/main.go | 18 +++----- cmd/test/main.go | 34 +++++++-------- contents/main.go | 2 +- context.go | 108 +++++++++++++++++++++++++++++++++++++++++++++++ decoder.go | 5 +++ go.mod | 1 + handler.go | 15 ------- http.go | 17 ++++++++ method.go | 25 +++++------ path.go | 65 ++++++++++++++++++++++++++++ root.go | 27 ++++++++++++ router.go | 56 ------------------------ short.go | 8 ++++ static.go | 10 +++++ statuses/main.go | 4 +- 16 files changed, 291 insertions(+), 211 deletions(-) create mode 100644 context.go create mode 100644 decoder.go delete mode 100644 handler.go create mode 100644 http.go create mode 100644 path.go create mode 100644 root.go delete mode 100644 router.go create mode 100644 short.go create mode 100644 static.go diff --git a/api.go b/api.go index 4e5be4a..7a039f2 100644 --- a/api.go +++ b/api.go @@ -1,107 +1,24 @@ package bond import ( - "io" - "encoding/json" - "net/http" - "net/url" - "fmt" - "vultras.su/core/bond/contents" ) -type Decoder interface { - Decode(any) error +type Handler interface { + Handle(c *Context) } +// The type is used for custom-one-place implementation +// of handling. type Func func(*Context) - -func (fn Func) ServeHTTP(w ResponseWriter, r *Request) { - fn(&Context{ - R: r, - W: w, - }) +func (fn Func) Handle(c *Context) { + fn(c) } -type Context struct { - R *Request - W ResponseWriter - // Custom data to store stuff. - Data any - - scanErr error - dec Decoder +// The wrapper for the standard http.Handler(s). +type Wrap struct { + HttpHandler +} +func (w Wrap) Handle(c *Context) { + w.ServeHTTP(c.W, c.R) } -func (c *Context) SetContentType(typ contents.Type) { - c.SetHeader("Content-Type", string(typ)) -} - -func (c *Context) ContentType() contents.Type { - ret, ok := c.Header("Content-Type") - if !ok { - return "" - } - if len(ret) < 1 { - return "" - } - return contents.Type(ret[0]) -} - -func (c *Context) SetHeader(k, v string) { - c.W.Header().Set(k, v) -} - -func (c *Context) Header(name string) ([]string, bool) { - ret, ok := c.R.Header[name] - return ret, ok -} - -// Closes the requests body after finishes scaning. -func (c *Context) Close() { - c.R.Body.Close() -} - -// Scan the incoming value from body depending -// on the content type of the request. -func (c *Context) Scan(v any) bool { - if c.dec == nil { - typ := c.ContentType() - switch typ { - case contents.Json: - c.dec = json.NewDecoder(c.R.Body) - //case contents.UrlEncoded: - // return false - default: - c.scanErr = UnknownContentTypeErr - return false - } - } - err := c.dec.Decode(v) - if err != nil { - if err != io.EOF { - c.scanErr = err - } - return false - } - return true -} - -func (c *Context) ScanErr() error { - return c.scanErr -} - -func (c *Context) Path() string { - return c.R.URL.Path -} - -func (c *Context) NotFound() { - http.NotFound(c.W, c.R) -} - -func (c *Context) Printf(format string, v ...any) (int, error) { - return fmt.Fprintf(c.W, format, v...) -} - -func (c *Context) Query() url.Values { - return c.R.URL.Query() -} diff --git a/cmd/hook/main.go b/cmd/hook/main.go index 18cfa7f..6e56205 100644 --- a/cmd/hook/main.go +++ b/cmd/hook/main.go @@ -14,17 +14,15 @@ type GetNotesOptions struct { Name string `json:"name"` } -var root = bond.Mux(). +var root = bond.Root(bond.Path(). Def( - "", - bond.Func(func(c *bond.Context) { + "", bond.Func(func(c *bond.Context) { c.W.Write([]byte("This is the index page")) }), ).Def( "hello", - bond.Mux().Def( - "en", - bond.Func(func(c *bond.Context) { + bond.Path().Def( + "en", bond.Func(func(c *bond.Context) { c.Printf("Hello, World!") }), ).Def( @@ -34,11 +32,9 @@ var root = bond.Mux(). }), ), ).Def( - "web", - bond.Static("./static"), + "web", bond.Static("./static"), ).Def( - "test", - bond.Func(func(c *bond.Context) { + "test", bond.Func(func(c *bond.Context) { c.SetContentType(contents.Plain) c.Printf( "Path: %q\n"+ @@ -82,7 +78,7 @@ var root = bond.Mux(). fmt.Printf("values: %q", values) }), //), -) +)) func main() { srv := bond.Server{ diff --git a/cmd/test/main.go b/cmd/test/main.go index 945b965..fd6bc25 100644 --- a/cmd/test/main.go +++ b/cmd/test/main.go @@ -4,6 +4,7 @@ import ( "vultras.su/core/bond" "vultras.su/core/bond/methods" "vultras.su/core/bond/contents" + "vultras.su/core/bond/statuses" ) type GetNotesOptions struct { @@ -11,31 +12,30 @@ type GetNotesOptions struct { Name string `json:"name"` } -var root = bond.Mux(). - Def( - "", - bond.Func(func(c *bond.Context) { - c.W.Write([]byte("This is the index page")) - }), - ).Def( +var root = bond.Root(bond.Path(). +Def( + "", + bond.Func(func(c *bond.Context) { + c.W.Write([]byte("This is the index page")) + }), +).Def( "hello", - bond.Mux().Def( - "en", - bond.Func(func(c *bond.Context) { + bond.Path().Def( + // Using the relative redirect to force us to the en. + "", bond.Redirect("/hello/en", statuses.SeeOther), + ).Def( + "en", bond.Func(func(c *bond.Context) { c.Printf("Hello, World!") }), ).Def( - "ru", - bond.Func(func(c *bond.Context) { + "ru", bond.Func(func(c *bond.Context) { c.Printf("Привет, Мир!") }), ), ).Def( - "web", - bond.Static("./static"), + "web", bond.Static("./static"), ).Def( - "test", - bond.Func(func(c *bond.Context) { + "test", bond.Func(func(c *bond.Context) { c.SetContentType(contents.Plain) c.Printf( "Path: %q\n"+ @@ -60,7 +60,7 @@ var root = bond.Mux(). c.Printf("%v", opts) }), ), -) +)) func main() { srv := bond.Server{ diff --git a/contents/main.go b/contents/main.go index 5f334b8..7c9e53c 100644 --- a/contents/main.go +++ b/contents/main.go @@ -13,7 +13,7 @@ const ( // Using the UTF-8 by default. Unknown Type = "application/octet-stream" Binary - Plain Type = "text/plain; charset=utf-8" + Plain Type = "text/plain" Css Type = "text/css" Html Type = "text/html" Json Type = "application/json" diff --git a/context.go b/context.go new file mode 100644 index 0000000..bc37c7b --- /dev/null +++ b/context.go @@ -0,0 +1,108 @@ +package bond + +import ( + "io" + "encoding/json" + "net/http" + "net/url" + "fmt" + "vultras.su/core/bond/contents" +) + +type Context struct { + R *Request + W ResponseWriter + // Custom data to store stuff. + Data any + + scanErr error + dec Decoder +} + +func (c *Context) Method() ReqMethod { + return ReqMethod(c.R.Method) +} + +// Set the reply status code. +func (c *Context) SetStatus(status Status) { +} + +// Set the reply content type. +func (c *Context) SetContentType(typ contents.Type) { + c.SetHeader("Content-Type", string(typ)) +} + +// Get the request content type. +func (c *Context) ContentType() ContentType { + ret, ok := c.Header("Content-Type") + if !ok { + return "" + } + if len(ret) < 1 { + return "" + } + return contents.Type(ret[0]) +} + +func (c *Context) SetHeader(k, v string) { + c.W.Header().Set(k, v) +} + +func (c *Context) Header(name string) ([]string, bool) { + ret, ok := c.R.Header[name] + return ret, ok +} + +// Closes the requests body after finishes scaning. +func (c *Context) Close() { + c.R.Body.Close() +} + +// Scan the incoming value from body depending +// on the content type of the request. +func (c *Context) Scan(v any) bool { + if c.dec == nil { + typ := c.ContentType() + switch typ { + case contents.Json: + c.dec = json.NewDecoder(c.R.Body) + //case contents.UrlEncoded: + // return false + default: + c.scanErr = UnknownContentTypeErr + return false + } + } + err := c.dec.Decode(v) + if err != nil { + if err != io.EOF { + c.scanErr = err + } + return false + } + return true +} + +func (c *Context) ScanErr() error { + return c.scanErr +} + +func (c *Context) Path() string { + return c.R.URL.Path +} + +func (c *Context) NotFound() { + http.NotFound(c.W, c.R) +} + +func (c *Context) Printf(format string, v ...any) (int, error) { + return fmt.Fprintf(c.W, format, v...) +} + +func (c *Context) Query() url.Values { + return c.R.URL.Query() +} + +func (c *Context) Redirect(u string, status Status) { + http.Redirect(c.W, c.R, u, int(status)) +} diff --git a/decoder.go b/decoder.go new file mode 100644 index 0000000..2e9b108 --- /dev/null +++ b/decoder.go @@ -0,0 +1,5 @@ +package bond + +type Decoder interface { + Decode(any) error +} diff --git a/go.mod b/go.mod index f22545d..aa148a6 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,4 @@ module vultras.su/core/bond go 1.21.3 + diff --git a/handler.go b/handler.go deleted file mode 100644 index d94829f..0000000 --- a/handler.go +++ /dev/null @@ -1,15 +0,0 @@ -package bond - -import ( - "net/http" -) - -type Request = http.Request -type ResponseWriter = http.ResponseWriter -type HandlerFunc = http.HandlerFunc -type Handler = http.Handler -type Server = http.Server - -func Static(pth string) Handler { - return http.FileServer(http.Dir(pth)) -} diff --git a/http.go b/http.go new file mode 100644 index 0000000..edc955a --- /dev/null +++ b/http.go @@ -0,0 +1,17 @@ +package bond +import ( + "net/http" + "vultras.su/core/bond/contents" + "vultras.su/core/bond/statuses" + "vultras.su/core/bond/methods" +) + +type Request = http.Request +type ResponseWriter = http.ResponseWriter +type HttpHandlerFunc = http.HandlerFunc +type HttpHandler = http.Handler +type Server = http.Server + +type ContentType = contents.Type +type Status = statuses.Code +type ReqMethod = methods.Method diff --git a/method.go b/method.go index f5bb74d..620b5af 100644 --- a/method.go +++ b/method.go @@ -1,24 +1,20 @@ package bond -import ( - "net/http" - "vultras.su/core/bond/methods" -) - -// The type implements functionality for multiplexing -// the methods. +// The type implements method routing. type MethodRouter struct { - methodMap map[methods.Method]Handler + methodMap map[ReqMethod]Handler } +// Returns new empty MethodRouter. func Method() *MethodRouter { ret := &MethodRouter{} - ret.methodMap = make(map[methods.Method]Handler) + ret.methodMap = make(map[ReqMethod]Handler) return ret } +// Define new handler for the specified method. func (mr *MethodRouter) Def( - method methods.Method, + method ReqMethod, handler Handler, ) *MethodRouter { _, dup := mr.methodMap[method] @@ -30,11 +26,12 @@ func (mr *MethodRouter) Def( return mr } -func (mr *MethodRouter) ServeHTTP(w ResponseWriter, r *Request) { - handler, ok := mr.methodMap[methods.Method(r.Method)] +// Implementing the Handler. +func (mr *MethodRouter) Handle(c *Context) { + handler, ok := mr.methodMap[c.Method()] if !ok { - http.NotFound(w, r) + c.NotFound() return } - handler.ServeHTTP(w, r) + handler.Handle(c) } diff --git a/path.go b/path.go new file mode 100644 index 0000000..baed553 --- /dev/null +++ b/path.go @@ -0,0 +1,65 @@ +package bond + +import ( + "strings" + //"fmt" + //"path" +) + +// The type implements path routing for requests. +type PathRouter struct { + pathMap map[string] Handler +} + +// Returns new empty PathRouter. +func Path() *PathRouter { + ret := &PathRouter{} + ret.pathMap = map[string] Handler{} + return ret +} + +// Define new handler for the specified path. +// The defined path must not contain slashes and will panic otherwise. +func (router *PathRouter) Def(pth string, handler Handler) *PathRouter { + _, dup := router.pathMap[pth] + if dup { + panic(DupDefErr) + } + router.pathMap[pth] = handler + return router +} + +// Implementing the Handler. +func (router *PathRouter) Handle(c *Context) { + pth := c.Path() + var splits []string + var name, rest string + + if len(pth)>0 && pth[0] == '/' { // Handling the root path. + splits = strings.SplitN(pth, "/", 3) + if len(splits) > 1 { + name = splits[1] + } + if len(splits) > 2 { + rest = splits[2] + } + } else { // Handling the relative path. (second or n-th call) + splits = strings.SplitN(pth, "/", 2) + if len(splits) > 0 { + name = splits[0] + } + if len(splits) > 1 { + rest = splits[1] + } + } + + handler, ok := router.pathMap[name] + if !ok { + c.NotFound() + return + } + + c.R.URL.Path = rest + handler.Handle(c) +} + diff --git a/root.go b/root.go new file mode 100644 index 0000000..29d02f0 --- /dev/null +++ b/root.go @@ -0,0 +1,27 @@ +package bond + +// The type implements the entry point +// for all the request for the server. +type RootRouter struct { + handler Handler +} + +// Return the new RootRouter with the specified +// handler. +func Root(handler Handler) *RootRouter { + if handler == nil { + panic("the root handler is nil") + } + ret := &RootRouter{} + ret.handler = handler + return ret + +} + +// Implementing the http.Handler . +func (router *RootRouter) ServeHTTP(w ResponseWriter, r *Request) { + ctx := Context{} + ctx.W = w + ctx.R = r + router.handler.Handle(&ctx) +} diff --git a/router.go b/router.go deleted file mode 100644 index 6fd1fed..0000000 --- a/router.go +++ /dev/null @@ -1,56 +0,0 @@ -package bond - -import ( - "net/http" - "strings" - //"fmt" - "path" -) - -type Router struct { - pathMap map[string] Handler -} - -func Mux() *Router { - ret := &Router{} - ret.pathMap = map[string] Handler{} - return ret -} - -func (router *Router) Def(pth string, handler Handler) *Router { - _, dup := router.pathMap[pth] - if dup { - panic(DupDefErr) - } - router.pathMap[pth] = handler - return router -} - -func (router *Router) ServeHTTP(w ResponseWriter, r *Request) { - pth := r.URL.Path - pth = path.Clean(pth) - pths := strings.SplitN(pth, "/", 3) - - var name string - if len(pths) > 1 { - name = pths[1] - } - name, _ = strings.CutSuffix(name, "/") - - prefix := "/" - if pth != "/" { - prefix = path.Clean("/" + name) - } - - //fmt.Printf("Path: %q\n", r.URL.Path) - //fmt.Printf("%q %q %q %q\n", pth, prefix, pths, name) - handler, ok := router.pathMap[name] - if !ok { - http.NotFound(w, r) - return - } - - r.URL.Path = pth - http.StripPrefix(prefix, handler).ServeHTTP(w, r) -} - diff --git a/short.go b/short.go new file mode 100644 index 0000000..afbdcf8 --- /dev/null +++ b/short.go @@ -0,0 +1,8 @@ +package bond + +type RedirectRouter string +func Redirect(u string, status Status) Handler { + return Func(func(c *Context){ + c.Redirect(u, status) + }) +} diff --git a/static.go b/static.go new file mode 100644 index 0000000..76961e5 --- /dev/null +++ b/static.go @@ -0,0 +1,10 @@ +package bond + +import ( + "net/http" +) + +func Static(pth string) Handler { + return Wrap{http.FileServer(http.Dir(pth))} +} + diff --git a/statuses/main.go b/statuses/main.go index f7d2b96..cea6108 100644 --- a/statuses/main.go +++ b/statuses/main.go @@ -4,9 +4,9 @@ import ( "net/http" ) -type Status int +type Code int const ( - Continue Status = http.StatusContinue + Continue Code = http.StatusContinue SwitchingProtocols = http.StatusSwitchingProtocols // RFC 9110, 15.2.2 Processing = http.StatusProcessing // RFC 2518, 10.1 EarlyHints = http.StatusEarlyHints // RFC 8297