package http import ( "context" "git.nspix.com/golang/micro/gateway/http/router" "golang.org/x/net/websocket" "net" "net/http" "sync" ) var ( contextPool sync.Pool ) type contextRouteKey struct{} type HandleFunc func(ctx *Context) (err error) type Middleware func(next HandleFunc) HandleFunc type Node struct { Method string Path string Handle HandleFunc } type Server struct { svr *http.Server middleware []Middleware keepAlive bool isSetKeepAlive bool router *router.Router } func getContext() *Context { if v := contextPool.Get(); v != nil { return v.(*Context) } return &Context{} } func releaseContext(c *Context) { contextPool.Put(c) } func (r *Server) Group(prefix string, nodes []Node, middleware ...Middleware) { for _, node := range nodes { r.Handle(node.Method, prefix+node.Path, node.Handle, middleware...) } } func (r *Server) Handler(method, path string, h http.Handler) { r.router.Handler(method, path, h) } func (r *Server) createWebsocket(h websocket.Handler) *websocket.Server { return &websocket.Server{ Handshake: func(cfg *websocket.Config, req *http.Request) (err error) { cfg.Origin, err = websocket.Origin(cfg, req) if err == nil && cfg != nil { if cfg.Header == nil { cfg.Header = http.Header(make(map[string][]string)) } if cfg.Origin != nil { cfg.Header.Set("Access-Control-Allow-Origin", cfg.Origin.Host) } cfg.Header.Set("Access-Control-Request-Headers", "GET,POST,PUT,OPTIONS") cfg.Header.Set("Access-Control-Allow-Credentials", "true") } return }, Handler: h, } } func (r *Server) Websocket(path string, h websocket.Handler) { r.Handler("GET", path, r.createWebsocket(h)) } func (r *Server) Handle(method string, path string, h HandleFunc, middleware ...Middleware) { r.router.Handle(method, path, func(writer http.ResponseWriter, request *http.Request, params router.Params) { ctx := getContext() ps := make(map[string]string) for _, v := range params { ps[v.Key] = v.Value } ctx.Reset(request, writer, ps) for i := len(r.middleware) - 1; i >= 0; i-- { h = r.middleware[i](h) } for i := len(middleware) - 1; i >= 0; i-- { h = middleware[i](h) } if err := h(ctx); err != nil { writer.WriteHeader(http.StatusInternalServerError) } releaseContext(ctx) }) } func (r *Server) Use(ms ...Middleware) { r.middleware = append(r.middleware, ms...) } func (r *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { if req.Method == http.MethodOptions { 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) return } else { 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) } r.router.ServeHTTP(res, req) } } func (r *Server) Server() *http.Server { return r.svr } func (r *Server) SetKeepAlivesEnabled(enable bool) { r.isSetKeepAlive = true r.keepAlive = enable } func (r *Server) ListenAndServe(addr string) (err error) { r.svr = &http.Server{ Addr: addr, Handler: r, } if r.isSetKeepAlive { r.svr.SetKeepAlivesEnabled(r.keepAlive) } return r.svr.ListenAndServe() } func (r *Server) Serve(l net.Listener) (err error) { r.svr = &http.Server{ Handler: r, } if r.isSetKeepAlive { r.svr.SetKeepAlivesEnabled(r.keepAlive) } return r.svr.Serve(l) } func (r *Server) Shutdown(ctx context.Context) (err error) { if r.svr != nil { return r.svr.Shutdown(ctx) } return } func New() *Server { return &Server{ router: router.New(), } } func WithContext(ctx context.Context, route *Server) context.Context { return context.WithValue(ctx, contextRouteKey{}, route) } func FromContext(ctx context.Context) *Server { if v := ctx.Value(contextRouteKey{}); v != nil { return v.(*Server) } return nil }