feat: the new architucture basing on the context.

This commit is contained in:
Andrey Parhomenko 2024-01-10 20:11:02 +03:00
parent 9209039b32
commit 893a53c482
16 changed files with 291 additions and 211 deletions

107
api.go
View file

@ -1,107 +1,24 @@
package bond package bond
import ( import (
"io"
"encoding/json"
"net/http"
"net/url"
"fmt"
"vultras.su/core/bond/contents"
) )
type Decoder interface { type Handler interface {
Decode(any) error Handle(c *Context)
} }
// The type is used for custom-one-place implementation
// of handling.
type Func func(*Context) type Func func(*Context)
func (fn Func) Handle(c *Context) {
func (fn Func) ServeHTTP(w ResponseWriter, r *Request) { fn(c)
fn(&Context{
R: r,
W: w,
})
} }
type Context struct { // The wrapper for the standard http.Handler(s).
R *Request type Wrap struct {
W ResponseWriter HttpHandler
// Custom data to store stuff. }
Data any func (w Wrap) Handle(c *Context) {
w.ServeHTTP(c.W, c.R)
scanErr error
dec Decoder
} }
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()
}

View file

@ -14,17 +14,15 @@ type GetNotesOptions struct {
Name string `json:"name"` Name string `json:"name"`
} }
var root = bond.Mux(). var root = bond.Root(bond.Path().
Def( Def(
"", "", bond.Func(func(c *bond.Context) {
bond.Func(func(c *bond.Context) {
c.W.Write([]byte("This is the index page")) c.W.Write([]byte("This is the index page"))
}), }),
).Def( ).Def(
"hello", "hello",
bond.Mux().Def( bond.Path().Def(
"en", "en", bond.Func(func(c *bond.Context) {
bond.Func(func(c *bond.Context) {
c.Printf("Hello, World!") c.Printf("Hello, World!")
}), }),
).Def( ).Def(
@ -34,11 +32,9 @@ var root = bond.Mux().
}), }),
), ),
).Def( ).Def(
"web", "web", bond.Static("./static"),
bond.Static("./static"),
).Def( ).Def(
"test", "test", bond.Func(func(c *bond.Context) {
bond.Func(func(c *bond.Context) {
c.SetContentType(contents.Plain) c.SetContentType(contents.Plain)
c.Printf( c.Printf(
"Path: %q\n"+ "Path: %q\n"+
@ -82,7 +78,7 @@ var root = bond.Mux().
fmt.Printf("values: %q", values) fmt.Printf("values: %q", values)
}), }),
//), //),
) ))
func main() { func main() {
srv := bond.Server{ srv := bond.Server{

View file

@ -4,6 +4,7 @@ import (
"vultras.su/core/bond" "vultras.su/core/bond"
"vultras.su/core/bond/methods" "vultras.su/core/bond/methods"
"vultras.su/core/bond/contents" "vultras.su/core/bond/contents"
"vultras.su/core/bond/statuses"
) )
type GetNotesOptions struct { type GetNotesOptions struct {
@ -11,31 +12,30 @@ type GetNotesOptions struct {
Name string `json:"name"` Name string `json:"name"`
} }
var root = bond.Mux(). var root = bond.Root(bond.Path().
Def( Def(
"", "",
bond.Func(func(c *bond.Context) { bond.Func(func(c *bond.Context) {
c.W.Write([]byte("This is the index page")) c.W.Write([]byte("This is the index page"))
}), }),
).Def( ).Def(
"hello", "hello",
bond.Mux().Def( bond.Path().Def(
"en", // Using the relative redirect to force us to the en.
bond.Func(func(c *bond.Context) { "", bond.Redirect("/hello/en", statuses.SeeOther),
).Def(
"en", bond.Func(func(c *bond.Context) {
c.Printf("Hello, World!") c.Printf("Hello, World!")
}), }),
).Def( ).Def(
"ru", "ru", bond.Func(func(c *bond.Context) {
bond.Func(func(c *bond.Context) {
c.Printf("Привет, Мир!") c.Printf("Привет, Мир!")
}), }),
), ),
).Def( ).Def(
"web", "web", bond.Static("./static"),
bond.Static("./static"),
).Def( ).Def(
"test", "test", bond.Func(func(c *bond.Context) {
bond.Func(func(c *bond.Context) {
c.SetContentType(contents.Plain) c.SetContentType(contents.Plain)
c.Printf( c.Printf(
"Path: %q\n"+ "Path: %q\n"+
@ -60,7 +60,7 @@ var root = bond.Mux().
c.Printf("%v", opts) c.Printf("%v", opts)
}), }),
), ),
) ))
func main() { func main() {
srv := bond.Server{ srv := bond.Server{

View file

@ -13,7 +13,7 @@ const (
// Using the UTF-8 by default. // Using the UTF-8 by default.
Unknown Type = "application/octet-stream" Unknown Type = "application/octet-stream"
Binary Binary
Plain Type = "text/plain; charset=utf-8" Plain Type = "text/plain"
Css Type = "text/css" Css Type = "text/css"
Html Type = "text/html" Html Type = "text/html"
Json Type = "application/json" Json Type = "application/json"

108
context.go Normal file
View file

@ -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))
}

5
decoder.go Normal file
View file

@ -0,0 +1,5 @@
package bond
type Decoder interface {
Decode(any) error
}

1
go.mod
View file

@ -1,3 +1,4 @@
module vultras.su/core/bond module vultras.su/core/bond
go 1.21.3 go 1.21.3

View file

@ -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))
}

17
http.go Normal file
View file

@ -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

View file

@ -1,24 +1,20 @@
package bond package bond
import ( // The type implements method routing.
"net/http"
"vultras.su/core/bond/methods"
)
// The type implements functionality for multiplexing
// the methods.
type MethodRouter struct { type MethodRouter struct {
methodMap map[methods.Method]Handler methodMap map[ReqMethod]Handler
} }
// Returns new empty MethodRouter.
func Method() *MethodRouter { func Method() *MethodRouter {
ret := &MethodRouter{} ret := &MethodRouter{}
ret.methodMap = make(map[methods.Method]Handler) ret.methodMap = make(map[ReqMethod]Handler)
return ret return ret
} }
// Define new handler for the specified method.
func (mr *MethodRouter) Def( func (mr *MethodRouter) Def(
method methods.Method, method ReqMethod,
handler Handler, handler Handler,
) *MethodRouter { ) *MethodRouter {
_, dup := mr.methodMap[method] _, dup := mr.methodMap[method]
@ -30,11 +26,12 @@ func (mr *MethodRouter) Def(
return mr return mr
} }
func (mr *MethodRouter) ServeHTTP(w ResponseWriter, r *Request) { // Implementing the Handler.
handler, ok := mr.methodMap[methods.Method(r.Method)] func (mr *MethodRouter) Handle(c *Context) {
handler, ok := mr.methodMap[c.Method()]
if !ok { if !ok {
http.NotFound(w, r) c.NotFound()
return return
} }
handler.ServeHTTP(w, r) handler.Handle(c)
} }

65
path.go Normal file
View file

@ -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)
}

27
root.go Normal file
View file

@ -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)
}

View file

@ -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)
}

8
short.go Normal file
View file

@ -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)
})
}

10
static.go Normal file
View file

@ -0,0 +1,10 @@
package bond
import (
"net/http"
)
func Static(pth string) Handler {
return Wrap{http.FileServer(http.Dir(pth))}
}

View file

@ -4,9 +4,9 @@ import (
"net/http" "net/http"
) )
type Status int type Code int
const ( const (
Continue Status = http.StatusContinue Continue Code = http.StatusContinue
SwitchingProtocols = http.StatusSwitchingProtocols // RFC 9110, 15.2.2 SwitchingProtocols = http.StatusSwitchingProtocols // RFC 9110, 15.2.2
Processing = http.StatusProcessing // RFC 2518, 10.1 Processing = http.StatusProcessing // RFC 2518, 10.1
EarlyHints = http.StatusEarlyHints // RFC 8297 EarlyHints = http.StatusEarlyHints // RFC 8297