Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f4316cc13a | ||
|
19860f713c |
28
container/set/interface.go
Normal file
28
container/set/interface.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package set
|
||||||
|
|
||||||
|
var x = struct{}{}
|
||||||
|
|
||||||
|
type Set map[any]struct{}
|
||||||
|
|
||||||
|
func (s *Set) Init() {
|
||||||
|
for k := range *s {
|
||||||
|
delete(*s, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Set) Add(e any) {
|
||||||
|
(*s)[e] = x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Set) Remove(e any) {
|
||||||
|
delete(*s, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Set) Contains(e any) bool {
|
||||||
|
_, c := (*s)[e]
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Set {
|
||||||
|
return new(Set)
|
||||||
|
}
|
@@ -2,13 +2,14 @@ package log
|
|||||||
|
|
||||||
// Logger is a logging interface with only the essentials that a function that needs to log should care about. Compatible with standard Go logger.
|
// Logger is a logging interface with only the essentials that a function that needs to log should care about. Compatible with standard Go logger.
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
Fatal(v ...any)
|
Print(v ...interface{})
|
||||||
Fatalf(format string, v ...any)
|
Printf(format string, v ...interface{})
|
||||||
Fatalln(v ...any)
|
Println(v ...interface{})
|
||||||
Panic(v ...any)
|
|
||||||
Panicf(format string, v ...any)
|
|
||||||
Panicln(v ...any)
|
|
||||||
Print(v ...any)
|
|
||||||
Printf(format string, v ...any)
|
|
||||||
Println(v ...any)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// None provides a logger that doesnt log anything
|
||||||
|
type None struct{}
|
||||||
|
|
||||||
|
func (n None) Print(v ...interface{}) {}
|
||||||
|
func (n None) Printf(format string, v ...interface{}) {}
|
||||||
|
func (n None) Println(v ...interface{}) {}
|
||||||
|
14
log/none.go
14
log/none.go
@@ -1,14 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
// None provides a logger that doesnt log anything
|
|
||||||
type None struct{}
|
|
||||||
|
|
||||||
func (n None) Fatal(v ...any) {}
|
|
||||||
func (n None) Fatalf(format string, v ...any) {}
|
|
||||||
func (n None) Fatalln(v ...any) {}
|
|
||||||
func (n None) Panic(v ...any) {}
|
|
||||||
func (n None) Panicf(format string, v ...any) {}
|
|
||||||
func (n None) Panicln(v ...any) {}
|
|
||||||
func (n None) Print(v ...any) {}
|
|
||||||
func (n None) Printf(format string, v ...any) {}
|
|
||||||
func (n None) Println(v ...any) {}
|
|
@@ -30,7 +30,7 @@ func MutliHandler(h map[string]http.Handler) (http.HandlerFunc, error) {
|
|||||||
if hdlr, ok := h[r.Method]; ok {
|
if hdlr, ok := h[r.Method]; ok {
|
||||||
hdlr.ServeHTTP(w, r)
|
hdlr.ServeHTTP(w, r)
|
||||||
} else {
|
} else {
|
||||||
NotAllowedHandler.ServeHTTP(w, r)
|
NotImplementedHandler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
155
net/http/mux.go
155
net/http/mux.go
@@ -1,106 +1,95 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// wayContextKey is the context key type for storing
|
|
||||||
// parameters in context.Context.
|
|
||||||
type wayContextKey string
|
|
||||||
|
|
||||||
// Router routes HTTP requests.
|
|
||||||
type ServeMux struct {
|
type ServeMux struct {
|
||||||
routes []*route
|
routes []route
|
||||||
// NotFound is the http.Handler to call when no routes
|
|
||||||
// match. By default uses http.NotFoundHandler().
|
|
||||||
NotFound http.Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRouter makes a new Router.
|
func (mux *ServeMux) Handle(pattern string, handler http.Handler, pathParams ...any) {
|
||||||
func NewServeMux() *ServeMux {
|
mux.routes = append(mux.routes, newRoute(pattern, handler, pathParams...))
|
||||||
return &ServeMux{
|
}
|
||||||
NotFound: http.NotFoundHandler(),
|
|
||||||
|
func (mux *ServeMux) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request), pathParams ...any) {
|
||||||
|
mux.routes = append(mux.routes, newRoute(pattern, http.HandlerFunc(handler), pathParams...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mux *ServeMux) Handler(r *http.Request) (h http.Handler, pattern string) {
|
||||||
|
for _, rte := range mux.routes {
|
||||||
|
switch {
|
||||||
|
case rte.matcher(r):
|
||||||
|
return rte.handler, rte.pattern
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ServeMux) pathSegments(p string) []string {
|
|
||||||
return strings.Split(strings.Trim(p, "/"), "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle adds a handler with the specified pattern.
|
|
||||||
// Pattern can contain path segments such as: /item/:id which is
|
|
||||||
// accessible via the Param function.
|
|
||||||
// If pattern ends with trailing /, it acts as a prefix.
|
|
||||||
func (r *ServeMux) Handle(pattern string, handler http.Handler) {
|
|
||||||
route := &route{
|
|
||||||
segs: r.pathSegments(pattern),
|
|
||||||
handler: handler,
|
|
||||||
prefix: strings.HasSuffix(pattern, "/") || strings.HasSuffix(pattern, "..."),
|
|
||||||
}
|
}
|
||||||
r.routes = append(r.routes, route)
|
return http.HandlerFunc(http.NotFound), ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleFunc is the http.HandlerFunc alternative to http.Handle.
|
func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
func (r *ServeMux) HandleFunc(pattern string, fn http.HandlerFunc) {
|
if r.RequestURI == "*" {
|
||||||
r.Handle(pattern, fn)
|
if r.ProtoAtLeast(1, 1) {
|
||||||
}
|
w.Header().Set("Connection", "close")
|
||||||
|
}
|
||||||
// ServeHTTP routes the incoming http.Request based on path
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
func (r *ServeMux) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
segs := r.pathSegments(req.URL.Path)
|
|
||||||
for _, route := range r.routes {
|
|
||||||
if ctx, ok := route.match(req.Context(), r, segs); ok {
|
|
||||||
route.handler.ServeHTTP(w, req.WithContext(ctx))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
h, _ := mux.Handler(r)
|
||||||
r.NotFound.ServeHTTP(w, req)
|
h.ServeHTTP(w, r)
|
||||||
}
|
|
||||||
|
|
||||||
// Param gets the path parameter from the specified Context.
|
|
||||||
// Returns an empty string if the parameter was not found.
|
|
||||||
func Param(ctx context.Context, param string) string {
|
|
||||||
vStr, ok := ctx.Value(wayContextKey(param)).(string)
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return vStr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type route struct {
|
type route struct {
|
||||||
segs []string
|
pattern string
|
||||||
handler http.Handler
|
matcher func(r *http.Request) bool
|
||||||
prefix bool
|
handler http.HandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *route) match(ctx context.Context, router *ServeMux, segs []string) (context.Context, bool) {
|
func newRoute(pattern string, handler http.Handler, vars ...interface{}) route {
|
||||||
if len(segs) > len(r.segs) && !r.prefix {
|
return route{
|
||||||
return nil, false
|
pattern,
|
||||||
|
func(r *http.Request) bool {
|
||||||
|
return match(r.URL.Path, pattern, vars...)
|
||||||
|
},
|
||||||
|
handler.ServeHTTP,
|
||||||
}
|
}
|
||||||
for i, seg := range r.segs {
|
}
|
||||||
if i > len(segs)-1 {
|
|
||||||
return nil, false
|
// match reports whether path matches the given pattern, which is a
|
||||||
}
|
// path with '+' wildcards wherever you want to use a parameter. Path
|
||||||
isParam := false
|
// parameters are assigned to the pointers in vars (len(vars) must be
|
||||||
if strings.HasPrefix(seg, ":") {
|
// the number of wildcards), which must be of type *string or *int.
|
||||||
isParam = true
|
func match(path, pattern string, vars ...interface{}) bool {
|
||||||
seg = strings.TrimPrefix(seg, ":")
|
for ; pattern != "" && path != ""; pattern = pattern[1:] {
|
||||||
}
|
switch pattern[0] {
|
||||||
if !isParam { // verbatim check
|
case '+':
|
||||||
if strings.HasSuffix(seg, "...") {
|
// '+' matches till next slash in path
|
||||||
if strings.HasPrefix(segs[i], seg[:len(seg)-3]) {
|
slash := strings.IndexByte(path, '/')
|
||||||
return ctx, true
|
if slash < 0 {
|
||||||
}
|
slash = len(path)
|
||||||
}
|
}
|
||||||
if seg != segs[i] {
|
segment := path[:slash]
|
||||||
return nil, false
|
path = path[slash:]
|
||||||
}
|
switch p := vars[0].(type) {
|
||||||
}
|
case *string:
|
||||||
if isParam {
|
*p = segment
|
||||||
ctx = context.WithValue(ctx, wayContextKey(seg), segs[i])
|
case *int:
|
||||||
}
|
n, err := strconv.Atoi(segment)
|
||||||
}
|
if err != nil || n < 0 {
|
||||||
return ctx, true
|
return false
|
||||||
|
}
|
||||||
|
*p = n
|
||||||
|
default:
|
||||||
|
panic("vars must be *string or *int")
|
||||||
|
}
|
||||||
|
vars = vars[1:]
|
||||||
|
case path[0]:
|
||||||
|
// non-'+' pattern byte must match path byte
|
||||||
|
path = path[1:]
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path == "" && pattern == ""
|
||||||
}
|
}
|
||||||
|
@@ -1,231 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var tests = []struct {
|
|
||||||
// RouteMethod string
|
|
||||||
RoutePattern string
|
|
||||||
|
|
||||||
Method string
|
|
||||||
Path string
|
|
||||||
Match bool
|
|
||||||
Params map[string]string
|
|
||||||
}{
|
|
||||||
// simple path matching
|
|
||||||
{
|
|
||||||
"/one",
|
|
||||||
"GET", "/one", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/two",
|
|
||||||
"GET", "/two", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/three",
|
|
||||||
"GET", "/three", true, nil,
|
|
||||||
},
|
|
||||||
// methods
|
|
||||||
{
|
|
||||||
"/methodcase",
|
|
||||||
"GET", "/methodcase", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/methodcase",
|
|
||||||
"get", "/methodcase", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/methodcase",
|
|
||||||
"get", "/methodcase", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/method1",
|
|
||||||
"POST", "/method1", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/method2",
|
|
||||||
"GET", "/method2", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/method3",
|
|
||||||
"PUT", "/method3", true, nil,
|
|
||||||
},
|
|
||||||
// all methods
|
|
||||||
{
|
|
||||||
"/all-methods",
|
|
||||||
"GET", "/all-methods", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/all-methods",
|
|
||||||
"POST", "/all-methods", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/all-methods",
|
|
||||||
"PUT", "/all-methods", true, nil,
|
|
||||||
},
|
|
||||||
// nested
|
|
||||||
{
|
|
||||||
"/parent/child/one",
|
|
||||||
"GET", "/parent/child/one", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/parent/child/two",
|
|
||||||
"GET", "/parent/child/two", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/parent/child/three",
|
|
||||||
"GET", "/parent/child/three", true, nil,
|
|
||||||
},
|
|
||||||
// slashes
|
|
||||||
{
|
|
||||||
"slashes/one",
|
|
||||||
"GET", "/slashes/one", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/slashes/two",
|
|
||||||
"GET", "slashes/two", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"slashes/three/",
|
|
||||||
"GET", "/slashes/three", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/slashes/four",
|
|
||||||
"GET", "slashes/four/", true, nil,
|
|
||||||
},
|
|
||||||
// prefix
|
|
||||||
{
|
|
||||||
"/prefix/",
|
|
||||||
"GET", "/prefix/anything/else", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/not-prefix",
|
|
||||||
"GET", "/not-prefix/anything/else", false, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/prefixdots...",
|
|
||||||
"GET", "/prefixdots/anything/else", true, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/prefixdots...",
|
|
||||||
"GET", "/prefixdots", true, nil,
|
|
||||||
},
|
|
||||||
// path params
|
|
||||||
{
|
|
||||||
"/path-param/:id",
|
|
||||||
"GET", "/path-param/123", true, map[string]string{"id": "123"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/path-params/:era/:group/:member",
|
|
||||||
"GET", "/path-params/60s/beatles/lennon", true, map[string]string{
|
|
||||||
"era": "60s",
|
|
||||||
"group": "beatles",
|
|
||||||
"member": "lennon",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/path-params-prefix/:era/:group/:member/",
|
|
||||||
"GET", "/path-params-prefix/60s/beatles/lennon/yoko", true, map[string]string{
|
|
||||||
"era": "60s",
|
|
||||||
"group": "beatles",
|
|
||||||
"member": "lennon",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// misc no matches
|
|
||||||
{
|
|
||||||
"/not/enough",
|
|
||||||
"GET", "/not/enough/items", false, nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/not/enough/items",
|
|
||||||
"GET", "/not/enough", false, nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWay(t *testing.T) {
|
|
||||||
for _, test := range tests {
|
|
||||||
r := NewServeMux()
|
|
||||||
match := false
|
|
||||||
var ctx context.Context
|
|
||||||
r.Handle(test.RoutePattern, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
match = true
|
|
||||||
ctx = r.Context()
|
|
||||||
}))
|
|
||||||
req, err := http.NewRequest(test.Method, test.Path, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("NewRequest: %s", err)
|
|
||||||
}
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
r.ServeHTTP(w, req)
|
|
||||||
if match != test.Match {
|
|
||||||
t.Errorf("expected match %v but was %v: %s %s", test.Match, match, test.Method, test.Path)
|
|
||||||
}
|
|
||||||
if len(test.Params) > 0 {
|
|
||||||
for expK, expV := range test.Params {
|
|
||||||
// check using helper
|
|
||||||
actualValStr := Param(ctx, expK)
|
|
||||||
if actualValStr != expV {
|
|
||||||
t.Errorf("Param: context value %s expected \"%s\" but was \"%s\"", expK, expV, actualValStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultipleRoutesDifferentMethods(t *testing.T) {
|
|
||||||
r := NewServeMux()
|
|
||||||
var match string
|
|
||||||
|
|
||||||
r.Handle("/route", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
match = "GET /route"
|
|
||||||
case http.MethodDelete:
|
|
||||||
match = "DELETE /route"
|
|
||||||
case http.MethodPost:
|
|
||||||
match = "POST /route"
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
r.Handle("/route", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
match = "GET /route"
|
|
||||||
}))
|
|
||||||
r.Handle("/route", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
match = "DELETE /route"
|
|
||||||
}))
|
|
||||||
r.Handle("/route", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
match = "POST /route"
|
|
||||||
}))
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "/route", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("NewRequest: %s", err)
|
|
||||||
}
|
|
||||||
r.ServeHTTP(httptest.NewRecorder(), req)
|
|
||||||
if match != "GET /route" {
|
|
||||||
t.Errorf("unexpected: %s", match)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err = http.NewRequest(http.MethodDelete, "/route", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("NewRequest: %s", err)
|
|
||||||
}
|
|
||||||
r.ServeHTTP(httptest.NewRecorder(), req)
|
|
||||||
if match != "DELETE /route" {
|
|
||||||
t.Errorf("unexpected: %s", match)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err = http.NewRequest(http.MethodPost, "/route", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("NewRequest: %s", err)
|
|
||||||
}
|
|
||||||
r.ServeHTTP(httptest.NewRecorder(), req)
|
|
||||||
if match != "POST /route" {
|
|
||||||
t.Errorf("unexpected: %s", match)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -15,8 +15,7 @@ func (s StatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
NotFoundHandler = StatusHandler(http.StatusNotFound)
|
NotFoundHandler = StatusHandler(404)
|
||||||
NotImplementedHandler = StatusHandler(http.StatusNotImplemented)
|
NotImplementedHandler = StatusHandler(501)
|
||||||
NotLegalHandler = StatusHandler(http.StatusUnavailableForLegalReasons)
|
NotLegalHandler = StatusHandler(451)
|
||||||
NotAllowedHandler = StatusHandler(http.StatusMethodNotAllowed)
|
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user