kos/entry/http/server.go

246 lines
6.1 KiB
Go
Raw Normal View History

2023-04-23 17:57:36 +08:00
package http
import (
"context"
"embed"
"git.nspix.com/golang/kos/entry/http/router"
"net"
"net/http"
"path"
"strings"
"sync"
2024-04-29 10:52:19 +08:00
"sync/atomic"
2023-06-19 16:13:34 +08:00
"time"
2023-04-23 17:57:36 +08:00
)
var (
ctxPool sync.Pool
)
type Server struct {
2023-08-25 09:51:03 +08:00
ctx context.Context
serve *http.Server
router *router.Router
middleware []Middleware
uptime time.Time
enableDocumentRoot bool
fileSystem http.FileSystem
2024-04-15 11:49:42 +08:00
beforeRequests []HandleFunc
2023-08-25 09:51:03 +08:00
anyRequests map[string]http.Handler
2024-04-29 10:52:19 +08:00
exitFlag int32
2023-04-23 17:57:36 +08:00
}
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()
2024-04-15 11:49:42 +08:00
ps := make(map[string]string, 4)
2023-04-23 17:57:36 +08:00
defer func() {
svr.releaseContext(ctx)
2024-04-15 11:49:42 +08:00
ps = make(map[string]string, 0)
2023-04-23 17:57:36 +08:00
}()
for _, v := range params {
ps[v.Key] = v.Value
}
ctx.reset(request, writer, ps)
2024-04-15 11:49:42 +08:00
if len(svr.beforeRequests) > 0 {
for i := len(svr.beforeRequests) - 1; i >= 0; i-- {
if err := svr.beforeRequests[i](ctx); err != nil {
ctx.Status(http.StatusServiceUnavailable)
return
}
}
}
2023-04-23 17:57:36 +08:00
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)
}
}
}
2024-04-15 11:49:42 +08:00
func (svr *Server) Before(cb ...HandleFunc) {
svr.beforeRequests = append(svr.beforeRequests, cb...)
}
2023-04-23 17:57:36 +08:00
func (svr *Server) Use(middleware ...Middleware) {
svr.middleware = append(svr.middleware, middleware...)
}
2023-05-31 11:14:15 +08:00
func (svr *Server) Any(prefix string, handle http.Handler) {
2023-06-06 10:59:13 +08:00
if !strings.HasPrefix(prefix, "/") {
2023-05-31 11:14:15 +08:00
prefix = "/" + prefix
}
svr.anyRequests[prefix] = handle
}
2023-04-23 17:57:36 +08:00
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...)
}
}
2023-08-25 09:51:03 +08:00
func (svr *Server) Root(prefix string, fs http.FileSystem) {
svr.enableDocumentRoot = true
s := newFS(svr.uptime, fs)
s.SetPrefix(prefix)
s.DenyAccessDirectory()
s.SetIndexFile("/index.html")
svr.fileSystem = s
}
2023-04-23 17:57:36 +08:00
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
2023-06-19 16:13:34 +08:00
http.FileServer(newFS(svr.uptime, httpFs)).ServeHTTP(ctx.Response(), ctx.Request())
2023-04-23 17:57:36 +08:00
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) {
2023-08-25 09:51:03 +08:00
var (
err error
file http.File
)
2023-05-31 11:14:15 +08:00
for prefix, handle := range svr.anyRequests {
if strings.HasPrefix(request.URL.Path, prefix) {
handle.ServeHTTP(writer, request)
return
}
}
2023-08-25 09:51:03 +08:00
if svr.enableDocumentRoot && request.Method == http.MethodGet {
uri := path.Clean(request.URL.Path)
if file, err = svr.fileSystem.Open(uri); err == nil {
http.ServeContent(writer, request, path.Base(uri), svr.uptime, file)
err = file.Close()
return
}
}
2023-04-23 17:57:36 +08:00
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{}
2024-04-29 10:52:19 +08:00
atomic.StoreInt32(&svr.exitFlag, 0)
2023-04-23 17:57:36 +08:00
return svr.serve.Serve(l)
}
func (svr *Server) Shutdown() (err error) {
2024-04-29 10:52:19 +08:00
if !atomic.CompareAndSwapInt32(&svr.exitFlag, 0, 1) {
return
}
2023-04-23 17:57:36 +08:00
if svr.serve != nil {
err = svr.serve.Shutdown(svr.ctx)
}
return
}
func New(ctx context.Context) *Server {
svr := &Server{
2024-04-15 11:49:42 +08:00
ctx: ctx,
uptime: time.Now(),
router: router.New(),
beforeRequests: make([]HandleFunc, 0, 10),
anyRequests: make(map[string]http.Handler),
middleware: make([]Middleware, 0, 10),
2023-04-23 17:57:36 +08:00
}
return svr
}