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