feat: the new architucture basing on the context.
This commit is contained in:
parent
9209039b32
commit
893a53c482
16 changed files with 291 additions and 211 deletions
107
api.go
107
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()
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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(
|
||||
var root = bond.Root(bond.Path().
|
||||
Def(
|
||||
"",
|
||||
bond.Func(func(c *bond.Context) {
|
||||
c.W.Write([]byte("This is the index page"))
|
||||
}),
|
||||
).Def(
|
||||
).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{
|
||||
|
|
|
@ -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"
|
||||
|
|
108
context.go
Normal file
108
context.go
Normal 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
5
decoder.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package bond
|
||||
|
||||
type Decoder interface {
|
||||
Decode(any) error
|
||||
}
|
1
go.mod
1
go.mod
|
@ -1,3 +1,4 @@
|
|||
module vultras.su/core/bond
|
||||
|
||||
go 1.21.3
|
||||
|
||||
|
|
15
handler.go
15
handler.go
|
@ -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
17
http.go
Normal 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
|
25
method.go
25
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)
|
||||
}
|
||||
|
|
65
path.go
Normal file
65
path.go
Normal 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
27
root.go
Normal 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)
|
||||
}
|
56
router.go
56
router.go
|
@ -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
8
short.go
Normal 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
10
static.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package bond
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Static(pth string) Handler {
|
||||
return Wrap{http.FileServer(http.Dir(pth))}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue