master
parent
2cd5b2ded0
commit
75c9c1c909
22 changed files with 1149 additions and 10 deletions
@ -0,0 +1,25 @@ |
||||
package encoding |
||||
|
||||
import "strings" |
||||
|
||||
type Codec interface { |
||||
Marshal(any) ([]byte, error) |
||||
Unmarshal([]byte, any) error |
||||
Name() string |
||||
} |
||||
|
||||
var registeredCodecs = make(map[string]Codec) |
||||
|
||||
func RegisterCodec(codec Codec) { |
||||
if codec == nil { |
||||
panic("cannot register a nil Codec") |
||||
} |
||||
if codec.Name() == "" { |
||||
panic("cannot register Codec with empty string result for Name()") |
||||
} |
||||
contentSubType := strings.ToLower(codec.Name()) |
||||
registeredCodecs[contentSubType] = codec |
||||
} |
||||
func GetCodec(contentSubType string) Codec { |
||||
return registeredCodecs[contentSubType] |
||||
} |
@ -0,0 +1,80 @@ |
||||
package form |
||||
|
||||
import ( |
||||
"git.diulo.com/mogfee/kit/encoding" |
||||
"github.com/go-playground/form" |
||||
"google.golang.org/protobuf/proto" |
||||
"net/url" |
||||
"reflect" |
||||
) |
||||
|
||||
const ( |
||||
Name = "x-www-form-urlencoded" |
||||
nullStr = "null" |
||||
) |
||||
|
||||
var ( |
||||
encoder = form.NewEncoder() |
||||
decoder = form.NewDecoder() |
||||
) |
||||
|
||||
func init() { |
||||
decoder.SetTagName("json") |
||||
encoder.SetTagName("json") |
||||
encoding.RegisterCodec(codec{ |
||||
encoder: encoder, |
||||
decoder: decoder, |
||||
}) |
||||
} |
||||
|
||||
type codec struct { |
||||
encoder *form.Encoder |
||||
decoder *form.Decoder |
||||
} |
||||
|
||||
func (c codec) Marshal(v any) ([]byte, error) { |
||||
var vs url.Values |
||||
var err error |
||||
if m, ok := v.(proto.Message); ok { |
||||
vs, err = EncodeValues(m) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} else { |
||||
vs, err = c.encoder.Encode(v) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
for k, v := range vs { |
||||
if len(v) == 0 { |
||||
delete(vs, k) |
||||
} |
||||
} |
||||
return []byte(vs.Encode()), err |
||||
} |
||||
|
||||
func (c codec) Unmarshal(data []byte, v any) error { |
||||
vs, err := url.ParseQuery(string(data)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
rv := reflect.ValueOf(v) |
||||
if rv.Kind() == reflect.Ptr { |
||||
if rv.IsNil() { |
||||
rv.Set(reflect.New(rv.Type().Elem())) |
||||
} |
||||
rv = rv.Elem() |
||||
} |
||||
if m, ok := v.(proto.Message); ok { |
||||
return DecodeValues(m, vs) |
||||
} |
||||
if m, ok := rv.Interface().(proto.Message); ok { |
||||
return DecodeValues(m, vs) |
||||
} |
||||
return c.decoder.Decode(v, vs) |
||||
} |
||||
|
||||
func (c codec) Name() string { |
||||
return Name |
||||
} |
@ -1,4 +1,4 @@ |
||||
package xerrors |
||||
package errors |
||||
|
||||
import ( |
||||
"errors" |
@ -1,5 +1,5 @@ |
||||
// nolint:gomnd
|
||||
package xerrors |
||||
package errors |
||||
|
||||
// BadRequest new BadRequest error that is mapped to a 400 response.
|
||||
func BadRequest(reason, message string) *Error { |
@ -0,0 +1,26 @@ |
||||
package httputil |
||||
|
||||
import "strings" |
||||
|
||||
const ( |
||||
baseContentType = "application" |
||||
) |
||||
|
||||
func ContentType(subtype string) string { |
||||
return strings.Join([]string{baseContentType, subtype}, "/") |
||||
} |
||||
|
||||
func ContentSubtype(contentType string) string { |
||||
left := strings.Index(contentType, "/") |
||||
if left == -1 { |
||||
return "" |
||||
} |
||||
right := strings.Index(contentType, ";") |
||||
if right == -1 { |
||||
right = len(contentType) |
||||
} |
||||
if right < left { |
||||
return "" |
||||
} |
||||
return contentType[left+1 : right] |
||||
} |
@ -0,0 +1,56 @@ |
||||
package matcher |
||||
|
||||
import ( |
||||
"git.diulo.com/mogfee/kit/middleware" |
||||
"sort" |
||||
"strings" |
||||
) |
||||
|
||||
type Matcher interface { |
||||
Use(ms ...middleware.Middleware) |
||||
Add(selector string, ms ...middleware.Middleware) |
||||
Match(operation string) []middleware.Middleware |
||||
} |
||||
|
||||
func New() Matcher { |
||||
return &matcher{ |
||||
matchs: make(map[string][]middleware.Middleware), |
||||
} |
||||
} |
||||
|
||||
type matcher struct { |
||||
prefix []string |
||||
defaults []middleware.Middleware |
||||
matchs map[string][]middleware.Middleware |
||||
} |
||||
|
||||
func (m *matcher) Use(ms ...middleware.Middleware) { |
||||
m.defaults = ms |
||||
} |
||||
|
||||
func (m *matcher) Add(selector string, ms ...middleware.Middleware) { |
||||
if strings.HasSuffix(selector, "*") { |
||||
selector = strings.TrimSuffix(selector, "*") |
||||
m.prefix = append(m.prefix, selector) |
||||
sort.Slice(m.prefix, func(i, j int) bool { |
||||
return m.prefix[i] > m.prefix[j] |
||||
}) |
||||
} |
||||
m.matchs[selector] = ms |
||||
} |
||||
|
||||
func (m *matcher) Match(operation string) []middleware.Middleware { |
||||
ms := make([]middleware.Middleware, 0, len(m.defaults)) |
||||
if len(m.defaults) > 0 { |
||||
ms = append(ms, m.defaults...) |
||||
} |
||||
if next, ok := m.matchs[operation]; ok { |
||||
ms = append(ms, next...) |
||||
} |
||||
for _, prefix := range m.prefix { |
||||
if strings.HasPrefix(operation, prefix) { |
||||
return append(ms, m.matchs[prefix]...) |
||||
} |
||||
} |
||||
return ms |
||||
} |
@ -0,0 +1,65 @@ |
||||
package registry |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"sort" |
||||
) |
||||
|
||||
type Registrar interface { |
||||
Register(ctx context.Context, service *ServiceInstance) error |
||||
Deregister(ctx context.Context, service *ServiceInstance) error |
||||
} |
||||
type Discovery interface { |
||||
GetService(ctx context.Context, serverName string) ([]ServiceInstance, error) |
||||
Watch(ctx context.Context, serviceName string) (Watch, error) |
||||
} |
||||
type Watch interface { |
||||
Next() ([]*ServiceInstance, error) |
||||
Stop() error |
||||
} |
||||
|
||||
type ServiceInstance struct { |
||||
ID string `json:"id"` |
||||
Name string `json:"name"` |
||||
Version string `json:"version"` |
||||
Metadata map[string]string `json:"metadata"` |
||||
Endpoints []string `json:"endpoints"` |
||||
} |
||||
|
||||
func (i *ServiceInstance) String() string { |
||||
return fmt.Sprintf("%s-%s", i.Name, i.ID) |
||||
} |
||||
func (i *ServiceInstance) Equal(o any) bool { |
||||
if i == nil && o == nil { |
||||
return true |
||||
} |
||||
if i == nil || o == nil { |
||||
return false |
||||
} |
||||
t, ok := o.(*ServiceInstance) |
||||
if !ok { |
||||
return false |
||||
} |
||||
if len(i.Endpoints) != len(t.Endpoints) { |
||||
return false |
||||
} |
||||
|
||||
sort.Strings(i.Endpoints) |
||||
sort.Strings(t.Endpoints) |
||||
|
||||
for j := 0; j < len(i.Endpoints); j++ { |
||||
if i.Endpoints[j] != t.Endpoints[j] { |
||||
return false |
||||
} |
||||
} |
||||
if len(i.Metadata) != len(t.Metadata) { |
||||
return false |
||||
} |
||||
for k, v := range i.Metadata { |
||||
if v != t.Metadata[k] { |
||||
return false |
||||
} |
||||
} |
||||
return i.ID == t.ID && i.Name != t.Name && i.Version != t.Version |
||||
} |
@ -0,0 +1,16 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
protoc_gen_kit "git.diulo.com/mogfee/protoc-gen-kit" |
||||
) |
||||
|
||||
func main() { |
||||
app := protoc_gen_kit.New( |
||||
protoc_gen_kit.Name("user-server"), |
||||
protoc_gen_kit.Server()) |
||||
fmt.Println("run start") |
||||
app.Run() |
||||
fmt.Println("run end") |
||||
app.Stop() |
||||
} |
@ -0,0 +1,25 @@ |
||||
package binding |
||||
|
||||
import ( |
||||
"git.diulo.com/mogfee/kit/encoding" |
||||
"git.diulo.com/mogfee/kit/encoding/form" |
||||
"git.diulo.com/mogfee/kit/errors" |
||||
"net/http" |
||||
"net/url" |
||||
) |
||||
|
||||
func BindQuery(vars url.Values, target any) error { |
||||
if err := encoding.GetCodec(form.Name).Unmarshal([]byte(vars.Encode()), target); err != nil { |
||||
return errors.BadRequest("CODEC", err.Error()) |
||||
} |
||||
return nil |
||||
} |
||||
func BindForm(req *http.Request, target any) error { |
||||
if err := req.ParseForm(); err != nil { |
||||
return err |
||||
} |
||||
if err := encoding.GetCodec(form.Name).Unmarshal([]byte(req.Form.Encode()), target); err != nil { |
||||
return errors.BadRequest("CODEC", err.Error()) |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,95 @@ |
||||
package http |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"git.diulo.com/mogfee/kit/encoding" |
||||
"git.diulo.com/mogfee/kit/errors" |
||||
"git.diulo.com/mogfee/kit/internal/httputil" |
||||
"git.diulo.com/mogfee/kit/transport/http/binding" |
||||
"github.com/gorilla/mux" |
||||
"io" |
||||
"net/http" |
||||
"net/url" |
||||
) |
||||
|
||||
const SupportPackageIsVersion1 = true |
||||
|
||||
type Redirector interface { |
||||
Redirect() (string, int) |
||||
} |
||||
type Request = http.Request |
||||
type ResponseWriter = http.ResponseWriter |
||||
type Flusher = http.Flusher |
||||
type DecodeRequestFunc func(*http.Request, any) error |
||||
type EncodeResponseFunc func(http.ResponseWriter, *http.Request, any) error |
||||
type EncodeErrorFunc func(w http.ResponseWriter, r *http.Request, err error) |
||||
|
||||
func DefaultRequestVars(r *http.Request, v any) error { |
||||
raws := mux.Vars(r) |
||||
vars := make(url.Values, len(raws)) |
||||
for k, v := range raws { |
||||
vars[k] = []string{v} |
||||
} |
||||
return binding.BindQuery(vars, v) |
||||
} |
||||
func DefaultRequestQuery(r *http.Request, v any) error { |
||||
return binding.BindQuery(r.URL.Query(), v) |
||||
} |
||||
func DefaultRequestDecoder(r *http.Request, v any) error { |
||||
codec, ok := CodeForRequest(r, "Content-type") |
||||
if !ok { |
||||
return errors.BadRequest("CODEC", fmt.Sprintf("unregister Content-Type: %s", codec)) |
||||
} |
||||
data, err := io.ReadAll(r.Body) |
||||
if err != nil { |
||||
return errors.BadRequest("CODEC", err.Error()) |
||||
} |
||||
r.Body = io.NopCloser(bytes.NewBuffer(data)) |
||||
if len(data) == 0 { |
||||
return nil |
||||
} |
||||
if err = codec.Unmarshal(data, v); err != nil { |
||||
return errors.BadRequest("CODEC", err.Error()) |
||||
} |
||||
return nil |
||||
} |
||||
func DefaultResponseEncoder(w http.ResponseWriter, r *http.Request, v any) error { |
||||
if v == nil { |
||||
return nil |
||||
} |
||||
if rd, ok := v.(Redirector); ok { |
||||
url, code := rd.Redirect() |
||||
http.Redirect(w, r, url, code) |
||||
return nil |
||||
} |
||||
codec, _ := CodeForRequest(r, "Accept") |
||||
data, err := codec.Marshal(v) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
w.Header().Set("Content-type", httputil.ContentType(codec.Name())) |
||||
_, err = w.Write(data) |
||||
return err |
||||
} |
||||
func DefaultErrorEncoder(w http.ResponseWriter, r *http.Request, err error) { |
||||
se := errors.FromError(err) |
||||
codec, _ := CodeForRequest(r, "Accept") |
||||
body, err := codec.Marshal(se) |
||||
if err != nil { |
||||
w.WriteHeader(http.StatusInternalServerError) |
||||
return |
||||
} |
||||
w.Header().Set("Content-Type", httputil.ContentType(codec.Name())) |
||||
w.WriteHeader(int(se.Status)) |
||||
_, _ = w.Write(body) |
||||
} |
||||
func CodeForRequest(r *http.Request, name string) (encoding.Codec, bool) { |
||||
for _, accept := range r.Header[name] { |
||||
codec := encoding.GetCodec(httputil.ContentSubtype(accept)) |
||||
if codec != nil { |
||||
return codec, true |
||||
} |
||||
} |
||||
return encoding.GetCodec("json"), false |
||||
} |
@ -0,0 +1,201 @@ |
||||
package http |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"encoding/xml" |
||||
"git.diulo.com/mogfee/kit/middleware" |
||||
"git.diulo.com/mogfee/kit/transport" |
||||
"git.diulo.com/mogfee/kit/transport/http/binding" |
||||
"github.com/gorilla/mux" |
||||
"io" |
||||
"net/http" |
||||
"net/url" |
||||
"time" |
||||
) |
||||
|
||||
type Context interface { |
||||
context.Context |
||||
Vars() url.Values |
||||
Query() url.Values |
||||
Form() url.Values |
||||
Header() http.Header |
||||
Request() *http.Request |
||||
Response() http.ResponseWriter |
||||
Middleware(middleware.Handler) middleware.Handler |
||||
Bind(interface{}) error |
||||
BindVars(interface{}) error |
||||
BindQuery(interface{}) error |
||||
BindForm(interface{}) error |
||||
Returns(interface{}, error) error |
||||
Result(int, interface{}) error |
||||
JSON(int, interface{}) error |
||||
XML(int, interface{}) error |
||||
String(int, string) error |
||||
Blob(int, string, []byte) error |
||||
Stream(int, string, io.Reader) error |
||||
Reset(http.ResponseWriter, *http.Request) |
||||
} |
||||
type responseWriter struct { |
||||
code int |
||||
w http.ResponseWriter |
||||
} |
||||
|
||||
func (r *responseWriter) Header() http.Header { |
||||
return r.w.Header() |
||||
} |
||||
|
||||
func (r *responseWriter) Write(bytes []byte) (int, error) { |
||||
r.w.WriteHeader(r.code) |
||||
return r.w.Write(bytes) |
||||
} |
||||
|
||||
func (r *responseWriter) WriteHeader(statusCode int) { |
||||
r.code = statusCode |
||||
} |
||||
|
||||
func (r *responseWriter) reset(res http.ResponseWriter) { |
||||
r.w = res |
||||
r.code = http.StatusOK |
||||
} |
||||
|
||||
type wrapper struct { |
||||
router *Router |
||||
req *http.Request |
||||
res http.ResponseWriter |
||||
w responseWriter |
||||
} |
||||
|
||||
func (w *wrapper) Deadline() (deadline time.Time, ok bool) { |
||||
if w.req == nil { |
||||
return time.Time{}, false |
||||
} |
||||
return w.req.Context().Deadline() |
||||
} |
||||
|
||||
func (w *wrapper) Done() <-chan struct{} { |
||||
if w.req == nil { |
||||
return nil |
||||
} |
||||
return w.req.Context().Done() |
||||
} |
||||
|
||||
func (w *wrapper) Err() error { |
||||
if w.req == nil { |
||||
return context.Canceled |
||||
} |
||||
return w.req.Context().Err() |
||||
} |
||||
|
||||
func (w *wrapper) Value(key any) any { |
||||
if w.req == nil { |
||||
return nil |
||||
} |
||||
return w.req.Context().Value(key) |
||||
} |
||||
|
||||
func (w *wrapper) Vars() url.Values { |
||||
raws := mux.Vars(w.req) |
||||
vars := make(url.Values, len(raws)) |
||||
for k, v := range raws { |
||||
vars[k] = []string{v} |
||||
} |
||||
return vars |
||||
} |
||||
|
||||
func (w *wrapper) Query() url.Values { |
||||
return w.req.URL.Query() |
||||
} |
||||
|
||||
func (w *wrapper) Form() url.Values { |
||||
if err := w.req.ParseForm(); err != nil { |
||||
return url.Values{} |
||||
} |
||||
return w.req.Form |
||||
} |
||||
|
||||
func (w *wrapper) Header() http.Header { |
||||
return w.req.Header |
||||
} |
||||
|
||||
func (w *wrapper) Request() *http.Request { |
||||
return w.req |
||||
} |
||||
|
||||
func (w *wrapper) Response() http.ResponseWriter { |
||||
return w.res |
||||
} |
||||
|
||||
func (w *wrapper) Middleware(handler middleware.Handler) middleware.Handler { |
||||
if tr, ok := transport.FromServerContext(w.req.Context()); ok { |
||||
return middleware.Chain(w.router.srv.middleware.Match(tr.Operation())...)(handler) |
||||
} |
||||
return middleware.Chain(w.router.srv.middleware.Match(w.req.URL.Path)...)(handler) |
||||
} |
||||
|
||||
func (w *wrapper) Bind(i any) error { |
||||
return w.router.srv.decBody(w.req, i) |
||||
} |
||||
|
||||
func (w *wrapper) BindVars(i interface{}) error { |
||||
return w.router.srv.decVars(w.req, i) |
||||
} |
||||
|
||||
func (w *wrapper) BindQuery(i interface{}) error { |
||||
return w.router.srv.decQuery(w.req, i) |
||||
} |
||||
|
||||
func (w *wrapper) BindForm(i interface{}) error { |
||||
return binding.BindForm(w.req, i) |
||||
} |
||||
|
||||
func (w *wrapper) Returns(i interface{}, err error) error { |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return w.router.srv.enc(&w.w, w.req, i) |
||||
} |
||||
|
||||
func (w *wrapper) Result(i int, i2 interface{}) error { |
||||
w.w.WriteHeader(i) |
||||
return w.router.srv.enc(&w.w, w.req, i2) |
||||
} |
||||
|
||||
func (w *wrapper) JSON(i int, i2 interface{}) error { |
||||
w.res.Header().Set("Content-Type", "application/json") |
||||
w.res.WriteHeader(i) |
||||
return json.NewEncoder(w.res).Encode(i2) |
||||
} |
||||
|
||||
func (w *wrapper) XML(i int, i2 interface{}) error { |
||||
w.res.Header().Set("Content-Type", "application/xml") |
||||
w.res.WriteHeader(i) |
||||
return xml.NewEncoder(w.res).Encode(i2) |
||||
} |
||||
|
||||
func (w *wrapper) String(i int, s string) error { |
||||
w.res.Header().Set("Content-Type", "text/plain") |
||||
w.res.WriteHeader(i) |
||||
_, err := w.res.Write([]byte(s)) |
||||
return err |
||||
} |
||||
|
||||
func (w *wrapper) Blob(i int, s string, bytes []byte) error { |
||||
w.res.Header().Set("Content-Type", s) |
||||
w.res.WriteHeader(i) |
||||
_, err := w.res.Write(bytes) |
||||
return err |
||||
} |
||||
|
||||
func (w *wrapper) Stream(i int, s string, reader io.Reader) error { |
||||
w.res.Header().Set("Content-Type", s) |
||||
w.res.WriteHeader(i) |
||||
_, err := io.Copy(w.res, reader) |
||||
return err |
||||
} |
||||
|
||||
func (w *wrapper) Reset(writer http.ResponseWriter, request *http.Request) { |
||||
w.w.reset(writer) |
||||
w.res = writer |
||||
w.req = request |
||||
} |
@ -0,0 +1,14 @@ |
||||
package http |
||||
|
||||
import "net/http" |
||||
|
||||
type FilterFunc func(http.Handler) http.Handler |
||||
|
||||
func FilterChain(filters ...FilterFunc) FilterFunc { |
||||
return func(next http.Handler) http.Handler { |
||||
for i := len(filters) - 1; i >= 0; i-- { |
||||
next = filters[i](next) |
||||
} |
||||
return next |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
package http |
||||
|
||||
import ( |
||||
"net/http" |
||||
"path" |
||||
"sync" |
||||
) |
||||
|
||||
type WalkRoutFunc func(RouteInfo) error |
||||
|
||||
type RouteInfo struct { |
||||
Path string |
||||
Method string |
||||
} |
||||
type HandlerFunc func(Context) error |
||||
type Router struct { |
||||
prefix string |
||||
pool sync.Pool |
||||
srv *Server |
||||
filters []FilterFunc |
||||
} |
||||
|
||||
func newRouter(prefix string, srv *Server, filters ...FilterFunc) *Router { |
||||
r := &Router{ |
||||
prefix: prefix, |
||||
srv: srv, |
||||
filters: filters, |
||||
} |
||||
r.pool.New = func() any { |
||||
return &wrapper{router: r} |
||||
} |
||||
return r |
||||
} |
||||
func (r *Router) Group(prefix string, filters ...FilterFunc) *Router { |
||||
var newFilters []FilterFunc |
||||
newFilters = append(newFilters, r.filters...) |
||||
newFilters = append(newFilters, filters...) |
||||
return newRouter(path.Join(r.prefix, prefix), r.srv, newFilters...) |
||||
} |
||||
func (r *Router) Handle(method string, relativePath string, h HandlerFunc, filters ...FilterFunc) { |
||||
next := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { |
||||
ctx := r.pool.Get().(Context) |
||||
ctx.Reset(res, req) |
||||
if err := h(ctx); err != nil { |
||||
r.srv.ene(res, req, err) |
||||
} |
||||
ctx.Reset(nil, nil) |
||||
r.pool.Put(ctx) |
||||
})) |
||||
next = FilterChain(filters...)(next) |
||||
next = FilterChain(r.filters...)(next) |
||||
r.srv.router.Handle(path.Join(r.prefix, relativePath), next).Methods(method) |
||||
} |
||||
func (r *Router) GET(path string, h HandlerFunc, m ...FilterFunc) { |
||||
r.Handle(http.MethodGet, path, h, m...) |
||||
} |
||||
func (r *Router) POST(path string, h HandlerFunc, m ...FilterFunc) { |
||||
r.Handle(http.MethodPost, path, h, m...) |
||||
} |
@ -0,0 +1,263 @@ |
||||
package http |
||||
|
||||
import ( |
||||
"context" |
||||
"crypto/tls" |
||||
"git.diulo.com/mogfee/kit/internal/matcher" |
||||
"git.diulo.com/mogfee/kit/middleware" |
||||
"git.diulo.com/mogfee/kit/transport" |
||||
"github.com/gorilla/mux" |
||||
"net" |
||||
"net/http" |
||||
"net/url" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
_ transport.Server = (*Server)(nil) |
||||
_ transport.Endpointer = (*Server)(nil) |
||||
_ http.Handler = (*Server)(nil) |
||||
) |
||||
|
||||
type ServerOption func(server *Server) |
||||
|
||||
// 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() |
||||
} |
||||
} |
||||
|
||||
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 |
||||
} |
||||
|
||||
func NewServer(opts ...ServerOption) *Server { |
||||
srv := &Server{ |
||||
network: "tcp", |
||||
address: ":0", |
||||
timeout: 1 * time.Second, |
||||
middleware: matcher.New(), |
||||
decVars: DefaultRequestVars, |
||||
decQuery: DefaultRequestQuery, |
||||
decBody: DefaultRequestDecoder, |
||||
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 |
||||
} |
||||
func (s *Server) Use(selector string, m ...middleware.Middleware) { |
||||
s.middleware.Add(selector, m...) |
||||
} |
||||
func (s *Server) WalkRoute(fn WalkRoutFunc) error { |
||||
return s.router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { |
||||
methods, err := route.GetMethods() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
path, err := route.GetPathTemplate() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
for _, method := range methods { |
||||
if err = fn(RouteInfo{ |
||||
Method: method, |
||||
Path: path, |
||||
}); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
} |
||||
func (s *Server) Kind() transport.Kind { |
||||
return transport.htt |
||||
} |
||||
func (s *Server) Route(prefix string, filters ...FilterFunc) *Router { |
||||
return newRouter(prefix, s, filters...) |
||||
} |
||||
func (s *Server) Handle(path string, h http.Handler) { |
||||
s.router.Handle(path, h) |
||||
} |
||||
func (s *Server) HandlePrefix(prefix string, h http.Handler) { |
||||
s.router.PathPrefix(prefix).Handler(h) |
||||
} |
||||
func (s *Server) HandleFunc(path string, h http.HandlerFunc) { |
||||
s.router.HandleFunc(path, h) |
||||
} |
||||
func (s *Server) HandleHeader(key, val string, h http.Handler) { |
||||
s.router.Headers(key, val).Handler(h) |
||||
} |
||||
func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { |
||||
s.Handler.ServeHTTP(res, req) |
||||
} |
||||
func (s *Server) filter() mux.MiddlewareFunc { |
||||
return func(handler http.Handler) http.Handler { |
||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { |
||||
var ( |
||||
ctx context.Context |
||||
cancel context.CancelFunc |
||||
) |
||||
|
||||
if s.timeout > 0 { |
||||
ctx, cancel = context.WithTimeout(request.Context(), s.timeout) |
||||
} else { |
||||
ctx, cancel = context.WithCancel(request.Context()) |
||||
} |
||||
defer cancel() |
||||
|
||||
pathTemplate := request.URL.Path |
||||
if route := mux.CurrentRoute(request); route != nil { |
||||
pathTemplate, _ = route.GetPathTemplate() |
||||
} |
||||
tr := &Transport{} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func (s *Server) Endpoint() string { |
||||
//TODO implement me
|
||||
panic("implement me") |
||||
} |
||||
|
||||
func (s *Server) Operation() string { |
||||
//TODO implement me
|
||||
panic("implement me") |
||||
} |
||||
|
||||
func (s *Server) RequestHeader() transport.Header { |
||||
//TODO implement me
|
||||
panic("implement me") |
||||
} |
||||
|
||||
func (s *Server) ReplyHeader() transport.Header { |
||||
//TODO implement me
|
||||
panic("implement me") |
||||
} |
@ -0,0 +1,65 @@ |
||||
package http |
||||
|
||||
import ( |
||||
"context" |
||||
"git.diulo.com/mogfee/kit/transport" |
||||
"net/http" |
||||
) |
||||
|
||||
type Transporter interface { |
||||
transport.Transporter |
||||
Request() *http.Request |
||||
PathTemplate() string |
||||
} |
||||
|
||||
type Transport struct { |
||||
endpoint string |
||||
operation string |
||||
reqHeader headerCarrier |
||||
replyHeader headerCarrier |
||||
request *http.Request |
||||
pathTemplate string |
||||
} |
||||
|
||||
func (t *Transport) Kind() transport.Kind { |
||||
return transport.KindHTTP |
||||
} |
||||
|
||||
func (t *Transport) Endpoint() string { |
||||
return t.endpoint |
||||
} |
||||
|
||||
func (t *Transport) Operation() string { |
||||
return t.operation |
||||
} |
||||
|
||||
func (t *Transport) RequestHeader() transport.Header { |
||||
return t.reqHeader |
||||
} |
||||
|
||||
func (t *Transport) ReplyHeader() transport.Header { |
||||
return t.replyHeader |
||||
} |
||||
|
||||
func (t *Transport) Request() *http.Request { |
||||
return t.request |
||||
} |
||||
|
||||
func (t *Transport) PathTemplate() string { |
||||
return t.pathTemplate |
||||
} |
||||
func SetOperation(ctx context.Context, op string) { |
||||
if tr, ok := transport.FromServerContext(ctx); ok { |
||||
if tr, ok := tr.(*Transport); ok { |
||||
tr.operation = op |
||||
} |
||||
} |
||||
} |
||||
func RequestFromServerContext(ctx context.Context) (*http.Request, bool) { |
||||
if tr, ok := transport.FromServerContext(ctx); ok { |
||||
if tr, ok := tr.(*Transport); ok { |
||||
return tr.request, true |
||||
} |
||||
} |
||||
return nil, false |
||||
} |
@ -0,0 +1,58 @@ |
||||
package transport |
||||
|
||||
import ( |
||||
"context" |
||||
"net/url" |
||||
) |
||||
|
||||
type Server interface { |
||||
Start(context.Context) error |
||||
Stop(context.Context) error |
||||
} |
||||
|
||||
type Endpointer interface { |
||||
Endpoint() (*url.URL, error) |
||||
} |
||||
type Header interface { |
||||
Get(key string) string |
||||
Set(key string, value string) |
||||
Keys() []string |
||||
} |
||||
type Transporter interface { |
||||
Kind() Kind |
||||
Endpoint() string |
||||
Operation() string |
||||
RequestHeader() Header |
||||
ReplyHeader() Header |
||||
} |
||||
type Kind string |
||||
|
||||
func (k Kind) String() string { |
||||
return string(k) |
||||
} |
||||
|
||||
const ( |
||||
KindGRPC Kind = "grpc" |
||||
KindHTTP Kind = "http" |
||||
) |
||||
|
||||
type ( |
||||
serverTransportKey struct{} |
||||
clientTransportKey struct{} |
||||
) |
||||
|
||||
func NewServerContext(ctx context.Context, tr Transporter) context.Context { |
||||
return context.WithValue(ctx, serverTransportKey{}, tr) |
||||
} |
||||
func FromServerContext(ctx context.Context) (tr Transporter, ok bool) { |
||||
tr, ok = ctx.Value(serverTransportKey{}).(Transporter) |
||||
return |
||||
} |
||||
func NewClientContext(ctx context.Context, tr Transporter) context.Context { |
||||
return context.WithValue(ctx, clientTransportKey{}, tr) |
||||
} |
||||
|
||||
func FromClientContext(ctx context.Context) (tr Transporter, ok bool) { |
||||
tr, ok = ctx.Value(clientTransportKey{}).(Transporter) |
||||
return |
||||
} |
Loading…
Reference in new issue