Commit c4a48b35 by Torkel Ödegaard

Updated libs

parent 9cc47b6b
......@@ -18,3 +18,4 @@ data/sessions
data/*.db
data/log
/bin/*
/grafana-pro
......@@ -4,7 +4,7 @@
"Deps": [
{
"ImportPath": "github.com/Unknwon/com",
"Rev": "6c41aba9600e474c1a9e0521402c996b52944c4b"
"Rev": "d9bcf409c8a368d06c9b347705c381e7c12d54df"
},
{
"ImportPath": "github.com/Unknwon/goconfig",
......@@ -12,7 +12,7 @@
},
{
"ImportPath": "github.com/Unknwon/macaron",
"Rev": "634a5d99660f7104f290a54e95ada2f8e34e46b2"
"Rev": "0f55900b417c019233ec27f86950bfb9feda8379"
},
{
"ImportPath": "github.com/codegangsta/cli",
......
......@@ -16,6 +16,7 @@ package com
import (
"errors"
"fmt"
"os"
"path"
"strings"
......@@ -95,6 +96,36 @@ func GetAllSubDirs(rootPath string) ([]string, error) {
return statDir(rootPath, "", true, true)
}
// GetFileListBySuffix returns an ordered list of file paths.
// It recognize if given path is a file, and don't do recursive find.
func GetFileListBySuffix(dirPath, suffix string) ([]string, error) {
if !IsExist(dirPath) {
return nil, fmt.Errorf("given path does not exist: %s", dirPath)
} else if IsFile(dirPath) {
return []string{dirPath}, nil
}
// Given path is a directory.
dir, err := os.Open(dirPath)
if err != nil {
return nil, err
}
fis, err := dir.Readdir(0)
if err != nil {
return nil, err
}
files := make([]string, 0, len(fis))
for _, fi := range fis {
if strings.HasSuffix(fi.Name(), suffix) {
files = append(files, path.Join(dirPath, fi.Name()))
}
}
return files, nil
}
// CopyDir copy files recursively from source to target directory.
//
// The filter accepts a function that process the path info.
......
......@@ -15,6 +15,7 @@
package com
import (
"bytes"
"encoding/json"
"fmt"
"io"
......@@ -43,10 +44,9 @@ func (e *RemoteError) Error() string {
var UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1541.0 Safari/537.36"
// HttpGet gets the specified resource. ErrNotFound is returned if the
// server responds with status 404.
func HttpGet(client *http.Client, url string, header http.Header) (io.ReadCloser, error) {
req, err := http.NewRequest("GET", url, nil)
// HttpCall makes HTTP method call.
func HttpCall(client *http.Client, method, url string, header http.Header, body io.Reader) (io.ReadCloser, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
......@@ -65,11 +65,23 @@ func HttpGet(client *http.Client, url string, header http.Header) (io.ReadCloser
if resp.StatusCode == 404 { // 403 can be rate limit error. || resp.StatusCode == 403 {
err = fmt.Errorf("resource not found: %s", url)
} else {
err = fmt.Errorf("get %s -> %d", url, resp.StatusCode)
err = fmt.Errorf("%s %s -> %d", method, url, resp.StatusCode)
}
return nil, err
}
// HttpGet gets the specified resource.
// ErrNotFound is returned if the server responds with status 404.
func HttpGet(client *http.Client, url string, header http.Header) (io.ReadCloser, error) {
return HttpCall(client, "GET", url, header, nil)
}
// HttpPost posts the specified resource.
// ErrNotFound is returned if the server responds with status 404.
func HttpPost(client *http.Client, url string, header http.Header, body []byte) (io.ReadCloser, error) {
return HttpCall(client, "POST", url, header, bytes.NewBuffer(body))
}
// HttpGetToFile gets the specified resource and writes to file.
// ErrNotFound is returned if the server responds with status 404.
func HttpGetToFile(client *http.Client, url string, header http.Header, fileName string) error {
......@@ -115,6 +127,26 @@ func HttpGetJSON(client *http.Client, url string, v interface{}) error {
return nil
}
// HttpPostJSON posts the specified resource with struct values,
// and maps results to struct.
// ErrNotFound is returned if the server responds with status 404.
func HttpPostJSON(client *http.Client, url string, body, v interface{}) error {
data, err := json.Marshal(body)
if err != nil {
return err
}
rc, err := HttpPost(client, url, http.Header{"content-type": []string{"application/json"}}, data)
if err != nil {
return err
}
defer rc.Close()
err = json.NewDecoder(rc).Decode(v)
if _, ok := err.(*json.SyntaxError); ok {
return fmt.Errorf("JSON syntax error at %s", url)
}
return nil
}
// A RawFile describes a file that can be downloaded.
type RawFile interface {
Name() string
......
......@@ -15,47 +15,53 @@
package com
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"fmt"
"hash"
"encoding/base64"
"errors"
"io"
r "math/rand"
"strconv"
"strings"
"time"
"unicode"
)
func sha(m hash.Hash, str string) string {
io.WriteString(m, str)
return fmt.Sprintf("%x", m.Sum(nil))
}
// sha1 hash string
func Sha1(str string) string {
return sha(sha1.New(), str)
}
// sha256 hash string
func Sha256(str string) string {
return sha(sha256.New(), str)
}
// trim space on left
func Ltrim(str string) string {
return strings.TrimLeftFunc(str, unicode.IsSpace)
}
// trim space on right
func Rtrim(str string) string {
return strings.TrimRightFunc(str, unicode.IsSpace)
// AESEncrypt encrypts text and given key with AES.
func AESEncrypt(key, text []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
b := base64.StdEncoding.EncodeToString(text)
ciphertext := make([]byte, aes.BlockSize+len(b))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
return ciphertext, nil
}
// replace find all occurs to string
func StrReplace(str string, find string, to string) string {
return strings.Replace(str, find, to, -1)
// AESDecrypt decrypts text and given key with AES.
func AESDecrypt(key, text []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(text) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
iv := text[:aes.BlockSize]
text = text[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(text, text)
data, err := base64.StdEncoding.DecodeString(string(text))
if err != nil {
return nil, err
}
return data, nil
}
// IsLetter returns true if the 'l' is an English letter.
......
......@@ -5,7 +5,7 @@ Macaron [![Build Status](https://drone.io/github.com/Unknwon/macaron/status.png)
Package macaron is a high productive and modular design web framework in Go.
##### Current version: 0.4.5
##### Current version: 0.4.9
## Getting Started
......@@ -36,6 +36,7 @@ func main() {
- Unlimited nested group routers.
- Directly integrate with existing services.
- Dynamically change template files at runtime.
- Allow to use in-memory template and static files.
- Easy to plugin/unplugin features with modular design.
- Handy dependency injection powered by [inject](https://github.com/codegangsta/inject).
- Better router layer and less reflection make faster speed.
......@@ -46,6 +47,9 @@ Middlewares allow you easily plugin/unplugin features for your Macaron applicati
There are already many [middlewares](https://github.com/macaron-contrib) to simplify your work:
- gzip - Gzip compression to all requests
- render - Go template engine
- static - Serves static files
- [binding](https://github.com/macaron-contrib/binding) - Request data binding and validation
- [i18n](https://github.com/macaron-contrib/i18n) - Internationalization and Localization
- [cache](https://github.com/macaron-contrib/cache) - Cache manager
......@@ -54,8 +58,12 @@ There are already many [middlewares](https://github.com/macaron-contrib) to simp
- [captcha](https://github.com/macaron-contrib/captcha) - Captcha service
- [pongo2](https://github.com/macaron-contrib/pongo2) - Pongo2 template engine support
- [sockets](https://github.com/macaron-contrib/sockets) - WebSockets channels binding
- [bindata](https://github.com/macaron-contrib/bindata) - Embed binary data as static and template files
- [toolbox](https://github.com/macaron-contrib/toolbox) - Health check, pprof, profile and statistic services
- [oauth2](https://github.com/macaron-contrib/oauth2) - OAuth 2.0 backend
- [switcher](https://github.com/macaron-contrib/switcher) - Multiple-site support
- [method](https://github.com/macaron-contrib/method) - HTTP method override
- [permissions2](https://github.com/xyproto/permissions2) - Cookies, users and permissions
- [renders](https://github.com/macaron-contrib/renders) - Beego-like render engine(Macaron has built-in template engine, this is another option)
## Use Cases
......
......@@ -15,19 +15,17 @@
package macaron
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"crypto/md5"
"encoding/hex"
"html/template"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"path"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
......@@ -42,25 +40,28 @@ type Locale interface {
Tr(string, ...interface{}) string
}
// Body is the request's body.
// RequestBody represents a request body.
type RequestBody struct {
reader io.ReadCloser
}
// Bytes reads and returns content of request body in bytes.
func (rb *RequestBody) Bytes() ([]byte, error) {
return ioutil.ReadAll(rb.reader)
}
// String reads and returns content of request body in string.
func (rb *RequestBody) String() (string, error) {
data, err := rb.Bytes()
return string(data), err
}
// ReadCloser returns a ReadCloser for request body.
func (rb *RequestBody) ReadCloser() io.ReadCloser {
return rb.reader
}
// A Request represents an HTTP request received by a server or to be sent by a client.
// Request represents an HTTP request received by a server or to be sent by a client.
type Request struct {
*http.Request
}
......@@ -239,7 +240,7 @@ func (ctx *Context) GetFile(name string) (multipart.File, *multipart.FileHeader,
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
cookie := http.Cookie{}
cookie.Name = name
cookie.Value = value
cookie.Value = url.QueryEscape(value)
if len(others) > 0 {
switch v := others[0].(type) {
......@@ -296,7 +297,8 @@ func (ctx *Context) GetCookie(name string) string {
if err != nil {
return ""
}
return cookie.Value
val, _ := url.QueryUnescape(cookie.Value)
return val
}
// GetCookieInt returns cookie result in int type.
......@@ -327,41 +329,32 @@ func (ctx *Context) GetSecureCookie(key string) (string, bool) {
}
// SetSuperSecureCookie sets given cookie value to response header with secret string.
func (ctx *Context) SetSuperSecureCookie(Secret, name, value string, others ...interface{}) {
vs := base64.URLEncoding.EncodeToString([]byte(value))
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
sig := fmt.Sprintf("%02x", h.Sum(nil))
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
ctx.SetCookie(name, cookie, others...)
func (ctx *Context) SetSuperSecureCookie(secret, name, value string, others ...interface{}) {
m := md5.Sum([]byte(secret))
secret = hex.EncodeToString(m[:])
text, err := com.AESEncrypt([]byte(secret), []byte(value))
if err != nil {
panic("error encrypting cookie: " + err.Error())
}
ctx.SetCookie(name, hex.EncodeToString(text), others...)
}
// GetSuperSecureCookie returns given cookie value from request header with secret string.
func (ctx *Context) GetSuperSecureCookie(Secret, key string) (string, bool) {
func (ctx *Context) GetSuperSecureCookie(secret, key string) (string, bool) {
val := ctx.GetCookie(key)
if val == "" {
return "", false
}
parts := strings.SplitN(val, "|", 3)
if len(parts) != 3 {
data, err := hex.DecodeString(val)
if err != nil {
return "", false
}
vs := parts[0]
timestamp := parts[1]
sig := parts[2]
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
return "", false
}
res, _ := base64.URLEncoding.DecodeString(vs)
return string(res), true
m := md5.Sum([]byte(secret))
secret = hex.EncodeToString(m[:])
text, err := com.AESDecrypt([]byte(secret), data)
return string(text), err == nil
}
// ServeContent serves given content to response.
......
......@@ -191,7 +191,8 @@ func Test_Context(t *testing.T) {
req, err := http.NewRequest("GET", "/set", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(strings.HasPrefix(resp.Header().Get("Set-Cookie"), "user=VW5rbndvbg==|"), ShouldBeTrue)
cookie := resp.Header().Get("Set-Cookie")
m.Get("/get", func(ctx *Context) string {
name, ok := ctx.GetSecureCookie("user")
......@@ -202,7 +203,7 @@ func Test_Context(t *testing.T) {
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/get", nil)
So(err, ShouldBeNil)
req.Header.Set("Cookie", "user=VW5rbndvbg==|1409244667158399419|6097781707f68d9940ba1ef0e78cc84aaeebc48f; Path=/; Max-Age=1")
req.Header.Set("Cookie", cookie)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "Unknwon")
})
......
## 多站点支持
如果您想要运行 2 或 2 个以上的实例在一个程序里,[HostSwitcher](https://gowalker.org/github.com/Unknwon/macaron#HostSwitcher) 就是您需要的特性:
```go
func main() {
m1 := macaron.Classic()
// Register m1 middlewares and routers.
m2 := macaron.Classic()
// Register m2 middlewares and routers.
hs := macaron.NewHostSwitcher()
// Set instance corresponding to host address.
hs.Set("gowalker.org", m1)
hs.Set("gogs.io", m2)
hs.Run()
}
```
......@@ -31,21 +31,25 @@ func init() {
// Logger returns a middleware handler that logs the request as it goes in and the response as it goes out.
func Logger() Handler {
return func(res http.ResponseWriter, req *http.Request, c *Context, log *log.Logger) {
return func(ctx *Context, log *log.Logger) {
start := time.Now()
log.Printf("Started %s %s for %s", req.Method, req.URL.Path, c.RemoteAddr())
log.Printf("Started %s %s for %s", ctx.Req.Method,ctx.Req.RequestURI, ctx.RemoteAddr())
rw := res.(ResponseWriter)
c.Next()
rw := ctx.Resp.(ResponseWriter)
ctx.Next()
content := fmt.Sprintf("Completed %s %v %s in %v", req.URL.Path, rw.Status(), http.StatusText(rw.Status()), time.Since(start))
content := fmt.Sprintf("Completed %s %v %s in %v", ctx.Req.RequestURI, rw.Status(), http.StatusText(rw.Status()), time.Since(start))
if !isWindows {
switch rw.Status() {
case 200:
case 200, 201, 202:
content = fmt.Sprintf("\033[1;32m%s\033[0m", content)
case 301, 302:
content = fmt.Sprintf("\033[1;37m%s\033[0m", content)
case 304:
content = fmt.Sprintf("\033[1;33m%s\033[0m", content)
case 401, 403:
content = fmt.Sprintf("\033[4;31m%s\033[0m", content)
case 404:
content = fmt.Sprintf("\033[1;31m%s\033[0m", content)
case 500:
......
......@@ -22,6 +22,8 @@ import (
"net/http/httptest"
"testing"
"github.com/Unknwon/com"
. "github.com/smartystreets/goconvey/convey"
)
......@@ -43,4 +45,23 @@ func Test_Logger(t *testing.T) {
So(resp.Code, ShouldEqual, http.StatusNotFound)
So(len(buf.String()), ShouldBeGreaterThan, 0)
})
if !isWindows {
Convey("Color console output", t, func() {
m := Classic()
m.Get("/:code:int", func(ctx *Context) (int, string) {
return ctx.ParamsInt(":code"), ""
})
// Just for testing if logger would capture.
codes := []int{200, 201, 202, 301, 302, 304, 401, 403, 404, 500}
for _, code := range codes {
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://localhost:4000/"+com.ToStr(code), nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, code)
}
})
}
}
......@@ -24,12 +24,15 @@ import (
"strings"
"github.com/Unknwon/com"
"gopkg.in/ini.v1"
"github.com/Unknwon/macaron/inject"
)
const _VERSION = "0.4.9.1229"
func Version() string {
return "0.4.5.1122"
return _VERSION
}
// Handler can be any callable function.
......@@ -39,9 +42,17 @@ type Handler interface{}
// validateHandler makes sure a handler is a callable function,
// and panics if it is not.
func validateHandler(handler Handler) {
if reflect.TypeOf(handler).Kind() != reflect.Func {
panic("mocaron handler must be a callable function")
func validateHandler(h Handler) {
if reflect.TypeOf(h).Kind() != reflect.Func {
panic("Macaron handler must be a callable function")
}
}
// validateHandlers makes sure handlers are callable functions,
// and panics if any of them is not.
func validateHandlers(handlers []Handler) {
for _, h := range handlers {
validateHandler(h)
}
}
......@@ -210,9 +221,9 @@ func (m *Macaron) SetURLPrefix(prefix string) {
// \/ \/ \/ \/ \/
const (
DEV string = "development"
PROD string = "production"
TEST string = "test"
DEV = "development"
PROD = "production"
TEST = "test"
)
var (
......@@ -225,6 +236,9 @@ var (
// Flash applies to current request.
FlashNow bool
// Configuration convention object.
cfg *ini.File
)
func setENV(e string) {
......@@ -235,9 +249,25 @@ func setENV(e string) {
func init() {
setENV(os.Getenv("MACARON_ENV"))
var err error
Root, err = os.Getwd()
if err != nil {
panic(err)
panic("error getting work directory: " + err.Error())
}
}
// SetConfig sets data sources for configuration.
func SetConfig(source interface{}, others ...interface{}) (err error) {
cfg, err = ini.Load(source, others...)
return err
}
// Config returns configuration convention object.
// It returns an empty object if there is no one available.
func Config() *ini.File {
if cfg == nil {
return &ini.File{}
}
return cfg
}
......@@ -25,6 +25,12 @@ import (
. "github.com/smartystreets/goconvey/convey"
)
func Test_Version(t *testing.T) {
Convey("Get version", t, func() {
So(Version(), ShouldEqual, _VERSION)
})
}
func Test_New(t *testing.T) {
Convey("Initialize a new instance", t, func() {
So(New(), ShouldNotBeNil)
......@@ -171,11 +177,14 @@ func Test_Macaron_Written(t *testing.T) {
func Test_Macaron_Basic_NoRace(t *testing.T) {
Convey("Make sure no race between requests", t, func() {
m := New()
handlers := []Handler{func() {}, func() {}}
// Ensure append will not realloc to trigger the race condition
m.handlers = handlers[:1]
m.Get("/", func() {})
req, _ := http.NewRequest("GET", "/", nil)
for i := 0; i < 2; i++ {
go func() {
resp := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/", nil)
m.ServeHTTP(resp, req)
}()
}
......@@ -199,8 +208,10 @@ func Test_SetENV(t *testing.T) {
})
}
func Test_Version(t *testing.T) {
Convey("Get version", t, func() {
Version()
func Test_Config(t *testing.T) {
Convey("Set and get configuration object", t, func() {
So(Config(), ShouldNotBeNil)
So(SetConfig([]byte("")), ShouldBeNil)
So(Config(), ShouldNotBeNil)
})
}
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// 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
......
......@@ -307,9 +307,6 @@ func prepareOptions(options []RenderOptions) RenderOptions {
if len(opt.HTMLContentType) == 0 {
opt.HTMLContentType = ContentHTML
}
// if opt.TemplateFileSystem == nil {
// opt.TemplateFileSystem = newTemplateFileSystem(opt)
// }
return opt
}
......
......@@ -147,10 +147,7 @@ func (r *Router) Handle(method string, pattern string, handlers []Handler) {
h = append(h, handlers...)
handlers = h
}
// verify handlers by cnphpbb at 20140803 23:51
for _, handler := range handlers {
validateHandler(handler)
}
validateHandlers(handlers)
r.handle(method, pattern, func(resp http.ResponseWriter, req *http.Request, params Params) {
c := r.m.createContext(resp, req)
......@@ -217,8 +214,8 @@ func (r *Router) Route(pattern, methods string, h ...Handler) {
}
// Combo returns a combo router.
func (r *Router) Combo(pattern string) *ComboRouter {
return &ComboRouter{r, pattern, map[string]bool{}}
func (r *Router) Combo(pattern string, h ...Handler) *ComboRouter {
return &ComboRouter{r, pattern, h, map[string]bool{}}
}
// Configurable http.HandlerFunc which is called when no matching route is
......@@ -255,6 +252,7 @@ func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
type ComboRouter struct {
router *Router
pattern string
handlers []Handler
methods map[string]bool // Registered methods.
}
......@@ -265,44 +263,36 @@ func (cr *ComboRouter) checkMethod(name string) {
cr.methods[name] = true
}
func (cr *ComboRouter) Get(h ...Handler) *ComboRouter {
cr.checkMethod("GET")
cr.router.Get(cr.pattern, h...)
func (cr *ComboRouter) route(fn func(string, ...Handler), method string, h ...Handler) *ComboRouter {
cr.checkMethod(method)
fn(cr.pattern, append(cr.handlers, h...)...)
return cr
}
func (cr *ComboRouter) Get(h ...Handler) *ComboRouter {
return cr.route(cr.router.Get, "GET", h...)
}
func (cr *ComboRouter) Patch(h ...Handler) *ComboRouter {
cr.checkMethod("PATCH")
cr.router.Patch(cr.pattern, h...)
return cr
return cr.route(cr.router.Patch, "PATCH", h...)
}
func (cr *ComboRouter) Post(h ...Handler) *ComboRouter {
cr.checkMethod("POST")
cr.router.Post(cr.pattern, h...)
return cr
return cr.route(cr.router.Post, "POST", h...)
}
func (cr *ComboRouter) Put(h ...Handler) *ComboRouter {
cr.checkMethod("PUT")
cr.router.Put(cr.pattern, h...)
return cr
return cr.route(cr.router.Put, "PUT", h...)
}
func (cr *ComboRouter) Delete(h ...Handler) *ComboRouter {
cr.checkMethod("DELETE")
cr.router.Delete(cr.pattern, h...)
return cr
return cr.route(cr.router.Delete, "DELETE", h...)
}
func (cr *ComboRouter) Options(h ...Handler) *ComboRouter {
cr.checkMethod("OPTIONS")
cr.router.Options(cr.pattern, h...)
return cr
return cr.route(cr.router.Options, "OPTIONS", h...)
}
func (cr *ComboRouter) Head(h ...Handler) *ComboRouter {
cr.checkMethod("HEAD")
cr.router.Head(cr.pattern, h...)
return cr
return cr.route(cr.router.Head, "HEAD", h...)
}
......@@ -110,21 +110,24 @@ func Test_Router_Handle(t *testing.T) {
Convey("Register all HTTP methods routes with combo", t, func() {
m := Classic()
m.SetURLPrefix("/prefix")
m.Combo("/").
Get(func() string { return "GET" }).
Patch(func() string { return "PATCH" }).
Post(func() string { return "POST" }).
Put(func() string { return "PUT" }).
Delete(func() string { return "DELETE" }).
Options(func() string { return "OPTIONS" }).
Head(func() string { return "HEAD" })
m.Use(Renderer())
m.Combo("/", func(ctx *Context) {
ctx.Data["prefix"] = "Prefix_"
}).
Get(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "GET" }).
Patch(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "PATCH" }).
Post(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "POST" }).
Put(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "PUT" }).
Delete(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "DELETE" }).
Options(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "OPTIONS" }).
Head(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "HEAD" })
for name := range _HTTP_METHODS {
resp := httptest.NewRecorder()
req, err := http.NewRequest(name, "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, name)
So(resp.Body.String(), ShouldEqual, "Prefix_"+name)
}
defer func() {
......
......@@ -116,7 +116,7 @@ func prepareStaticOptions(dir string, options []StaticOptions) StaticOptions {
func staticHandler(ctx *Context, log *log.Logger, opt StaticOptions) bool {
if ctx.Req.Method != "GET" && ctx.Req.Method != "HEAD" {
return true
return false
}
file := ctx.Req.URL.Path
......@@ -185,6 +185,7 @@ func Static(directory string, staticOpt ...StaticOptions) Handler {
}
}
// Statics registers multiple static middleware handlers all at once.
func Statics(opt StaticOptions, dirs ...string) Handler {
if len(dirs) == 0 {
panic("no static directory is given")
......
......@@ -15,7 +15,7 @@
package macaron
// NOTE: last sync 90cff5f on Nov 2, 2014.
// NOTE: last sync 0c93364 on Dec 19, 2014.
import (
"path"
......@@ -142,6 +142,10 @@ func NewTree() *Tree {
// "/admin/" -> ["admin"]
// "/admin/users" -> ["admin", "users"]
func splitPath(pattern string) []string {
if len(pattern) == 0 {
return []string{}
}
elements := strings.Split(pattern, "/")
if elements[0] == "" {
elements = elements[1:]
......
......@@ -66,6 +66,7 @@ func Test_Tree_Match(t *testing.T) {
{"/:id", "/123", map[string]string{":id": "123"}},
{"/hello/?:id", "/hello", map[string]string{":id": ""}},
{"/", "/", nil},
{"", "", nil},
{"/customer/login", "/customer/login", nil},
{"/customer/login", "/customer/login.json", map[string]string{":ext": "json"}},
{"/*", "/customer/123", map[string]string{":splat": "customer/123"}},
......
box: wercker/default
\ No newline at end of file
......@@ -7,7 +7,7 @@ build:
go test ./pkg/...
lint:
@gofmt -w . && go tool vet pkg/**/*.go && echo "$(GOLINT)"
@gofmt -w pkg && go tool vet pkg/**/*.go && echo "$(GOLINT)"
setup:
go get github.com/tools/godep
......
No preview for this file type
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment