You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

333 lines
8.0 KiB

2 years ago
package http
import (
"context"
"crypto/tls"
2 years ago
"errors"
"git.diulo.com/mogfee/kit/internal/endpoint"
"git.diulo.com/mogfee/kit/internal/host"
2 years ago
"git.diulo.com/mogfee/kit/internal/matcher"
2 years ago
"git.diulo.com/mogfee/kit/log"
2 years ago
"git.diulo.com/mogfee/kit/middleware"
"git.diulo.com/mogfee/kit/transport"
"net"
"net/http"
"net/url"
"time"
2 years ago
"github.com/gorilla/mux"
2 years ago
)
var (
_ transport.Server = (*Server)(nil)
_ transport.Endpointer = (*Server)(nil)
_ http.Handler = (*Server)(nil)
)
2 years ago
// ServerOption is an HTTP server option.
type ServerOption func(*Server)
2 years ago
// Network with server network.
func Network(network string) ServerOption {
return func(s *Server) {
s.network = network
}
}
// Address with server address.
func Address(addr string) ServerOption {
return func(s *Server) {
s.address = addr
}
}
// Timeout with server timeout.
func Timeout(timeout time.Duration) ServerOption {
return func(s *Server) {
s.timeout = timeout
}
}
// Logger with server logger.
// Deprecated: use global logger instead.
func Logger(logger log.Logger) ServerOption {
return func(s *Server) {}
}
// Middleware with service middleware option.
func Middleware(m ...middleware.Middleware) ServerOption {
return func(o *Server) {
o.middleware.Use(m...)
}
}
// Filter with HTTP middleware option.
func Filter(filters ...FilterFunc) ServerOption {
return func(o *Server) {
o.filters = filters
}
}
// RequestVarsDecoder with request decoder.
func RequestVarsDecoder(dec DecodeRequestFunc) ServerOption {
return func(o *Server) {
o.decVars = dec
}
}
// RequestQueryDecoder with request decoder.
func RequestQueryDecoder(dec DecodeRequestFunc) ServerOption {
return func(o *Server) {
o.decQuery = dec
}
}
// RequestDecoder with request decoder.
func RequestDecoder(dec DecodeRequestFunc) ServerOption {
return func(o *Server) {
o.decBody = dec
}
}
// ResponseEncoder with response encoder.
func ResponseEncoder(en EncodeResponseFunc) ServerOption {
return func(o *Server) {
o.enc = en
}
}
// ErrorEncoder with error encoder.
func ErrorEncoder(en EncodeErrorFunc) ServerOption {
return func(o *Server) {
o.ene = en
}
}
// TLSConfig with TLS config.
func TLSConfig(c *tls.Config) ServerOption {
return func(o *Server) {
o.tlsConf = c
}
}
// StrictSlash is with mux's StrictSlash
// If true, when the path pattern is "/path/", accessing "/path" will
// redirect to the former and vice versa.
func StrictSlash(strictSlash bool) ServerOption {
return func(o *Server) {
o.strictSlash = strictSlash
}
}
// Listener with server lis
func Listener(lis net.Listener) ServerOption {
return func(s *Server) {
s.lis = lis
}
}
// PathPrefix with mux's PathPrefix, router will replaced by a subrouter that start with prefix.
func PathPrefix(prefix string) ServerOption {
return func(s *Server) {
s.router = s.router.PathPrefix(prefix).Subrouter()
}
}
2 years ago
// Server is an HTTP server wrapper.
2 years ago
type Server struct {
*http.Server
lis net.Listener
tlsConf *tls.Config
endpoint *url.URL
err error
network string
address string
timeout time.Duration
filters []FilterFunc
middleware matcher.Matcher
decVars DecodeRequestFunc
decQuery DecodeRequestFunc
decBody DecodeRequestFunc
enc EncodeResponseFunc
ene EncodeErrorFunc
strictSlash bool
router *mux.Router
}
2 years ago
// NewServer creates an HTTP server by options.
2 years ago
func NewServer(opts ...ServerOption) *Server {
srv := &Server{
network: "tcp",
address: ":0",
timeout: 1 * time.Second,
middleware: matcher.New(),
decVars: DefaultRequestVars,
decQuery: DefaultRequestQuery,
decBody: DefaultRequestDecoder,
2 years ago
enc: DefaultResponseEncoder,
ene: DefaultErrorEncoder,
2 years ago
strictSlash: true,
router: mux.NewRouter(),
}
for _, o := range opts {
o(srv)
}
srv.router.StrictSlash(srv.strictSlash)
srv.router.NotFoundHandler = http.DefaultServeMux
srv.router.MethodNotAllowedHandler = http.DefaultServeMux
srv.router.Use(srv.filter())
srv.Server = &http.Server{
Handler: FilterChain(srv.filters...)(srv.router),
TLSConfig: srv.tlsConf,
}
return srv
}
2 years ago
// Use uses a service middleware with selector.
// selector:
// - '/*'
// - '/helloworld.v1.Greeter/*'
// - '/helloworld.v1.Greeter/SayHello'
2 years ago
func (s *Server) Use(selector string, m ...middleware.Middleware) {
s.middleware.Add(selector, m...)
}
2 years ago
// WalkRoute walks the router and all its sub-routers, calling walkFn for each route in the tree.
func (s *Server) WalkRoute(fn WalkRouteFunc) error {
2 years ago
return s.router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
methods, err := route.GetMethods()
if err != nil {
2 years ago
return nil // ignore no methods
2 years ago
}
path, err := route.GetPathTemplate()
if err != nil {
return err
}
for _, method := range methods {
2 years ago
if err := fn(RouteInfo{Method: method, Path: path}); err != nil {
2 years ago
return err
}
}
return nil
})
}
2 years ago
// Route registers an HTTP router.
2 years ago
func (s *Server) Kind() transport.Kind {
2 years ago
return transport.KindHTTP
2 years ago
}
func (s *Server) Route(prefix string, filters ...FilterFunc) *Router {
return newRouter(prefix, s, filters...)
}
2 years ago
// Handle registers a new route with a matcher for the URL path.
2 years ago
func (s *Server) Handle(path string, h http.Handler) {
s.router.Handle(path, h)
}
2 years ago
// HandlePrefix registers a new route with a matcher for the URL path prefix.
2 years ago
func (s *Server) HandlePrefix(prefix string, h http.Handler) {
s.router.PathPrefix(prefix).Handler(h)
}
2 years ago
// HandleFunc registers a new route with a matcher for the URL path.
2 years ago
func (s *Server) HandleFunc(path string, h http.HandlerFunc) {
s.router.HandleFunc(path, h)
}
2 years ago
// HandleHeader registers a new route with a matcher for the header.
func (s *Server) HandleHeader(key, val string, h http.HandlerFunc) {
2 years ago
s.router.Headers(key, val).Handler(h)
}
2 years ago
// ServeHTTP should write reply headers and data to the ResponseWriter and then return.
2 years ago
func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
s.Handler.ServeHTTP(res, req)
}
2 years ago
2 years ago
func (s *Server) Endpoint() (*url.URL, error) {
if err := s.listenAndEndpoint(); err != nil {
return nil, err
}
return s.endpoint, nil
}
func (s *Server) Start(ctx context.Context) error {
if err := s.listenAndEndpoint(); err != nil {
return err
}
s.BaseContext = func(listener net.Listener) context.Context {
return ctx
}
log.Infof("[HTTP] server listening on: %s", s.endpoint)
var err error
if s.tlsConf != nil {
err = s.ServeTLS(s.lis, "", "")
} else {
err = s.Serve(s.lis)
}
if !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
}
func (s *Server) Stop(ctx context.Context) error {
log.Info("[HTTP] server stopping")
return s.Shutdown(ctx)
}
2 years ago
func (s *Server) filter() mux.MiddlewareFunc {
2 years ago
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
2 years ago
var (
ctx context.Context
cancel context.CancelFunc
)
if s.timeout > 0 {
2 years ago
ctx, cancel = context.WithTimeout(req.Context(), s.timeout)
2 years ago
} else {
2 years ago
ctx, cancel = context.WithCancel(req.Context())
2 years ago
}
defer cancel()
2 years ago
pathTemplate := req.URL.Path
if route := mux.CurrentRoute(req); route != nil {
// /path/123 -> /path/{id}
2 years ago
pathTemplate, _ = route.GetPathTemplate()
}
2 years ago
tr := &Transport{
operation: pathTemplate,
pathTemplate: pathTemplate,
reqHeader: headerCarrier(req.Header),
2 years ago
replyHeader: headerCarrier(w.Header()),
2 years ago
request: req,
}
if s.endpoint != nil {
tr.endpoint = s.endpoint.String()
}
tr.request = req.WithContext(transport.NewServerContext(ctx, tr))
next.ServeHTTP(w, tr.request)
2 years ago
})
}
}
2 years ago
func (s *Server) listenAndEndpoint() error {
if s.lis == nil {
lis, err := net.Listen(s.network, s.address)
if err != nil {
s.err = err
return err
}
s.lis = lis
}
if s.endpoint == nil {
addr, err := host.Extract(s.address, s.lis)
if err != nil {
s.err = err
return err
}
s.endpoint = endpoint.NewEndpoint(endpoint.Scheme("http", s.tlsConf != nil), addr)
}
return s.err
2 years ago
}