mirror of https://github.com/grafana/grafana
Gradually remove Macaron web framework (#36325)
* add macaron code to the code base * remove unused secure cookies support from macaron * clean up modules * remove com dependency * fix silly typos * little cleanup, remove recovery middleware * remove logger middleware * remove static handler and remove unused context methods * bring inject into macaron codebase * remove unused applicator * add back macaron license * more cleanups in macaron code * remove unused injector Set method * remove unused context methods: param to int conversion, body helper type, cookie helpers * remove action from context * remove complex environment handling, we only use Env variable * restore ReplaceAllParams to fix the testspull/36559/head
parent
4c0acf335b
commit
9b2d7d6d69
@ -0,0 +1,191 @@ |
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and |
||||
distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright |
||||
owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities |
||||
that control, are controlled by, or are under common control with that entity. |
||||
For the purposes of this definition, "control" means (i) the power, direct or |
||||
indirect, to cause the direction or management of such entity, whether by |
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising |
||||
permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including |
||||
but not limited to software source code, documentation source, and configuration |
||||
files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or |
||||
translation of a Source form, including but not limited to compiled object code, |
||||
generated documentation, and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made |
||||
available under the License, as indicated by a copyright notice that is included |
||||
in or attached to the work (an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that |
||||
is based on (or derived from) the Work and for which the editorial revisions, |
||||
annotations, elaborations, or other modifications represent, as a whole, an |
||||
original work of authorship. For the purposes of this License, Derivative Works |
||||
shall not include works that remain separable from, or merely link (or bind by |
||||
name) to the interfaces of, the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version |
||||
of the Work and any modifications or additions to that Work or Derivative Works |
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work |
||||
by the copyright owner or by an individual or Legal Entity authorized to submit |
||||
on behalf of the copyright owner. For the purposes of this definition, |
||||
"submitted" means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, and |
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for |
||||
the purpose of discussing and improving the Work, but excluding communication |
||||
that is conspicuously marked or otherwise designated in writing by the copyright |
||||
owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf |
||||
of whom a Contribution has been received by Licensor and subsequently |
||||
incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. |
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby |
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, |
||||
irrevocable copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the Work and such |
||||
Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. |
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby |
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, |
||||
irrevocable (except as stated in this section) patent license to make, have |
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where |
||||
such license applies only to those patent claims licensable by such Contributor |
||||
that are necessarily infringed by their Contribution(s) alone or by combination |
||||
of their Contribution(s) with the Work to which such Contribution(s) was |
||||
submitted. If You institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a |
||||
Contribution incorporated within the Work constitutes direct or contributory |
||||
patent infringement, then any patent licenses granted to You under this License |
||||
for that Work shall terminate as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. |
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof |
||||
in any medium, with or without modifications, and in Source or Object form, |
||||
provided that You meet the following conditions: |
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of |
||||
this License; and |
||||
You must cause any modified files to carry prominent notices stating that You |
||||
changed the files; and |
||||
You must retain, in the Source form of any Derivative Works that You distribute, |
||||
all copyright, patent, trademark, and attribution notices from the Source form |
||||
of the Work, excluding those notices that do not pertain to any part of the |
||||
Derivative Works; and |
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any |
||||
Derivative Works that You distribute must include a readable copy of the |
||||
attribution notices contained within such NOTICE file, excluding those notices |
||||
that do not pertain to any part of the Derivative Works, in at least one of the |
||||
following places: within a NOTICE text file distributed as part of the |
||||
Derivative Works; within the Source form or documentation, if provided along |
||||
with the Derivative Works; or, within a display generated by the Derivative |
||||
Works, if and wherever such third-party notices normally appear. The contents of |
||||
the NOTICE file are for informational purposes only and do not modify the |
||||
License. You may add Your own attribution notices within Derivative Works that |
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work, |
||||
provided that such additional attribution notices cannot be construed as |
||||
modifying the License. |
||||
You may add Your own copyright statement to Your modifications and may provide |
||||
additional or different license terms and conditions for use, reproduction, or |
||||
distribution of Your modifications, or for any such Derivative Works as a whole, |
||||
provided Your use, reproduction, and distribution of the Work otherwise complies |
||||
with the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. |
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted |
||||
for inclusion in the Work by You to the Licensor shall be under the terms and |
||||
conditions of this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of |
||||
any separate license agreement you may have executed with Licensor regarding |
||||
such Contributions. |
||||
|
||||
6. Trademarks. |
||||
|
||||
This License does not grant permission to use the trade names, trademarks, |
||||
service marks, or product names of the Licensor, except as required for |
||||
reasonable and customary use in describing the origin of the Work and |
||||
reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. |
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the |
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, |
||||
including, without limitation, any warranties or conditions of TITLE, |
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are |
||||
solely responsible for determining the appropriateness of using or |
||||
redistributing the Work and assume any risks associated with Your exercise of |
||||
permissions under this License. |
||||
|
||||
8. Limitation of Liability. |
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence), |
||||
contract, or otherwise, unless required by applicable law (such as deliberate |
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, incidental, |
||||
or consequential damages of any character arising as a result of this License or |
||||
out of the use or inability to use the Work (including but not limited to |
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or |
||||
any and all other commercial damages or losses), even if such Contributor has |
||||
been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. |
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to |
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or |
||||
other liability obligations and/or rights consistent with this License. However, |
||||
in accepting such obligations, You may act only on Your own behalf and on Your |
||||
sole responsibility, not on behalf of any other Contributor, and only if You |
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason of your |
||||
accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work |
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate |
||||
notice, with the fields enclosed by brackets "[]" replaced with your own |
||||
identifying information. (Don't include the brackets!) The text should be |
||||
enclosed in the appropriate comment syntax for the file format. We also |
||||
recommend that a file or class name and description of purpose be included on |
||||
the same "printed page" as the copyright notice for easier identification within |
||||
third-party archives. |
||||
|
||||
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. |
||||
@ -0,0 +1,226 @@ |
||||
// 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 |
||||
|
||||
import ( |
||||
"net/http" |
||||
"net/url" |
||||
"reflect" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
// Request represents an HTTP request received by a server or to be sent by a client.
|
||||
type Request struct { |
||||
*http.Request |
||||
} |
||||
|
||||
// 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 Request |
||||
Resp ResponseWriter |
||||
params Params |
||||
Render |
||||
Data map[string]interface{} |
||||
} |
||||
|
||||
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() |
||||
} |
||||
|
||||
// Written returns whether the context response has been written to
|
||||
func (ctx *Context) Written() bool { |
||||
return ctx.Resp.Written() |
||||
} |
||||
|
||||
func (ctx *Context) run() { |
||||
for ctx.index <= len(ctx.handlers) { |
||||
vals, err := ctx.Invoke(ctx.handler()) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
ctx.index++ |
||||
|
||||
// if the handler returned something, write it to the http response
|
||||
if len(vals) > 0 { |
||||
ev := ctx.GetVal(reflect.TypeOf(ReturnHandler(nil))) |
||||
handleReturn := ev.Interface().(ReturnHandler) |
||||
handleReturn(ctx, vals) |
||||
} |
||||
|
||||
if ctx.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 |
||||
} |
||||
|
||||
func (ctx *Context) renderHTML(status int, setName, tplName string, data ...interface{}) { |
||||
if len(data) <= 0 { |
||||
ctx.Render.HTMLSet(status, setName, tplName, ctx.Data) |
||||
} else if len(data) == 1 { |
||||
ctx.Render.HTMLSet(status, setName, tplName, data[0]) |
||||
} else { |
||||
ctx.Render.HTMLSet(status, setName, tplName, data[0], data[1].(HTMLOptions)) |
||||
} |
||||
} |
||||
|
||||
// HTML renders the HTML with default template set.
|
||||
func (ctx *Context) HTML(status int, name string, data ...interface{}) { |
||||
ctx.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data...) |
||||
} |
||||
|
||||
// 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.Request, 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(_CONTENT_TYPE) |
||||
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 |
||||
} |
||||
|
||||
// Params returns value of given param name.
|
||||
// e.g. ctx.Params(":uid") or ctx.Params("uid")
|
||||
func (ctx *Context) Params(name string) string { |
||||
if len(name) == 0 { |
||||
return "" |
||||
} |
||||
if len(name) > 1 && name[0] != ':' { |
||||
name = ":" + name |
||||
} |
||||
return ctx.params[name] |
||||
} |
||||
|
||||
// AllParams returns all params.
|
||||
func (ctx *Context) AllParams() Params { |
||||
return ctx.params |
||||
} |
||||
|
||||
// ReplaceAllParams replace all current params with given params
|
||||
func (ctx *Context) ReplaceAllParams(params Params) { |
||||
ctx.params = params |
||||
} |
||||
|
||||
// ParamsInt64 returns params result in int64 type.
|
||||
// e.g. ctx.ParamsInt64(":uid")
|
||||
func (ctx *Context) ParamsInt64(name string) int64 { |
||||
n, _ := strconv.ParseInt(ctx.Params(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,3 @@ |
||||
module gopkg.in/macaron.v1 |
||||
|
||||
go 1.16 |
||||
@ -0,0 +1,208 @@ |
||||
// 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 macaron |
||||
|
||||
import ( |
||||
"fmt" |
||||
"reflect" |
||||
) |
||||
|
||||
// Injector represents an interface for mapping and injecting dependencies into structs
|
||||
// and function arguments.
|
||||
type Injector interface { |
||||
Invoker |
||||
TypeMapper |
||||
// SetParent sets the parent of the injector. If the injector cannot find a
|
||||
// dependency in its Type map it will check its parent before returning an
|
||||
// error.
|
||||
SetParent(Injector) |
||||
} |
||||
|
||||
// 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 |
||||
parent Injector |
||||
} |
||||
|
||||
// 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 (i *injector) Map(val interface{}) TypeMapper { |
||||
i.values[reflect.TypeOf(val)] = reflect.ValueOf(val) |
||||
return i |
||||
} |
||||
|
||||
func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper { |
||||
i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val) |
||||
return i |
||||
} |
||||
|
||||
func (i *injector) GetVal(t reflect.Type) reflect.Value { |
||||
val := i.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 i.values { |
||||
if k.Implements(t) { |
||||
val = v |
||||
break |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Still no type found, try to look it up on the parent
|
||||
if !val.IsValid() && i.parent != nil { |
||||
val = i.parent.GetVal(t) |
||||
} |
||||
|
||||
return val |
||||
|
||||
} |
||||
|
||||
func (i *injector) SetParent(parent Injector) { |
||||
i.parent = parent |
||||
} |
||||
@ -0,0 +1,211 @@ |
||||
// +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 macaron // import "gopkg.in/macaron.v1"
|
||||
|
||||
import ( |
||||
"log" |
||||
"net/http" |
||||
"os" |
||||
"reflect" |
||||
"strconv" |
||||
"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 fullfilled 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 |
||||
} |
||||
|
||||
// internalServerErrorInvoker is an inject.FastInvoker wrapper of func(rw http.ResponseWriter, err error).
|
||||
type internalServerErrorInvoker func(rw http.ResponseWriter, err error) |
||||
|
||||
func (invoke internalServerErrorInvoker) Invoke(params []interface{}) ([]reflect.Value, error) { |
||||
invoke(params[0].(http.ResponseWriter), params[1].(error)) |
||||
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) |
||||
case func(http.ResponseWriter, error): |
||||
return internalServerErrorInvoker(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, wrappers ...func(Handler) Handler) []Handler { |
||||
var wrapper func(Handler) Handler |
||||
if len(wrappers) > 0 { |
||||
wrapper = wrappers[0] |
||||
} |
||||
|
||||
wrappedHandlers := make([]Handler, len(handlers)) |
||||
for i, h := range handlers { |
||||
h = validateAndWrapHandler(h) |
||||
if wrapper != nil && !IsFastInvoker(h) { |
||||
h = wrapper(h) |
||||
} |
||||
wrappedHandlers[i] = 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 { |
||||
Injector |
||||
befores []BeforeHandler |
||||
handlers []Handler |
||||
|
||||
hasURLPrefix bool |
||||
urlPrefix string // For suburl support.
|
||||
*Router |
||||
|
||||
logger *log.Logger |
||||
} |
||||
|
||||
// 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{ |
||||
Injector: NewInjector(), |
||||
Router: NewRouter(), |
||||
logger: log.New(os.Stdout, "[Macaron] ", 0), |
||||
} |
||||
m.Router.m = m |
||||
m.Map(m.logger) |
||||
m.Map(defaultReturnHandler()) |
||||
m.NotFound(http.NotFound) |
||||
m.InternalServerError(func(rw http.ResponseWriter, err error) { |
||||
http.Error(rw, err.Error(), 500) |
||||
}) |
||||
return m |
||||
} |
||||
|
||||
// Handlers sets the entire middleware stack with the given Handlers.
|
||||
// This will clear any current middleware handlers,
|
||||
// and panics if any of the handlers is not a callable function
|
||||
func (m *Macaron) Handlers(handlers ...Handler) { |
||||
m.handlers = make([]Handler, 0) |
||||
for _, handler := range handlers { |
||||
m.Use(handler) |
||||
} |
||||
} |
||||
|
||||
// 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 |
||||
|
||||
// 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, |
||||
Req: Request{req}, |
||||
Resp: NewResponseWriter(req.Method, rw), |
||||
Render: &DummyRender{rw}, |
||||
Data: make(map[string]interface{}), |
||||
} |
||||
c.SetParent(m) |
||||
c.Map(c) |
||||
c.MapTo(c.Resp, (*http.ResponseWriter)(nil)) |
||||
c.Map(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) { |
||||
if m.hasURLPrefix { |
||||
req.URL.Path = strings.TrimPrefix(req.URL.Path, m.urlPrefix) |
||||
} |
||||
for _, h := range m.befores { |
||||
if h(rw, req) { |
||||
return |
||||
} |
||||
} |
||||
m.Router.ServeHTTP(rw, req) |
||||
} |
||||
|
||||
func getDefaultListenInfo() (string, int) { |
||||
host := os.Getenv("HOST") |
||||
if len(host) == 0 { |
||||
host = "0.0.0.0" |
||||
} |
||||
port, _ := strconv.Atoi(os.Getenv("PORT")) |
||||
if port == 0 { |
||||
port = 4000 |
||||
} |
||||
return host, port |
||||
} |
||||
|
||||
// SetURLPrefix sets URL prefix of router layer, so that it support suburl.
|
||||
func (m *Macaron) SetURLPrefix(prefix string) { |
||||
m.urlPrefix = prefix |
||||
m.hasURLPrefix = len(m.urlPrefix) > 0 |
||||
} |
||||
@ -0,0 +1,723 @@ |
||||
// 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 macaron |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"encoding/xml" |
||||
"fmt" |
||||
"html/template" |
||||
"io" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
_CONTENT_TYPE = "Content-Type" |
||||
_CONTENT_BINARY = "application/octet-stream" |
||||
_CONTENT_JSON = "application/json" |
||||
_CONTENT_HTML = "text/html" |
||||
_CONTENT_PLAIN = "text/plain" |
||||
_CONTENT_XHTML = "application/xhtml+xml" |
||||
_CONTENT_XML = "text/xml" |
||||
_DEFAULT_CHARSET = "UTF-8" |
||||
) |
||||
|
||||
var ( |
||||
// Provides a temporary buffer to execute templates into and catch errors.
|
||||
bufpool = sync.Pool{ |
||||
New: func() interface{} { return new(bytes.Buffer) }, |
||||
} |
||||
|
||||
// Included helper functions for use when rendering html
|
||||
helperFuncs = template.FuncMap{ |
||||
"yield": func() (string, error) { |
||||
return "", fmt.Errorf("yield called with no layout defined") |
||||
}, |
||||
"current": func() (string, error) { |
||||
return "", nil |
||||
}, |
||||
} |
||||
) |
||||
|
||||
type ( |
||||
// TemplateFile represents a interface of template file that has name and can be read.
|
||||
TemplateFile interface { |
||||
Name() string |
||||
Data() []byte |
||||
Ext() string |
||||
} |
||||
// TemplateFileSystem represents a interface of template file system that able to list all files.
|
||||
TemplateFileSystem interface { |
||||
ListFiles() []TemplateFile |
||||
Get(string) (io.Reader, error) |
||||
} |
||||
|
||||
// Delims represents a set of Left and Right delimiters for HTML template rendering
|
||||
Delims struct { |
||||
// Left delimiter, defaults to {{
|
||||
Left string |
||||
// Right delimiter, defaults to }}
|
||||
Right string |
||||
} |
||||
|
||||
// RenderOptions represents a struct for specifying configuration options for the Render middleware.
|
||||
RenderOptions struct { |
||||
// Directory to load templates. Default is "templates".
|
||||
Directory string |
||||
// Addtional directories to overwite templates.
|
||||
AppendDirectories []string |
||||
// Layout template name. Will not render a layout if "". Default is to "".
|
||||
Layout string |
||||
// Extensions to parse template files from. Defaults are [".tmpl", ".html"].
|
||||
Extensions []string |
||||
// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
|
||||
Funcs []template.FuncMap |
||||
// Delims sets the action delimiters to the specified strings in the Delims struct.
|
||||
Delims Delims |
||||
// Appends the given charset to the Content-Type header. Default is "UTF-8".
|
||||
Charset string |
||||
// Outputs human readable JSON.
|
||||
IndentJSON bool |
||||
// Outputs human readable XML.
|
||||
IndentXML bool |
||||
// Prefixes the JSON output with the given bytes.
|
||||
PrefixJSON []byte |
||||
// Prefixes the XML output with the given bytes.
|
||||
PrefixXML []byte |
||||
// Allows changing of output to XHTML instead of HTML. Default is "text/html"
|
||||
HTMLContentType string |
||||
// TemplateFileSystem is the interface for supporting any implmentation of template file system.
|
||||
TemplateFileSystem |
||||
} |
||||
|
||||
// HTMLOptions is a struct for overriding some rendering Options for specific HTML call
|
||||
HTMLOptions struct { |
||||
// Layout template name. Overrides Options.Layout.
|
||||
Layout string |
||||
} |
||||
|
||||
Render interface { |
||||
http.ResponseWriter |
||||
SetResponseWriter(http.ResponseWriter) |
||||
|
||||
JSON(int, interface{}) |
||||
JSONString(interface{}) (string, error) |
||||
RawData(int, []byte) // Serve content as binary
|
||||
PlainText(int, []byte) // Serve content as plain text
|
||||
HTML(int, string, interface{}, ...HTMLOptions) |
||||
HTMLSet(int, string, string, interface{}, ...HTMLOptions) |
||||
HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) |
||||
HTMLString(string, interface{}, ...HTMLOptions) (string, error) |
||||
HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) |
||||
HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) |
||||
XML(int, interface{}) |
||||
Error(int, ...string) |
||||
Status(int) |
||||
SetTemplatePath(string, string) |
||||
HasTemplateSet(string) bool |
||||
} |
||||
) |
||||
|
||||
// TplFile implements TemplateFile interface.
|
||||
type TplFile struct { |
||||
name string |
||||
data []byte |
||||
ext string |
||||
} |
||||
|
||||
// NewTplFile cerates new template file with given name and data.
|
||||
func NewTplFile(name string, data []byte, ext string) *TplFile { |
||||
return &TplFile{name, data, ext} |
||||
} |
||||
|
||||
func (f *TplFile) Name() string { |
||||
return f.name |
||||
} |
||||
|
||||
func (f *TplFile) Data() []byte { |
||||
return f.data |
||||
} |
||||
|
||||
func (f *TplFile) Ext() string { |
||||
return f.ext |
||||
} |
||||
|
||||
// TplFileSystem implements TemplateFileSystem interface.
|
||||
type TplFileSystem struct { |
||||
files []TemplateFile |
||||
} |
||||
|
||||
// NewTemplateFileSystem creates new template file system with given options.
|
||||
func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem { |
||||
fs := TplFileSystem{} |
||||
fs.files = make([]TemplateFile, 0, 10) |
||||
|
||||
// Directories are composed in reverse order because later one overwrites previous ones,
|
||||
// so once found, we can directly jump out of the loop.
|
||||
dirs := make([]string, 0, len(opt.AppendDirectories)+1) |
||||
for i := len(opt.AppendDirectories) - 1; i >= 0; i-- { |
||||
dirs = append(dirs, opt.AppendDirectories[i]) |
||||
} |
||||
dirs = append(dirs, opt.Directory) |
||||
|
||||
var err error |
||||
for i := range dirs { |
||||
// Skip ones that does not exists for symlink test,
|
||||
// but allow non-symlink ones added after start.
|
||||
if _, err := os.Stat(dirs[i]); err != nil && os.IsNotExist(err) { |
||||
continue |
||||
} |
||||
|
||||
dirs[i], err = filepath.EvalSymlinks(dirs[i]) |
||||
if err != nil { |
||||
panic("EvalSymlinks(" + dirs[i] + "): " + err.Error()) |
||||
} |
||||
} |
||||
lastDir := dirs[len(dirs)-1] |
||||
|
||||
// We still walk the last (original) directory because it's non-sense we load templates not exist in original directory.
|
||||
if err = filepath.Walk(lastDir, func(path string, info os.FileInfo, _ error) error { |
||||
r, err := filepath.Rel(lastDir, path) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
ext := GetExt(r) |
||||
|
||||
for _, extension := range opt.Extensions { |
||||
if ext != extension { |
||||
continue |
||||
} |
||||
|
||||
var data []byte |
||||
if !omitData { |
||||
// Loop over candidates of directory, break out once found.
|
||||
// The file always exists because it's inside the walk function,
|
||||
// and read original file is the worst case.
|
||||
for i := range dirs { |
||||
path = filepath.Join(dirs[i], r) |
||||
if f, err := os.Stat(path); err != nil || f.IsDir() { |
||||
continue |
||||
} |
||||
|
||||
data, err = ioutil.ReadFile(path) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
break |
||||
} |
||||
} |
||||
|
||||
name := filepath.ToSlash((r[0 : len(r)-len(ext)])) |
||||
fs.files = append(fs.files, NewTplFile(name, data, ext)) |
||||
} |
||||
|
||||
return nil |
||||
}); err != nil { |
||||
panic("NewTemplateFileSystem: " + err.Error()) |
||||
} |
||||
|
||||
return fs |
||||
} |
||||
|
||||
func (fs TplFileSystem) ListFiles() []TemplateFile { |
||||
return fs.files |
||||
} |
||||
|
||||
func (fs TplFileSystem) Get(name string) (io.Reader, error) { |
||||
for i := range fs.files { |
||||
if fs.files[i].Name()+fs.files[i].Ext() == name { |
||||
return bytes.NewReader(fs.files[i].Data()), nil |
||||
} |
||||
} |
||||
return nil, fmt.Errorf("file '%s' not found", name) |
||||
} |
||||
|
||||
func PrepareCharset(charset string) string { |
||||
if len(charset) != 0 { |
||||
return "; charset=" + charset |
||||
} |
||||
|
||||
return "; charset=" + _DEFAULT_CHARSET |
||||
} |
||||
|
||||
func GetExt(s string) string { |
||||
index := strings.Index(s, ".") |
||||
if index == -1 { |
||||
return "" |
||||
} |
||||
return s[index:] |
||||
} |
||||
|
||||
func compile(opt RenderOptions) *template.Template { |
||||
t := template.New(opt.Directory) |
||||
t.Delims(opt.Delims.Left, opt.Delims.Right) |
||||
// Parse an initial template in case we don't have any.
|
||||
template.Must(t.Parse("Macaron")) |
||||
|
||||
if opt.TemplateFileSystem == nil { |
||||
opt.TemplateFileSystem = NewTemplateFileSystem(opt, false) |
||||
} |
||||
|
||||
for _, f := range opt.TemplateFileSystem.ListFiles() { |
||||
tmpl := t.New(f.Name()) |
||||
for _, funcs := range opt.Funcs { |
||||
tmpl.Funcs(funcs) |
||||
} |
||||
// Bomb out if parse fails. We don't want any silent server starts.
|
||||
template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data()))) |
||||
} |
||||
|
||||
return t |
||||
} |
||||
|
||||
const ( |
||||
DEFAULT_TPL_SET_NAME = "DEFAULT" |
||||
) |
||||
|
||||
// TemplateSet represents a template set of type *template.Template.
|
||||
type TemplateSet struct { |
||||
lock sync.RWMutex |
||||
sets map[string]*template.Template |
||||
dirs map[string]string |
||||
} |
||||
|
||||
// NewTemplateSet initializes a new empty template set.
|
||||
func NewTemplateSet() *TemplateSet { |
||||
return &TemplateSet{ |
||||
sets: make(map[string]*template.Template), |
||||
dirs: make(map[string]string), |
||||
} |
||||
} |
||||
|
||||
func (ts *TemplateSet) Set(name string, opt *RenderOptions) *template.Template { |
||||
t := compile(*opt) |
||||
|
||||
ts.lock.Lock() |
||||
defer ts.lock.Unlock() |
||||
|
||||
ts.sets[name] = t |
||||
ts.dirs[name] = opt.Directory |
||||
return t |
||||
} |
||||
|
||||
func (ts *TemplateSet) Get(name string) *template.Template { |
||||
ts.lock.RLock() |
||||
defer ts.lock.RUnlock() |
||||
|
||||
return ts.sets[name] |
||||
} |
||||
|
||||
func (ts *TemplateSet) GetDir(name string) string { |
||||
ts.lock.RLock() |
||||
defer ts.lock.RUnlock() |
||||
|
||||
return ts.dirs[name] |
||||
} |
||||
|
||||
func prepareRenderOptions(options []RenderOptions) RenderOptions { |
||||
var opt RenderOptions |
||||
if len(options) > 0 { |
||||
opt = options[0] |
||||
} |
||||
|
||||
// Defaults.
|
||||
if len(opt.Directory) == 0 { |
||||
opt.Directory = "templates" |
||||
} |
||||
if len(opt.Extensions) == 0 { |
||||
opt.Extensions = []string{".tmpl", ".html"} |
||||
} |
||||
if len(opt.HTMLContentType) == 0 { |
||||
opt.HTMLContentType = _CONTENT_HTML |
||||
} |
||||
|
||||
return opt |
||||
} |
||||
|
||||
func ParseTplSet(tplSet string) (tplName string, tplDir string) { |
||||
tplSet = strings.TrimSpace(tplSet) |
||||
if len(tplSet) == 0 { |
||||
panic("empty template set argument") |
||||
} |
||||
infos := strings.Split(tplSet, ":") |
||||
if len(infos) == 1 { |
||||
tplDir = infos[0] |
||||
tplName = path.Base(tplDir) |
||||
} else { |
||||
tplName = infos[0] |
||||
tplDir = infos[1] |
||||
} |
||||
|
||||
dir, err := os.Stat(tplDir) |
||||
if err != nil || !dir.IsDir() { |
||||
panic("template set path does not exist or is not a directory") |
||||
} |
||||
return tplName, tplDir |
||||
} |
||||
|
||||
func renderHandler(opt RenderOptions, tplSets []string) Handler { |
||||
cs := PrepareCharset(opt.Charset) |
||||
ts := NewTemplateSet() |
||||
ts.Set(DEFAULT_TPL_SET_NAME, &opt) |
||||
|
||||
var tmpOpt RenderOptions |
||||
for _, tplSet := range tplSets { |
||||
tplName, tplDir := ParseTplSet(tplSet) |
||||
tmpOpt = opt |
||||
tmpOpt.Directory = tplDir |
||||
ts.Set(tplName, &tmpOpt) |
||||
} |
||||
|
||||
return func(ctx *Context) { |
||||
r := &TplRender{ |
||||
ResponseWriter: ctx.Resp, |
||||
TemplateSet: ts, |
||||
Opt: &opt, |
||||
CompiledCharset: cs, |
||||
} |
||||
ctx.Data["TmplLoadTimes"] = func() string { |
||||
if r.startTime.IsZero() { |
||||
return "" |
||||
} |
||||
return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms" |
||||
} |
||||
|
||||
ctx.Render = r |
||||
ctx.MapTo(r, (*Render)(nil)) |
||||
} |
||||
} |
||||
|
||||
// Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
|
||||
// An single variadic macaron.RenderOptions struct can be optionally provided to configure
|
||||
// HTML rendering. The default directory for templates is "templates" and the default
|
||||
// file extension is ".tmpl" and ".html".
|
||||
//
|
||||
// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
|
||||
// MACARON_ENV environment variable to "production".
|
||||
func Renderer(options ...RenderOptions) Handler { |
||||
return renderHandler(prepareRenderOptions(options), []string{}) |
||||
} |
||||
|
||||
func Renderers(options RenderOptions, tplSets ...string) Handler { |
||||
return renderHandler(prepareRenderOptions([]RenderOptions{options}), tplSets) |
||||
} |
||||
|
||||
type TplRender struct { |
||||
http.ResponseWriter |
||||
*TemplateSet |
||||
Opt *RenderOptions |
||||
CompiledCharset string |
||||
|
||||
startTime time.Time |
||||
} |
||||
|
||||
func (r *TplRender) SetResponseWriter(rw http.ResponseWriter) { |
||||
r.ResponseWriter = rw |
||||
} |
||||
|
||||
func (r *TplRender) JSON(status int, v interface{}) { |
||||
var ( |
||||
result []byte |
||||
err error |
||||
) |
||||
if r.Opt.IndentJSON { |
||||
result, err = json.MarshalIndent(v, "", " ") |
||||
} else { |
||||
result, err = json.Marshal(v) |
||||
} |
||||
if err != nil { |
||||
http.Error(r, err.Error(), 500) |
||||
return |
||||
} |
||||
|
||||
// json rendered fine, write out the result
|
||||
r.Header().Set(_CONTENT_TYPE, _CONTENT_JSON+r.CompiledCharset) |
||||
r.WriteHeader(status) |
||||
if len(r.Opt.PrefixJSON) > 0 { |
||||
_, _ = r.Write(r.Opt.PrefixJSON) |
||||
} |
||||
_, _ = r.Write(result) |
||||
} |
||||
|
||||
func (r *TplRender) JSONString(v interface{}) (string, error) { |
||||
var result []byte |
||||
var err error |
||||
if r.Opt.IndentJSON { |
||||
result, err = json.MarshalIndent(v, "", " ") |
||||
} else { |
||||
result, err = json.Marshal(v) |
||||
} |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return string(result), nil |
||||
} |
||||
|
||||
func (r *TplRender) XML(status int, v interface{}) { |
||||
var result []byte |
||||
var err error |
||||
if r.Opt.IndentXML { |
||||
result, err = xml.MarshalIndent(v, "", " ") |
||||
} else { |
||||
result, err = xml.Marshal(v) |
||||
} |
||||
if err != nil { |
||||
http.Error(r, err.Error(), 500) |
||||
return |
||||
} |
||||
|
||||
// XML rendered fine, write out the result
|
||||
r.Header().Set(_CONTENT_TYPE, _CONTENT_XML+r.CompiledCharset) |
||||
r.WriteHeader(status) |
||||
if len(r.Opt.PrefixXML) > 0 { |
||||
_, _ = r.Write(r.Opt.PrefixXML) |
||||
} |
||||
_, _ = r.Write(result) |
||||
} |
||||
|
||||
func (r *TplRender) data(status int, contentType string, v []byte) { |
||||
if r.Header().Get(_CONTENT_TYPE) == "" { |
||||
r.Header().Set(_CONTENT_TYPE, contentType) |
||||
} |
||||
r.WriteHeader(status) |
||||
_, _ = r.Write(v) |
||||
} |
||||
|
||||
func (r *TplRender) RawData(status int, v []byte) { |
||||
r.data(status, _CONTENT_BINARY, v) |
||||
} |
||||
|
||||
func (r *TplRender) PlainText(status int, v []byte) { |
||||
r.data(status, _CONTENT_PLAIN, v) |
||||
} |
||||
|
||||
func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) { |
||||
buf := bufpool.Get().(*bytes.Buffer) |
||||
return buf, t.ExecuteTemplate(buf, name, data) |
||||
} |
||||
|
||||
func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) { |
||||
funcs := template.FuncMap{ |
||||
"yield": func() (template.HTML, error) { |
||||
buf, err := r.execute(t, tplName, data) |
||||
// return safe html here since we are rendering our own template
|
||||
return template.HTML(buf.String()), err |
||||
}, |
||||
"current": func() (string, error) { |
||||
return tplName, nil |
||||
}, |
||||
} |
||||
t.Funcs(funcs) |
||||
} |
||||
|
||||
func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) { |
||||
t := r.TemplateSet.Get(setName) |
||||
if Env == DEV { |
||||
opt := *r.Opt |
||||
opt.Directory = r.TemplateSet.GetDir(setName) |
||||
t = r.TemplateSet.Set(setName, &opt) |
||||
} |
||||
if t == nil { |
||||
return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName) |
||||
} |
||||
|
||||
opt := r.prepareHTMLOptions(htmlOpt) |
||||
|
||||
if len(opt.Layout) > 0 { |
||||
r.addYield(t, tplName, data) |
||||
tplName = opt.Layout |
||||
} |
||||
|
||||
out, err := r.execute(t, tplName, data) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return out, nil |
||||
} |
||||
|
||||
func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) { |
||||
r.startTime = time.Now() |
||||
|
||||
out, err := r.renderBytes(setName, tplName, data, htmlOpt...) |
||||
if err != nil { |
||||
http.Error(r, err.Error(), http.StatusInternalServerError) |
||||
return |
||||
} |
||||
|
||||
r.Header().Set(_CONTENT_TYPE, r.Opt.HTMLContentType+r.CompiledCharset) |
||||
r.WriteHeader(status) |
||||
|
||||
if _, err := out.WriteTo(r); err != nil { |
||||
out.Reset() |
||||
} |
||||
bufpool.Put(out) |
||||
} |
||||
|
||||
func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) { |
||||
r.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data, htmlOpt...) |
||||
} |
||||
|
||||
func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) { |
||||
r.renderHTML(status, setName, tplName, data, htmlOpt...) |
||||
} |
||||
|
||||
func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) { |
||||
out, err := r.renderBytes(setName, tplName, data, htmlOpt...) |
||||
if err != nil { |
||||
return []byte(""), err |
||||
} |
||||
return out.Bytes(), nil |
||||
} |
||||
|
||||
func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) { |
||||
return r.HTMLSetBytes(DEFAULT_TPL_SET_NAME, name, data, htmlOpt...) |
||||
} |
||||
|
||||
func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) { |
||||
p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...) |
||||
return string(p), err |
||||
} |
||||
|
||||
func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) { |
||||
p, err := r.HTMLBytes(name, data, htmlOpt...) |
||||
return string(p), err |
||||
} |
||||
|
||||
// Error writes the given HTTP status to the current ResponseWriter
|
||||
func (r *TplRender) Error(status int, message ...string) { |
||||
r.WriteHeader(status) |
||||
if len(message) > 0 { |
||||
_, _ = r.Write([]byte(message[0])) |
||||
} |
||||
} |
||||
|
||||
func (r *TplRender) Status(status int) { |
||||
r.WriteHeader(status) |
||||
} |
||||
|
||||
func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions { |
||||
if len(htmlOpt) > 0 { |
||||
return htmlOpt[0] |
||||
} |
||||
|
||||
return HTMLOptions{ |
||||
Layout: r.Opt.Layout, |
||||
} |
||||
} |
||||
|
||||
func (r *TplRender) SetTemplatePath(setName, dir string) { |
||||
if len(setName) == 0 { |
||||
setName = DEFAULT_TPL_SET_NAME |
||||
} |
||||
opt := *r.Opt |
||||
opt.Directory = dir |
||||
r.TemplateSet.Set(setName, &opt) |
||||
} |
||||
|
||||
func (r *TplRender) HasTemplateSet(name string) bool { |
||||
return r.TemplateSet.Get(name) != nil |
||||
} |
||||
|
||||
// DummyRender is used when user does not choose any real render to use.
|
||||
// This way, we can print out friendly message which asks them to register one,
|
||||
// instead of ugly and confusing 'nil pointer' panic.
|
||||
type DummyRender struct { |
||||
http.ResponseWriter |
||||
} |
||||
|
||||
func renderNotRegistered() { |
||||
panic("middleware render hasn't been registered") |
||||
} |
||||
|
||||
func (r *DummyRender) SetResponseWriter(http.ResponseWriter) { |
||||
renderNotRegistered() |
||||
} |
||||
|
||||
func (r *DummyRender) JSON(int, interface{}) { |
||||
renderNotRegistered() |
||||
} |
||||
|
||||
func (r *DummyRender) JSONString(interface{}) (string, error) { |
||||
renderNotRegistered() |
||||
return "", nil |
||||
} |
||||
|
||||
func (r *DummyRender) RawData(int, []byte) { |
||||
renderNotRegistered() |
||||
} |
||||
|
||||
func (r *DummyRender) PlainText(int, []byte) { |
||||
renderNotRegistered() |
||||
} |
||||
|
||||
func (r *DummyRender) HTML(int, string, interface{}, ...HTMLOptions) { |
||||
renderNotRegistered() |
||||
} |
||||
|
||||
func (r *DummyRender) HTMLSet(int, string, string, interface{}, ...HTMLOptions) { |
||||
renderNotRegistered() |
||||
} |
||||
|
||||
func (r *DummyRender) HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) { |
||||
renderNotRegistered() |
||||
return "", nil |
||||
} |
||||
|
||||
func (r *DummyRender) HTMLString(string, interface{}, ...HTMLOptions) (string, error) { |
||||
renderNotRegistered() |
||||
return "", nil |
||||
} |
||||
|
||||
func (r *DummyRender) HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) { |
||||
renderNotRegistered() |
||||
return nil, nil |
||||
} |
||||
|
||||
func (r *DummyRender) HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) { |
||||
renderNotRegistered() |
||||
return nil, nil |
||||
} |
||||
|
||||
func (r *DummyRender) XML(int, interface{}) { |
||||
renderNotRegistered() |
||||
} |
||||
|
||||
func (r *DummyRender) Error(int, ...string) { |
||||
renderNotRegistered() |
||||
} |
||||
|
||||
func (r *DummyRender) Status(int) { |
||||
renderNotRegistered() |
||||
} |
||||
|
||||
func (r *DummyRender) SetTemplatePath(string, string) { |
||||
renderNotRegistered() |
||||
} |
||||
|
||||
func (r *DummyRender) HasTemplateSet(string) bool { |
||||
renderNotRegistered() |
||||
return false |
||||
} |
||||
@ -0,0 +1,124 @@ |
||||
// 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 macaron |
||||
|
||||
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 |
||||
http.Pusher |
||||
// 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() |
||||
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() |
||||
} |
||||
|
||||
//nolint
|
||||
func (rw *responseWriter) CloseNotify() <-chan bool { |
||||
return rw.ResponseWriter.(http.CloseNotifier).CloseNotify() |
||||
} |
||||
|
||||
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() |
||||
} |
||||
} |
||||
|
||||
func (rw *responseWriter) Push(target string, opts *http.PushOptions) error { |
||||
pusher, ok := rw.ResponseWriter.(http.Pusher) |
||||
if !ok { |
||||
return errors.New("the ResponseWriter doesn't support the Pusher interface") |
||||
} |
||||
return pusher.Push(target, opts) |
||||
} |
||||
@ -0,0 +1,74 @@ |
||||
// 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 macaron |
||||
|
||||
import ( |
||||
"net/http" |
||||
"reflect" |
||||
) |
||||
|
||||
// ReturnHandler is a service that Martini provides that is called
|
||||
// when a route handler returns something. The ReturnHandler is
|
||||
// responsible for writing to the ResponseWriter based on the values
|
||||
// that are passed into this function.
|
||||
type ReturnHandler func(*Context, []reflect.Value) |
||||
|
||||
func canDeref(val reflect.Value) bool { |
||||
return val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr |
||||
} |
||||
|
||||
func isError(val reflect.Value) bool { |
||||
_, ok := val.Interface().(error) |
||||
return ok |
||||
} |
||||
|
||||
func isByteSlice(val reflect.Value) bool { |
||||
return val.Kind() == reflect.Slice && val.Type().Elem().Kind() == reflect.Uint8 |
||||
} |
||||
|
||||
func defaultReturnHandler() ReturnHandler { |
||||
return func(ctx *Context, vals []reflect.Value) { |
||||
rv := ctx.GetVal(InterfaceOf((*http.ResponseWriter)(nil))) |
||||
resp := rv.Interface().(http.ResponseWriter) |
||||
var respVal reflect.Value |
||||
if len(vals) > 1 && vals[0].Kind() == reflect.Int { |
||||
resp.WriteHeader(int(vals[0].Int())) |
||||
respVal = vals[1] |
||||
} else if len(vals) > 0 { |
||||
respVal = vals[0] |
||||
|
||||
if isError(respVal) { |
||||
err := respVal.Interface().(error) |
||||
if err != nil { |
||||
ctx.internalServerError(ctx, err) |
||||
} |
||||
return |
||||
} else if canDeref(respVal) { |
||||
if respVal.IsNil() { |
||||
return // Ignore nil error
|
||||
} |
||||
} |
||||
} |
||||
if canDeref(respVal) { |
||||
respVal = respVal.Elem() |
||||
} |
||||
if isByteSlice(respVal) { |
||||
_, _ = resp.Write(respVal.Bytes()) |
||||
} else { |
||||
_, _ = resp.Write([]byte(respVal.String())) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,380 @@ |
||||
// 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 |
||||
|
||||
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 |
||||
autoHead bool |
||||
routers map[string]*Tree |
||||
*routeMap |
||||
namedRoutes map[string]*Leaf |
||||
|
||||
groups []group |
||||
notFound http.HandlerFunc |
||||
internalServerError func(*Context, error) |
||||
|
||||
// handlerWrapper is used to wrap arbitrary function from Handler to inject.FastInvoker.
|
||||
handlerWrapper func(Handler) Handler |
||||
} |
||||
|
||||
func NewRouter() *Router { |
||||
return &Router{ |
||||
routers: make(map[string]*Tree), |
||||
routeMap: NewRouteMap(), |
||||
namedRoutes: make(map[string]*Leaf), |
||||
} |
||||
} |
||||
|
||||
// SetAutoHead sets the value who determines whether add HEAD method automatically
|
||||
// when GET method is added.
|
||||
func (r *Router) SetAutoHead(v bool) { |
||||
r.autoHead = v |
||||
} |
||||
|
||||
type Params map[string]string |
||||
|
||||
// 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, Params) |
||||
|
||||
// Route represents a wrapper of leaf route and upper level router.
|
||||
type Route struct { |
||||
router *Router |
||||
leaf *Leaf |
||||
} |
||||
|
||||
// Name sets name of route.
|
||||
func (r *Route) Name(name string) { |
||||
if len(name) == 0 { |
||||
panic("route name cannot be empty") |
||||
} else if r.router.namedRoutes[name] != nil { |
||||
panic("route with given name already exists: " + name) |
||||
} |
||||
r.router.namedRoutes[name] = r.leaf |
||||
} |
||||
|
||||
// handle adds new route to the router tree.
|
||||
func (r *Router) handle(method, pattern string, handle Handle) *Route { |
||||
method = strings.ToUpper(method) |
||||
|
||||
var leaf *Leaf |
||||
// Prevent duplicate routes.
|
||||
if leaf = r.getLeaf(method, pattern); leaf != nil { |
||||
return &Route{r, leaf} |
||||
} |
||||
|
||||
// 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) |
||||
} |
||||
return &Route{r, leaf} |
||||
} |
||||
|
||||
// Handle registers a new request handle with the given pattern, method and handlers.
|
||||
func (r *Router) Handle(method string, pattern string, handlers []Handler) *Route { |
||||
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.handlerWrapper) |
||||
|
||||
return r.handle(method, pattern, func(resp http.ResponseWriter, req *http.Request, params Params) { |
||||
c := r.m.createContext(resp, req) |
||||
c.params = 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) (leaf *Route) { |
||||
leaf = r.Handle("GET", pattern, h) |
||||
if r.autoHead { |
||||
r.Head(pattern, h...) |
||||
} |
||||
return leaf |
||||
} |
||||
|
||||
// Patch is a shortcut for r.Handle("PATCH", pattern, handlers)
|
||||
func (r *Router) Patch(pattern string, h ...Handler) *Route { |
||||
return r.Handle("PATCH", pattern, h) |
||||
} |
||||
|
||||
// Post is a shortcut for r.Handle("POST", pattern, handlers)
|
||||
func (r *Router) Post(pattern string, h ...Handler) *Route { |
||||
return r.Handle("POST", pattern, h) |
||||
} |
||||
|
||||
// Put is a shortcut for r.Handle("PUT", pattern, handlers)
|
||||
func (r *Router) Put(pattern string, h ...Handler) *Route { |
||||
return r.Handle("PUT", pattern, h) |
||||
} |
||||
|
||||
// Delete is a shortcut for r.Handle("DELETE", pattern, handlers)
|
||||
func (r *Router) Delete(pattern string, h ...Handler) *Route { |
||||
return r.Handle("DELETE", pattern, h) |
||||
} |
||||
|
||||
// Options is a shortcut for r.Handle("OPTIONS", pattern, handlers)
|
||||
func (r *Router) Options(pattern string, h ...Handler) *Route { |
||||
return r.Handle("OPTIONS", pattern, h) |
||||
} |
||||
|
||||
// Head is a shortcut for r.Handle("HEAD", pattern, handlers)
|
||||
func (r *Router) Head(pattern string, h ...Handler) *Route { |
||||
return r.Handle("HEAD", pattern, h) |
||||
} |
||||
|
||||
// Any is a shortcut for r.Handle("*", pattern, handlers)
|
||||
func (r *Router) Any(pattern string, h ...Handler) *Route { |
||||
return r.Handle("*", pattern, h) |
||||
} |
||||
|
||||
// Route is a shortcut for same handlers but different HTTP methods.
|
||||
//
|
||||
// Example:
|
||||
// m.Route("/", "GET,POST", h)
|
||||
func (r *Router) Route(pattern, methods string, h ...Handler) (route *Route) { |
||||
for _, m := range strings.Split(methods, ",") { |
||||
route = r.Handle(strings.TrimSpace(m), pattern, h) |
||||
} |
||||
return route |
||||
} |
||||
|
||||
// Combo returns a combo router.
|
||||
func (r *Router) Combo(pattern string, h ...Handler) *ComboRouter { |
||||
return &ComboRouter{r, pattern, h, map[string]bool{}, nil} |
||||
} |
||||
|
||||
// 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() |
||||
} |
||||
} |
||||
|
||||
// InternalServerError configurates handler which is called when route handler returns
|
||||
// error. If it is not set, default handler is used.
|
||||
// Be sure to set 500 response code in your handler.
|
||||
func (r *Router) InternalServerError(handlers ...Handler) { |
||||
handlers = validateAndWrapHandlers(handlers) |
||||
r.internalServerError = func(c *Context, err error) { |
||||
c.index = 0 |
||||
c.handlers = handlers |
||||
c.Map(err) |
||||
c.run() |
||||
} |
||||
} |
||||
|
||||
// SetHandlerWrapper sets handlerWrapper for the router.
|
||||
func (r *Router) SetHandlerWrapper(f func(Handler) Handler) { |
||||
r.handlerWrapper = f |
||||
} |
||||
|
||||
func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) { |
||||
if t, ok := r.routers[req.Method]; ok { |
||||
// Fast match for static routes
|
||||
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) |
||||
} |
||||
|
||||
// URLFor builds path part of URL by given pair values.
|
||||
func (r *Router) URLFor(name string, pairs ...string) string { |
||||
leaf, ok := r.namedRoutes[name] |
||||
if !ok { |
||||
panic("route with given name does not exists: " + name) |
||||
} |
||||
return leaf.URLPath(pairs...) |
||||
} |
||||
|
||||
// ComboRouter represents a combo router.
|
||||
type ComboRouter struct { |
||||
router *Router |
||||
pattern string |
||||
handlers []Handler |
||||
methods map[string]bool // Registered methods.
|
||||
|
||||
lastRoute *Route |
||||
} |
||||
|
||||
func (cr *ComboRouter) checkMethod(name string) { |
||||
if cr.methods[name] { |
||||
panic("method '" + name + "' has already been registered") |
||||
} |
||||
cr.methods[name] = true |
||||
} |
||||
|
||||
func (cr *ComboRouter) route(fn func(string, ...Handler) *Route, method string, h ...Handler) *ComboRouter { |
||||
cr.checkMethod(method) |
||||
cr.lastRoute = fn(cr.pattern, append(cr.handlers, h...)...) |
||||
return cr |
||||
} |
||||
|
||||
func (cr *ComboRouter) Get(h ...Handler) *ComboRouter { |
||||
if cr.router.autoHead { |
||||
cr.Head(h...) |
||||
} |
||||
return cr.route(cr.router.Get, "GET", h...) |
||||
} |
||||
|
||||
func (cr *ComboRouter) Patch(h ...Handler) *ComboRouter { |
||||
return cr.route(cr.router.Patch, "PATCH", h...) |
||||
} |
||||
|
||||
func (cr *ComboRouter) Post(h ...Handler) *ComboRouter { |
||||
return cr.route(cr.router.Post, "POST", h...) |
||||
} |
||||
|
||||
func (cr *ComboRouter) Put(h ...Handler) *ComboRouter { |
||||
return cr.route(cr.router.Put, "PUT", h...) |
||||
} |
||||
|
||||
func (cr *ComboRouter) Delete(h ...Handler) *ComboRouter { |
||||
return cr.route(cr.router.Delete, "DELETE", h...) |
||||
} |
||||
|
||||
func (cr *ComboRouter) Options(h ...Handler) *ComboRouter { |
||||
return cr.route(cr.router.Options, "OPTIONS", h...) |
||||
} |
||||
|
||||
func (cr *ComboRouter) Head(h ...Handler) *ComboRouter { |
||||
return cr.route(cr.router.Head, "HEAD", h...) |
||||
} |
||||
|
||||
// Name sets name of ComboRouter route.
|
||||
func (cr *ComboRouter) Name(name string) { |
||||
if cr.lastRoute == nil { |
||||
panic("no corresponding route to be named") |
||||
} |
||||
cr.lastRoute.Name(name) |
||||
} |
||||
@ -0,0 +1,390 @@ |
||||
// 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 macaron |
||||
|
||||
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 Params) (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 Params) (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 |
||||
} |
||||
} |
||||
} |
||||
|
||||
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 Params) (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, Params, bool) { |
||||
url = strings.TrimPrefix(url, "/") |
||||
url = strings.TrimSuffix(url, "/") |
||||
params := make(Params) |
||||
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 |
||||
} |
||||
Loading…
Reference in new issue