mirror of
https://github.com/grafana/grafana.git
synced 2026-09-05 04:40:13 -05:00
Chore: Move remaining web framework code to pkg/web, remove macaron binding module (#43018)
* remove macaron binding dependency * completely purge macaron binding * move everything to pkg/web * remove non-go files from pkg/web * clean up leftovers of macaron imports * make linter happy
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Bind deserializes JSON payload from the request
|
||||
func Bind(req *http.Request, v interface{}) error {
|
||||
if req.Body != nil {
|
||||
defer func() { _ = req.Body.Close() }()
|
||||
err := json.NewDecoder(req.Body).Decode(v)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return validate(v)
|
||||
}
|
||||
|
||||
type Validator interface {
|
||||
Validate() error
|
||||
}
|
||||
|
||||
func validate(obj interface{}) error {
|
||||
// If type has a Validate() method - use that
|
||||
if validator, ok := obj.(Validator); ok {
|
||||
return validator.Validate()
|
||||
}
|
||||
// Otherwise, use reflection to match `binding:"Required"` struct field tags.
|
||||
// Resolve all pointers and interfaces, until we get a concrete type.
|
||||
t := reflect.TypeOf(obj)
|
||||
v := reflect.ValueOf(obj)
|
||||
for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
v = v.Elem()
|
||||
}
|
||||
switch v.Kind() {
|
||||
// For arrays and slices - iterate over each element and validate it recursively
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
e := v.Index(i).Interface()
|
||||
if err := validate(e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// For structs - iterate over each field, check for the "Required" constraint (Macaron legacy), then validate it recursively
|
||||
case reflect.Struct:
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
value := v.Field(i)
|
||||
rule := field.Tag.Get("binding")
|
||||
if !value.CanInterface() {
|
||||
continue
|
||||
}
|
||||
if rule == "Required" {
|
||||
zero := reflect.Zero(field.Type).Interface()
|
||||
if value.Kind() == reflect.Slice {
|
||||
if value.Len() == 0 {
|
||||
return fmt.Errorf("required slice %s must not be empty", field.Name)
|
||||
}
|
||||
} else if reflect.DeepEqual(zero, value.Interface()) {
|
||||
return fmt.Errorf("required value %s must not be empty", field.Name)
|
||||
}
|
||||
}
|
||||
if err := validate(value.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default: // ignore
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type StructWithInt struct {
|
||||
A int `binding:"Required"`
|
||||
}
|
||||
|
||||
type StructWithPrimitives struct {
|
||||
A int `binding:"Required"`
|
||||
B string `binding:"Required"`
|
||||
C bool `binding:"Required"`
|
||||
D float64 `binding:"Required"`
|
||||
}
|
||||
|
||||
type StructWithPrivateFields struct {
|
||||
A int `binding:"Required"` // must be validated
|
||||
b int `binding:"Required"` // will not be used
|
||||
}
|
||||
|
||||
type StructWithInterface struct {
|
||||
A interface{} `binding:"Required"`
|
||||
}
|
||||
type StructWithSliceInts struct {
|
||||
A []int `binding:"Required"`
|
||||
}
|
||||
type StructWithSliceStructs struct {
|
||||
A []StructWithInt `binding:"Required"`
|
||||
}
|
||||
type StructWithSliceInterfaces struct {
|
||||
A []interface{} `binding:"Required"`
|
||||
}
|
||||
type StructWithStruct struct {
|
||||
A StructWithInt `binding:"Required"`
|
||||
}
|
||||
type StructWithStructPointer struct {
|
||||
A *StructWithInt `binding:"Required"`
|
||||
}
|
||||
type StructWithValidation struct {
|
||||
A int
|
||||
}
|
||||
|
||||
func (sv StructWithValidation) Validate() error {
|
||||
if sv.A < 10 {
|
||||
return errors.New("too small")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestValidationSuccess(t *testing.T) {
|
||||
for _, x := range []interface{}{
|
||||
42,
|
||||
"foo",
|
||||
struct{ A int }{},
|
||||
StructWithInt{42},
|
||||
StructWithPrimitives{42, "foo", true, 12.34},
|
||||
StructWithPrivateFields{12, 0},
|
||||
StructWithInterface{"foo"},
|
||||
StructWithSliceInts{[]int{1, 2, 3}},
|
||||
StructWithSliceInterfaces{[]interface{}{1, 2, 3}},
|
||||
StructWithSliceStructs{[]StructWithInt{{1}, {2}}},
|
||||
StructWithStruct{StructWithInt{3}},
|
||||
StructWithStructPointer{&StructWithInt{3}},
|
||||
StructWithValidation{42},
|
||||
} {
|
||||
if err := validate(x); err != nil {
|
||||
t.Error("Validation failed:", x, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestValidationFailure(t *testing.T) {
|
||||
for i, x := range []interface{}{
|
||||
StructWithInt{0},
|
||||
StructWithPrimitives{0, "foo", true, 12.34},
|
||||
StructWithPrimitives{42, "", true, 12.34},
|
||||
StructWithPrimitives{42, "foo", false, 12.34},
|
||||
StructWithPrimitives{42, "foo", true, 0},
|
||||
StructWithPrivateFields{0, 1},
|
||||
StructWithInterface{},
|
||||
StructWithInterface{nil},
|
||||
StructWithSliceInts{},
|
||||
StructWithSliceInts{[]int{}},
|
||||
StructWithSliceStructs{[]StructWithInt{}},
|
||||
StructWithSliceStructs{[]StructWithInt{{0}, {2}}},
|
||||
StructWithSliceStructs{[]StructWithInt{{2}, {0}}},
|
||||
StructWithSliceInterfaces{[]interface{}{}},
|
||||
StructWithSliceInterfaces{nil},
|
||||
StructWithStruct{StructWithInt{}},
|
||||
StructWithStruct{StructWithInt{0}},
|
||||
StructWithStructPointer{},
|
||||
StructWithStructPointer{&StructWithInt{}},
|
||||
StructWithValidation{2},
|
||||
} {
|
||||
if err := validate(x); err == nil {
|
||||
t.Error("Validation should fail:", i, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ContextInvoker is an inject.FastInvoker wrapper of func(ctx *Context).
|
||||
type ContextInvoker func(ctx *Context)
|
||||
|
||||
// Invoke implements inject.FastInvoker which simplifies calls of `func(ctx *Context)` function.
|
||||
func (invoke ContextInvoker) Invoke(params []interface{}) ([]reflect.Value, error) {
|
||||
invoke(params[0].(*Context))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Context represents the runtime context of current request of Macaron instance.
|
||||
// It is the integration of most frequently used middlewares and helper methods.
|
||||
type Context struct {
|
||||
Injector
|
||||
handlers []Handler
|
||||
index int
|
||||
|
||||
*Router
|
||||
Req *http.Request
|
||||
Resp ResponseWriter
|
||||
template *template.Template
|
||||
}
|
||||
|
||||
func (ctx *Context) handler() Handler {
|
||||
if ctx.index < len(ctx.handlers) {
|
||||
return ctx.handlers[ctx.index]
|
||||
}
|
||||
if ctx.index == len(ctx.handlers) {
|
||||
return func() {}
|
||||
}
|
||||
panic("invalid index for context handler")
|
||||
}
|
||||
|
||||
// Next runs the next handler in the context chain
|
||||
func (ctx *Context) Next() {
|
||||
ctx.index++
|
||||
ctx.run()
|
||||
}
|
||||
|
||||
func (ctx *Context) run() {
|
||||
for ctx.index <= len(ctx.handlers) {
|
||||
if _, err := ctx.Invoke(ctx.handler()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctx.index++
|
||||
if ctx.Resp.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoteAddr returns more real IP address.
|
||||
func (ctx *Context) RemoteAddr() string {
|
||||
addr := ctx.Req.Header.Get("X-Real-IP")
|
||||
if len(addr) == 0 {
|
||||
addr = ctx.Req.Header.Get("X-Forwarded-For")
|
||||
if addr == "" {
|
||||
addr = ctx.Req.RemoteAddr
|
||||
if i := strings.LastIndex(addr, ":"); i > -1 {
|
||||
addr = addr[:i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
const (
|
||||
headerContentType = "Content-Type"
|
||||
contentTypeJSON = "application/json; charset=UTF-8"
|
||||
contentTypeHTML = "text/html; charset=UTF-8"
|
||||
)
|
||||
|
||||
// HTML renders the HTML with default template set.
|
||||
func (ctx *Context) HTML(status int, name string, data interface{}) {
|
||||
ctx.Resp.Header().Set(headerContentType, contentTypeHTML)
|
||||
ctx.Resp.WriteHeader(status)
|
||||
if err := ctx.template.ExecuteTemplate(ctx.Resp, name, data); err != nil {
|
||||
panic("Context.HTML:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *Context) JSON(status int, data interface{}) {
|
||||
ctx.Resp.Header().Set(headerContentType, contentTypeJSON)
|
||||
ctx.Resp.WriteHeader(status)
|
||||
enc := json.NewEncoder(ctx.Resp)
|
||||
if Env != PROD {
|
||||
enc.SetIndent("", " ")
|
||||
}
|
||||
if err := enc.Encode(data); err != nil {
|
||||
panic("Context.JSON: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect sends a redirect response
|
||||
func (ctx *Context) Redirect(location string, status ...int) {
|
||||
code := http.StatusFound
|
||||
if len(status) == 1 {
|
||||
code = status[0]
|
||||
}
|
||||
|
||||
http.Redirect(ctx.Resp, ctx.Req, location, code)
|
||||
}
|
||||
|
||||
// MaxMemory is the maximum amount of memory to use when parsing a multipart form.
|
||||
// Set this to whatever value you prefer; default is 10 MB.
|
||||
var MaxMemory = int64(1024 * 1024 * 10)
|
||||
|
||||
func (ctx *Context) parseForm() {
|
||||
if ctx.Req.Form != nil {
|
||||
return
|
||||
}
|
||||
|
||||
contentType := ctx.Req.Header.Get(headerContentType)
|
||||
if (ctx.Req.Method == "POST" || ctx.Req.Method == "PUT") &&
|
||||
len(contentType) > 0 && strings.Contains(contentType, "multipart/form-data") {
|
||||
_ = ctx.Req.ParseMultipartForm(MaxMemory)
|
||||
} else {
|
||||
_ = ctx.Req.ParseForm()
|
||||
}
|
||||
}
|
||||
|
||||
// Query querys form parameter.
|
||||
func (ctx *Context) Query(name string) string {
|
||||
ctx.parseForm()
|
||||
return ctx.Req.Form.Get(name)
|
||||
}
|
||||
|
||||
// QueryStrings returns a list of results by given query name.
|
||||
func (ctx *Context) QueryStrings(name string) []string {
|
||||
ctx.parseForm()
|
||||
|
||||
vals, ok := ctx.Req.Form[name]
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
// QueryBool returns query result in bool type.
|
||||
func (ctx *Context) QueryBool(name string) bool {
|
||||
v, _ := strconv.ParseBool(ctx.Query(name))
|
||||
return v
|
||||
}
|
||||
|
||||
// QueryInt returns query result in int type.
|
||||
func (ctx *Context) QueryInt(name string) int {
|
||||
n, _ := strconv.Atoi(ctx.Query(name))
|
||||
return n
|
||||
}
|
||||
|
||||
// QueryInt64 returns query result in int64 type.
|
||||
func (ctx *Context) QueryInt64(name string) int64 {
|
||||
n, _ := strconv.ParseInt(ctx.Query(name), 10, 64)
|
||||
return n
|
||||
}
|
||||
|
||||
// ParamsInt64 returns params result in int64 type.
|
||||
// e.g. ctx.ParamsInt64(":uid")
|
||||
func (ctx *Context) ParamsInt64(name string) int64 {
|
||||
n, _ := strconv.ParseInt(Params(ctx.Req)[name], 10, 64)
|
||||
return n
|
||||
}
|
||||
|
||||
// GetCookie returns given cookie value from request header.
|
||||
func (ctx *Context) GetCookie(name string) string {
|
||||
cookie, err := ctx.Req.Cookie(name)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
val, _ := url.QueryUnescape(cookie.Value)
|
||||
return val
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
// Copyright 2013 Jeremy Saenz
|
||||
// Copyright 2015 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// Package inject provides utilities for mapping and injecting dependencies in various ways.
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Injector represents an interface for mapping and injecting dependencies into structs
|
||||
// and function arguments.
|
||||
type Injector interface {
|
||||
Invoker
|
||||
TypeMapper
|
||||
}
|
||||
|
||||
// Invoker represents an interface for calling functions via reflection.
|
||||
type Invoker interface {
|
||||
// Invoke attempts to call the interface{} provided as a function,
|
||||
// providing dependencies for function arguments based on Type. Returns
|
||||
// a slice of reflect.Value representing the returned values of the function.
|
||||
// Returns an error if the injection fails.
|
||||
Invoke(interface{}) ([]reflect.Value, error)
|
||||
}
|
||||
|
||||
// FastInvoker represents an interface in order to avoid the calling function via reflection.
|
||||
//
|
||||
// example:
|
||||
// type handlerFuncHandler func(http.ResponseWriter, *http.Request) error
|
||||
// func (f handlerFuncHandler)Invoke([]interface{}) ([]reflect.Value, error){
|
||||
// ret := f(p[0].(http.ResponseWriter), p[1].(*http.Request))
|
||||
// return []reflect.Value{reflect.ValueOf(ret)}, nil
|
||||
// }
|
||||
//
|
||||
// type funcHandler func(int, string)
|
||||
// func (f funcHandler)Invoke([]interface{}) ([]reflect.Value, error){
|
||||
// f(p[0].(int), p[1].(string))
|
||||
// return nil, nil
|
||||
// }
|
||||
type FastInvoker interface {
|
||||
// Invoke attempts to call the ordinary functions. If f is a function
|
||||
// with the appropriate signature, f.Invoke([]interface{}) is a Call that calls f.
|
||||
// Returns a slice of reflect.Value representing the returned values of the function.
|
||||
// Returns an error if the injection fails.
|
||||
Invoke([]interface{}) ([]reflect.Value, error)
|
||||
}
|
||||
|
||||
// IsFastInvoker check interface is FastInvoker
|
||||
func IsFastInvoker(h interface{}) bool {
|
||||
_, ok := h.(FastInvoker)
|
||||
return ok
|
||||
}
|
||||
|
||||
// TypeMapper represents an interface for mapping interface{} values based on type.
|
||||
type TypeMapper interface {
|
||||
// Maps the interface{} value based on its immediate type from reflect.TypeOf.
|
||||
Map(interface{}) TypeMapper
|
||||
// Maps the interface{} value based on the pointer of an Interface provided.
|
||||
// This is really only useful for mapping a value as an interface, as interfaces
|
||||
// cannot at this time be referenced directly without a pointer.
|
||||
MapTo(interface{}, interface{}) TypeMapper
|
||||
// Returns the Value that is mapped to the current type. Returns a zeroed Value if
|
||||
// the Type has not been mapped.
|
||||
GetVal(reflect.Type) reflect.Value
|
||||
}
|
||||
|
||||
type injector struct {
|
||||
values map[reflect.Type]reflect.Value
|
||||
}
|
||||
|
||||
// InterfaceOf dereferences a pointer to an Interface type.
|
||||
// It panics if value is not an pointer to an interface.
|
||||
func InterfaceOf(value interface{}) reflect.Type {
|
||||
t := reflect.TypeOf(value)
|
||||
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Interface {
|
||||
panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)")
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// New returns a new Injector.
|
||||
func NewInjector() Injector {
|
||||
return &injector{
|
||||
values: make(map[reflect.Type]reflect.Value),
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke attempts to call the interface{} provided as a function,
|
||||
// providing dependencies for function arguments based on Type.
|
||||
// Returns a slice of reflect.Value representing the returned values of the function.
|
||||
// Returns an error if the injection fails.
|
||||
// It panics if f is not a function
|
||||
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
|
||||
t := reflect.TypeOf(f)
|
||||
switch v := f.(type) {
|
||||
case FastInvoker:
|
||||
return inj.fastInvoke(v, t, t.NumIn())
|
||||
default:
|
||||
return inj.callInvoke(f, t, t.NumIn())
|
||||
}
|
||||
}
|
||||
|
||||
func (inj *injector) fastInvoke(f FastInvoker, t reflect.Type, numIn int) ([]reflect.Value, error) {
|
||||
var in []interface{}
|
||||
if numIn > 0 {
|
||||
in = make([]interface{}, numIn) // Panic if t is not kind of Func
|
||||
var argType reflect.Type
|
||||
var val reflect.Value
|
||||
for i := 0; i < numIn; i++ {
|
||||
argType = t.In(i)
|
||||
val = inj.GetVal(argType)
|
||||
if !val.IsValid() {
|
||||
return nil, fmt.Errorf("value not found for type %v", argType)
|
||||
}
|
||||
|
||||
in[i] = val.Interface()
|
||||
}
|
||||
}
|
||||
return f.Invoke(in)
|
||||
}
|
||||
|
||||
// callInvoke reflect.Value.Call
|
||||
func (inj *injector) callInvoke(f interface{}, t reflect.Type, numIn int) ([]reflect.Value, error) {
|
||||
var in []reflect.Value
|
||||
if numIn > 0 {
|
||||
in = make([]reflect.Value, numIn)
|
||||
var argType reflect.Type
|
||||
var val reflect.Value
|
||||
for i := 0; i < numIn; i++ {
|
||||
argType = t.In(i)
|
||||
val = inj.GetVal(argType)
|
||||
if !val.IsValid() {
|
||||
return nil, fmt.Errorf("value not found for type %v", argType)
|
||||
}
|
||||
|
||||
in[i] = val
|
||||
}
|
||||
}
|
||||
return reflect.ValueOf(f).Call(in), nil
|
||||
}
|
||||
|
||||
// Maps the concrete value of val to its dynamic type using reflect.TypeOf,
|
||||
// It returns the TypeMapper registered in.
|
||||
func (inj *injector) Map(val interface{}) TypeMapper {
|
||||
inj.values[reflect.TypeOf(val)] = reflect.ValueOf(val)
|
||||
return inj
|
||||
}
|
||||
|
||||
func (inj *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper {
|
||||
inj.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val)
|
||||
return inj
|
||||
}
|
||||
|
||||
func (inj *injector) GetVal(t reflect.Type) reflect.Value {
|
||||
val := inj.values[t]
|
||||
|
||||
if val.IsValid() {
|
||||
return val
|
||||
}
|
||||
|
||||
// no concrete types found, try to find implementors
|
||||
// if t is an interface
|
||||
if t.Kind() == reflect.Interface {
|
||||
for k, v := range inj.values {
|
||||
if k.Implements(t) {
|
||||
val = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
//go:build go1.3
|
||||
// +build go1.3
|
||||
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// Package macaron is a high productive and modular web framework in Go.
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _VERSION = "1.3.4.0805"
|
||||
|
||||
const (
|
||||
DEV = "development"
|
||||
PROD = "production"
|
||||
)
|
||||
|
||||
var (
|
||||
// Env is the environment that Macaron is executing in.
|
||||
// The MACARON_ENV is read on initialization to set this variable.
|
||||
Env = DEV
|
||||
)
|
||||
|
||||
func Version() string {
|
||||
return _VERSION
|
||||
}
|
||||
|
||||
// Handler can be any callable function.
|
||||
// Macaron attempts to inject services into the handler's argument list,
|
||||
// and panics if an argument could not be fulfilled via dependency injection.
|
||||
type Handler interface{}
|
||||
|
||||
// handlerFuncInvoker is an inject.FastInvoker wrapper of func(http.ResponseWriter, *http.Request).
|
||||
type handlerFuncInvoker func(http.ResponseWriter, *http.Request)
|
||||
|
||||
func (invoke handlerFuncInvoker) Invoke(params []interface{}) ([]reflect.Value, error) {
|
||||
invoke(params[0].(http.ResponseWriter), params[1].(*http.Request))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// validateAndWrapHandler makes sure a handler is a callable function, it panics if not.
|
||||
// When the handler is also potential to be any built-in inject.FastInvoker,
|
||||
// it wraps the handler automatically to have some performance gain.
|
||||
func validateAndWrapHandler(h Handler) Handler {
|
||||
if reflect.TypeOf(h).Kind() != reflect.Func {
|
||||
panic("Macaron handler must be a callable function")
|
||||
}
|
||||
|
||||
if !IsFastInvoker(h) {
|
||||
switch v := h.(type) {
|
||||
case func(*Context):
|
||||
return ContextInvoker(v)
|
||||
case func(http.ResponseWriter, *http.Request):
|
||||
return handlerFuncInvoker(v)
|
||||
}
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// validateAndWrapHandlers preforms validation and wrapping for each input handler.
|
||||
// It accepts an optional wrapper function to perform custom wrapping on handlers.
|
||||
func validateAndWrapHandlers(handlers []Handler) []Handler {
|
||||
wrappedHandlers := make([]Handler, len(handlers))
|
||||
for i, h := range handlers {
|
||||
wrappedHandlers[i] = validateAndWrapHandler(h)
|
||||
}
|
||||
|
||||
return wrappedHandlers
|
||||
}
|
||||
|
||||
// Macaron represents the top level web application.
|
||||
// Injector methods can be invoked to map services on a global level.
|
||||
type Macaron struct {
|
||||
handlers []Handler
|
||||
|
||||
urlPrefix string // For suburl support.
|
||||
*Router
|
||||
}
|
||||
|
||||
// New creates a bare bones Macaron instance.
|
||||
// Use this method if you want to have full control over the middleware that is used.
|
||||
func New() *Macaron {
|
||||
m := &Macaron{Router: NewRouter()}
|
||||
m.Router.m = m
|
||||
m.NotFound(http.NotFound)
|
||||
return m
|
||||
}
|
||||
|
||||
// BeforeHandler represents a handler executes at beginning of every request.
|
||||
// Macaron stops future process when it returns true.
|
||||
type BeforeHandler func(rw http.ResponseWriter, req *http.Request) bool
|
||||
|
||||
// macaronContextKey is used to store/fetch web.Context inside context.Context
|
||||
type macaronContextKey struct{}
|
||||
|
||||
// FromContext returns the macaron context stored in a context.Context, if any.
|
||||
func FromContext(c context.Context) *Context {
|
||||
if mc, ok := c.Value(macaronContextKey{}).(*Context); ok {
|
||||
return mc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type paramsKey struct{}
|
||||
|
||||
// Params returns the named route parameters for the current request, if any.
|
||||
func Params(r *http.Request) map[string]string {
|
||||
if rv := r.Context().Value(paramsKey{}); rv != nil {
|
||||
return rv.(map[string]string)
|
||||
}
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
// SetURLParams sets the named URL parameters for the given request. This should only be used for testing purposes.
|
||||
func SetURLParams(r *http.Request, vars map[string]string) *http.Request {
|
||||
return r.WithContext(context.WithValue(r.Context(), paramsKey{}, vars))
|
||||
}
|
||||
|
||||
// UseMiddleware is a traditional approach to writing middleware in Go.
|
||||
// A middleware is a function that has a reference to the next handler in the chain
|
||||
// and returns the actual middleware handler, that may do its job and optionally
|
||||
// call next.
|
||||
// Due to how Macaron handles/injects requests and responses we patch the web.Context
|
||||
// to use the new ResponseWriter and http.Request here. The caller may only call
|
||||
// `next.ServeHTTP(rw, req)` to pass a modified response writer and/or a request to the
|
||||
// further middlewares in the chain.
|
||||
func (m *Macaron) UseMiddleware(middleware func(http.Handler) http.Handler) {
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
c := FromContext(req.Context())
|
||||
c.Req = req
|
||||
if mrw, ok := rw.(*responseWriter); ok {
|
||||
c.Resp = mrw
|
||||
} else {
|
||||
c.Resp = NewResponseWriter(req.Method, rw)
|
||||
}
|
||||
c.Map(req)
|
||||
c.MapTo(rw, (*http.ResponseWriter)(nil))
|
||||
c.Next()
|
||||
})
|
||||
m.handlers = append(m.handlers, Handler(middleware(next)))
|
||||
}
|
||||
|
||||
// Use adds a middleware Handler to the stack,
|
||||
// and panics if the handler is not a callable func.
|
||||
// Middleware Handlers are invoked in the order that they are added.
|
||||
func (m *Macaron) Use(handler Handler) {
|
||||
handler = validateAndWrapHandler(handler)
|
||||
m.handlers = append(m.handlers, handler)
|
||||
}
|
||||
|
||||
func (m *Macaron) createContext(rw http.ResponseWriter, req *http.Request) *Context {
|
||||
c := &Context{
|
||||
Injector: NewInjector(),
|
||||
handlers: m.handlers,
|
||||
index: 0,
|
||||
Router: m.Router,
|
||||
Resp: NewResponseWriter(req.Method, rw),
|
||||
}
|
||||
req = req.WithContext(context.WithValue(req.Context(), macaronContextKey{}, c))
|
||||
c.Map(c)
|
||||
c.MapTo(c.Resp, (*http.ResponseWriter)(nil))
|
||||
c.Map(req)
|
||||
c.Req = req
|
||||
return c
|
||||
}
|
||||
|
||||
// ServeHTTP is the HTTP Entry point for a Macaron instance.
|
||||
// Useful if you want to control your own HTTP server.
|
||||
// Be aware that none of middleware will run without registering any router.
|
||||
func (m *Macaron) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
req.URL.Path = strings.TrimPrefix(req.URL.Path, m.urlPrefix)
|
||||
m.Router.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
// SetURLPrefix sets URL prefix of router layer, so that it support suburl.
|
||||
func (m *Macaron) SetURLPrefix(prefix string) {
|
||||
m.urlPrefix = prefix
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Renderer is a Middleware that injects a template renderer into the macaron context, enabling ctx.HTML calls in the handlers.
|
||||
// If MACARON_ENV is set to "development" then templates will be recompiled on every request. For more performance, set the
|
||||
// MACARON_ENV environment variable to "production".
|
||||
func Renderer(dir, leftDelim, rightDelim string) func(http.Handler) http.Handler {
|
||||
fs := os.DirFS(dir)
|
||||
t, err := compileTemplates(fs, leftDelim, rightDelim)
|
||||
if err != nil {
|
||||
panic("Renderer: " + err.Error())
|
||||
}
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := FromContext(req.Context())
|
||||
if Env == DEV {
|
||||
if t, err = compileTemplates(fs, leftDelim, rightDelim); err != nil {
|
||||
panic("Context.HTML:" + err.Error())
|
||||
}
|
||||
}
|
||||
ctx.template = t
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func compileTemplates(filesystem fs.FS, leftDelim, rightDelim string) (*template.Template, error) {
|
||||
t := template.New("")
|
||||
t.Delims(leftDelim, rightDelim)
|
||||
err := fs.WalkDir(filesystem, ".", func(path string, d fs.DirEntry, e error) error {
|
||||
if e != nil {
|
||||
return nil // skip unreadable or erroneous filesystem items
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
ext := filepath.Ext(path)
|
||||
if ext != ".html" && ext != ".tmpl" {
|
||||
return nil
|
||||
}
|
||||
data, err := fs.ReadFile(filesystem, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
basename := path[:len(path)-len(ext)]
|
||||
_, err = t.New(basename).Parse(string(data))
|
||||
return err
|
||||
})
|
||||
return t, err
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about
|
||||
// the response. It is recommended that middleware handlers use this construct to wrap a responsewriter
|
||||
// if the functionality calls for it.
|
||||
type ResponseWriter interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
// Status returns the status code of the response or 0 if the response has not been written.
|
||||
Status() int
|
||||
// Written returns whether or not the ResponseWriter has been written.
|
||||
Written() bool
|
||||
// Size returns the size of the response body.
|
||||
Size() int
|
||||
// Before allows for a function to be called before the ResponseWriter has been written to. This is
|
||||
// useful for setting headers or any other operations that must happen before a response has been written.
|
||||
Before(BeforeFunc)
|
||||
}
|
||||
|
||||
// BeforeFunc is a function that is called before the ResponseWriter has been written to.
|
||||
type BeforeFunc func(ResponseWriter)
|
||||
|
||||
// NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter
|
||||
func NewResponseWriter(method string, rw http.ResponseWriter) ResponseWriter {
|
||||
return &responseWriter{method, rw, 0, 0, nil}
|
||||
}
|
||||
|
||||
type responseWriter struct {
|
||||
method string
|
||||
http.ResponseWriter
|
||||
status int
|
||||
size int
|
||||
beforeFuncs []BeforeFunc
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(s int) {
|
||||
rw.callBefore()
|
||||
|
||||
// Avoid panic if status code is not a valid HTTP status code
|
||||
if s < 100 || s > 999 {
|
||||
rw.ResponseWriter.WriteHeader(500)
|
||||
rw.status = 500
|
||||
return
|
||||
}
|
||||
|
||||
rw.ResponseWriter.WriteHeader(s)
|
||||
rw.status = s
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Write(b []byte) (size int, err error) {
|
||||
if !rw.Written() {
|
||||
// The status will be StatusOK if WriteHeader has not been called yet
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
if rw.method != "HEAD" {
|
||||
size, err = rw.ResponseWriter.Write(b)
|
||||
rw.size += size
|
||||
}
|
||||
return size, err
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Status() int {
|
||||
return rw.status
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Size() int {
|
||||
return rw.size
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Written() bool {
|
||||
return rw.status != 0
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Before(before BeforeFunc) {
|
||||
rw.beforeFuncs = append(rw.beforeFuncs, before)
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hijacker, ok := rw.ResponseWriter.(http.Hijacker)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("the ResponseWriter doesn't support the Hijacker interface")
|
||||
}
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
|
||||
func (rw *responseWriter) callBefore() {
|
||||
for i := len(rw.beforeFuncs) - 1; i >= 0; i-- {
|
||||
rw.beforeFuncs[i](rw)
|
||||
}
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Flush() {
|
||||
flusher, ok := rw.ResponseWriter.(http.Flusher)
|
||||
if ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_responseWriter_WriteHeader(t *testing.T) {
|
||||
t.Run("it should set status code as expected", func(t *testing.T) {
|
||||
f := fakeResponseWriter{}
|
||||
rw := NewResponseWriter("GET", &f)
|
||||
rw.WriteHeader(200)
|
||||
require.Equal(t, 200, rw.Status())
|
||||
require.Equal(t, 200, f.Status)
|
||||
})
|
||||
|
||||
t.Run("it should set status code to 500 if WriteHeader is called with invalid HTTP status", func(t *testing.T) {
|
||||
f := fakeResponseWriter{}
|
||||
rw := NewResponseWriter("GET", &f)
|
||||
rw.WriteHeader(0)
|
||||
require.Equal(t, 500, rw.Status())
|
||||
require.Equal(t, 500, f.Status)
|
||||
})
|
||||
}
|
||||
|
||||
type fakeResponseWriter struct {
|
||||
Status int
|
||||
}
|
||||
|
||||
func (f *fakeResponseWriter) Header() http.Header {
|
||||
return http.Header{}
|
||||
}
|
||||
|
||||
func (f *fakeResponseWriter) Write([]byte) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (f *fakeResponseWriter) WriteHeader(statusCode int) {
|
||||
f.Status = statusCode
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// Known HTTP methods.
|
||||
_HTTP_METHODS = map[string]bool{
|
||||
"GET": true,
|
||||
"POST": true,
|
||||
"PUT": true,
|
||||
"DELETE": true,
|
||||
"PATCH": true,
|
||||
"OPTIONS": true,
|
||||
"HEAD": true,
|
||||
}
|
||||
)
|
||||
|
||||
// routeMap represents a thread-safe map for route tree.
|
||||
type routeMap struct {
|
||||
lock sync.RWMutex
|
||||
routes map[string]map[string]*Leaf
|
||||
}
|
||||
|
||||
// NewRouteMap initializes and returns a new routeMap.
|
||||
func newRouteMap() *routeMap {
|
||||
rm := &routeMap{
|
||||
routes: make(map[string]map[string]*Leaf),
|
||||
}
|
||||
for m := range _HTTP_METHODS {
|
||||
rm.routes[m] = make(map[string]*Leaf)
|
||||
}
|
||||
return rm
|
||||
}
|
||||
|
||||
// getLeaf returns Leaf object if a route has been registered.
|
||||
func (rm *routeMap) getLeaf(method, pattern string) *Leaf {
|
||||
rm.lock.RLock()
|
||||
defer rm.lock.RUnlock()
|
||||
|
||||
return rm.routes[method][pattern]
|
||||
}
|
||||
|
||||
// add adds new route to route tree map.
|
||||
func (rm *routeMap) add(method, pattern string, leaf *Leaf) {
|
||||
rm.lock.Lock()
|
||||
defer rm.lock.Unlock()
|
||||
|
||||
rm.routes[method][pattern] = leaf
|
||||
}
|
||||
|
||||
type group struct {
|
||||
pattern string
|
||||
handlers []Handler
|
||||
}
|
||||
|
||||
// Router represents a Macaron router layer.
|
||||
type Router struct {
|
||||
m *Macaron
|
||||
routers map[string]*Tree
|
||||
*routeMap
|
||||
namedRoutes map[string]*Leaf
|
||||
|
||||
groups []group
|
||||
notFound http.HandlerFunc
|
||||
}
|
||||
|
||||
func NewRouter() *Router {
|
||||
return &Router{
|
||||
routers: make(map[string]*Tree),
|
||||
routeMap: newRouteMap(),
|
||||
namedRoutes: make(map[string]*Leaf),
|
||||
}
|
||||
}
|
||||
|
||||
// Handle is a function that can be registered to a route to handle HTTP requests.
|
||||
// Like http.HandlerFunc, but has a third parameter for the values of wildcards (variables).
|
||||
type Handle func(http.ResponseWriter, *http.Request, map[string]string)
|
||||
|
||||
// handle adds new route to the router tree.
|
||||
func (r *Router) handle(method, pattern string, handle Handle) {
|
||||
method = strings.ToUpper(method)
|
||||
|
||||
var leaf *Leaf
|
||||
// Prevent duplicate routes.
|
||||
if leaf = r.getLeaf(method, pattern); leaf != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Validate HTTP methods.
|
||||
if !_HTTP_METHODS[method] && method != "*" {
|
||||
panic("unknown HTTP method: " + method)
|
||||
}
|
||||
|
||||
// Generate methods need register.
|
||||
methods := make(map[string]bool)
|
||||
if method == "*" {
|
||||
for m := range _HTTP_METHODS {
|
||||
methods[m] = true
|
||||
}
|
||||
} else {
|
||||
methods[method] = true
|
||||
}
|
||||
|
||||
// Add to router tree.
|
||||
for m := range methods {
|
||||
if t, ok := r.routers[m]; ok {
|
||||
leaf = t.Add(pattern, handle)
|
||||
} else {
|
||||
t := NewTree()
|
||||
leaf = t.Add(pattern, handle)
|
||||
r.routers[m] = t
|
||||
}
|
||||
r.add(m, pattern, leaf)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle registers a new request handle with the given pattern, method and handlers.
|
||||
func (r *Router) Handle(method string, pattern string, handlers []Handler) {
|
||||
if len(r.groups) > 0 {
|
||||
groupPattern := ""
|
||||
h := make([]Handler, 0)
|
||||
for _, g := range r.groups {
|
||||
groupPattern += g.pattern
|
||||
h = append(h, g.handlers...)
|
||||
}
|
||||
|
||||
pattern = groupPattern + pattern
|
||||
h = append(h, handlers...)
|
||||
handlers = h
|
||||
}
|
||||
handlers = validateAndWrapHandlers(handlers)
|
||||
|
||||
r.handle(method, pattern, func(resp http.ResponseWriter, req *http.Request, params map[string]string) {
|
||||
c := r.m.createContext(resp, SetURLParams(req, params))
|
||||
c.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers))
|
||||
c.handlers = append(c.handlers, r.m.handlers...)
|
||||
c.handlers = append(c.handlers, handlers...)
|
||||
c.run()
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Router) Group(pattern string, fn func(), h ...Handler) {
|
||||
r.groups = append(r.groups, group{pattern, h})
|
||||
fn()
|
||||
r.groups = r.groups[:len(r.groups)-1]
|
||||
}
|
||||
|
||||
// Get is a shortcut for r.Handle("GET", pattern, handlers)
|
||||
func (r *Router) Get(pattern string, h ...Handler) {
|
||||
r.Handle("GET", pattern, h)
|
||||
r.Head(pattern, h...)
|
||||
}
|
||||
|
||||
// Patch is a shortcut for r.Handle("PATCH", pattern, handlers)
|
||||
func (r *Router) Patch(pattern string, h ...Handler) { r.Handle("PATCH", pattern, h) }
|
||||
|
||||
// Post is a shortcut for r.Handle("POST", pattern, handlers)
|
||||
func (r *Router) Post(pattern string, h ...Handler) { r.Handle("POST", pattern, h) }
|
||||
|
||||
// Put is a shortcut for r.Handle("PUT", pattern, handlers)
|
||||
func (r *Router) Put(pattern string, h ...Handler) { r.Handle("PUT", pattern, h) }
|
||||
|
||||
// Delete is a shortcut for r.Handle("DELETE", pattern, handlers)
|
||||
func (r *Router) Delete(pattern string, h ...Handler) { r.Handle("DELETE", pattern, h) }
|
||||
|
||||
// Options is a shortcut for r.Handle("OPTIONS", pattern, handlers)
|
||||
func (r *Router) Options(pattern string, h ...Handler) { r.Handle("OPTIONS", pattern, h) }
|
||||
|
||||
// Head is a shortcut for r.Handle("HEAD", pattern, handlers)
|
||||
func (r *Router) Head(pattern string, h ...Handler) { r.Handle("HEAD", pattern, h) }
|
||||
|
||||
// Any is a shortcut for r.Handle("*", pattern, handlers)
|
||||
func (r *Router) Any(pattern string, h ...Handler) { r.Handle("*", pattern, h) }
|
||||
|
||||
// NotFound configurates http.HandlerFunc which is called when no matching route is
|
||||
// found. If it is not set, http.NotFound is used.
|
||||
// Be sure to set 404 response code in your handler.
|
||||
func (r *Router) NotFound(handlers ...Handler) {
|
||||
handlers = validateAndWrapHandlers(handlers)
|
||||
r.notFound = func(rw http.ResponseWriter, req *http.Request) {
|
||||
c := r.m.createContext(rw, req)
|
||||
c.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers))
|
||||
c.handlers = append(c.handlers, r.m.handlers...)
|
||||
c.handlers = append(c.handlers, handlers...)
|
||||
c.run()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if t, ok := r.routers[req.Method]; ok {
|
||||
// Fast match for static routes
|
||||
if !strings.ContainsAny(req.URL.Path, ":*") {
|
||||
leaf := r.getLeaf(req.Method, req.URL.Path)
|
||||
if leaf != nil {
|
||||
leaf.handle(rw, req, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
h, p, ok := t.Match(req.URL.EscapedPath())
|
||||
if ok {
|
||||
if splat, ok := p["*0"]; ok {
|
||||
p["*"] = splat // Easy name.
|
||||
}
|
||||
h(rw, req, p)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.notFound(rw, req)
|
||||
}
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
// Copyright 2015 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
urlpkg "net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type patternType int8
|
||||
|
||||
const (
|
||||
_PATTERN_STATIC patternType = iota // /home
|
||||
_PATTERN_REGEXP // /:id([0-9]+)
|
||||
_PATTERN_PATH_EXT // /*.*
|
||||
_PATTERN_HOLDER // /:user
|
||||
_PATTERN_MATCH_ALL // /*
|
||||
)
|
||||
|
||||
// Leaf represents a leaf route information.
|
||||
type Leaf struct {
|
||||
parent *Tree
|
||||
|
||||
typ patternType
|
||||
pattern string
|
||||
rawPattern string // Contains wildcard instead of regexp
|
||||
wildcards []string
|
||||
reg *regexp.Regexp
|
||||
optional bool
|
||||
|
||||
handle Handle
|
||||
}
|
||||
|
||||
var wildcardPattern = regexp.MustCompile(`:[a-zA-Z0-9]+`)
|
||||
|
||||
func isSpecialRegexp(pattern, regStr string, pos []int) bool {
|
||||
return len(pattern) >= pos[1]+len(regStr) && pattern[pos[1]:pos[1]+len(regStr)] == regStr
|
||||
}
|
||||
|
||||
// getNextWildcard tries to find next wildcard and update pattern with corresponding regexp.
|
||||
func getNextWildcard(pattern string) (wildcard, _ string) {
|
||||
pos := wildcardPattern.FindStringIndex(pattern)
|
||||
if pos == nil {
|
||||
return "", pattern
|
||||
}
|
||||
wildcard = pattern[pos[0]:pos[1]]
|
||||
|
||||
// Reach last character or no regexp is given.
|
||||
if len(pattern) == pos[1] {
|
||||
return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1)
|
||||
} else if pattern[pos[1]] != '(' {
|
||||
switch {
|
||||
case isSpecialRegexp(pattern, ":int", pos):
|
||||
pattern = strings.Replace(pattern, ":int", "([0-9]+)", 1)
|
||||
case isSpecialRegexp(pattern, ":string", pos):
|
||||
pattern = strings.Replace(pattern, ":string", "([\\w]+)", 1)
|
||||
default:
|
||||
return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Cut out placeholder directly.
|
||||
return wildcard, pattern[:pos[0]] + pattern[pos[1]:]
|
||||
}
|
||||
|
||||
func getWildcards(pattern string) (string, []string) {
|
||||
wildcards := make([]string, 0, 2)
|
||||
|
||||
// Keep getting next wildcard until nothing is left.
|
||||
var wildcard string
|
||||
for {
|
||||
wildcard, pattern = getNextWildcard(pattern)
|
||||
if len(wildcard) > 0 {
|
||||
wildcards = append(wildcards, wildcard)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return pattern, wildcards
|
||||
}
|
||||
|
||||
// getRawPattern removes all regexp but keeps wildcards for building URL path.
|
||||
func getRawPattern(rawPattern string) string {
|
||||
rawPattern = strings.Replace(rawPattern, ":int", "", -1)
|
||||
rawPattern = strings.Replace(rawPattern, ":string", "", -1)
|
||||
|
||||
for {
|
||||
startIdx := strings.Index(rawPattern, "(")
|
||||
if startIdx == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
closeIdx := strings.Index(rawPattern, ")")
|
||||
if closeIdx > -1 {
|
||||
rawPattern = rawPattern[:startIdx] + rawPattern[closeIdx+1:]
|
||||
}
|
||||
}
|
||||
return rawPattern
|
||||
}
|
||||
|
||||
func checkPattern(pattern string) (typ patternType, rawPattern string, wildcards []string, reg *regexp.Regexp) {
|
||||
pattern = strings.TrimLeft(pattern, "?")
|
||||
rawPattern = getRawPattern(pattern)
|
||||
|
||||
if pattern == "*" {
|
||||
typ = _PATTERN_MATCH_ALL
|
||||
} else if pattern == "*.*" {
|
||||
typ = _PATTERN_PATH_EXT
|
||||
} else if strings.Contains(pattern, ":") {
|
||||
typ = _PATTERN_REGEXP
|
||||
pattern, wildcards = getWildcards(pattern)
|
||||
if pattern == "(.+)" {
|
||||
typ = _PATTERN_HOLDER
|
||||
} else {
|
||||
reg = regexp.MustCompile(pattern)
|
||||
}
|
||||
}
|
||||
return typ, rawPattern, wildcards, reg
|
||||
}
|
||||
|
||||
func NewLeaf(parent *Tree, pattern string, handle Handle) *Leaf {
|
||||
typ, rawPattern, wildcards, reg := checkPattern(pattern)
|
||||
optional := false
|
||||
if len(pattern) > 0 && pattern[0] == '?' {
|
||||
optional = true
|
||||
}
|
||||
return &Leaf{parent, typ, pattern, rawPattern, wildcards, reg, optional, handle}
|
||||
}
|
||||
|
||||
// URLPath build path part of URL by given pair values.
|
||||
func (l *Leaf) URLPath(pairs ...string) string {
|
||||
if len(pairs)%2 != 0 {
|
||||
panic("number of pairs does not match")
|
||||
}
|
||||
|
||||
urlPath := l.rawPattern
|
||||
parent := l.parent
|
||||
for parent != nil {
|
||||
urlPath = parent.rawPattern + "/" + urlPath
|
||||
parent = parent.parent
|
||||
}
|
||||
for i := 0; i < len(pairs); i += 2 {
|
||||
if len(pairs[i]) == 0 {
|
||||
panic("pair value cannot be empty: " + strconv.Itoa(i))
|
||||
} else if pairs[i][0] != ':' && pairs[i] != "*" && pairs[i] != "*.*" {
|
||||
pairs[i] = ":" + pairs[i]
|
||||
}
|
||||
urlPath = strings.Replace(urlPath, pairs[i], pairs[i+1], 1)
|
||||
}
|
||||
return urlPath
|
||||
}
|
||||
|
||||
// Tree represents a router tree in Macaron.
|
||||
type Tree struct {
|
||||
parent *Tree
|
||||
|
||||
typ patternType
|
||||
pattern string
|
||||
rawPattern string
|
||||
wildcards []string
|
||||
reg *regexp.Regexp
|
||||
|
||||
subtrees []*Tree
|
||||
leaves []*Leaf
|
||||
}
|
||||
|
||||
func NewSubtree(parent *Tree, pattern string) *Tree {
|
||||
typ, rawPattern, wildcards, reg := checkPattern(pattern)
|
||||
return &Tree{parent, typ, pattern, rawPattern, wildcards, reg, make([]*Tree, 0, 5), make([]*Leaf, 0, 5)}
|
||||
}
|
||||
|
||||
func NewTree() *Tree {
|
||||
return NewSubtree(nil, "")
|
||||
}
|
||||
|
||||
func (t *Tree) addLeaf(pattern string, handle Handle) *Leaf {
|
||||
for i := 0; i < len(t.leaves); i++ {
|
||||
if t.leaves[i].pattern == pattern {
|
||||
return t.leaves[i]
|
||||
}
|
||||
}
|
||||
|
||||
leaf := NewLeaf(t, pattern, handle)
|
||||
|
||||
// Add exact same leaf to grandparent/parent level without optional.
|
||||
if leaf.optional {
|
||||
parent := leaf.parent
|
||||
if parent.parent != nil {
|
||||
parent.parent.addLeaf(parent.pattern, handle)
|
||||
} else {
|
||||
parent.addLeaf("", handle) // Root tree can add as empty pattern.
|
||||
}
|
||||
}
|
||||
|
||||
i := 0
|
||||
for ; i < len(t.leaves); i++ {
|
||||
if leaf.typ < t.leaves[i].typ {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if i == len(t.leaves) {
|
||||
t.leaves = append(t.leaves, leaf)
|
||||
} else {
|
||||
t.leaves = append(t.leaves[:i], append([]*Leaf{leaf}, t.leaves[i:]...)...)
|
||||
}
|
||||
return leaf
|
||||
}
|
||||
|
||||
func (t *Tree) addSubtree(segment, pattern string, handle Handle) *Leaf {
|
||||
for i := 0; i < len(t.subtrees); i++ {
|
||||
if t.subtrees[i].pattern == segment {
|
||||
return t.subtrees[i].addNextSegment(pattern, handle)
|
||||
}
|
||||
}
|
||||
|
||||
subtree := NewSubtree(t, segment)
|
||||
i := 0
|
||||
for ; i < len(t.subtrees); i++ {
|
||||
if subtree.typ < t.subtrees[i].typ {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if i == len(t.subtrees) {
|
||||
t.subtrees = append(t.subtrees, subtree)
|
||||
} else {
|
||||
t.subtrees = append(t.subtrees[:i], append([]*Tree{subtree}, t.subtrees[i:]...)...)
|
||||
}
|
||||
return subtree.addNextSegment(pattern, handle)
|
||||
}
|
||||
|
||||
func (t *Tree) addNextSegment(pattern string, handle Handle) *Leaf {
|
||||
pattern = strings.TrimPrefix(pattern, "/")
|
||||
|
||||
i := strings.Index(pattern, "/")
|
||||
if i == -1 {
|
||||
return t.addLeaf(pattern, handle)
|
||||
}
|
||||
return t.addSubtree(pattern[:i], pattern[i+1:], handle)
|
||||
}
|
||||
|
||||
func (t *Tree) Add(pattern string, handle Handle) *Leaf {
|
||||
pattern = strings.TrimSuffix(pattern, "/")
|
||||
return t.addNextSegment(pattern, handle)
|
||||
}
|
||||
|
||||
func (t *Tree) matchLeaf(globLevel int, url string, params map[string]string) (Handle, bool) {
|
||||
url, err := urlpkg.PathUnescape(url)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
for i := 0; i < len(t.leaves); i++ {
|
||||
switch t.leaves[i].typ {
|
||||
case _PATTERN_STATIC:
|
||||
if t.leaves[i].pattern == url {
|
||||
return t.leaves[i].handle, true
|
||||
}
|
||||
case _PATTERN_REGEXP:
|
||||
results := t.leaves[i].reg.FindStringSubmatch(url)
|
||||
// Number of results and wildcasrd should be exact same.
|
||||
if len(results)-1 != len(t.leaves[i].wildcards) {
|
||||
break
|
||||
}
|
||||
|
||||
for j := 0; j < len(t.leaves[i].wildcards); j++ {
|
||||
params[t.leaves[i].wildcards[j]] = results[j+1]
|
||||
}
|
||||
return t.leaves[i].handle, true
|
||||
case _PATTERN_PATH_EXT:
|
||||
j := strings.LastIndex(url, ".")
|
||||
if j > -1 {
|
||||
params[":path"] = url[:j]
|
||||
params[":ext"] = url[j+1:]
|
||||
} else {
|
||||
params[":path"] = url
|
||||
}
|
||||
return t.leaves[i].handle, true
|
||||
case _PATTERN_HOLDER:
|
||||
params[t.leaves[i].wildcards[0]] = url
|
||||
return t.leaves[i].handle, true
|
||||
case _PATTERN_MATCH_ALL:
|
||||
params["*"] = url
|
||||
params["*"+strconv.Itoa(globLevel)] = url
|
||||
return t.leaves[i].handle, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (t *Tree) matchSubtree(globLevel int, segment, url string, params map[string]string) (Handle, bool) {
|
||||
unescapedSegment, err := urlpkg.PathUnescape(segment)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
for i := 0; i < len(t.subtrees); i++ {
|
||||
switch t.subtrees[i].typ {
|
||||
case _PATTERN_STATIC:
|
||||
if t.subtrees[i].pattern == unescapedSegment {
|
||||
if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok {
|
||||
return handle, true
|
||||
}
|
||||
}
|
||||
case _PATTERN_REGEXP:
|
||||
results := t.subtrees[i].reg.FindStringSubmatch(unescapedSegment)
|
||||
if len(results)-1 != len(t.subtrees[i].wildcards) {
|
||||
break
|
||||
}
|
||||
|
||||
for j := 0; j < len(t.subtrees[i].wildcards); j++ {
|
||||
params[t.subtrees[i].wildcards[j]] = results[j+1]
|
||||
}
|
||||
if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok {
|
||||
return handle, true
|
||||
}
|
||||
case _PATTERN_HOLDER:
|
||||
if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok {
|
||||
params[t.subtrees[i].wildcards[0]] = unescapedSegment
|
||||
return handle, true
|
||||
}
|
||||
case _PATTERN_MATCH_ALL:
|
||||
if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok {
|
||||
params["*"+strconv.Itoa(globLevel)] = unescapedSegment
|
||||
return handle, true
|
||||
}
|
||||
default: // ignore
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.leaves) > 0 {
|
||||
leaf := t.leaves[len(t.leaves)-1]
|
||||
unescapedURL, err := urlpkg.PathUnescape(segment + "/" + url)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if leaf.typ == _PATTERN_PATH_EXT {
|
||||
j := strings.LastIndex(unescapedURL, ".")
|
||||
if j > -1 {
|
||||
params[":path"] = unescapedURL[:j]
|
||||
params[":ext"] = unescapedURL[j+1:]
|
||||
} else {
|
||||
params[":path"] = unescapedURL
|
||||
}
|
||||
return leaf.handle, true
|
||||
} else if leaf.typ == _PATTERN_MATCH_ALL {
|
||||
params["*"] = unescapedURL
|
||||
params["*"+strconv.Itoa(globLevel)] = unescapedURL
|
||||
return leaf.handle, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (t *Tree) matchNextSegment(globLevel int, url string, params map[string]string) (Handle, bool) {
|
||||
i := strings.Index(url, "/")
|
||||
if i == -1 {
|
||||
return t.matchLeaf(globLevel, url, params)
|
||||
}
|
||||
return t.matchSubtree(globLevel, url[:i], url[i+1:], params)
|
||||
}
|
||||
|
||||
func (t *Tree) Match(url string) (Handle, map[string]string, bool) {
|
||||
url = strings.TrimPrefix(url, "/")
|
||||
url = strings.TrimSuffix(url, "/")
|
||||
params := map[string]string{}
|
||||
handle, ok := t.matchNextSegment(0, url, params)
|
||||
return handle, params, ok
|
||||
}
|
||||
|
||||
// MatchTest returns true if given URL is matched by given pattern.
|
||||
func MatchTest(pattern, url string) bool {
|
||||
t := NewTree()
|
||||
t.Add(pattern, nil)
|
||||
_, _, ok := t.Match(url)
|
||||
return ok
|
||||
}
|
||||
+1
-15
@@ -1,17 +1,3 @@
|
||||
package web
|
||||
|
||||
import "gopkg.in/macaron.v1"
|
||||
|
||||
type Context = macaron.Context
|
||||
type Handler = macaron.Handler
|
||||
type BeforeFunc = macaron.BeforeFunc
|
||||
type ResponseWriter = macaron.ResponseWriter
|
||||
type Mux = macaron.Macaron
|
||||
|
||||
var Params = macaron.Params
|
||||
var SetURLParams = macaron.SetURLParams
|
||||
var NewResponseWriter = macaron.NewResponseWriter
|
||||
var New = macaron.New
|
||||
var Env = macaron.Env
|
||||
var Renderer = macaron.Renderer
|
||||
var Bind = macaron.Bind
|
||||
type Mux = Macaron
|
||||
|
||||
Reference in New Issue
Block a user