184 lines
4.5 KiB
Go
184 lines
4.5 KiB
Go
|
package http
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"embed"
|
||
|
"git.nspix.com/golang/kos/entry/http/router"
|
||
|
"net"
|
||
|
"net/http"
|
||
|
"path"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
ctxPool sync.Pool
|
||
|
)
|
||
|
|
||
|
type Server struct {
|
||
|
ctx context.Context
|
||
|
serve *http.Server
|
||
|
router *router.Router
|
||
|
middleware []Middleware
|
||
|
}
|
||
|
|
||
|
func (svr *Server) applyContext() *Context {
|
||
|
if v := ctxPool.Get(); v != nil {
|
||
|
if ctx, ok := v.(*Context); ok {
|
||
|
return ctx
|
||
|
}
|
||
|
}
|
||
|
return &Context{}
|
||
|
}
|
||
|
|
||
|
func (svr *Server) releaseContext(ctx *Context) {
|
||
|
ctxPool.Put(ctx)
|
||
|
}
|
||
|
|
||
|
func (svr *Server) wrapHandle(cb HandleFunc, middleware ...Middleware) router.Handle {
|
||
|
return func(writer http.ResponseWriter, request *http.Request, params router.Params) {
|
||
|
ctx := svr.applyContext()
|
||
|
defer func() {
|
||
|
svr.releaseContext(ctx)
|
||
|
}()
|
||
|
ps := make(map[string]string)
|
||
|
for _, v := range params {
|
||
|
ps[v.Key] = v.Value
|
||
|
}
|
||
|
ctx.reset(request, writer, ps)
|
||
|
for i := len(svr.middleware) - 1; i >= 0; i-- {
|
||
|
cb = svr.middleware[i](cb)
|
||
|
}
|
||
|
for i := len(middleware) - 1; i >= 0; i-- {
|
||
|
cb = middleware[i](cb)
|
||
|
}
|
||
|
if err := cb(ctx); err != nil {
|
||
|
ctx.Status(http.StatusServiceUnavailable)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (svr *Server) Use(middleware ...Middleware) {
|
||
|
svr.middleware = append(svr.middleware, middleware...)
|
||
|
}
|
||
|
|
||
|
func (svr *Server) Handle(method string, path string, cb HandleFunc, middleware ...Middleware) {
|
||
|
if method == "" {
|
||
|
method = http.MethodPost
|
||
|
}
|
||
|
if path == "" {
|
||
|
path = "/"
|
||
|
}
|
||
|
if !strings.HasPrefix(path, "/") {
|
||
|
path = "/" + path
|
||
|
}
|
||
|
svr.router.Replace(method, path, svr.wrapHandle(cb, middleware...))
|
||
|
}
|
||
|
|
||
|
func (svr *Server) Group(prefix string, routes []Route, middleware ...Middleware) {
|
||
|
for _, route := range routes {
|
||
|
svr.Handle(route.Method, path.Join(prefix, route.Path), route.Handle, middleware...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (svr *Server) Embed(prefix string, root string, embedFs embed.FS) {
|
||
|
routePath := prefix
|
||
|
if !strings.HasSuffix(routePath, "/*filepath") {
|
||
|
if strings.HasSuffix(routePath, "/") {
|
||
|
routePath += "/"
|
||
|
} else {
|
||
|
routePath += "/*filepath"
|
||
|
}
|
||
|
}
|
||
|
httpFs := http.FS(embedFs)
|
||
|
svr.Handle(MethodGet, routePath, func(ctx *Context) (err error) {
|
||
|
filename := strings.TrimPrefix(ctx.Request().URL.Path, prefix)
|
||
|
if filename == "" || filename == "/" {
|
||
|
filename = root + "/"
|
||
|
if !strings.HasSuffix(filename, "/") {
|
||
|
filename = filename + "/"
|
||
|
}
|
||
|
} else {
|
||
|
if !strings.HasPrefix(filename, root) {
|
||
|
filename = path.Clean(path.Join(root, filename))
|
||
|
}
|
||
|
}
|
||
|
if !strings.HasPrefix(filename, "/") {
|
||
|
filename = "/" + filename
|
||
|
}
|
||
|
ctx.Request().URL.Path = filename
|
||
|
http.FileServer(httpFs).ServeHTTP(ctx.Response(), ctx.Request())
|
||
|
return
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (svr *Server) Static(path string, root http.FileSystem) {
|
||
|
if !strings.HasSuffix(path, "/*filepath") {
|
||
|
if strings.HasSuffix(path, "/") {
|
||
|
path += "/"
|
||
|
} else {
|
||
|
path += "/*filepath"
|
||
|
}
|
||
|
}
|
||
|
svr.router.ServeFiles(path, root)
|
||
|
}
|
||
|
|
||
|
func (svr *Server) handleOption(res http.ResponseWriter, req *http.Request) {
|
||
|
res.Header().Add("Vary", "Origin")
|
||
|
res.Header().Add("Vary", "Access-Control-Request-Method")
|
||
|
res.Header().Add("Vary", "Access-Control-Request-Headers")
|
||
|
res.Header().Set("Access-Control-Allow-Origin", "*")
|
||
|
res.Header().Set("Access-Control-Allow-Credentials", "true")
|
||
|
res.Header().Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
|
||
|
h := req.Header.Get("Access-Control-Request-Headers")
|
||
|
if h != "" {
|
||
|
res.Header().Set("Access-Control-Allow-Headers", h)
|
||
|
}
|
||
|
res.WriteHeader(http.StatusNoContent)
|
||
|
}
|
||
|
|
||
|
func (svr *Server) handleRequest(res http.ResponseWriter, req *http.Request) {
|
||
|
res.Header().Add("Vary", "Origin")
|
||
|
res.Header().Set("Access-Control-Allow-Origin", "*")
|
||
|
res.Header().Set("Access-Control-Allow-Credentials", "true")
|
||
|
h := req.Header.Get("Access-Control-Request-Headers")
|
||
|
if h != "" {
|
||
|
res.Header().Set("Access-Control-Allow-Headers", h)
|
||
|
}
|
||
|
svr.router.ServeHTTP(res, req)
|
||
|
}
|
||
|
|
||
|
func (svr *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||
|
switch request.Method {
|
||
|
case http.MethodOptions:
|
||
|
svr.handleOption(writer, request)
|
||
|
default:
|
||
|
svr.handleRequest(writer, request)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (svr *Server) Serve(l net.Listener) (err error) {
|
||
|
svr.serve = &http.Server{
|
||
|
Handler: svr,
|
||
|
}
|
||
|
svr.router.NotFound = NotFound{}
|
||
|
svr.router.MethodNotAllowed = NotAllowed{}
|
||
|
return svr.serve.Serve(l)
|
||
|
}
|
||
|
|
||
|
func (svr *Server) Shutdown() (err error) {
|
||
|
if svr.serve != nil {
|
||
|
err = svr.serve.Shutdown(svr.ctx)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func New(ctx context.Context) *Server {
|
||
|
svr := &Server{
|
||
|
ctx: ctx,
|
||
|
router: router.New(),
|
||
|
middleware: make([]Middleware, 0, 10),
|
||
|
}
|
||
|
return svr
|
||
|
}
|