mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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 tests
This commit is contained in:
parent
4c0acf335b
commit
9b2d7d6d69
2
go.mod
2
go.mod
@ -121,3 +121,5 @@ require (
|
||||
)
|
||||
|
||||
replace github.com/apache/thrift => github.com/apache/thrift v0.14.1
|
||||
|
||||
replace gopkg.in/macaron.v1 => ./pkg/macaron
|
||||
|
@ -553,7 +553,7 @@ func GetDashboardVersion(c *models.ReqContext) response.Response {
|
||||
query := models.GetDashboardVersionQuery{
|
||||
OrgId: c.OrgId,
|
||||
DashboardId: dashID,
|
||||
Version: c.ParamsInt(":id"),
|
||||
Version: int(c.ParamsInt64(":id")),
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
|
191
pkg/macaron/LICENSE.txt
Normal file
191
pkg/macaron/LICENSE.txt
Normal file
@ -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.
|
226
pkg/macaron/context.go
Normal file
226
pkg/macaron/context.go
Normal file
@ -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
|
||||
}
|
3
pkg/macaron/go.mod
Normal file
3
pkg/macaron/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module gopkg.in/macaron.v1
|
||||
|
||||
go 1.16
|
208
pkg/macaron/inject.go
Normal file
208
pkg/macaron/inject.go
Normal file
@ -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
|
||||
}
|
211
pkg/macaron/macaron.go
Normal file
211
pkg/macaron/macaron.go
Normal file
@ -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
|
||||
}
|
723
pkg/macaron/render.go
Normal file
723
pkg/macaron/render.go
Normal file
@ -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
|
||||
}
|
124
pkg/macaron/response_writer.go
Normal file
124
pkg/macaron/response_writer.go
Normal file
@ -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)
|
||||
}
|
74
pkg/macaron/return_handler.go
Normal file
74
pkg/macaron/return_handler.go
Normal file
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
380
pkg/macaron/router.go
Normal file
380
pkg/macaron/router.go
Normal file
@ -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)
|
||||
}
|
390
pkg/macaron/tree.go
Normal file
390
pkg/macaron/tree.go
Normal file
@ -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
|
||||
}
|
@ -3,6 +3,7 @@ package pushhttp
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@ -58,7 +59,7 @@ func (g *Gateway) Handle(ctx *models.ReqContext) {
|
||||
urlValues := ctx.Req.URL.Query()
|
||||
frameFormat := pushurl.FrameFormatFromValues(urlValues)
|
||||
|
||||
body, err := ctx.Req.Body().Bytes()
|
||||
body, err := io.ReadAll(ctx.Req.Request.Body)
|
||||
if err != nil {
|
||||
logger.Error("Error reading body", "error", err)
|
||||
ctx.Resp.WriteHeader(http.StatusInternalServerError)
|
||||
|
Loading…
Reference in New Issue
Block a user