mirror of
https://github.com/labstack/echo.git
synced 2024-12-24 20:14:31 +02:00
parent
04f45046b1
commit
2aec0353f5
@ -10,8 +10,6 @@ before_install:
|
||||
script:
|
||||
- go test -coverprofile=echo.coverprofile
|
||||
- go test -coverprofile=middleware.coverprofile ./middleware
|
||||
- go test -coverprofile=engine_standatd.coverprofile ./engine/standard
|
||||
- go test -coverprofile=engine_fasthttp.coverprofile ./engine/fasthttp
|
||||
- $HOME/gopath/bin/gover
|
||||
- $HOME/gopath/bin/goveralls -coverprofile=gover.coverprofile -service=travis-ci
|
||||
matrix:
|
||||
|
42
binder.go
42
binder.go
@ -22,43 +22,47 @@ type (
|
||||
|
||||
func (b *binder) Bind(i interface{}, c Context) (err error) {
|
||||
req := c.Request()
|
||||
if req.Method() == GET {
|
||||
if req.Method == GET {
|
||||
if err = b.bindData(i, c.QueryParams()); err != nil {
|
||||
err = NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
ctype := req.Header().Get(HeaderContentType)
|
||||
if req.Body() == nil {
|
||||
err = NewHTTPError(http.StatusBadRequest, "request body can't be empty")
|
||||
return
|
||||
ctype := req.Header.Get(HeaderContentType)
|
||||
if req.ContentLength == 0 {
|
||||
return NewHTTPError(http.StatusBadRequest, "request body can't be empty")
|
||||
}
|
||||
err = ErrUnsupportedMediaType
|
||||
switch {
|
||||
case strings.HasPrefix(ctype, MIMEApplicationJSON):
|
||||
if err = json.NewDecoder(req.Body()).Decode(i); err != nil {
|
||||
if err = json.NewDecoder(req.Body).Decode(i); err != nil {
|
||||
if ute, ok := err.(*json.UnmarshalTypeError); ok {
|
||||
err = NewHTTPError(http.StatusBadRequest, fmt.Sprintf("unmarshal type error: expected=%v, got=%v, offset=%v", ute.Type, ute.Value, ute.Offset))
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("unmarshal type error: expected=%v, got=%v, offset=%v", ute.Type, ute.Value, ute.Offset))
|
||||
} else if se, ok := err.(*json.SyntaxError); ok {
|
||||
err = NewHTTPError(http.StatusBadRequest, fmt.Sprintf("syntax error: offset=%v, error=%v", se.Offset, se.Error()))
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("syntax error: offset=%v, error=%v", se.Offset, se.Error()))
|
||||
} else {
|
||||
err = NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(ctype, MIMEApplicationXML):
|
||||
if err = xml.NewDecoder(req.Body()).Decode(i); err != nil {
|
||||
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
|
||||
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
|
||||
err = NewHTTPError(http.StatusBadRequest, fmt.Sprintf("unsupported type error: type=%v, error=%v", ute.Type, ute.Error()))
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("unsupported type error: type=%v, error=%v", ute.Type, ute.Error()))
|
||||
} else if se, ok := err.(*xml.SyntaxError); ok {
|
||||
err = NewHTTPError(http.StatusBadRequest, fmt.Sprintf("syntax error: line=%v, error=%v", se.Line, se.Error()))
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("syntax error: line=%v, error=%v", se.Line, se.Error()))
|
||||
} else {
|
||||
err = NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
|
||||
if err = b.bindData(i, req.FormParams()); err != nil {
|
||||
err = NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
params, err := c.FormParams()
|
||||
if err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if err = b.bindData(i, params); err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
default:
|
||||
return ErrUnsupportedMediaType
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -100,8 +104,8 @@ func (b *binder) bindData(ptr interface{}, data map[string][]string) error {
|
||||
if structFieldKind == reflect.Slice && numElems > 0 {
|
||||
sliceOf := structField.Type().Elem().Kind()
|
||||
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
|
||||
for i := 0; i < numElems; i++ {
|
||||
if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil {
|
||||
for j := 0; j < numElems; j++ {
|
||||
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,11 @@ import (
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -70,19 +70,19 @@ func TestBinderForm(t *testing.T) {
|
||||
testBinderOkay(t, strings.NewReader(userForm), MIMEApplicationForm)
|
||||
testBinderError(t, nil, MIMEApplicationForm)
|
||||
e := New()
|
||||
req := test.NewRequest(POST, "/", strings.NewReader(userForm))
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(POST, "/", strings.NewReader(userForm))
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
req.Header().Set(HeaderContentType, MIMEApplicationForm)
|
||||
var obj = make([]struct{ Field string }, 0)
|
||||
req.Header.Set(HeaderContentType, MIMEApplicationForm)
|
||||
obj := []struct{ Field string }{}
|
||||
err := c.Bind(&obj)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestBinderQueryParams(t *testing.T) {
|
||||
e := New()
|
||||
req := test.NewRequest(GET, "/?id=1&name=Jon Snow", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(GET, "/?id=1&name=Jon Snow", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
u := new(user)
|
||||
err := c.Bind(u)
|
||||
@ -105,11 +105,6 @@ func TestBinderUnsupportedMediaType(t *testing.T) {
|
||||
testBinderError(t, strings.NewReader(invalidContent), MIMEApplicationJSON)
|
||||
}
|
||||
|
||||
// func assertCustomer(t *testing.T, c *user) {
|
||||
// assert.Equal(t, 1, c.ID)
|
||||
// assert.Equal(t, "Joe", c.Name)
|
||||
// }
|
||||
|
||||
func TestBinderbindForm(t *testing.T) {
|
||||
ts := new(binderTestStruct)
|
||||
b := new(binder)
|
||||
@ -201,10 +196,10 @@ func assertBinderTestStruct(t *testing.T, ts *binderTestStruct) {
|
||||
|
||||
func testBinderOkay(t *testing.T, r io.Reader, ctype string) {
|
||||
e := New()
|
||||
req := test.NewRequest(POST, "/", r)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(POST, "/", r)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
req.Header().Set(HeaderContentType, ctype)
|
||||
req.Header.Set(HeaderContentType, ctype)
|
||||
u := new(user)
|
||||
err := c.Bind(u)
|
||||
if assert.NoError(t, err) {
|
||||
@ -215,10 +210,10 @@ func testBinderOkay(t *testing.T, r io.Reader, ctype string) {
|
||||
|
||||
func testBinderError(t *testing.T, r io.Reader, ctype string) {
|
||||
e := New()
|
||||
req := test.NewRequest(POST, "/", r)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(POST, "/", r)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
req.Header().Set(HeaderContentType, ctype)
|
||||
req.Header.Set(HeaderContentType, ctype)
|
||||
u := new(user)
|
||||
err := c.Bind(u)
|
||||
|
||||
|
159
context.go
159
context.go
@ -7,12 +7,14 @@ import (
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/labstack/echo/log"
|
||||
|
||||
"bytes"
|
||||
@ -32,11 +34,21 @@ type (
|
||||
// SetStdContext sets `context.Context`.
|
||||
SetStdContext(context.Context)
|
||||
|
||||
// Request returns `engine.Request` interface.
|
||||
Request() engine.Request
|
||||
// Request returns `*http.Request`.
|
||||
Request() *http.Request
|
||||
|
||||
// Request returns `engine.Response` interface.
|
||||
Response() engine.Response
|
||||
// Request returns `*Response`.
|
||||
Response() *Response
|
||||
|
||||
// IsTLS returns true if HTTP connection is TLS otherwise false.
|
||||
IsTLS() bool
|
||||
|
||||
// Scheme returns the HTTP protocol scheme, `http` or `https`.
|
||||
Scheme() string
|
||||
|
||||
// RealIP returns the client's network address based on `X-Forwarded-For`
|
||||
// or `X-Real-IP` request header.
|
||||
RealIP() string
|
||||
|
||||
// Path returns the registered path for the handler.
|
||||
Path() string
|
||||
@ -62,41 +74,35 @@ type (
|
||||
// SetParamValues sets path parameter values.
|
||||
SetParamValues(...string)
|
||||
|
||||
// QueryParam returns the query param for the provided name. It is an alias
|
||||
// for `engine.URL#QueryParam()`.
|
||||
// QueryParam returns the query param for the provided name.
|
||||
QueryParam(string) string
|
||||
|
||||
// QueryParams returns the query parameters as map.
|
||||
// It is an alias for `engine.URL#QueryParams()`.
|
||||
QueryParams() map[string][]string
|
||||
// QueryParams returns the query parameters as `url.Values`.
|
||||
QueryParams() url.Values
|
||||
|
||||
// FormValue returns the form field value for the provided name. It is an
|
||||
// alias for `engine.Request#FormValue()`.
|
||||
// QueryString returns the URL query string.
|
||||
QueryString() string
|
||||
|
||||
// FormValue returns the form field value for the provided name.
|
||||
FormValue(string) string
|
||||
|
||||
// FormParams returns the form parameters as map.
|
||||
// It is an alias for `engine.Request#FormParams()`.
|
||||
FormParams() map[string][]string
|
||||
// FormParams returns the form parameters as `url.Values`.
|
||||
FormParams() (url.Values, error)
|
||||
|
||||
// FormFile returns the multipart form file for the provided name. It is an
|
||||
// alias for `engine.Request#FormFile()`.
|
||||
// FormFile returns the multipart form file for the provided name.
|
||||
FormFile(string) (*multipart.FileHeader, error)
|
||||
|
||||
// MultipartForm returns the multipart form.
|
||||
// It is an alias for `engine.Request#MultipartForm()`.
|
||||
MultipartForm() (*multipart.Form, error)
|
||||
|
||||
// Cookie returns the named cookie provided in the request.
|
||||
// It is an alias for `engine.Request#Cookie()`.
|
||||
Cookie(string) (engine.Cookie, error)
|
||||
Cookie(string) (*http.Cookie, error)
|
||||
|
||||
// SetCookie adds a `Set-Cookie` header in HTTP response.
|
||||
// It is an alias for `engine.Response#SetCookie()`.
|
||||
SetCookie(engine.Cookie)
|
||||
SetCookie(*http.Cookie)
|
||||
|
||||
// Cookies returns the HTTP cookies sent with the request.
|
||||
// It is an alias for `engine.Request#Cookies()`.
|
||||
Cookies() []engine.Cookie
|
||||
Cookies() []*http.Cookie
|
||||
|
||||
// Get retrieves data from the context.
|
||||
Get(string) interface{}
|
||||
@ -184,23 +190,25 @@ type (
|
||||
// Reset resets the context after request completes. It must be called along
|
||||
// with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
|
||||
// See `Echo#ServeHTTP()`
|
||||
Reset(engine.Request, engine.Response)
|
||||
Reset(*http.Request, http.ResponseWriter)
|
||||
}
|
||||
|
||||
echoContext struct {
|
||||
context context.Context
|
||||
request engine.Request
|
||||
response engine.Response
|
||||
request *http.Request
|
||||
response *Response
|
||||
path string
|
||||
pnames []string
|
||||
pvalues []string
|
||||
query url.Values
|
||||
handler HandlerFunc
|
||||
echo *Echo
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
indexPage = "index.html"
|
||||
defaultMemory = 32 << 20 // 32 MB
|
||||
indexPage = "index.html"
|
||||
)
|
||||
|
||||
func (c *echoContext) StdContext() context.Context {
|
||||
@ -227,14 +235,39 @@ func (c *echoContext) Value(key interface{}) interface{} {
|
||||
return c.context.Value(key)
|
||||
}
|
||||
|
||||
func (c *echoContext) Request() engine.Request {
|
||||
func (c *echoContext) Request() *http.Request {
|
||||
return c.request
|
||||
}
|
||||
|
||||
func (c *echoContext) Response() engine.Response {
|
||||
func (c *echoContext) Response() *Response {
|
||||
return c.response
|
||||
}
|
||||
|
||||
func (c *echoContext) IsTLS() bool {
|
||||
return c.request.TLS != nil
|
||||
}
|
||||
|
||||
func (c *echoContext) Scheme() string {
|
||||
// Can't use `r.Request.URL.Scheme`
|
||||
// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
|
||||
if c.IsTLS() {
|
||||
return "https"
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (c *echoContext) RealIP() string {
|
||||
ra := c.request.RemoteAddr
|
||||
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
|
||||
ra = ip
|
||||
} else if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
|
||||
ra = ip
|
||||
} else {
|
||||
ra, _, _ = net.SplitHostPort(ra)
|
||||
}
|
||||
return ra
|
||||
}
|
||||
|
||||
func (c *echoContext) Path() string {
|
||||
return c.path
|
||||
}
|
||||
@ -279,38 +312,59 @@ func (c *echoContext) SetParamValues(values ...string) {
|
||||
}
|
||||
|
||||
func (c *echoContext) QueryParam(name string) string {
|
||||
return c.request.URL().QueryParam(name)
|
||||
if c.query == nil {
|
||||
c.query = c.request.URL.Query()
|
||||
}
|
||||
return c.query.Get(name)
|
||||
}
|
||||
|
||||
func (c *echoContext) QueryParams() map[string][]string {
|
||||
return c.request.URL().QueryParams()
|
||||
func (c *echoContext) QueryParams() url.Values {
|
||||
if c.query == nil {
|
||||
c.query = c.request.URL.Query()
|
||||
}
|
||||
return c.query
|
||||
}
|
||||
|
||||
func (c *echoContext) QueryString() string {
|
||||
return c.request.URL.RawQuery
|
||||
}
|
||||
|
||||
func (c *echoContext) FormValue(name string) string {
|
||||
return c.request.FormValue(name)
|
||||
}
|
||||
|
||||
func (c *echoContext) FormParams() map[string][]string {
|
||||
return c.request.FormParams()
|
||||
func (c *echoContext) FormParams() (url.Values, error) {
|
||||
if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) {
|
||||
if err := c.request.ParseMultipartForm(defaultMemory); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := c.request.ParseForm(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.request.Form, nil
|
||||
}
|
||||
|
||||
func (c *echoContext) FormFile(name string) (*multipart.FileHeader, error) {
|
||||
return c.request.FormFile(name)
|
||||
_, fh, err := c.request.FormFile(name)
|
||||
return fh, err
|
||||
}
|
||||
|
||||
func (c *echoContext) MultipartForm() (*multipart.Form, error) {
|
||||
return c.request.MultipartForm()
|
||||
err := c.request.ParseMultipartForm(defaultMemory)
|
||||
return c.request.MultipartForm, err
|
||||
}
|
||||
|
||||
func (c *echoContext) Cookie(name string) (engine.Cookie, error) {
|
||||
func (c *echoContext) Cookie(name string) (*http.Cookie, error) {
|
||||
return c.request.Cookie(name)
|
||||
}
|
||||
|
||||
func (c *echoContext) SetCookie(cookie engine.Cookie) {
|
||||
c.response.SetCookie(cookie)
|
||||
func (c *echoContext) SetCookie(cookie *http.Cookie) {
|
||||
http.SetCookie(c.Response(), cookie)
|
||||
}
|
||||
|
||||
func (c *echoContext) Cookies() []engine.Cookie {
|
||||
func (c *echoContext) Cookies() []*http.Cookie {
|
||||
return c.request.Cookies()
|
||||
}
|
||||
|
||||
@ -323,15 +377,15 @@ func (c *echoContext) Get(key string) interface{} {
|
||||
}
|
||||
|
||||
func (c *echoContext) Bind(i interface{}) error {
|
||||
return c.echo.binder.Bind(i, c)
|
||||
return c.echo.Binder.Bind(i, c)
|
||||
}
|
||||
|
||||
func (c *echoContext) Render(code int, name string, data interface{}) (err error) {
|
||||
if c.echo.renderer == nil {
|
||||
if c.echo.Renderer == nil {
|
||||
return ErrRendererNotRegistered
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
if err = c.echo.renderer.Render(buf, name, data, c); err != nil {
|
||||
if err = c.echo.Renderer.Render(buf, name, data, c); err != nil {
|
||||
return
|
||||
}
|
||||
c.response.Header().Set(HeaderContentType, MIMETextHTMLCharsetUTF8)
|
||||
@ -356,7 +410,7 @@ func (c *echoContext) String(code int, s string) (err error) {
|
||||
|
||||
func (c *echoContext) JSON(code int, i interface{}) (err error) {
|
||||
b, err := json.Marshal(i)
|
||||
if c.echo.Debug() {
|
||||
if c.echo.Debug {
|
||||
b, err = json.MarshalIndent(i, "", " ")
|
||||
}
|
||||
if err != nil {
|
||||
@ -392,7 +446,7 @@ func (c *echoContext) JSONPBlob(code int, callback string, b []byte) (err error)
|
||||
|
||||
func (c *echoContext) XML(code int, i interface{}) (err error) {
|
||||
b, err := xml.Marshal(i)
|
||||
if c.echo.Debug() {
|
||||
if c.echo.Debug {
|
||||
b, err = xml.MarshalIndent(i, "", " ")
|
||||
}
|
||||
if err != nil {
|
||||
@ -474,7 +528,7 @@ func (c *echoContext) Redirect(code int, url string) error {
|
||||
}
|
||||
|
||||
func (c *echoContext) Error(err error) {
|
||||
c.echo.httpErrorHandler(err, c)
|
||||
c.echo.HTTPErrorHandler(err, c)
|
||||
}
|
||||
|
||||
func (c *echoContext) Echo() *Echo {
|
||||
@ -490,14 +544,14 @@ func (c *echoContext) SetHandler(h HandlerFunc) {
|
||||
}
|
||||
|
||||
func (c *echoContext) Logger() log.Logger {
|
||||
return c.echo.logger
|
||||
return c.echo.Logger
|
||||
}
|
||||
|
||||
func (c *echoContext) ServeContent(content io.ReadSeeker, name string, modtime time.Time) error {
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
|
||||
if t, err := time.Parse(http.TimeFormat, req.Header().Get(HeaderIfModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) {
|
||||
if t, err := time.Parse(http.TimeFormat, req.Header.Get(HeaderIfModifiedSince)); err == nil && modtime.Before(t.Add(1*time.Second)) {
|
||||
res.Header().Del(HeaderContentType)
|
||||
res.Header().Del(HeaderContentLength)
|
||||
return c.NoContent(http.StatusNotModified)
|
||||
@ -520,9 +574,10 @@ func ContentTypeByExtension(name string) (t string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *echoContext) Reset(req engine.Request, res engine.Response) {
|
||||
func (c *echoContext) Reset(r *http.Request, w http.ResponseWriter) {
|
||||
// c.query = nil
|
||||
c.context = context.Background()
|
||||
c.request = req
|
||||
c.response = res
|
||||
c.request = r
|
||||
c.response.reset(w)
|
||||
c.handler = NotFoundHandler
|
||||
}
|
||||
|
140
context_test.go
140
context_test.go
@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"text/template"
|
||||
@ -19,7 +20,6 @@ import (
|
||||
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -35,185 +35,182 @@ func (t *Template) Render(w io.Writer, name string, data interface{}, c Context)
|
||||
|
||||
func TestContext(t *testing.T) {
|
||||
e := New()
|
||||
req := test.NewRequest(POST, "/", strings.NewReader(userJSON))
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(POST, "/", strings.NewReader(userJSON))
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec).(*echoContext)
|
||||
|
||||
// Echo
|
||||
assert.Equal(t, e, c.Echo())
|
||||
|
||||
// Request
|
||||
assert.Equal(t, req, c.Request())
|
||||
assert.NotNil(t, c.Request())
|
||||
|
||||
// Response
|
||||
assert.Equal(t, rec, c.Response())
|
||||
|
||||
// Logger
|
||||
assert.Equal(t, e.logger, c.Logger())
|
||||
assert.NotNil(t, c.Response())
|
||||
|
||||
//--------
|
||||
// Render
|
||||
//--------
|
||||
|
||||
tpl := &Template{
|
||||
tmpl := &Template{
|
||||
templates: template.Must(template.New("hello").Parse("Hello, {{.}}!")),
|
||||
}
|
||||
c.echo.SetRenderer(tpl)
|
||||
c.echo.Renderer = tmpl
|
||||
err := c.Render(http.StatusOK, "hello", "Jon Snow")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, "Hello, Jon Snow!", rec.Body.String())
|
||||
}
|
||||
|
||||
c.echo.renderer = nil
|
||||
c.echo.Renderer = nil
|
||||
err = c.Render(http.StatusOK, "hello", "Jon Snow")
|
||||
assert.Error(t, err)
|
||||
|
||||
// JSON
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
err = c.JSON(http.StatusOK, user{1, "Jon Snow"})
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, MIMEApplicationJSONCharsetUTF8, rec.Header().Get(HeaderContentType))
|
||||
assert.Equal(t, userJSON, rec.Body.String())
|
||||
}
|
||||
|
||||
// JSON (error)
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
err = c.JSON(http.StatusOK, make(chan bool))
|
||||
assert.Error(t, err)
|
||||
|
||||
// JSONP
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
callback := "callback"
|
||||
err = c.JSONP(http.StatusOK, callback, user{1, "Jon Snow"})
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, MIMEApplicationJavaScriptCharsetUTF8, rec.Header().Get(HeaderContentType))
|
||||
assert.Equal(t, callback+"("+userJSON+");", rec.Body.String())
|
||||
}
|
||||
|
||||
// XML
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
err = c.XML(http.StatusOK, user{1, "Jon Snow"})
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, MIMEApplicationXMLCharsetUTF8, rec.Header().Get(HeaderContentType))
|
||||
assert.Equal(t, xml.Header+userXML, rec.Body.String())
|
||||
}
|
||||
|
||||
// XML (error)
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
err = c.XML(http.StatusOK, make(chan bool))
|
||||
assert.Error(t, err)
|
||||
|
||||
// String
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
err = c.String(http.StatusOK, "Hello, World!")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, MIMETextPlainCharsetUTF8, rec.Header().Get(HeaderContentType))
|
||||
assert.Equal(t, "Hello, World!", rec.Body.String())
|
||||
}
|
||||
|
||||
// HTML
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
err = c.HTML(http.StatusOK, "Hello, <strong>World!</strong>")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, MIMETextHTMLCharsetUTF8, rec.Header().Get(HeaderContentType))
|
||||
assert.Equal(t, "Hello, <strong>World!</strong>", rec.Body.String())
|
||||
}
|
||||
|
||||
// Stream
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
r := strings.NewReader("response from a stream")
|
||||
err = c.Stream(http.StatusOK, "application/octet-stream", r)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, "application/octet-stream", rec.Header().Get(HeaderContentType))
|
||||
assert.Equal(t, "response from a stream", rec.Body.String())
|
||||
}
|
||||
|
||||
// Attachment
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
file, err := os.Open("_fixture/images/walle.png")
|
||||
if assert.NoError(t, err) {
|
||||
err = c.Attachment(file, "walle.png")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, "attachment; filename=walle.png", rec.Header().Get(HeaderContentDisposition))
|
||||
assert.Equal(t, 219885, rec.Body.Len())
|
||||
}
|
||||
}
|
||||
|
||||
// Inline
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
file, err = os.Open("_fixture/images/walle.png")
|
||||
if assert.NoError(t, err) {
|
||||
err = c.Inline(file, "walle.png")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, "inline; filename=walle.png", rec.Header().Get(HeaderContentDisposition))
|
||||
assert.Equal(t, 219885, rec.Body.Len())
|
||||
}
|
||||
}
|
||||
|
||||
// NoContent
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
c.NoContent(http.StatusOK)
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
|
||||
// Error
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec).(*echoContext)
|
||||
c.Error(errors.New("error"))
|
||||
assert.Equal(t, http.StatusInternalServerError, rec.Status())
|
||||
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||
|
||||
// Reset
|
||||
c.Reset(req, test.NewResponseRecorder())
|
||||
c.Reset(req, httptest.NewRecorder())
|
||||
}
|
||||
|
||||
func TestContextCookie(t *testing.T) {
|
||||
e := New()
|
||||
req := test.NewRequest(GET, "/", nil)
|
||||
req, _ := http.NewRequest(GET, "/", nil)
|
||||
theme := "theme=light"
|
||||
user := "user=Jon Snow"
|
||||
req.Header().Add(HeaderCookie, theme)
|
||||
req.Header().Add(HeaderCookie, user)
|
||||
rec := test.NewResponseRecorder()
|
||||
req.Header.Add(HeaderCookie, theme)
|
||||
req.Header.Add(HeaderCookie, user)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec).(*echoContext)
|
||||
|
||||
// Read single
|
||||
cookie, err := c.Cookie("theme")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, "theme", cookie.Name())
|
||||
assert.Equal(t, "light", cookie.Value())
|
||||
assert.Equal(t, "theme", cookie.Name)
|
||||
assert.Equal(t, "light", cookie.Value)
|
||||
}
|
||||
|
||||
// Read multiple
|
||||
for _, cookie := range c.Cookies() {
|
||||
switch cookie.Name() {
|
||||
switch cookie.Name {
|
||||
case "theme":
|
||||
assert.Equal(t, "light", cookie.Value())
|
||||
assert.Equal(t, "light", cookie.Value)
|
||||
case "user":
|
||||
assert.Equal(t, "Jon Snow", cookie.Value())
|
||||
assert.Equal(t, "Jon Snow", cookie.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Write
|
||||
cookie = &test.Cookie{Cookie: &http.Cookie{
|
||||
cookie = &http.Cookie{
|
||||
Name: "SSID",
|
||||
Value: "Ap4PGTEq",
|
||||
Domain: "labstack.com",
|
||||
@ -221,7 +218,7 @@ func TestContextCookie(t *testing.T) {
|
||||
Expires: time.Now(),
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
}}
|
||||
}
|
||||
c.SetCookie(cookie)
|
||||
assert.Contains(t, rec.Header().Get(HeaderSetCookie), "SSID")
|
||||
assert.Contains(t, rec.Header().Get(HeaderSetCookie), "Ap4PGTEq")
|
||||
@ -247,7 +244,7 @@ func TestContextPath(t *testing.T) {
|
||||
|
||||
func TestContextPathParam(t *testing.T) {
|
||||
e := New()
|
||||
req := test.NewRequest(GET, "/", nil)
|
||||
req, _ := http.NewRequest(GET, "/", nil)
|
||||
c := e.NewContext(req, nil)
|
||||
|
||||
// ParamNames
|
||||
@ -271,8 +268,8 @@ func TestContextFormValue(t *testing.T) {
|
||||
f.Set("email", "jon@labstack.com")
|
||||
|
||||
e := New()
|
||||
req := test.NewRequest(POST, "/", strings.NewReader(f.Encode()))
|
||||
req.Header().Add(HeaderContentType, MIMEApplicationForm)
|
||||
req, _ := http.NewRequest(POST, "/", strings.NewReader(f.Encode()))
|
||||
req.Header.Add(HeaderContentType, MIMEApplicationForm)
|
||||
c := e.NewContext(req, nil)
|
||||
|
||||
// FormValue
|
||||
@ -280,17 +277,20 @@ func TestContextFormValue(t *testing.T) {
|
||||
assert.Equal(t, "jon@labstack.com", c.FormValue("email"))
|
||||
|
||||
// FormParams
|
||||
assert.Equal(t, map[string][]string{
|
||||
"name": []string{"Jon Snow"},
|
||||
"email": []string{"jon@labstack.com"},
|
||||
}, c.FormParams())
|
||||
params, err := c.FormParams()
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, url.Values{
|
||||
"name": []string{"Jon Snow"},
|
||||
"email": []string{"jon@labstack.com"},
|
||||
}, params)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextQueryParam(t *testing.T) {
|
||||
q := make(url.Values)
|
||||
q.Set("name", "Jon Snow")
|
||||
q.Set("email", "jon@labstack.com")
|
||||
req := test.NewRequest(GET, "/?"+q.Encode(), nil)
|
||||
req, _ := http.NewRequest(GET, "/?"+q.Encode(), nil)
|
||||
e := New()
|
||||
c := e.NewContext(req, nil)
|
||||
|
||||
@ -299,7 +299,7 @@ func TestContextQueryParam(t *testing.T) {
|
||||
assert.Equal(t, "jon@labstack.com", c.QueryParam("email"))
|
||||
|
||||
// QueryParams
|
||||
assert.Equal(t, map[string][]string{
|
||||
assert.Equal(t, url.Values{
|
||||
"name": []string{"Jon Snow"},
|
||||
"email": []string{"jon@labstack.com"},
|
||||
}, c.QueryParams())
|
||||
@ -314,9 +314,9 @@ func TestContextFormFile(t *testing.T) {
|
||||
w.Write([]byte("test"))
|
||||
}
|
||||
mr.Close()
|
||||
req := test.NewRequest(POST, "/", buf)
|
||||
req.Header().Set(HeaderContentType, mr.FormDataContentType())
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(POST, "/", buf)
|
||||
req.Header.Set(HeaderContentType, mr.FormDataContentType())
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
f, err := c.FormFile("file")
|
||||
if assert.NoError(t, err) {
|
||||
@ -330,9 +330,9 @@ func TestContextMultipartForm(t *testing.T) {
|
||||
mw := multipart.NewWriter(buf)
|
||||
mw.WriteField("name", "Jon Snow")
|
||||
mw.Close()
|
||||
req := test.NewRequest(POST, "/", buf)
|
||||
req.Header().Set(HeaderContentType, mw.FormDataContentType())
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(POST, "/", buf)
|
||||
req.Header.Set(HeaderContentType, mw.FormDataContentType())
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
f, err := c.MultipartForm()
|
||||
if assert.NoError(t, err) {
|
||||
@ -342,11 +342,11 @@ func TestContextMultipartForm(t *testing.T) {
|
||||
|
||||
func TestContextRedirect(t *testing.T) {
|
||||
e := New()
|
||||
req := test.NewRequest(GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
assert.Equal(t, nil, c.Redirect(http.StatusMovedPermanently, "http://labstack.github.io/echo"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, rec.Status())
|
||||
assert.Equal(t, http.StatusMovedPermanently, rec.Code)
|
||||
assert.Equal(t, "http://labstack.github.io/echo", rec.Header().Get(HeaderLocation))
|
||||
assert.Error(t, c.Redirect(310, "http://labstack.github.io/echo"))
|
||||
}
|
||||
@ -374,8 +374,8 @@ func TestContextStore(t *testing.T) {
|
||||
|
||||
func TestContextServeContent(t *testing.T) {
|
||||
e := New()
|
||||
req := test.NewRequest(GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
|
||||
fs := http.Dir("_fixture/images")
|
||||
@ -385,15 +385,15 @@ func TestContextServeContent(t *testing.T) {
|
||||
if assert.NoError(t, err) {
|
||||
// Not cached
|
||||
if assert.NoError(t, c.ServeContent(f, fi.Name(), fi.ModTime())) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
}
|
||||
|
||||
// Cached
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
req.Header().Set(HeaderIfModifiedSince, fi.ModTime().UTC().Format(http.TimeFormat))
|
||||
req.Header.Set(HeaderIfModifiedSince, fi.ModTime().UTC().Format(http.TimeFormat))
|
||||
if assert.NoError(t, c.ServeContent(f, fi.Name(), fi.ModTime())) {
|
||||
assert.Equal(t, http.StatusNotModified, rec.Status())
|
||||
assert.Equal(t, http.StatusNotModified, rec.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
218
echo.go
218
echo.go
@ -39,18 +39,20 @@ package echo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"path"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/labstack/echo/log"
|
||||
glog "github.com/labstack/gommon/log"
|
||||
)
|
||||
@ -58,18 +60,24 @@ import (
|
||||
type (
|
||||
// Echo is the top-level framework instance.
|
||||
Echo struct {
|
||||
server engine.Server
|
||||
premiddleware []MiddlewareFunc
|
||||
middleware []MiddlewareFunc
|
||||
maxParam *int
|
||||
notFoundHandler HandlerFunc
|
||||
httpErrorHandler HTTPErrorHandler
|
||||
binder Binder
|
||||
renderer Renderer
|
||||
pool sync.Pool
|
||||
debug bool
|
||||
router *Router
|
||||
logger log.Logger
|
||||
Server *http.Server
|
||||
TLSCertFile string
|
||||
TLSKeyFile string
|
||||
Listener net.Listener
|
||||
// DisableHTTP2 disables HTTP2
|
||||
DisableHTTP2 bool
|
||||
// Debug mode
|
||||
Debug bool
|
||||
HTTPErrorHandler
|
||||
Binder Binder
|
||||
Renderer Renderer
|
||||
Logger log.Logger
|
||||
premiddleware []MiddlewareFunc
|
||||
middleware []MiddlewareFunc
|
||||
maxParam *int
|
||||
notFoundHandler HandlerFunc
|
||||
pool sync.Pool
|
||||
router *Router
|
||||
}
|
||||
|
||||
// Route contains a handler and information for matching against requests.
|
||||
@ -226,22 +234,21 @@ func New() (e *Echo) {
|
||||
return e.NewContext(nil, nil)
|
||||
}
|
||||
e.router = NewRouter(e)
|
||||
|
||||
// Defaults
|
||||
e.SetHTTPErrorHandler(e.DefaultHTTPErrorHandler)
|
||||
e.SetBinder(&binder{})
|
||||
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
|
||||
e.Binder = &binder{}
|
||||
l := glog.New("echo")
|
||||
l.SetLevel(glog.OFF)
|
||||
e.SetLogger(l)
|
||||
e.Logger = l
|
||||
return
|
||||
}
|
||||
|
||||
// NewContext returns a Context instance.
|
||||
func (e *Echo) NewContext(req engine.Request, res engine.Response) Context {
|
||||
func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
|
||||
return &echoContext{
|
||||
context: context.Background(),
|
||||
request: req,
|
||||
response: res,
|
||||
request: r,
|
||||
response: NewResponse(w, e),
|
||||
echo: e,
|
||||
pvalues: make([]string, *e.maxParam),
|
||||
handler: NotFoundHandler,
|
||||
@ -253,26 +260,6 @@ func (e *Echo) Router() *Router {
|
||||
return e.router
|
||||
}
|
||||
|
||||
// Logger returns the logger instance.
|
||||
func (e *Echo) Logger() log.Logger {
|
||||
return e.logger
|
||||
}
|
||||
|
||||
// SetLogger defines a custom logger.
|
||||
func (e *Echo) SetLogger(l log.Logger) {
|
||||
e.logger = l
|
||||
}
|
||||
|
||||
// SetLogOutput sets the output destination for the logger. Default value is `os.Std*`
|
||||
func (e *Echo) SetLogOutput(w io.Writer) {
|
||||
e.logger.SetOutput(w)
|
||||
}
|
||||
|
||||
// SetLogLevel sets the log level for the logger. Default value ERROR.
|
||||
func (e *Echo) SetLogLevel(l glog.Lvl) {
|
||||
e.logger.SetLevel(l)
|
||||
}
|
||||
|
||||
// DefaultHTTPErrorHandler invokes the default HTTP error handler.
|
||||
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
|
||||
code := http.StatusInternalServerError
|
||||
@ -281,47 +268,17 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
|
||||
code = he.Code
|
||||
msg = he.Message
|
||||
}
|
||||
if e.debug {
|
||||
if e.Debug {
|
||||
msg = err.Error()
|
||||
}
|
||||
if !c.Response().Committed() {
|
||||
if c.Request().Method() == HEAD { // Issue #608
|
||||
if !c.Response().Committed {
|
||||
if c.Request().Method == HEAD { // Issue #608
|
||||
c.NoContent(code)
|
||||
} else {
|
||||
c.String(code, msg)
|
||||
}
|
||||
}
|
||||
e.logger.Error(err)
|
||||
}
|
||||
|
||||
// SetHTTPErrorHandler registers a custom Echo.HTTPErrorHandler.
|
||||
func (e *Echo) SetHTTPErrorHandler(h HTTPErrorHandler) {
|
||||
e.httpErrorHandler = h
|
||||
}
|
||||
|
||||
// SetBinder registers a custom binder. It's invoked by `Context#Bind()`.
|
||||
func (e *Echo) SetBinder(b Binder) {
|
||||
e.binder = b
|
||||
}
|
||||
|
||||
// Binder returns the binder instance.
|
||||
func (e *Echo) Binder() Binder {
|
||||
return e.binder
|
||||
}
|
||||
|
||||
// SetRenderer registers an HTML template renderer. It's invoked by `Context#Render()`.
|
||||
func (e *Echo) SetRenderer(r Renderer) {
|
||||
e.renderer = r
|
||||
}
|
||||
|
||||
// SetDebug enables/disables debug mode.
|
||||
func (e *Echo) SetDebug(on bool) {
|
||||
e.debug = on
|
||||
}
|
||||
|
||||
// Debug returns debug mode (enabled or disabled).
|
||||
func (e *Echo) Debug() bool {
|
||||
return e.debug
|
||||
e.Logger.Error(err)
|
||||
}
|
||||
|
||||
// Pre adds middleware to the chain which is run before router.
|
||||
@ -340,99 +297,54 @@ func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(CONNECT, path, h, m...)
|
||||
}
|
||||
|
||||
// Connect is deprecated, use `CONNECT()` instead.
|
||||
func (e *Echo) Connect(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.CONNECT(path, h, m...)
|
||||
}
|
||||
|
||||
// DELETE registers a new DELETE route for a path with matching handler in the router
|
||||
// with optional route-level middleware.
|
||||
func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(DELETE, path, h, m...)
|
||||
}
|
||||
|
||||
// Delete is deprecated, use `DELETE()` instead.
|
||||
func (e *Echo) Delete(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.DELETE(path, h, m...)
|
||||
}
|
||||
|
||||
// GET registers a new GET route for a path with matching handler in the router
|
||||
// with optional route-level middleware.
|
||||
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(GET, path, h, m...)
|
||||
}
|
||||
|
||||
// Get is deprecated, use `GET()` instead.
|
||||
func (e *Echo) Get(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.GET(path, h, m...)
|
||||
}
|
||||
|
||||
// HEAD registers a new HEAD route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(HEAD, path, h, m...)
|
||||
}
|
||||
|
||||
// Head is deprecated, use `HEAD()` instead.
|
||||
func (e *Echo) Head(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.HEAD(path, h, m...)
|
||||
}
|
||||
|
||||
// OPTIONS registers a new OPTIONS route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(OPTIONS, path, h, m...)
|
||||
}
|
||||
|
||||
// Options is deprecated, use `OPTIONS()` instead.
|
||||
func (e *Echo) Options(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.OPTIONS(path, h, m...)
|
||||
}
|
||||
|
||||
// PATCH registers a new PATCH route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(PATCH, path, h, m...)
|
||||
}
|
||||
|
||||
// Patch is deprecated, use `PATCH()` instead.
|
||||
func (e *Echo) Patch(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.PATCH(path, h, m...)
|
||||
}
|
||||
|
||||
// POST registers a new POST route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(POST, path, h, m...)
|
||||
}
|
||||
|
||||
// Post is deprecated, use `POST()` instead.
|
||||
func (e *Echo) Post(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.POST(path, h, m...)
|
||||
}
|
||||
|
||||
// PUT registers a new PUT route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(PUT, path, h, m...)
|
||||
}
|
||||
|
||||
// Put is deprecated, use `PUT()` instead.
|
||||
func (e *Echo) Put(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.PUT(path, h, m...)
|
||||
}
|
||||
|
||||
// TRACE registers a new TRACE route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.add(TRACE, path, h, m...)
|
||||
}
|
||||
|
||||
// Trace is deprecated, use `TRACE()` instead.
|
||||
func (e *Echo) Trace(path string, h HandlerFunc, m ...MiddlewareFunc) {
|
||||
e.TRACE(path, h, m...)
|
||||
}
|
||||
|
||||
// Any registers a new route for all HTTP methods and path with matching handler
|
||||
// in the router with optional route-level middleware.
|
||||
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
|
||||
@ -540,14 +452,15 @@ func (e *Echo) ReleaseContext(c Context) {
|
||||
e.pool.Put(c)
|
||||
}
|
||||
|
||||
func (e *Echo) ServeHTTP(req engine.Request, res engine.Response) {
|
||||
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
|
||||
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
c := e.pool.Get().(*echoContext)
|
||||
c.Reset(req, res)
|
||||
c.Reset(r, w)
|
||||
|
||||
// Middleware
|
||||
h := func(Context) error {
|
||||
method := req.Method()
|
||||
path := req.URL().Path()
|
||||
method := r.Method
|
||||
path := r.URL.Path
|
||||
e.router.Find(method, path, c)
|
||||
h := c.handler
|
||||
for i := len(e.middleware) - 1; i >= 0; i-- {
|
||||
@ -563,27 +476,64 @@ func (e *Echo) ServeHTTP(req engine.Request, res engine.Response) {
|
||||
|
||||
// Execute chain
|
||||
if err := h(c); err != nil {
|
||||
e.httpErrorHandler(err, c)
|
||||
e.HTTPErrorHandler(err, c)
|
||||
}
|
||||
|
||||
e.pool.Put(c)
|
||||
}
|
||||
|
||||
// Run starts the HTTP server.
|
||||
func (e *Echo) Run(s engine.Server) error {
|
||||
e.server = s
|
||||
s.SetHandler(e)
|
||||
s.SetLogger(e.logger)
|
||||
if e.Debug() {
|
||||
e.SetLogLevel(glog.DEBUG)
|
||||
e.logger.Debug("running in debug mode")
|
||||
func (e *Echo) Run(address string) (err error) {
|
||||
if e.Server == nil {
|
||||
e.Server = &http.Server{Handler: e}
|
||||
}
|
||||
return s.Start()
|
||||
if e.Listener == nil {
|
||||
e.Listener, err = net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if e.TLSCertFile != "" && e.TLSKeyFile != "" {
|
||||
// TODO: https://github.com/golang/go/commit/d24f446a90ea94b87591bf16228d7d871fec3d92
|
||||
config := &tls.Config{
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
if !e.DisableHTTP2 {
|
||||
config.NextProtos = append(config.NextProtos, "h2")
|
||||
}
|
||||
config.Certificates = make([]tls.Certificate, 1)
|
||||
config.Certificates[0], err = tls.LoadX509KeyPair(e.TLSCertFile, e.TLSKeyFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
e.Listener = tls.NewListener(tcpKeepAliveListener{e.Listener.(*net.TCPListener)}, config)
|
||||
} else {
|
||||
e.Listener = tcpKeepAliveListener{e.Listener.(*net.TCPListener)}
|
||||
}
|
||||
}
|
||||
return e.Server.Serve(e.Listener)
|
||||
}
|
||||
|
||||
// Stop stops the HTTP server.
|
||||
// Stop stops the HTTP server
|
||||
func (e *Echo) Stop() error {
|
||||
return e.server.Stop()
|
||||
return e.Listener.Close()
|
||||
}
|
||||
|
||||
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
|
||||
// connections. It's used by ListenAndServe and ListenAndServeTLS so
|
||||
// dead TCP connections (e.g. closing laptop mid-download) eventually
|
||||
// go away.
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
// NewHTTPError creates a new HTTPError instance.
|
||||
|
60
echo_test.go
60
echo_test.go
@ -2,9 +2,8 @@ package echo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"reflect"
|
||||
@ -12,8 +11,6 @@ import (
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/labstack/gommon/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -33,20 +30,16 @@ const (
|
||||
|
||||
func TestEcho(t *testing.T) {
|
||||
e := New()
|
||||
req := test.NewRequest(GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
|
||||
// Router
|
||||
assert.NotNil(t, e.Router())
|
||||
|
||||
// Debug
|
||||
e.SetDebug(true)
|
||||
assert.True(t, e.debug)
|
||||
|
||||
// DefaultHTTPErrorHandler
|
||||
e.DefaultHTTPErrorHandler(errors.New("error"), c)
|
||||
assert.Equal(t, http.StatusInternalServerError, rec.Status())
|
||||
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||
}
|
||||
|
||||
func TestEchoStatic(t *testing.T) {
|
||||
@ -306,10 +299,10 @@ func TestEchoGroup(t *testing.T) {
|
||||
|
||||
func TestEchoNotFound(t *testing.T) {
|
||||
e := New()
|
||||
req := test.NewRequest(GET, "/files", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
e.ServeHTTP(req, rec)
|
||||
assert.Equal(t, http.StatusNotFound, rec.Status())
|
||||
req, _ := http.NewRequest(GET, "/files", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
assert.Equal(t, http.StatusNotFound, rec.Code)
|
||||
}
|
||||
|
||||
func TestEchoMethodNotAllowed(t *testing.T) {
|
||||
@ -317,10 +310,10 @@ func TestEchoMethodNotAllowed(t *testing.T) {
|
||||
e.GET("/", func(c Context) error {
|
||||
return c.String(http.StatusOK, "Echo!")
|
||||
})
|
||||
req := test.NewRequest(POST, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
e.ServeHTTP(req, rec)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, rec.Status())
|
||||
req, _ := http.NewRequest(POST, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, rec.Code)
|
||||
}
|
||||
|
||||
func TestEchoHTTPError(t *testing.T) {
|
||||
@ -337,25 +330,13 @@ func TestEchoContext(t *testing.T) {
|
||||
e.ReleaseContext(c)
|
||||
}
|
||||
|
||||
func TestEchoLogger(t *testing.T) {
|
||||
e := New()
|
||||
l := log.New("test")
|
||||
e.SetLogger(l)
|
||||
assert.Equal(t, l, e.Logger())
|
||||
e.SetLogOutput(ioutil.Discard)
|
||||
assert.Equal(t, l.Output(), ioutil.Discard)
|
||||
e.SetLogLevel(log.OFF)
|
||||
assert.Equal(t, l.Level(), log.OFF)
|
||||
}
|
||||
|
||||
func testMethod(t *testing.T, method, path string, e *Echo) {
|
||||
m := fmt.Sprintf("%c%s", method[0], strings.ToLower(method[1:]))
|
||||
p := reflect.ValueOf(path)
|
||||
h := reflect.ValueOf(func(c Context) error {
|
||||
return c.String(http.StatusOK, method)
|
||||
})
|
||||
i := interface{}(e)
|
||||
reflect.ValueOf(i).MethodByName(m).Call([]reflect.Value{p, h})
|
||||
reflect.ValueOf(i).MethodByName(method).Call([]reflect.Value{p, h})
|
||||
_, body := request(method, path, e)
|
||||
if body != method {
|
||||
t.Errorf("expected body `%s`, got %s.", method, body)
|
||||
@ -363,15 +344,8 @@ func testMethod(t *testing.T, method, path string, e *Echo) {
|
||||
}
|
||||
|
||||
func request(method, path string, e *Echo) (int, string) {
|
||||
req := test.NewRequest(method, path, nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
e.ServeHTTP(req, rec)
|
||||
return rec.Status(), rec.Body.String()
|
||||
}
|
||||
|
||||
func TestEchoBinder(t *testing.T) {
|
||||
e := New()
|
||||
b := &binder{}
|
||||
e.SetBinder(b)
|
||||
assert.Equal(t, b, e.Binder())
|
||||
req, _ := http.NewRequest(method, path, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
return rec.Code, rec.Body.String()
|
||||
}
|
||||
|
233
engine/engine.go
233
engine/engine.go
@ -1,233 +0,0 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"time"
|
||||
|
||||
"net"
|
||||
|
||||
"github.com/labstack/echo/log"
|
||||
)
|
||||
|
||||
type (
|
||||
// Server defines the interface for HTTP server.
|
||||
Server interface {
|
||||
// SetHandler sets the handler for the HTTP server.
|
||||
SetHandler(Handler)
|
||||
|
||||
// SetLogger sets the logger for the HTTP server.
|
||||
SetLogger(log.Logger)
|
||||
|
||||
// Start starts the HTTP server.
|
||||
Start() error
|
||||
|
||||
// Stop stops the HTTP server by closing underlying TCP connection.
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// Request defines the interface for HTTP request.
|
||||
Request interface {
|
||||
// IsTLS returns true if HTTP connection is TLS otherwise false.
|
||||
IsTLS() bool
|
||||
|
||||
// Scheme returns the HTTP protocol scheme, `http` or `https`.
|
||||
Scheme() string
|
||||
|
||||
// Host returns HTTP request host. Per RFC 2616, this is either the value of
|
||||
// the `Host` header or the host name given in the URL itself.
|
||||
Host() string
|
||||
|
||||
// SetHost sets the host of the request.
|
||||
SetHost(string)
|
||||
|
||||
// URI returns the unmodified `Request-URI` sent by the client.
|
||||
URI() string
|
||||
|
||||
// SetURI sets the URI of the request.
|
||||
SetURI(string)
|
||||
|
||||
// URL returns `engine.URL`.
|
||||
URL() URL
|
||||
|
||||
// Header returns `engine.Header`.
|
||||
Header() Header
|
||||
|
||||
// Referer returns the referring URL, if sent in the request.
|
||||
Referer() string
|
||||
|
||||
// Protocol returns the protocol version string of the HTTP request.
|
||||
// Protocol() string
|
||||
|
||||
// ProtocolMajor returns the major protocol version of the HTTP request.
|
||||
// ProtocolMajor() int
|
||||
|
||||
// ProtocolMinor returns the minor protocol version of the HTTP request.
|
||||
// ProtocolMinor() int
|
||||
|
||||
// ContentLength returns the size of request's body.
|
||||
ContentLength() int64
|
||||
|
||||
// UserAgent returns the client's `User-Agent`.
|
||||
UserAgent() string
|
||||
|
||||
// RemoteAddress returns the client's network address.
|
||||
RemoteAddress() string
|
||||
|
||||
// RealIP returns the client's network address based on `X-Forwarded-For`
|
||||
// or `X-Real-IP` request header.
|
||||
RealIP() string
|
||||
|
||||
// Method returns the request's HTTP function.
|
||||
Method() string
|
||||
|
||||
// SetMethod sets the HTTP method of the request.
|
||||
SetMethod(string)
|
||||
|
||||
// Body returns request's body.
|
||||
Body() io.Reader
|
||||
|
||||
// Body sets request's body.
|
||||
SetBody(io.Reader)
|
||||
|
||||
// FormValue returns the form field value for the provided name.
|
||||
FormValue(string) string
|
||||
|
||||
// FormParams returns the form parameters.
|
||||
FormParams() map[string][]string
|
||||
|
||||
// FormFile returns the multipart form file for the provided name.
|
||||
FormFile(string) (*multipart.FileHeader, error)
|
||||
|
||||
// MultipartForm returns the multipart form.
|
||||
MultipartForm() (*multipart.Form, error)
|
||||
|
||||
// Cookie returns the named cookie provided in the request.
|
||||
Cookie(string) (Cookie, error)
|
||||
|
||||
// Cookies returns the HTTP cookies sent with the request.
|
||||
Cookies() []Cookie
|
||||
}
|
||||
|
||||
// Response defines the interface for HTTP response.
|
||||
Response interface {
|
||||
// Header returns `engine.Header`
|
||||
Header() Header
|
||||
|
||||
// WriteHeader sends an HTTP response header with status code.
|
||||
WriteHeader(int)
|
||||
|
||||
// Write writes the data to the connection as part of an HTTP reply.
|
||||
Write(b []byte) (int, error)
|
||||
|
||||
// SetCookie adds a `Set-Cookie` header in HTTP response.
|
||||
SetCookie(Cookie)
|
||||
|
||||
// Status returns the HTTP response status.
|
||||
Status() int
|
||||
|
||||
// Size returns the number of bytes written to HTTP response.
|
||||
Size() int64
|
||||
|
||||
// Committed returns true if HTTP response header is written, otherwise false.
|
||||
Committed() bool
|
||||
|
||||
// Write returns the HTTP response writer.
|
||||
Writer() io.Writer
|
||||
|
||||
// SetWriter sets the HTTP response writer.
|
||||
SetWriter(io.Writer)
|
||||
}
|
||||
|
||||
// Header defines the interface for HTTP header.
|
||||
Header interface {
|
||||
// Add adds the key, value pair to the header. It appends to any existing values
|
||||
// associated with key.
|
||||
Add(string, string)
|
||||
|
||||
// Del deletes the values associated with key.
|
||||
Del(string)
|
||||
|
||||
// Set sets the header entries associated with key to the single element value.
|
||||
// It replaces any existing values associated with key.
|
||||
Set(string, string)
|
||||
|
||||
// Get gets the first value associated with the given key. If there are
|
||||
// no values associated with the key, Get returns "".
|
||||
Get(string) string
|
||||
|
||||
// Keys returns the header keys.
|
||||
Keys() []string
|
||||
|
||||
// Contains checks if the header is set.
|
||||
Contains(string) bool
|
||||
}
|
||||
|
||||
// URL defines the interface for HTTP request url.
|
||||
URL interface {
|
||||
// Path returns the request URL path.
|
||||
Path() string
|
||||
|
||||
// SetPath sets the request URL path.
|
||||
SetPath(string)
|
||||
|
||||
// QueryParam returns the query param for the provided name.
|
||||
QueryParam(string) string
|
||||
|
||||
// QueryParam returns the query parameters as map.
|
||||
QueryParams() map[string][]string
|
||||
|
||||
// QueryString returns the URL query string.
|
||||
QueryString() string
|
||||
}
|
||||
|
||||
// Cookie defines the interface for HTTP cookie.
|
||||
Cookie interface {
|
||||
// Name returns the name of the cookie.
|
||||
Name() string
|
||||
|
||||
// Value returns the value of the cookie.
|
||||
Value() string
|
||||
|
||||
// Path returns the path of the cookie.
|
||||
Path() string
|
||||
|
||||
// Domain returns the domain of the cookie.
|
||||
Domain() string
|
||||
|
||||
// Expires returns the expiry time of the cookie.
|
||||
Expires() time.Time
|
||||
|
||||
// Secure indicates if cookie is secured.
|
||||
Secure() bool
|
||||
|
||||
// HTTPOnly indicate if cookies is HTTP only.
|
||||
HTTPOnly() bool
|
||||
}
|
||||
|
||||
// Config defines engine config.
|
||||
Config struct {
|
||||
Address string // TCP address to listen on.
|
||||
Listener net.Listener // Custom `net.Listener`. If set, server accepts connections on it.
|
||||
TLSCertFile string // TLS certificate file path.
|
||||
TLSKeyFile string // TLS key file path.
|
||||
DisableHTTP2 bool // Disables HTTP/2.
|
||||
ReadTimeout time.Duration // Maximum duration before timing out read of the request.
|
||||
WriteTimeout time.Duration // Maximum duration before timing out write of the response.
|
||||
}
|
||||
|
||||
// Handler defines an interface to server HTTP requests via `ServeHTTP(Request, Response)`
|
||||
// function.
|
||||
Handler interface {
|
||||
ServeHTTP(Request, Response)
|
||||
}
|
||||
|
||||
// HandlerFunc is an adapter to allow the use of `func(Request, Response)` as
|
||||
// an HTTP handler.
|
||||
HandlerFunc func(Request, Response)
|
||||
)
|
||||
|
||||
// ServeHTTP serves HTTP request.
|
||||
func (h HandlerFunc) ServeHTTP(req Request, res Response) {
|
||||
h(req, res)
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type (
|
||||
// Cookie implements `engine.Cookie`.
|
||||
Cookie struct {
|
||||
*fasthttp.Cookie
|
||||
}
|
||||
)
|
||||
|
||||
// Name implements `engine.Cookie#Name` function.
|
||||
func (c *Cookie) Name() string {
|
||||
return string(c.Cookie.Key())
|
||||
}
|
||||
|
||||
// Value implements `engine.Cookie#Value` function.
|
||||
func (c *Cookie) Value() string {
|
||||
return string(c.Cookie.Value())
|
||||
}
|
||||
|
||||
// Path implements `engine.Cookie#Path` function.
|
||||
func (c *Cookie) Path() string {
|
||||
return string(c.Cookie.Path())
|
||||
}
|
||||
|
||||
// Domain implements `engine.Cookie#Domain` function.
|
||||
func (c *Cookie) Domain() string {
|
||||
return string(c.Cookie.Domain())
|
||||
}
|
||||
|
||||
// Expires implements `engine.Cookie#Expires` function.
|
||||
func (c *Cookie) Expires() time.Time {
|
||||
return c.Cookie.Expire()
|
||||
}
|
||||
|
||||
// Secure implements `engine.Cookie#Secure` function.
|
||||
func (c *Cookie) Secure() bool {
|
||||
return c.Cookie.Secure()
|
||||
}
|
||||
|
||||
// HTTPOnly implements `engine.Cookie#HTTPOnly` function.
|
||||
func (c *Cookie) HTTPOnly() bool {
|
||||
return c.Cookie.HTTPOnly()
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/engine/test"
|
||||
fast "github.com/valyala/fasthttp"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCookie(t *testing.T) {
|
||||
fCookie := &fast.Cookie{}
|
||||
fCookie.SetKey("session")
|
||||
fCookie.SetValue("securetoken")
|
||||
fCookie.SetPath("/")
|
||||
fCookie.SetDomain("github.com")
|
||||
fCookie.SetExpire(time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC))
|
||||
fCookie.SetSecure(true)
|
||||
fCookie.SetHTTPOnly(true)
|
||||
|
||||
cookie := &Cookie{
|
||||
fCookie,
|
||||
}
|
||||
test.CookieTest(t, cookie)
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
// +build !appengine
|
||||
|
||||
package fasthttp
|
||||
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
type (
|
||||
// RequestHeader holds `fasthttp.RequestHeader`.
|
||||
RequestHeader struct {
|
||||
*fasthttp.RequestHeader
|
||||
}
|
||||
|
||||
// ResponseHeader holds `fasthttp.ResponseHeader`.
|
||||
ResponseHeader struct {
|
||||
*fasthttp.ResponseHeader
|
||||
}
|
||||
)
|
||||
|
||||
// Add implements `engine.Header#Add` function.
|
||||
func (h *RequestHeader) Add(key, val string) {
|
||||
h.RequestHeader.Add(key, val)
|
||||
}
|
||||
|
||||
// Del implements `engine.Header#Del` function.
|
||||
func (h *RequestHeader) Del(key string) {
|
||||
h.RequestHeader.Del(key)
|
||||
}
|
||||
|
||||
// Set implements `engine.Header#Set` function.
|
||||
func (h *RequestHeader) Set(key, val string) {
|
||||
h.RequestHeader.Set(key, val)
|
||||
}
|
||||
|
||||
// Get implements `engine.Header#Get` function.
|
||||
func (h *RequestHeader) Get(key string) string {
|
||||
return string(h.Peek(key))
|
||||
}
|
||||
|
||||
// Keys implements `engine.Header#Keys` function.
|
||||
func (h *RequestHeader) Keys() (keys []string) {
|
||||
keys = make([]string, h.Len())
|
||||
i := 0
|
||||
h.VisitAll(func(k, v []byte) {
|
||||
keys[i] = string(k)
|
||||
i++
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Contains implements `engine.Header#Contains` function.
|
||||
func (h *RequestHeader) Contains(key string) bool {
|
||||
return h.Peek(key) != nil
|
||||
}
|
||||
|
||||
func (h *RequestHeader) reset(hdr *fasthttp.RequestHeader) {
|
||||
h.RequestHeader = hdr
|
||||
}
|
||||
|
||||
// Add implements `engine.Header#Add` function.
|
||||
func (h *ResponseHeader) Add(key, val string) {
|
||||
h.ResponseHeader.Add(key, val)
|
||||
}
|
||||
|
||||
// Del implements `engine.Header#Del` function.
|
||||
func (h *ResponseHeader) Del(key string) {
|
||||
h.ResponseHeader.Del(key)
|
||||
}
|
||||
|
||||
// Get implements `engine.Header#Get` function.
|
||||
func (h *ResponseHeader) Get(key string) string {
|
||||
return string(h.Peek(key))
|
||||
}
|
||||
|
||||
// Set implements `engine.Header#Set` function.
|
||||
func (h *ResponseHeader) Set(key, val string) {
|
||||
h.ResponseHeader.Set(key, val)
|
||||
}
|
||||
|
||||
// Keys implements `engine.Header#Keys` function.
|
||||
func (h *ResponseHeader) Keys() (keys []string) {
|
||||
keys = make([]string, h.Len())
|
||||
i := 0
|
||||
h.VisitAll(func(k, v []byte) {
|
||||
keys[i] = string(k)
|
||||
i++
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Contains implements `engine.Header#Contains` function.
|
||||
func (h *ResponseHeader) Contains(key string) bool {
|
||||
return h.Peek(key) != nil
|
||||
}
|
||||
|
||||
func (h *ResponseHeader) reset(hdr *fasthttp.ResponseHeader) {
|
||||
h.ResponseHeader = hdr
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/engine/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
fast "github.com/valyala/fasthttp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestHeader(t *testing.T) {
|
||||
header := &RequestHeader{&fast.RequestHeader{}}
|
||||
test.HeaderTest(t, header)
|
||||
|
||||
header.reset(&fast.RequestHeader{})
|
||||
assert.Len(t, header.Keys(), 0)
|
||||
}
|
||||
|
||||
func TestResponseHeader(t *testing.T) {
|
||||
header := &ResponseHeader{&fast.ResponseHeader{}}
|
||||
test.HeaderTest(t, header)
|
||||
|
||||
header.reset(&fast.ResponseHeader{})
|
||||
assert.Len(t, header.Keys(), 1)
|
||||
}
|
@ -1,198 +0,0 @@
|
||||
// +build !appengine
|
||||
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/labstack/echo/log"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type (
|
||||
// Request implements `engine.Request`.
|
||||
Request struct {
|
||||
*fasthttp.RequestCtx
|
||||
header engine.Header
|
||||
url engine.URL
|
||||
logger log.Logger
|
||||
}
|
||||
)
|
||||
|
||||
// NewRequest returns `Request` instance.
|
||||
func NewRequest(c *fasthttp.RequestCtx, l log.Logger) *Request {
|
||||
return &Request{
|
||||
RequestCtx: c,
|
||||
url: &URL{URI: c.URI()},
|
||||
header: &RequestHeader{RequestHeader: &c.Request.Header},
|
||||
logger: l,
|
||||
}
|
||||
}
|
||||
|
||||
// IsTLS implements `engine.Request#TLS` function.
|
||||
func (r *Request) IsTLS() bool {
|
||||
return r.RequestCtx.IsTLS()
|
||||
}
|
||||
|
||||
// Scheme implements `engine.Request#Scheme` function.
|
||||
func (r *Request) Scheme() string {
|
||||
return string(r.RequestCtx.URI().Scheme())
|
||||
}
|
||||
|
||||
// Host implements `engine.Request#Host` function.
|
||||
func (r *Request) Host() string {
|
||||
return string(r.RequestCtx.Host())
|
||||
}
|
||||
|
||||
// SetHost implements `engine.Request#SetHost` function.
|
||||
func (r *Request) SetHost(host string) {
|
||||
r.RequestCtx.Request.SetHost(host)
|
||||
}
|
||||
|
||||
// URL implements `engine.Request#URL` function.
|
||||
func (r *Request) URL() engine.URL {
|
||||
return r.url
|
||||
}
|
||||
|
||||
// Header implements `engine.Request#Header` function.
|
||||
func (r *Request) Header() engine.Header {
|
||||
return r.header
|
||||
}
|
||||
|
||||
// Referer implements `engine.Request#Referer` function.
|
||||
func (r *Request) Referer() string {
|
||||
return string(r.Request.Header.Referer())
|
||||
}
|
||||
|
||||
// ContentLength implements `engine.Request#ContentLength` function.
|
||||
func (r *Request) ContentLength() int64 {
|
||||
return int64(r.Request.Header.ContentLength())
|
||||
}
|
||||
|
||||
// UserAgent implements `engine.Request#UserAgent` function.
|
||||
func (r *Request) UserAgent() string {
|
||||
return string(r.RequestCtx.UserAgent())
|
||||
}
|
||||
|
||||
// RemoteAddress implements `engine.Request#RemoteAddress` function.
|
||||
func (r *Request) RemoteAddress() string {
|
||||
return r.RemoteAddr().String()
|
||||
}
|
||||
|
||||
// RealIP implements `engine.Request#RealIP` function.
|
||||
func (r *Request) RealIP() string {
|
||||
ra := r.RemoteAddress()
|
||||
if ip := r.Header().Get(echo.HeaderXForwardedFor); ip != "" {
|
||||
ra = ip
|
||||
} else if ip := r.Header().Get(echo.HeaderXRealIP); ip != "" {
|
||||
ra = ip
|
||||
} else {
|
||||
ra, _, _ = net.SplitHostPort(ra)
|
||||
}
|
||||
return ra
|
||||
}
|
||||
|
||||
// Method implements `engine.Request#Method` function.
|
||||
func (r *Request) Method() string {
|
||||
return string(r.RequestCtx.Method())
|
||||
}
|
||||
|
||||
// SetMethod implements `engine.Request#SetMethod` function.
|
||||
func (r *Request) SetMethod(method string) {
|
||||
r.Request.Header.SetMethodBytes([]byte(method))
|
||||
}
|
||||
|
||||
// URI implements `engine.Request#URI` function.
|
||||
func (r *Request) URI() string {
|
||||
return string(r.RequestURI())
|
||||
}
|
||||
|
||||
// SetURI implements `engine.Request#SetURI` function.
|
||||
func (r *Request) SetURI(uri string) {
|
||||
r.Request.Header.SetRequestURI(uri)
|
||||
}
|
||||
|
||||
// Body implements `engine.Request#Body` function.
|
||||
func (r *Request) Body() io.Reader {
|
||||
return bytes.NewBuffer(r.Request.Body())
|
||||
}
|
||||
|
||||
// SetBody implements `engine.Request#SetBody` function.
|
||||
func (r *Request) SetBody(reader io.Reader) {
|
||||
r.Request.SetBodyStream(reader, 0)
|
||||
}
|
||||
|
||||
// FormValue implements `engine.Request#FormValue` function.
|
||||
func (r *Request) FormValue(name string) string {
|
||||
return string(r.RequestCtx.FormValue(name))
|
||||
}
|
||||
|
||||
// FormParams implements `engine.Request#FormParams` function.
|
||||
func (r *Request) FormParams() (params map[string][]string) {
|
||||
params = make(map[string][]string)
|
||||
mf, err := r.RequestCtx.MultipartForm()
|
||||
|
||||
if err == fasthttp.ErrNoMultipartForm {
|
||||
r.PostArgs().VisitAll(func(k, v []byte) {
|
||||
key := string(k)
|
||||
if _, ok := params[key]; ok {
|
||||
params[key] = append(params[key], string(v))
|
||||
} else {
|
||||
params[string(k)] = []string{string(v)}
|
||||
}
|
||||
})
|
||||
} else if err == nil {
|
||||
for k, v := range mf.Value {
|
||||
if len(v) > 0 {
|
||||
params[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FormFile implements `engine.Request#FormFile` function.
|
||||
func (r *Request) FormFile(name string) (*multipart.FileHeader, error) {
|
||||
return r.RequestCtx.FormFile(name)
|
||||
}
|
||||
|
||||
// MultipartForm implements `engine.Request#MultipartForm` function.
|
||||
func (r *Request) MultipartForm() (*multipart.Form, error) {
|
||||
return r.RequestCtx.MultipartForm()
|
||||
}
|
||||
|
||||
// Cookie implements `engine.Request#Cookie` function.
|
||||
func (r *Request) Cookie(name string) (engine.Cookie, error) {
|
||||
c := new(fasthttp.Cookie)
|
||||
b := r.Request.Header.Cookie(name)
|
||||
if b == nil {
|
||||
return nil, echo.ErrCookieNotFound
|
||||
}
|
||||
c.SetKey(name)
|
||||
c.SetValueBytes(b)
|
||||
return &Cookie{c}, nil
|
||||
}
|
||||
|
||||
// Cookies implements `engine.Request#Cookies` function.
|
||||
func (r *Request) Cookies() []engine.Cookie {
|
||||
cookies := []engine.Cookie{}
|
||||
r.Request.Header.VisitAllCookie(func(name, value []byte) {
|
||||
c := new(fasthttp.Cookie)
|
||||
c.SetKeyBytes(name)
|
||||
c.SetValueBytes(value)
|
||||
cookies = append(cookies, &Cookie{c})
|
||||
})
|
||||
return cookies
|
||||
}
|
||||
|
||||
func (r *Request) reset(c *fasthttp.RequestCtx, h engine.Header, u engine.URL) {
|
||||
r.RequestCtx = c
|
||||
r.header = h
|
||||
r.url = u
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"net"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo/engine/test"
|
||||
"github.com/labstack/gommon/log"
|
||||
fast "github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type fakeAddr struct {
|
||||
addr string
|
||||
net.Addr
|
||||
}
|
||||
|
||||
func (a fakeAddr) String() string {
|
||||
return a.addr
|
||||
}
|
||||
|
||||
func TestRequest(t *testing.T) {
|
||||
ctx := new(fast.RequestCtx)
|
||||
url, _ := url.Parse("http://github.com/labstack/echo")
|
||||
ctx.Init(&fast.Request{}, fakeAddr{addr: "127.0.0.1"}, nil)
|
||||
ctx.Request.Read(bufio.NewReader(bytes.NewBufferString(test.MultipartRequest)))
|
||||
ctx.Request.SetRequestURI(url.String())
|
||||
test.RequestTest(t, NewRequest(ctx, log.New("echo")))
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
// +build !appengine
|
||||
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/labstack/echo/log"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type (
|
||||
// Response implements `engine.Response`.
|
||||
Response struct {
|
||||
*fasthttp.RequestCtx
|
||||
header engine.Header
|
||||
status int
|
||||
size int64
|
||||
committed bool
|
||||
writer io.Writer
|
||||
logger log.Logger
|
||||
}
|
||||
)
|
||||
|
||||
// NewResponse returns `Response` instance.
|
||||
func NewResponse(c *fasthttp.RequestCtx, l log.Logger) *Response {
|
||||
return &Response{
|
||||
RequestCtx: c,
|
||||
header: &ResponseHeader{ResponseHeader: &c.Response.Header},
|
||||
writer: c,
|
||||
logger: l,
|
||||
}
|
||||
}
|
||||
|
||||
// Header implements `engine.Response#Header` function.
|
||||
func (r *Response) Header() engine.Header {
|
||||
return r.header
|
||||
}
|
||||
|
||||
// WriteHeader implements `engine.Response#WriteHeader` function.
|
||||
func (r *Response) WriteHeader(code int) {
|
||||
if r.committed {
|
||||
r.logger.Warn("response already committed")
|
||||
return
|
||||
}
|
||||
r.status = code
|
||||
r.SetStatusCode(code)
|
||||
r.committed = true
|
||||
}
|
||||
|
||||
// Write implements `engine.Response#Write` function.
|
||||
func (r *Response) Write(b []byte) (n int, err error) {
|
||||
if !r.committed {
|
||||
r.WriteHeader(http.StatusOK)
|
||||
}
|
||||
n, err = r.writer.Write(b)
|
||||
r.size += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
// SetCookie implements `engine.Response#SetCookie` function.
|
||||
func (r *Response) SetCookie(c engine.Cookie) {
|
||||
cookie := new(fasthttp.Cookie)
|
||||
cookie.SetKey(c.Name())
|
||||
cookie.SetValue(c.Value())
|
||||
cookie.SetPath(c.Path())
|
||||
cookie.SetDomain(c.Domain())
|
||||
cookie.SetExpire(c.Expires())
|
||||
cookie.SetSecure(c.Secure())
|
||||
cookie.SetHTTPOnly(c.HTTPOnly())
|
||||
r.Response.Header.SetCookie(cookie)
|
||||
}
|
||||
|
||||
// Status implements `engine.Response#Status` function.
|
||||
func (r *Response) Status() int {
|
||||
return r.status
|
||||
}
|
||||
|
||||
// Size implements `engine.Response#Size` function.
|
||||
func (r *Response) Size() int64 {
|
||||
return r.size
|
||||
}
|
||||
|
||||
// Committed implements `engine.Response#Committed` function.
|
||||
func (r *Response) Committed() bool {
|
||||
return r.committed
|
||||
}
|
||||
|
||||
// Writer implements `engine.Response#Writer` function.
|
||||
func (r *Response) Writer() io.Writer {
|
||||
return r.writer
|
||||
}
|
||||
|
||||
// SetWriter implements `engine.Response#SetWriter` function.
|
||||
func (r *Response) SetWriter(w io.Writer) {
|
||||
r.writer = w
|
||||
}
|
||||
|
||||
func (r *Response) reset(c *fasthttp.RequestCtx, h engine.Header) {
|
||||
r.RequestCtx = c
|
||||
r.header = h
|
||||
r.status = http.StatusOK
|
||||
r.size = 0
|
||||
r.committed = false
|
||||
r.writer = c
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"github.com/labstack/gommon/log"
|
||||
)
|
||||
|
||||
func TestResponseWriteHeader(t *testing.T) {
|
||||
c := new(fasthttp.RequestCtx)
|
||||
res := NewResponse(c, log.New("test"))
|
||||
res.WriteHeader(http.StatusOK)
|
||||
assert.True(t, res.Committed())
|
||||
assert.Equal(t, http.StatusOK, res.Status())
|
||||
}
|
||||
|
||||
func TestResponseWrite(t *testing.T) {
|
||||
c := new(fasthttp.RequestCtx)
|
||||
res := NewResponse(c, log.New("test"))
|
||||
res.Write([]byte("test"))
|
||||
assert.Equal(t, int64(4), res.Size())
|
||||
assert.Equal(t, "test", string(c.Response.Body()))
|
||||
}
|
||||
|
||||
func TestResponseSetCookie(t *testing.T) {
|
||||
c := new(fasthttp.RequestCtx)
|
||||
res := NewResponse(c, log.New("test"))
|
||||
cookie := new(fasthttp.Cookie)
|
||||
cookie.SetKey("name")
|
||||
cookie.SetValue("Jon Snow")
|
||||
res.SetCookie(&Cookie{cookie})
|
||||
c.Response.Header.SetCookie(cookie)
|
||||
ck := new(fasthttp.Cookie)
|
||||
ck.SetKey("name")
|
||||
assert.True(t, c.Response.Header.Cookie(ck))
|
||||
assert.Equal(t, "Jon Snow", string(ck.Value()))
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
// +build !appengine
|
||||
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/labstack/echo/log"
|
||||
glog "github.com/labstack/gommon/log"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type (
|
||||
// Server implements `engine.Server`.
|
||||
Server struct {
|
||||
*fasthttp.Server
|
||||
config engine.Config
|
||||
handler engine.Handler
|
||||
logger log.Logger
|
||||
pool *pool
|
||||
}
|
||||
|
||||
pool struct {
|
||||
request sync.Pool
|
||||
response sync.Pool
|
||||
requestHeader sync.Pool
|
||||
responseHeader sync.Pool
|
||||
url sync.Pool
|
||||
}
|
||||
)
|
||||
|
||||
// New returns `Server` with provided listen address.
|
||||
func New(addr string) *Server {
|
||||
c := engine.Config{Address: addr}
|
||||
return WithConfig(c)
|
||||
}
|
||||
|
||||
// WithTLS returns `Server` with provided TLS config.
|
||||
func WithTLS(addr, certFile, keyFile string) *Server {
|
||||
c := engine.Config{
|
||||
Address: addr,
|
||||
TLSCertFile: certFile,
|
||||
TLSKeyFile: keyFile,
|
||||
}
|
||||
return WithConfig(c)
|
||||
}
|
||||
|
||||
// WithConfig returns `Server` with provided config.
|
||||
func WithConfig(c engine.Config) (s *Server) {
|
||||
s = &Server{
|
||||
Server: new(fasthttp.Server),
|
||||
config: c,
|
||||
pool: &pool{
|
||||
request: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Request{logger: s.logger}
|
||||
},
|
||||
},
|
||||
response: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Response{logger: s.logger}
|
||||
},
|
||||
},
|
||||
requestHeader: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &RequestHeader{}
|
||||
},
|
||||
},
|
||||
responseHeader: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &ResponseHeader{}
|
||||
},
|
||||
},
|
||||
url: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &URL{}
|
||||
},
|
||||
},
|
||||
},
|
||||
handler: engine.HandlerFunc(func(req engine.Request, res engine.Response) {
|
||||
panic("echo: handler not set, use `Server#SetHandler()` to set it.")
|
||||
}),
|
||||
logger: glog.New("echo"),
|
||||
}
|
||||
s.ReadTimeout = c.ReadTimeout
|
||||
s.WriteTimeout = c.WriteTimeout
|
||||
s.Handler = s.ServeHTTP
|
||||
return
|
||||
}
|
||||
|
||||
// SetHandler implements `engine.Server#SetHandler` function.
|
||||
func (s *Server) SetHandler(h engine.Handler) {
|
||||
s.handler = h
|
||||
}
|
||||
|
||||
// SetLogger implements `engine.Server#SetLogger` function.
|
||||
func (s *Server) SetLogger(l log.Logger) {
|
||||
s.logger = l
|
||||
}
|
||||
|
||||
// Start implements `engine.Server#Start` function.
|
||||
func (s *Server) Start() error {
|
||||
if s.config.Listener == nil {
|
||||
return s.startDefaultListener()
|
||||
}
|
||||
return s.startCustomListener()
|
||||
|
||||
}
|
||||
|
||||
// Stop implements `engine.Server#Stop` function.
|
||||
func (s *Server) Stop() error {
|
||||
// TODO: implement `engine.Server#Stop` function
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) startDefaultListener() error {
|
||||
c := s.config
|
||||
if c.TLSCertFile != "" && c.TLSKeyFile != "" {
|
||||
return s.ListenAndServeTLS(c.Address, c.TLSCertFile, c.TLSKeyFile)
|
||||
}
|
||||
return s.ListenAndServe(c.Address)
|
||||
}
|
||||
|
||||
func (s *Server) startCustomListener() error {
|
||||
c := s.config
|
||||
if c.TLSCertFile != "" && c.TLSKeyFile != "" {
|
||||
return s.ServeTLS(c.Listener, c.TLSCertFile, c.TLSKeyFile)
|
||||
}
|
||||
return s.Serve(c.Listener)
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(c *fasthttp.RequestCtx) {
|
||||
// Request
|
||||
req := s.pool.request.Get().(*Request)
|
||||
reqHdr := s.pool.requestHeader.Get().(*RequestHeader)
|
||||
reqURL := s.pool.url.Get().(*URL)
|
||||
reqHdr.reset(&c.Request.Header)
|
||||
reqURL.reset(c.URI())
|
||||
req.reset(c, reqHdr, reqURL)
|
||||
|
||||
// Response
|
||||
res := s.pool.response.Get().(*Response)
|
||||
resHdr := s.pool.responseHeader.Get().(*ResponseHeader)
|
||||
resHdr.reset(&c.Response.Header)
|
||||
res.reset(c, resHdr)
|
||||
|
||||
s.handler.ServeHTTP(req, res)
|
||||
|
||||
// Return to pool
|
||||
s.pool.request.Put(req)
|
||||
s.pool.requestHeader.Put(reqHdr)
|
||||
s.pool.url.Put(reqURL)
|
||||
s.pool.response.Put(res)
|
||||
s.pool.responseHeader.Put(resHdr)
|
||||
}
|
||||
|
||||
// WrapHandler wraps `fasthttp.RequestHandler` into `echo.HandlerFunc`.
|
||||
func WrapHandler(h fasthttp.RequestHandler) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
req := c.Request().(*Request)
|
||||
res := c.Response().(*Response)
|
||||
ctx := req.RequestCtx
|
||||
h(ctx)
|
||||
res.status = ctx.Response.StatusCode()
|
||||
res.size = int64(ctx.Response.Header.ContentLength())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WrapMiddleware wraps `func(fasthttp.RequestHandler) fasthttp.RequestHandler`
|
||||
// into `echo.MiddlewareFunc`
|
||||
func WrapMiddleware(m func(fasthttp.RequestHandler) fasthttp.RequestHandler) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
req := c.Request().(*Request)
|
||||
res := c.Response().(*Response)
|
||||
ctx := req.RequestCtx
|
||||
m(func(ctx *fasthttp.RequestCtx) {
|
||||
next(c)
|
||||
})(ctx)
|
||||
res.status = ctx.Response.StatusCode()
|
||||
res.size = int64(ctx.Response.Header.ContentLength())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// TODO: Fix me
|
||||
func TestServer(t *testing.T) {
|
||||
s := New("")
|
||||
s.SetHandler(engine.HandlerFunc(func(req engine.Request, res engine.Response) {
|
||||
}))
|
||||
ctx := new(fasthttp.RequestCtx)
|
||||
s.ServeHTTP(ctx)
|
||||
}
|
||||
|
||||
func TestServerWrapHandler(t *testing.T) {
|
||||
e := echo.New()
|
||||
ctx := new(fasthttp.RequestCtx)
|
||||
req := NewRequest(ctx, nil)
|
||||
res := NewResponse(ctx, nil)
|
||||
c := e.NewContext(req, res)
|
||||
h := WrapHandler(func(ctx *fasthttp.RequestCtx) {
|
||||
ctx.Write([]byte("test"))
|
||||
})
|
||||
if assert.NoError(t, h(c)) {
|
||||
assert.Equal(t, http.StatusOK, ctx.Response.StatusCode())
|
||||
assert.Equal(t, "test", string(ctx.Response.Body()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerWrapMiddleware(t *testing.T) {
|
||||
e := echo.New()
|
||||
ctx := new(fasthttp.RequestCtx)
|
||||
req := NewRequest(ctx, nil)
|
||||
res := NewResponse(ctx, nil)
|
||||
c := e.NewContext(req, res)
|
||||
buf := new(bytes.Buffer)
|
||||
mw := WrapMiddleware(func(h fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
buf.Write([]byte("mw"))
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
h := mw(func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "OK")
|
||||
})
|
||||
if assert.NoError(t, h(c)) {
|
||||
assert.Equal(t, "mw", buf.String())
|
||||
assert.Equal(t, http.StatusOK, ctx.Response.StatusCode())
|
||||
assert.Equal(t, "OK", string(ctx.Response.Body()))
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
// +build !appengine
|
||||
|
||||
package fasthttp
|
||||
|
||||
import "github.com/valyala/fasthttp"
|
||||
|
||||
type (
|
||||
// URL implements `engine.URL`.
|
||||
URL struct {
|
||||
*fasthttp.URI
|
||||
}
|
||||
)
|
||||
|
||||
// Path implements `engine.URL#Path` function.
|
||||
func (u *URL) Path() string {
|
||||
return string(u.URI.PathOriginal())
|
||||
}
|
||||
|
||||
// SetPath implements `engine.URL#SetPath` function.
|
||||
func (u *URL) SetPath(path string) {
|
||||
u.URI.SetPath(path)
|
||||
}
|
||||
|
||||
// QueryParam implements `engine.URL#QueryParam` function.
|
||||
func (u *URL) QueryParam(name string) string {
|
||||
return string(u.QueryArgs().Peek(name))
|
||||
}
|
||||
|
||||
// QueryParams implements `engine.URL#QueryParams` function.
|
||||
func (u *URL) QueryParams() (params map[string][]string) {
|
||||
params = make(map[string][]string)
|
||||
u.QueryArgs().VisitAll(func(k, v []byte) {
|
||||
_, ok := params[string(k)]
|
||||
if !ok {
|
||||
params[string(k)] = make([]string, 0)
|
||||
}
|
||||
params[string(k)] = append(params[string(k)], string(v))
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// QueryString implements `engine.URL#QueryString` function.
|
||||
func (u *URL) QueryString() string {
|
||||
return string(u.URI.QueryString())
|
||||
}
|
||||
|
||||
func (u *URL) reset(uri *fasthttp.URI) {
|
||||
u.URI = uri
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/engine/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
fast "github.com/valyala/fasthttp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestURL(t *testing.T) {
|
||||
uri := &fast.URI{}
|
||||
uri.Parse([]byte("github.com"), []byte("/labstack/echo?param1=value1¶m1=value2¶m2=value3"))
|
||||
mUrl := &URL{uri}
|
||||
test.URLTest(t, mUrl)
|
||||
|
||||
mUrl.reset(&fast.URI{})
|
||||
assert.Equal(t, "", string(mUrl.Host()))
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// Cookie implements `engine.Cookie`.
|
||||
Cookie struct {
|
||||
*http.Cookie
|
||||
}
|
||||
)
|
||||
|
||||
// Name implements `engine.Cookie#Name` function.
|
||||
func (c *Cookie) Name() string {
|
||||
return c.Cookie.Name
|
||||
}
|
||||
|
||||
// Value implements `engine.Cookie#Value` function.
|
||||
func (c *Cookie) Value() string {
|
||||
return c.Cookie.Value
|
||||
}
|
||||
|
||||
// Path implements `engine.Cookie#Path` function.
|
||||
func (c *Cookie) Path() string {
|
||||
return c.Cookie.Path
|
||||
}
|
||||
|
||||
// Domain implements `engine.Cookie#Domain` function.
|
||||
func (c *Cookie) Domain() string {
|
||||
return c.Cookie.Domain
|
||||
}
|
||||
|
||||
// Expires implements `engine.Cookie#Expires` function.
|
||||
func (c *Cookie) Expires() time.Time {
|
||||
return c.Cookie.Expires
|
||||
}
|
||||
|
||||
// Secure implements `engine.Cookie#Secure` function.
|
||||
func (c *Cookie) Secure() bool {
|
||||
return c.Cookie.Secure
|
||||
}
|
||||
|
||||
// HTTPOnly implements `engine.Cookie#HTTPOnly` function.
|
||||
func (c *Cookie) HTTPOnly() bool {
|
||||
return c.Cookie.HttpOnly
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/engine/test"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCookie(t *testing.T) {
|
||||
cookie := &Cookie{&http.Cookie{
|
||||
Name: "session",
|
||||
Value: "securetoken",
|
||||
Path: "/",
|
||||
Domain: "github.com",
|
||||
Expires: time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
}}
|
||||
test.CookieTest(t, cookie)
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package standard
|
||||
|
||||
import "net/http"
|
||||
|
||||
type (
|
||||
// Header implements `engine.Header`.
|
||||
Header struct {
|
||||
http.Header
|
||||
}
|
||||
)
|
||||
|
||||
// Add implements `engine.Header#Add` function.
|
||||
func (h *Header) Add(key, val string) {
|
||||
h.Header.Add(key, val)
|
||||
}
|
||||
|
||||
// Del implements `engine.Header#Del` function.
|
||||
func (h *Header) Del(key string) {
|
||||
h.Header.Del(key)
|
||||
}
|
||||
|
||||
// Set implements `engine.Header#Set` function.
|
||||
func (h *Header) Set(key, val string) {
|
||||
h.Header.Set(key, val)
|
||||
}
|
||||
|
||||
// Get implements `engine.Header#Get` function.
|
||||
func (h *Header) Get(key string) string {
|
||||
return h.Header.Get(key)
|
||||
}
|
||||
|
||||
// Keys implements `engine.Header#Keys` function.
|
||||
func (h *Header) Keys() (keys []string) {
|
||||
keys = make([]string, len(h.Header))
|
||||
i := 0
|
||||
for k := range h.Header {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Contains implements `engine.Header#Contains` function.
|
||||
func (h *Header) Contains(key string) bool {
|
||||
_, ok := h.Header[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (h *Header) reset(hdr http.Header) {
|
||||
h.Header = hdr
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/engine/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHeader(t *testing.T) {
|
||||
header := &Header{http.Header{}}
|
||||
test.HeaderTest(t, header)
|
||||
|
||||
header.reset(http.Header{})
|
||||
assert.Len(t, header.Keys(), 0)
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/labstack/echo/log"
|
||||
)
|
||||
|
||||
type (
|
||||
// Request implements `engine.Request`.
|
||||
Request struct {
|
||||
*http.Request
|
||||
header engine.Header
|
||||
url engine.URL
|
||||
logger log.Logger
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMemory = 32 << 20 // 32 MB
|
||||
)
|
||||
|
||||
// NewRequest returns `Request` instance.
|
||||
func NewRequest(r *http.Request, l log.Logger) *Request {
|
||||
return &Request{
|
||||
Request: r,
|
||||
url: &URL{URL: r.URL},
|
||||
header: &Header{Header: r.Header},
|
||||
logger: l,
|
||||
}
|
||||
}
|
||||
|
||||
// IsTLS implements `engine.Request#TLS` function.
|
||||
func (r *Request) IsTLS() bool {
|
||||
return r.Request.TLS != nil
|
||||
}
|
||||
|
||||
// Scheme implements `engine.Request#Scheme` function.
|
||||
func (r *Request) Scheme() string {
|
||||
// Can't use `r.Request.URL.Scheme`
|
||||
// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
|
||||
if r.IsTLS() {
|
||||
return "https"
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
// Host implements `engine.Request#Host` function.
|
||||
func (r *Request) Host() string {
|
||||
return r.Request.Host
|
||||
}
|
||||
|
||||
// SetHost implements `engine.Request#SetHost` function.
|
||||
func (r *Request) SetHost(host string) {
|
||||
r.Request.Host = host
|
||||
}
|
||||
|
||||
// URL implements `engine.Request#URL` function.
|
||||
func (r *Request) URL() engine.URL {
|
||||
return r.url
|
||||
}
|
||||
|
||||
// Header implements `engine.Request#Header` function.
|
||||
func (r *Request) Header() engine.Header {
|
||||
return r.header
|
||||
}
|
||||
|
||||
// Referer implements `engine.Request#Referer` function.
|
||||
func (r *Request) Referer() string {
|
||||
return r.Request.Referer()
|
||||
}
|
||||
|
||||
// func Proto() string {
|
||||
// return r.request.Proto()
|
||||
// }
|
||||
//
|
||||
// func ProtoMajor() int {
|
||||
// return r.request.ProtoMajor()
|
||||
// }
|
||||
//
|
||||
// func ProtoMinor() int {
|
||||
// return r.request.ProtoMinor()
|
||||
// }
|
||||
|
||||
// ContentLength implements `engine.Request#ContentLength` function.
|
||||
func (r *Request) ContentLength() int64 {
|
||||
return r.Request.ContentLength
|
||||
}
|
||||
|
||||
// UserAgent implements `engine.Request#UserAgent` function.
|
||||
func (r *Request) UserAgent() string {
|
||||
return r.Request.UserAgent()
|
||||
}
|
||||
|
||||
// RemoteAddress implements `engine.Request#RemoteAddress` function.
|
||||
func (r *Request) RemoteAddress() string {
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
// RealIP implements `engine.Request#RealIP` function.
|
||||
func (r *Request) RealIP() string {
|
||||
ra := r.RemoteAddress()
|
||||
if ip := r.Header().Get(echo.HeaderXForwardedFor); ip != "" {
|
||||
ra = ip
|
||||
} else if ip := r.Header().Get(echo.HeaderXRealIP); ip != "" {
|
||||
ra = ip
|
||||
} else {
|
||||
ra, _, _ = net.SplitHostPort(ra)
|
||||
}
|
||||
return ra
|
||||
}
|
||||
|
||||
// Method implements `engine.Request#Method` function.
|
||||
func (r *Request) Method() string {
|
||||
return r.Request.Method
|
||||
}
|
||||
|
||||
// SetMethod implements `engine.Request#SetMethod` function.
|
||||
func (r *Request) SetMethod(method string) {
|
||||
r.Request.Method = method
|
||||
}
|
||||
|
||||
// URI implements `engine.Request#URI` function.
|
||||
func (r *Request) URI() string {
|
||||
return r.RequestURI
|
||||
}
|
||||
|
||||
// SetURI implements `engine.Request#SetURI` function.
|
||||
func (r *Request) SetURI(uri string) {
|
||||
r.RequestURI = uri
|
||||
}
|
||||
|
||||
// Body implements `engine.Request#Body` function.
|
||||
func (r *Request) Body() io.Reader {
|
||||
return r.Request.Body
|
||||
}
|
||||
|
||||
// SetBody implements `engine.Request#SetBody` function.
|
||||
func (r *Request) SetBody(reader io.Reader) {
|
||||
r.Request.Body = ioutil.NopCloser(reader)
|
||||
}
|
||||
|
||||
// FormValue implements `engine.Request#FormValue` function.
|
||||
func (r *Request) FormValue(name string) string {
|
||||
return r.Request.FormValue(name)
|
||||
}
|
||||
|
||||
// FormParams implements `engine.Request#FormParams` function.
|
||||
func (r *Request) FormParams() map[string][]string {
|
||||
if strings.HasPrefix(r.header.Get(echo.HeaderContentType), echo.MIMEMultipartForm) {
|
||||
if err := r.ParseMultipartForm(defaultMemory); err != nil {
|
||||
panic(fmt.Sprintf("echo: %v", err))
|
||||
}
|
||||
} else {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
panic(fmt.Sprintf("echo: %v", err))
|
||||
}
|
||||
}
|
||||
return map[string][]string(r.Request.Form)
|
||||
}
|
||||
|
||||
// FormFile implements `engine.Request#FormFile` function.
|
||||
func (r *Request) FormFile(name string) (*multipart.FileHeader, error) {
|
||||
_, fh, err := r.Request.FormFile(name)
|
||||
return fh, err
|
||||
}
|
||||
|
||||
// MultipartForm implements `engine.Request#MultipartForm` function.
|
||||
func (r *Request) MultipartForm() (*multipart.Form, error) {
|
||||
err := r.ParseMultipartForm(defaultMemory)
|
||||
return r.Request.MultipartForm, err
|
||||
}
|
||||
|
||||
// Cookie implements `engine.Request#Cookie` function.
|
||||
func (r *Request) Cookie(name string) (engine.Cookie, error) {
|
||||
c, err := r.Request.Cookie(name)
|
||||
if err != nil {
|
||||
return nil, echo.ErrCookieNotFound
|
||||
}
|
||||
return &Cookie{c}, nil
|
||||
}
|
||||
|
||||
// Cookies implements `engine.Request#Cookies` function.
|
||||
func (r *Request) Cookies() []engine.Cookie {
|
||||
cs := r.Request.Cookies()
|
||||
cookies := make([]engine.Cookie, len(cs))
|
||||
for i, c := range cs {
|
||||
cookies[i] = &Cookie{c}
|
||||
}
|
||||
return cookies
|
||||
}
|
||||
|
||||
func (r *Request) reset(req *http.Request, h engine.Header, u engine.URL) {
|
||||
r.Request = req
|
||||
r.header = h
|
||||
r.url = u
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo/engine/test"
|
||||
"github.com/labstack/gommon/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRequest(t *testing.T) {
|
||||
httpReq, _ := http.ReadRequest(bufio.NewReader(strings.NewReader(test.MultipartRequest)))
|
||||
url, _ := url.Parse("http://github.com/labstack/echo")
|
||||
httpReq.URL = url
|
||||
httpReq.RemoteAddr = "127.0.0.1"
|
||||
req := NewRequest(httpReq, log.New("echo"))
|
||||
test.RequestTest(t, req)
|
||||
nr, _ := http.NewRequest("GET", "/", nil)
|
||||
req.reset(nr, nil, nil)
|
||||
assert.Equal(t, "", req.Host())
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/labstack/echo/log"
|
||||
)
|
||||
|
||||
type (
|
||||
// Response implements `engine.Response`.
|
||||
Response struct {
|
||||
http.ResponseWriter
|
||||
adapter *responseAdapter
|
||||
header engine.Header
|
||||
status int
|
||||
size int64
|
||||
committed bool
|
||||
writer io.Writer
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
responseAdapter struct {
|
||||
*Response
|
||||
}
|
||||
)
|
||||
|
||||
// NewResponse returns `Response` instance.
|
||||
func NewResponse(w http.ResponseWriter, l log.Logger) (r *Response) {
|
||||
r = &Response{
|
||||
ResponseWriter: w,
|
||||
header: &Header{Header: w.Header()},
|
||||
writer: w,
|
||||
logger: l,
|
||||
}
|
||||
r.adapter = &responseAdapter{Response: r}
|
||||
return
|
||||
}
|
||||
|
||||
// Header implements `engine.Response#Header` function.
|
||||
func (r *Response) Header() engine.Header {
|
||||
return r.header
|
||||
}
|
||||
|
||||
// WriteHeader implements `engine.Response#WriteHeader` function.
|
||||
func (r *Response) WriteHeader(code int) {
|
||||
if r.committed {
|
||||
r.logger.Warn("response already committed")
|
||||
return
|
||||
}
|
||||
r.status = code
|
||||
r.ResponseWriter.WriteHeader(code)
|
||||
r.committed = true
|
||||
}
|
||||
|
||||
// Write implements `engine.Response#Write` function.
|
||||
func (r *Response) Write(b []byte) (n int, err error) {
|
||||
if !r.committed {
|
||||
r.WriteHeader(http.StatusOK)
|
||||
}
|
||||
n, err = r.writer.Write(b)
|
||||
r.size += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
// SetCookie implements `engine.Response#SetCookie` function.
|
||||
func (r *Response) SetCookie(c engine.Cookie) {
|
||||
http.SetCookie(r.ResponseWriter, &http.Cookie{
|
||||
Name: c.Name(),
|
||||
Value: c.Value(),
|
||||
Path: c.Path(),
|
||||
Domain: c.Domain(),
|
||||
Expires: c.Expires(),
|
||||
Secure: c.Secure(),
|
||||
HttpOnly: c.HTTPOnly(),
|
||||
})
|
||||
}
|
||||
|
||||
// Status implements `engine.Response#Status` function.
|
||||
func (r *Response) Status() int {
|
||||
return r.status
|
||||
}
|
||||
|
||||
// Size implements `engine.Response#Size` function.
|
||||
func (r *Response) Size() int64 {
|
||||
return r.size
|
||||
}
|
||||
|
||||
// Committed implements `engine.Response#Committed` function.
|
||||
func (r *Response) Committed() bool {
|
||||
return r.committed
|
||||
}
|
||||
|
||||
// Writer implements `engine.Response#Writer` function.
|
||||
func (r *Response) Writer() io.Writer {
|
||||
return r.writer
|
||||
}
|
||||
|
||||
// SetWriter implements `engine.Response#SetWriter` function.
|
||||
func (r *Response) SetWriter(w io.Writer) {
|
||||
r.writer = w
|
||||
}
|
||||
|
||||
// Flush implements the http.Flusher interface to allow an HTTP handler to flush
|
||||
// buffered data to the client.
|
||||
// See https://golang.org/pkg/net/http/#Flusher
|
||||
func (r *Response) Flush() {
|
||||
r.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
// Hijack implements the http.Hijacker interface to allow an HTTP handler to
|
||||
// take over the connection.
|
||||
// See https://golang.org/pkg/net/http/#Hijacker
|
||||
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return r.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// CloseNotify implements the http.CloseNotifier interface to allow detecting
|
||||
// when the underlying connection has gone away.
|
||||
// This mechanism can be used to cancel long operations on the server if the
|
||||
// client has disconnected before the response is ready.
|
||||
// See https://golang.org/pkg/net/http/#CloseNotifier
|
||||
func (r *Response) CloseNotify() <-chan bool {
|
||||
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (r *Response) reset(w http.ResponseWriter, a *responseAdapter, h engine.Header) {
|
||||
r.ResponseWriter = w
|
||||
r.adapter = a
|
||||
r.header = h
|
||||
r.status = http.StatusOK
|
||||
r.size = 0
|
||||
r.committed = false
|
||||
r.writer = w
|
||||
}
|
||||
|
||||
func (r *responseAdapter) Header() http.Header {
|
||||
return r.ResponseWriter.Header()
|
||||
}
|
||||
|
||||
func (r *responseAdapter) reset(res *Response) {
|
||||
r.Response = res
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/gommon/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestResponseWriteHeader(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
res := NewResponse(rec, log.New("test"))
|
||||
res.WriteHeader(http.StatusOK)
|
||||
assert.True(t, res.Committed())
|
||||
assert.Equal(t, http.StatusOK, res.Status())
|
||||
}
|
||||
|
||||
func TestResponseWrite(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
res := NewResponse(rec, log.New("test"))
|
||||
res.Write([]byte("test"))
|
||||
assert.Equal(t, int64(4), res.Size())
|
||||
assert.Equal(t, "test", rec.Body.String())
|
||||
res.Flush()
|
||||
assert.True(t, rec.Flushed)
|
||||
}
|
||||
|
||||
func TestResponseSetCookie(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
res := NewResponse(rec, log.New("test"))
|
||||
res.SetCookie(&Cookie{&http.Cookie{
|
||||
Name: "name",
|
||||
Value: "Jon Snow",
|
||||
}})
|
||||
assert.Equal(t, "name=Jon Snow", rec.Header().Get("Set-Cookie"))
|
||||
}
|
@ -1,208 +0,0 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/labstack/echo/log"
|
||||
glog "github.com/labstack/gommon/log"
|
||||
)
|
||||
|
||||
type (
|
||||
// Server implements `engine.Server`.
|
||||
Server struct {
|
||||
*http.Server
|
||||
config engine.Config
|
||||
handler engine.Handler
|
||||
logger log.Logger
|
||||
pool *pool
|
||||
}
|
||||
|
||||
pool struct {
|
||||
request sync.Pool
|
||||
response sync.Pool
|
||||
responseAdapter sync.Pool
|
||||
header sync.Pool
|
||||
url sync.Pool
|
||||
}
|
||||
)
|
||||
|
||||
// New returns `Server` instance with provided listen address.
|
||||
func New(addr string) *Server {
|
||||
c := engine.Config{Address: addr}
|
||||
return WithConfig(c)
|
||||
}
|
||||
|
||||
// WithTLS returns `Server` instance with provided TLS config.
|
||||
func WithTLS(addr, certFile, keyFile string) *Server {
|
||||
c := engine.Config{
|
||||
Address: addr,
|
||||
TLSCertFile: certFile,
|
||||
TLSKeyFile: keyFile,
|
||||
}
|
||||
return WithConfig(c)
|
||||
}
|
||||
|
||||
// WithConfig returns `Server` instance with provided config.
|
||||
func WithConfig(c engine.Config) (s *Server) {
|
||||
s = &Server{
|
||||
Server: new(http.Server),
|
||||
config: c,
|
||||
pool: &pool{
|
||||
request: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Request{logger: s.logger}
|
||||
},
|
||||
},
|
||||
response: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Response{logger: s.logger}
|
||||
},
|
||||
},
|
||||
responseAdapter: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &responseAdapter{}
|
||||
},
|
||||
},
|
||||
header: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Header{}
|
||||
},
|
||||
},
|
||||
url: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &URL{}
|
||||
},
|
||||
},
|
||||
},
|
||||
handler: engine.HandlerFunc(func(req engine.Request, res engine.Response) {
|
||||
panic("echo: handler not set, use `Server#SetHandler()` to set it.")
|
||||
}),
|
||||
logger: glog.New("echo"),
|
||||
}
|
||||
s.ReadTimeout = c.ReadTimeout
|
||||
s.WriteTimeout = c.WriteTimeout
|
||||
s.Addr = c.Address
|
||||
s.Handler = s
|
||||
return
|
||||
}
|
||||
|
||||
// SetHandler implements `engine.Server#SetHandler` function.
|
||||
func (s *Server) SetHandler(h engine.Handler) {
|
||||
s.handler = h
|
||||
}
|
||||
|
||||
// SetLogger implements `engine.Server#SetLogger` function.
|
||||
func (s *Server) SetLogger(l log.Logger) {
|
||||
s.logger = l
|
||||
}
|
||||
|
||||
// Start implements `engine.Server#Start` function.
|
||||
func (s *Server) Start() error {
|
||||
if s.config.Listener == nil {
|
||||
ln, err := net.Listen("tcp", s.config.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.config.TLSCertFile != "" && s.config.TLSKeyFile != "" {
|
||||
// TODO: https://github.com/golang/go/commit/d24f446a90ea94b87591bf16228d7d871fec3d92
|
||||
config := &tls.Config{
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
if !s.config.DisableHTTP2 {
|
||||
config.NextProtos = append(config.NextProtos, "h2")
|
||||
}
|
||||
config.Certificates = make([]tls.Certificate, 1)
|
||||
config.Certificates[0], err = tls.LoadX509KeyPair(s.config.TLSCertFile, s.config.TLSKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.config.Listener = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
|
||||
} else {
|
||||
s.config.Listener = tcpKeepAliveListener{ln.(*net.TCPListener)}
|
||||
}
|
||||
}
|
||||
|
||||
return s.Serve(s.config.Listener)
|
||||
}
|
||||
|
||||
// Stop implements `engine.Server#Stop` function.
|
||||
func (s *Server) Stop() error {
|
||||
return s.config.Listener.Close()
|
||||
}
|
||||
|
||||
// ServeHTTP implements `http.Handler` interface.
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Request
|
||||
req := s.pool.request.Get().(*Request)
|
||||
reqHdr := s.pool.header.Get().(*Header)
|
||||
reqURL := s.pool.url.Get().(*URL)
|
||||
reqHdr.reset(r.Header)
|
||||
reqURL.reset(r.URL)
|
||||
req.reset(r, reqHdr, reqURL)
|
||||
|
||||
// Response
|
||||
res := s.pool.response.Get().(*Response)
|
||||
resAdpt := s.pool.responseAdapter.Get().(*responseAdapter)
|
||||
resAdpt.reset(res)
|
||||
resHdr := s.pool.header.Get().(*Header)
|
||||
resHdr.reset(w.Header())
|
||||
res.reset(w, resAdpt, resHdr)
|
||||
|
||||
s.handler.ServeHTTP(req, res)
|
||||
|
||||
// Return to pool
|
||||
s.pool.request.Put(req)
|
||||
s.pool.header.Put(reqHdr)
|
||||
s.pool.url.Put(reqURL)
|
||||
s.pool.response.Put(res)
|
||||
s.pool.header.Put(resHdr)
|
||||
}
|
||||
|
||||
// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
|
||||
func WrapHandler(h http.Handler) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
req := c.Request().(*Request)
|
||||
res := c.Response().(*Response)
|
||||
h.ServeHTTP(res.adapter, req.Request)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WrapMiddleware wraps `func(http.Handler) http.Handler` into `echo.MiddlewareFunc`
|
||||
func WrapMiddleware(m func(http.Handler) http.Handler) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
req := c.Request().(*Request)
|
||||
res := c.Response().(*Response)
|
||||
m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
err = next(c)
|
||||
})).ServeHTTP(res.adapter, req.Request)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
|
||||
// connections. It's used by ListenAndServe and ListenAndServeTLS so
|
||||
// dead TCP connections (e.g. closing laptop mid-download) eventually
|
||||
// go away.
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||
return tc, nil
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TODO: Fix me
|
||||
func TestServer(t *testing.T) {
|
||||
s := New("")
|
||||
s.SetHandler(engine.HandlerFunc(func(req engine.Request, res engine.Response) {
|
||||
}))
|
||||
rec := httptest.NewRecorder()
|
||||
req := new(http.Request)
|
||||
s.ServeHTTP(rec, req)
|
||||
}
|
||||
|
||||
func TestServerWrapHandler(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := NewRequest(new(http.Request), nil)
|
||||
rec := httptest.NewRecorder()
|
||||
res := NewResponse(rec, nil)
|
||||
c := e.NewContext(req, res)
|
||||
h := WrapHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("test"))
|
||||
}))
|
||||
if assert.NoError(t, h(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, "test", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerWrapMiddleware(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := NewRequest(new(http.Request), nil)
|
||||
rec := httptest.NewRecorder()
|
||||
res := NewResponse(rec, nil)
|
||||
c := e.NewContext(req, res)
|
||||
buf := new(bytes.Buffer)
|
||||
mw := WrapMiddleware(func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf.Write([]byte("mw"))
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
h := mw(func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "OK")
|
||||
})
|
||||
if assert.NoError(t, h(c)) {
|
||||
assert.Equal(t, "mw", buf.String())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, "OK", rec.Body.String())
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package standard
|
||||
|
||||
import "net/url"
|
||||
|
||||
type (
|
||||
// URL implements `engine.URL`.
|
||||
URL struct {
|
||||
*url.URL
|
||||
query url.Values
|
||||
}
|
||||
)
|
||||
|
||||
// Path implements `engine.URL#Path` function.
|
||||
func (u *URL) Path() string {
|
||||
return u.URL.EscapedPath()
|
||||
}
|
||||
|
||||
// SetPath implements `engine.URL#SetPath` function.
|
||||
func (u *URL) SetPath(path string) {
|
||||
u.URL.Path = path
|
||||
}
|
||||
|
||||
// QueryParam implements `engine.URL#QueryParam` function.
|
||||
func (u *URL) QueryParam(name string) string {
|
||||
if u.query == nil {
|
||||
u.query = u.Query()
|
||||
}
|
||||
return u.query.Get(name)
|
||||
}
|
||||
|
||||
// QueryParams implements `engine.URL#QueryParams` function.
|
||||
func (u *URL) QueryParams() map[string][]string {
|
||||
if u.query == nil {
|
||||
u.query = u.Query()
|
||||
}
|
||||
return map[string][]string(u.query)
|
||||
}
|
||||
|
||||
// QueryString implements `engine.URL#QueryString` function.
|
||||
func (u *URL) QueryString() string {
|
||||
return u.URL.RawQuery
|
||||
}
|
||||
|
||||
func (u *URL) reset(url *url.URL) {
|
||||
u.URL = url
|
||||
u.query = nil
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package standard
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/engine/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestURL(t *testing.T) {
|
||||
u, _ := url.Parse("https://github.com/labstack/echo?param1=value1¶m1=value2¶m2=value3")
|
||||
mUrl := &URL{u, nil}
|
||||
test.URLTest(t, mUrl)
|
||||
|
||||
mUrl.reset(&url.URL{})
|
||||
assert.Equal(t, "", mUrl.Host)
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func HeaderTest(t *testing.T, header engine.Header) {
|
||||
h := "X-My-Header"
|
||||
v := "value"
|
||||
nv := "new value"
|
||||
h1 := "X-Another-Header"
|
||||
|
||||
header.Add(h, v)
|
||||
assert.Equal(t, v, header.Get(h))
|
||||
|
||||
header.Set(h, nv)
|
||||
assert.Equal(t, nv, header.Get(h))
|
||||
|
||||
assert.True(t, header.Contains(h))
|
||||
|
||||
header.Del(h)
|
||||
assert.False(t, header.Contains(h))
|
||||
|
||||
header.Add(h, v)
|
||||
header.Add(h1, v)
|
||||
|
||||
for _, expected := range []string{h, h1} {
|
||||
found := false
|
||||
for _, actual := range header.Keys() {
|
||||
if actual == expected {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Header %s not found", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func URLTest(t *testing.T, url engine.URL) {
|
||||
path := "/echo/test"
|
||||
url.SetPath(path)
|
||||
assert.Equal(t, path, url.Path())
|
||||
assert.Equal(t, map[string][]string{"param1": []string{"value1", "value2"}, "param2": []string{"value3"}}, url.QueryParams())
|
||||
assert.Equal(t, "value1", url.QueryParam("param1"))
|
||||
assert.Equal(t, "param1=value1¶m1=value2¶m2=value3", url.QueryString())
|
||||
}
|
||||
|
||||
func CookieTest(t *testing.T, cookie engine.Cookie) {
|
||||
assert.Equal(t, "github.com", cookie.Domain())
|
||||
assert.Equal(t, time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC), cookie.Expires())
|
||||
assert.True(t, cookie.HTTPOnly())
|
||||
assert.True(t, cookie.Secure())
|
||||
assert.Equal(t, "session", cookie.Name())
|
||||
assert.Equal(t, "/", cookie.Path())
|
||||
assert.Equal(t, "securetoken", cookie.Value())
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const MultipartRequest = `POST /labstack/echo HTTP/1.1
|
||||
Host: github.com
|
||||
Connection: close
|
||||
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; de-de) AppleWebKit/523.10.3 (KHTML, like Gecko) Version/3.0.4 Safari/523.10
|
||||
Content-Type: multipart/form-data; boundary=Asrf456BGe4h
|
||||
Content-Length: 261
|
||||
Accept-Encoding: gzip
|
||||
Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7
|
||||
Cache-Control: no-cache
|
||||
Accept-Language: de,en;q=0.7,en-us;q=0.3
|
||||
Referer: https://github.com/
|
||||
Cookie: session=securetoken; user=123
|
||||
X-Real-IP: 192.168.1.1
|
||||
|
||||
--Asrf456BGe4h
|
||||
Content-Disposition: form-data; name="foo"
|
||||
|
||||
bar
|
||||
--Asrf456BGe4h
|
||||
Content-Disposition: form-data; name="baz"
|
||||
|
||||
bat
|
||||
--Asrf456BGe4h
|
||||
Content-Disposition: form-data; name="note"; filename="note.txt"
|
||||
Content-Type: text/plain
|
||||
|
||||
Hello world!
|
||||
--Asrf456BGe4h--
|
||||
`
|
||||
|
||||
func RequestTest(t *testing.T, request engine.Request) {
|
||||
assert.Equal(t, "github.com", request.Host())
|
||||
request.SetHost("labstack.com")
|
||||
assert.Equal(t, "labstack.com", request.Host())
|
||||
request.SetURI("/labstack/echo?token=54321")
|
||||
assert.Equal(t, "/labstack/echo?token=54321", request.URI())
|
||||
assert.Equal(t, "/labstack/echo", request.URL().Path())
|
||||
assert.Equal(t, "https://github.com/", request.Referer())
|
||||
assert.Equal(t, "192.168.1.1", request.Header().Get("X-Real-IP"))
|
||||
assert.Equal(t, "http", request.Scheme())
|
||||
assert.Equal(t, "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; de-de) AppleWebKit/523.10.3 (KHTML, like Gecko) Version/3.0.4 Safari/523.10", request.UserAgent())
|
||||
assert.Equal(t, "127.0.0.1", request.RemoteAddress())
|
||||
assert.Equal(t, "192.168.1.1", request.RealIP())
|
||||
assert.Equal(t, "POST", request.Method())
|
||||
assert.Equal(t, int64(261), request.ContentLength())
|
||||
assert.Equal(t, "bar", request.FormValue("foo"))
|
||||
|
||||
if fHeader, err := request.FormFile("note"); assert.NoError(t, err) {
|
||||
if file, err := fHeader.Open(); assert.NoError(t, err) {
|
||||
text, _ := ioutil.ReadAll(file)
|
||||
assert.Equal(t, "Hello world!", string(text))
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, map[string][]string{"baz": []string{"bat"}, "foo": []string{"bar"}}, request.FormParams())
|
||||
|
||||
if form, err := request.MultipartForm(); assert.NoError(t, err) {
|
||||
_, ok := form.File["note"]
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
request.SetMethod("PUT")
|
||||
assert.Equal(t, "PUT", request.Method())
|
||||
|
||||
request.SetBody(strings.NewReader("Hello"))
|
||||
if body, err := ioutil.ReadAll(request.Body()); assert.NoError(t, err) {
|
||||
assert.Equal(t, "Hello", string(body))
|
||||
}
|
||||
|
||||
if cookie, err := request.Cookie("session"); assert.NoError(t, err) {
|
||||
assert.Equal(t, "session", cookie.Name())
|
||||
assert.Equal(t, "securetoken", cookie.Value())
|
||||
}
|
||||
|
||||
_, err := request.Cookie("foo")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Cookies
|
||||
cs := request.Cookies()
|
||||
if assert.Len(t, cs, 2) {
|
||||
assert.Equal(t, "session", cs[0].Name())
|
||||
assert.Equal(t, "securetoken", cs[0].Value())
|
||||
assert.Equal(t, "user", cs[1].Name())
|
||||
assert.Equal(t, "123", cs[1].Value())
|
||||
}
|
||||
}
|
@ -60,7 +60,7 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
auth := c.Request().Header().Get(echo.HeaderAuthorization)
|
||||
auth := c.Request().Header.Get(echo.HeaderAuthorization)
|
||||
l := len(basic)
|
||||
|
||||
if len(auth) > l+1 && auth[:l] == basic {
|
||||
|
@ -3,17 +3,17 @@ package middleware
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBasicAuth(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/", nil)
|
||||
res := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "/", nil)
|
||||
res := httptest.NewRecorder()
|
||||
c := e.NewContext(req, res)
|
||||
f := func(u, p string) bool {
|
||||
if u == "joe" && p == "secret" {
|
||||
@ -27,24 +27,24 @@ func TestBasicAuth(t *testing.T) {
|
||||
|
||||
// Valid credentials
|
||||
auth := basic + " " + base64.StdEncoding.EncodeToString([]byte("joe:secret"))
|
||||
req.Header().Set(echo.HeaderAuthorization, auth)
|
||||
req.Header.Set(echo.HeaderAuthorization, auth)
|
||||
assert.NoError(t, h(c))
|
||||
|
||||
// Incorrect password
|
||||
auth = basic + " " + base64.StdEncoding.EncodeToString([]byte("joe:password"))
|
||||
req.Header().Set(echo.HeaderAuthorization, auth)
|
||||
req.Header.Set(echo.HeaderAuthorization, auth)
|
||||
he := h(c).(*echo.HTTPError)
|
||||
assert.Equal(t, http.StatusUnauthorized, he.Code)
|
||||
assert.Equal(t, basic+" realm=Restricted", res.Header().Get(echo.HeaderWWWAuthenticate))
|
||||
|
||||
// Empty Authorization header
|
||||
req.Header().Set(echo.HeaderAuthorization, "")
|
||||
req.Header.Set(echo.HeaderAuthorization, "")
|
||||
he = h(c).(*echo.HTTPError)
|
||||
assert.Equal(t, http.StatusUnauthorized, he.Code)
|
||||
|
||||
// Invalid Authorization header
|
||||
auth = base64.StdEncoding.EncodeToString([]byte("invalid"))
|
||||
req.Header().Set(echo.HeaderAuthorization, auth)
|
||||
req.Header.Set(echo.HeaderAuthorization, auth)
|
||||
he = h(c).(*echo.HTTPError)
|
||||
assert.Equal(t, http.StatusUnauthorized, he.Code)
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ type (
|
||||
|
||||
limitedReader struct {
|
||||
BodyLimitConfig
|
||||
reader io.Reader
|
||||
reader io.ReadCloser
|
||||
read int64
|
||||
context echo.Context
|
||||
}
|
||||
@ -74,15 +74,15 @@ func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc {
|
||||
req := c.Request()
|
||||
|
||||
// Based on content length
|
||||
if req.ContentLength() > config.limit {
|
||||
if req.ContentLength > config.limit {
|
||||
return echo.ErrStatusRequestEntityTooLarge
|
||||
}
|
||||
|
||||
// Based on content read
|
||||
r := pool.Get().(*limitedReader)
|
||||
r.Reset(req.Body(), c)
|
||||
r.Reset(req.Body, c)
|
||||
defer pool.Put(r)
|
||||
req.SetBody(r)
|
||||
req.Body = r
|
||||
|
||||
return next(c)
|
||||
}
|
||||
@ -98,7 +98,11 @@ func (r *limitedReader) Read(b []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *limitedReader) Reset(reader io.Reader, context echo.Context) {
|
||||
func (r *limitedReader) Close() error {
|
||||
return r.reader.Close()
|
||||
}
|
||||
|
||||
func (r *limitedReader) Reset(reader io.ReadCloser, context echo.Context) {
|
||||
r.reader = reader
|
||||
r.context = context
|
||||
}
|
||||
|
@ -4,21 +4,22 @@ import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBodyLimit(t *testing.T) {
|
||||
return
|
||||
e := echo.New()
|
||||
hw := []byte("Hello, World!")
|
||||
req := test.NewRequest(echo.POST, "/", bytes.NewReader(hw))
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.POST, "/", bytes.NewReader(hw))
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
h := func(c echo.Context) error {
|
||||
body, err := ioutil.ReadAll(c.Request().Body())
|
||||
body, err := ioutil.ReadAll(c.Request().Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -27,8 +28,8 @@ func TestBodyLimit(t *testing.T) {
|
||||
|
||||
// Based on content length (within limit)
|
||||
if assert.NoError(t, BodyLimit("2M")(h)(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, hw, rec.Body.Bytes())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, hw, rec.Body.Bytes)
|
||||
}
|
||||
|
||||
// Based on content read (overlimit)
|
||||
@ -36,17 +37,17 @@ func TestBodyLimit(t *testing.T) {
|
||||
assert.Equal(t, http.StatusRequestEntityTooLarge, he.Code)
|
||||
|
||||
// Based on content read (within limit)
|
||||
req = test.NewRequest(echo.POST, "/", bytes.NewReader(hw))
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.POST, "/", bytes.NewReader(hw))
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
if assert.NoError(t, BodyLimit("2M")(h)(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, "Hello, World!", rec.Body.String())
|
||||
}
|
||||
|
||||
// Based on content read (overlimit)
|
||||
req = test.NewRequest(echo.POST, "/", bytes.NewReader(hw))
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.POST, "/", bytes.NewReader(hw))
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
he = BodyLimit("2B")(h)(c).(*echo.HTTPError)
|
||||
assert.Equal(t, http.StatusRequestEntityTooLarge, he.Code)
|
||||
|
@ -1,15 +1,16 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -24,8 +25,8 @@ type (
|
||||
}
|
||||
|
||||
gzipResponseWriter struct {
|
||||
engine.Response
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
)
|
||||
|
||||
@ -65,36 +66,51 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
|
||||
|
||||
res := c.Response()
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding)
|
||||
if strings.Contains(c.Request().Header().Get(echo.HeaderAcceptEncoding), scheme) {
|
||||
if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), scheme) {
|
||||
rw := res.Writer()
|
||||
gw := pool.Get().(*gzip.Writer)
|
||||
gw.Reset(rw)
|
||||
w := pool.Get().(*gzip.Writer)
|
||||
w.Reset(c.Response().Writer())
|
||||
// rw := res.Writer()
|
||||
// gw := pool.Get().(*gzip.Writer)
|
||||
// gw.Reset(rw)
|
||||
defer func() {
|
||||
if res.Size() == 0 {
|
||||
if res.Size == 0 {
|
||||
// We have to reset response to it's pristine state when
|
||||
// nothing is written to body or error is returned.
|
||||
// See issue #424, #407.
|
||||
res.SetWriter(rw)
|
||||
res.Header().Del(echo.HeaderContentEncoding)
|
||||
gw.Reset(ioutil.Discard)
|
||||
w.Reset(ioutil.Discard)
|
||||
}
|
||||
gw.Close()
|
||||
pool.Put(gw)
|
||||
w.Close()
|
||||
pool.Put(w)
|
||||
}()
|
||||
g := gzipResponseWriter{Response: res, Writer: gw}
|
||||
grw := gzipResponseWriter{Writer: w, ResponseWriter: res.Writer()}
|
||||
res.Header().Set(echo.HeaderContentEncoding, scheme)
|
||||
res.SetWriter(g)
|
||||
res.SetWriter(grw)
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
if g.Header().Get(echo.HeaderContentType) == "" {
|
||||
g.Header().Set(echo.HeaderContentType, http.DetectContentType(b))
|
||||
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
if w.Header().Get(echo.HeaderContentType) == "" {
|
||||
w.Header().Set(echo.HeaderContentType, http.DetectContentType(b))
|
||||
}
|
||||
return g.Writer.Write(b)
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
func (w gzipResponseWriter) Flush() error {
|
||||
return w.Writer.(*gzip.Writer).Flush()
|
||||
}
|
||||
|
||||
func (w gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return w.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) CloseNotify() <-chan bool {
|
||||
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func gzipPool(config GzipConfig) sync.Pool {
|
||||
|
@ -4,17 +4,17 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGzip(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
|
||||
// Skip if no Accept-Encoding header
|
||||
@ -25,9 +25,9 @@ func TestGzip(t *testing.T) {
|
||||
h(c)
|
||||
assert.Equal(t, "test", rec.Body.String())
|
||||
|
||||
req = test.NewRequest(echo.GET, "/", nil)
|
||||
req.Header().Set(echo.HeaderAcceptEncoding, "gzip")
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.GET, "/", nil)
|
||||
req.Header.Set(echo.HeaderAcceptEncoding, "gzip")
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
|
||||
// Gzip
|
||||
@ -45,8 +45,8 @@ func TestGzip(t *testing.T) {
|
||||
|
||||
func TestGzipNoContent(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
h := Gzip()(func(c echo.Context) error {
|
||||
return c.NoContent(http.StatusOK)
|
||||
@ -64,9 +64,9 @@ func TestGzipErrorReturned(t *testing.T) {
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "error")
|
||||
})
|
||||
req := test.NewRequest(echo.GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
e.ServeHTTP(req, rec)
|
||||
req, _ := http.NewRequest(echo.GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
e.ServeHTTP(rec, req)
|
||||
assert.Empty(t, rec.Header().Get(echo.HeaderContentEncoding))
|
||||
assert.Equal(t, "error", rec.Body.String())
|
||||
}
|
||||
|
@ -88,8 +88,8 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
origin := req.Header().Get(echo.HeaderOrigin)
|
||||
originSet := req.Header().Contains(echo.HeaderOrigin) // Issue #517
|
||||
origin := req.Header.Get(echo.HeaderOrigin)
|
||||
_, originSet := req.Header[echo.HeaderOrigin]
|
||||
|
||||
// Check allowed origins
|
||||
allowedOrigin := ""
|
||||
@ -101,7 +101,7 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
// Simple request
|
||||
if req.Method() != echo.OPTIONS {
|
||||
if req.Method != echo.OPTIONS {
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
|
||||
if !originSet || allowedOrigin == "" {
|
||||
return next(c)
|
||||
@ -131,7 +131,7 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
|
||||
if allowHeaders != "" {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowHeaders, allowHeaders)
|
||||
} else {
|
||||
h := req.Header().Get(echo.HeaderAccessControlRequestHeaders)
|
||||
h := req.Header.Get(echo.HeaderAccessControlRequestHeaders)
|
||||
if h != "" {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowHeaders, h)
|
||||
}
|
||||
|
@ -2,17 +2,17 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCORS(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
cors := CORSWithConfig(CORSConfig{
|
||||
AllowCredentials: true,
|
||||
@ -26,26 +26,26 @@ func TestCORS(t *testing.T) {
|
||||
assert.Equal(t, "", rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
|
||||
|
||||
// Empty origin header
|
||||
req = test.NewRequest(echo.GET, "/", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.GET, "/", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
req.Header().Set(echo.HeaderOrigin, "")
|
||||
req.Header.Set(echo.HeaderOrigin, "")
|
||||
h(c)
|
||||
assert.Equal(t, "*", rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
|
||||
|
||||
// Wildcard origin
|
||||
req = test.NewRequest(echo.GET, "/", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.GET, "/", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
req.Header().Set(echo.HeaderOrigin, "localhost")
|
||||
req.Header.Set(echo.HeaderOrigin, "localhost")
|
||||
h(c)
|
||||
assert.Equal(t, "*", rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
|
||||
|
||||
// Simple request
|
||||
req = test.NewRequest(echo.GET, "/", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.GET, "/", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
req.Header().Set(echo.HeaderOrigin, "localhost")
|
||||
req.Header.Set(echo.HeaderOrigin, "localhost")
|
||||
cors = CORSWithConfig(CORSConfig{
|
||||
AllowOrigins: []string{"localhost"},
|
||||
AllowCredentials: true,
|
||||
@ -58,11 +58,11 @@ func TestCORS(t *testing.T) {
|
||||
assert.Equal(t, "localhost", rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
|
||||
|
||||
// Preflight request
|
||||
req = test.NewRequest(echo.OPTIONS, "/", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.OPTIONS, "/", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
req.Header().Set(echo.HeaderOrigin, "localhost")
|
||||
req.Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
req.Header.Set(echo.HeaderOrigin, "localhost")
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
h(c)
|
||||
assert.Equal(t, "localhost", rec.Header().Get(echo.HeaderAccessControlAllowOrigin))
|
||||
assert.NotEmpty(t, rec.Header().Get(echo.HeaderAccessControlAllowMethods))
|
||||
|
@ -131,10 +131,10 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
|
||||
token = random.String(config.TokenLength)
|
||||
} else {
|
||||
// Reuse token
|
||||
token = k.Value()
|
||||
token = k.Value
|
||||
}
|
||||
|
||||
switch req.Method() {
|
||||
switch req.Method {
|
||||
case echo.GET, echo.HEAD, echo.OPTIONS, echo.TRACE:
|
||||
default:
|
||||
// Validate token only for requests which are not defined as 'safe' by RFC7231
|
||||
@ -148,18 +148,18 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
// Set CSRF cookie
|
||||
cookie := new(echo.Cookie)
|
||||
cookie.SetName(config.CookieName)
|
||||
cookie.SetValue(token)
|
||||
cookie := new(http.Cookie)
|
||||
cookie.Name = config.CookieName
|
||||
cookie.Value = token
|
||||
if config.CookiePath != "" {
|
||||
cookie.SetPath(config.CookiePath)
|
||||
cookie.Path = config.CookiePath
|
||||
}
|
||||
if config.CookieDomain != "" {
|
||||
cookie.SetDomain(config.CookieDomain)
|
||||
cookie.Domain = config.CookieDomain
|
||||
}
|
||||
cookie.SetExpires(time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second))
|
||||
cookie.SetSecure(config.CookieSecure)
|
||||
cookie.SetHTTPOnly(config.CookieHTTPOnly)
|
||||
cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second)
|
||||
cookie.Secure = config.CookieSecure
|
||||
cookie.HttpOnly = config.CookieHTTPOnly
|
||||
c.SetCookie(cookie)
|
||||
|
||||
// Store token in the context
|
||||
@ -177,7 +177,7 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
|
||||
// provided request header.
|
||||
func csrfTokenFromHeader(header string) csrfTokenExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
return c.Request().Header().Get(header), nil
|
||||
return c.Request().Header.Get(header), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,20 +2,20 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/labstack/gommon/random"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCSRF(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
csrf := CSRFWithConfig(CSRFConfig{
|
||||
TokenLength: 16,
|
||||
@ -29,24 +29,24 @@ func TestCSRF(t *testing.T) {
|
||||
assert.Contains(t, rec.Header().Get(echo.HeaderSetCookie), "_csrf")
|
||||
|
||||
// Without CSRF cookie
|
||||
req = test.NewRequest(echo.POST, "/", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.POST, "/", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
assert.Error(t, h(c))
|
||||
|
||||
// Empty/invalid CSRF token
|
||||
req = test.NewRequest(echo.POST, "/", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.POST, "/", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
req.Header().Set(echo.HeaderXCSRFToken, "")
|
||||
req.Header.Set(echo.HeaderXCSRFToken, "")
|
||||
assert.Error(t, h(c))
|
||||
|
||||
// Valid CSRF token
|
||||
token := random.String(16)
|
||||
req.Header().Set(echo.HeaderCookie, "_csrf="+token)
|
||||
req.Header().Set(echo.HeaderXCSRFToken, token)
|
||||
req.Header.Set(echo.HeaderCookie, "_csrf="+token)
|
||||
req.Header.Set(echo.HeaderXCSRFToken, token)
|
||||
if assert.NoError(t, h(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,8 +54,8 @@ func TestCSRFTokenFromForm(t *testing.T) {
|
||||
f := make(url.Values)
|
||||
f.Set("csrf", "token")
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.POST, "/", strings.NewReader(f.Encode()))
|
||||
req.Header().Add(echo.HeaderContentType, echo.MIMEApplicationForm)
|
||||
req, _ := http.NewRequest(echo.POST, "/", strings.NewReader(f.Encode()))
|
||||
req.Header.Add(echo.HeaderContentType, echo.MIMEApplicationForm)
|
||||
c := e.NewContext(req, nil)
|
||||
token, err := csrfTokenFromForm("csrf")(c)
|
||||
if assert.NoError(t, err) {
|
||||
@ -69,8 +69,8 @@ func TestCSRFTokenFromQuery(t *testing.T) {
|
||||
q := make(url.Values)
|
||||
q.Set("csrf", "token")
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/?"+q.Encode(), nil)
|
||||
req.Header().Add(echo.HeaderContentType, echo.MIMEApplicationForm)
|
||||
req, _ := http.NewRequest(echo.GET, "/?"+q.Encode(), nil)
|
||||
req.Header.Add(echo.HeaderContentType, echo.MIMEApplicationForm)
|
||||
c := e.NewContext(req, nil)
|
||||
token, err := csrfTokenFromQuery("csrf")(c)
|
||||
if assert.NoError(t, err) {
|
||||
|
@ -153,7 +153,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
|
||||
// jwtFromHeader returns a `jwtExtractor` that extracts token from request header.
|
||||
func jwtFromHeader(header string) jwtExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
auth := c.Request().Header().Get(header)
|
||||
auth := c.Request().Header.Get(header)
|
||||
l := len(bearer)
|
||||
if len(auth) > l+1 && auth[:l] == bearer {
|
||||
return auth[l+1:], nil
|
||||
@ -181,6 +181,6 @@ func jwtFromCookie(name string) jwtExtractor {
|
||||
if err != nil {
|
||||
return "", errors.New("empty jwt in cookie")
|
||||
}
|
||||
return cookie.Value(), nil
|
||||
return cookie.Value, nil
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -148,10 +148,10 @@ func TestJWT(t *testing.T) {
|
||||
tc.reqURL = "/"
|
||||
}
|
||||
|
||||
req := test.NewRequest(echo.GET, tc.reqURL, nil)
|
||||
res := test.NewResponseRecorder()
|
||||
req.Header().Set(echo.HeaderAuthorization, tc.hdrAuth)
|
||||
req.Header().Set(echo.HeaderCookie, tc.hdrCookie)
|
||||
req, _ := http.NewRequest(echo.GET, tc.reqURL, nil)
|
||||
res := httptest.NewRecorder()
|
||||
req.Header.Set(echo.HeaderAuthorization, tc.hdrAuth)
|
||||
req.Header.Set(echo.HeaderCookie, tc.hdrCookie)
|
||||
c := e.NewContext(req, res)
|
||||
|
||||
if tc.expPanic {
|
||||
|
@ -117,16 +117,16 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
|
||||
case "time_rfc3339":
|
||||
return w.Write([]byte(time.Now().Format(time.RFC3339)))
|
||||
case "remote_ip":
|
||||
ra := req.RealIP()
|
||||
ra := c.RealIP()
|
||||
return w.Write([]byte(ra))
|
||||
case "host":
|
||||
return w.Write([]byte(req.Host()))
|
||||
return w.Write([]byte(req.Host))
|
||||
case "uri":
|
||||
return w.Write([]byte(req.URI()))
|
||||
return w.Write([]byte(req.RequestURI))
|
||||
case "method":
|
||||
return w.Write([]byte(req.Method()))
|
||||
return w.Write([]byte(req.Method))
|
||||
case "path":
|
||||
p := req.URL().Path()
|
||||
p := req.URL.Path
|
||||
if p == "" {
|
||||
p = "/"
|
||||
}
|
||||
@ -136,7 +136,7 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
|
||||
case "user_agent":
|
||||
return w.Write([]byte(req.UserAgent()))
|
||||
case "status":
|
||||
n := res.Status()
|
||||
n := res.Status
|
||||
s := config.color.Green(n)
|
||||
switch {
|
||||
case n >= 500:
|
||||
@ -153,13 +153,13 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
|
||||
case "latency_human":
|
||||
return w.Write([]byte(stop.Sub(start).String()))
|
||||
case "bytes_in":
|
||||
b := req.Header().Get(echo.HeaderContentLength)
|
||||
b := req.Header.Get(echo.HeaderContentLength)
|
||||
if b == "" {
|
||||
b = "0"
|
||||
}
|
||||
return w.Write([]byte(b))
|
||||
case "bytes_out":
|
||||
return w.Write([]byte(strconv.FormatInt(res.Size(), 10)))
|
||||
return w.Write([]byte(strconv.FormatInt(res.Size, 10)))
|
||||
}
|
||||
return 0, nil
|
||||
})
|
||||
|
@ -4,18 +4,18 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLogger(t *testing.T) {
|
||||
// Note: Just for the test coverage, not a real test.
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
h := Logger()(func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "test")
|
||||
@ -25,7 +25,7 @@ func TestLogger(t *testing.T) {
|
||||
h(c)
|
||||
|
||||
// Status 3xx
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
h = Logger()(func(c echo.Context) error {
|
||||
return c.String(http.StatusTemporaryRedirect, "test")
|
||||
@ -33,7 +33,7 @@ func TestLogger(t *testing.T) {
|
||||
h(c)
|
||||
|
||||
// Status 4xx
|
||||
rec = test.NewResponseRecorder()
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
h = Logger()(func(c echo.Context) error {
|
||||
return c.String(http.StatusNotFound, "test")
|
||||
@ -41,8 +41,8 @@ func TestLogger(t *testing.T) {
|
||||
h(c)
|
||||
|
||||
// Status 5xx with empty path
|
||||
req = test.NewRequest(echo.GET, "", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.GET, "/", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
h = Logger()(func(c echo.Context) error {
|
||||
return errors.New("error")
|
||||
@ -52,25 +52,25 @@ func TestLogger(t *testing.T) {
|
||||
|
||||
func TestLoggerIPAddress(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
buf := new(bytes.Buffer)
|
||||
e.Logger().SetOutput(buf)
|
||||
e.Logger.SetOutput(buf)
|
||||
ip := "127.0.0.1"
|
||||
h := Logger()(func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "test")
|
||||
})
|
||||
|
||||
// With X-Real-IP
|
||||
req.Header().Add(echo.HeaderXRealIP, ip)
|
||||
req.Header.Add(echo.HeaderXRealIP, ip)
|
||||
h(c)
|
||||
assert.Contains(t, ip, buf.String())
|
||||
|
||||
// With X-Forwarded-For
|
||||
buf.Reset()
|
||||
req.Header().Del(echo.HeaderXRealIP)
|
||||
req.Header().Add(echo.HeaderXForwardedFor, ip)
|
||||
req.Header.Del(echo.HeaderXRealIP)
|
||||
req.Header.Add(echo.HeaderXForwardedFor, ip)
|
||||
h(c)
|
||||
assert.Contains(t, ip, buf.String())
|
||||
|
||||
|
@ -52,10 +52,10 @@ func MethodOverrideWithConfig(config MethodOverrideConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
if req.Method() == echo.POST {
|
||||
if req.Method == echo.POST {
|
||||
m := config.Getter(c)
|
||||
if m != "" {
|
||||
req.SetMethod(m)
|
||||
req.Method = m
|
||||
}
|
||||
}
|
||||
return next(c)
|
||||
@ -67,7 +67,7 @@ func MethodOverrideWithConfig(config MethodOverrideConfig) echo.MiddlewareFunc {
|
||||
// the request header.
|
||||
func MethodFromHeader(header string) MethodOverrideGetter {
|
||||
return func(c echo.Context) string {
|
||||
return c.Request().Header().Get(header)
|
||||
return c.Request().Header.Get(header)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,10 @@ package middleware
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -18,32 +18,32 @@ func TestMethodOverride(t *testing.T) {
|
||||
}
|
||||
|
||||
// Override with http header
|
||||
req := test.NewRequest(echo.POST, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req.Header().Set(echo.HeaderXHTTPMethodOverride, echo.DELETE)
|
||||
req, _ := http.NewRequest(echo.POST, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
req.Header.Set(echo.HeaderXHTTPMethodOverride, echo.DELETE)
|
||||
c := e.NewContext(req, rec)
|
||||
m(h)(c)
|
||||
assert.Equal(t, echo.DELETE, req.Method())
|
||||
assert.Equal(t, echo.DELETE, req.Method)
|
||||
|
||||
// Override with form parameter
|
||||
m = MethodOverrideWithConfig(MethodOverrideConfig{Getter: MethodFromForm("_method")})
|
||||
req = test.NewRequest(echo.POST, "/", bytes.NewReader([]byte("_method="+echo.DELETE)))
|
||||
rec = test.NewResponseRecorder()
|
||||
req.Header().Set(echo.HeaderContentType, echo.MIMEApplicationForm)
|
||||
req, _ = http.NewRequest(echo.POST, "/", bytes.NewReader([]byte("_method="+echo.DELETE)))
|
||||
rec = httptest.NewRecorder()
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
|
||||
c = e.NewContext(req, rec)
|
||||
m(h)(c)
|
||||
assert.Equal(t, echo.DELETE, req.Method())
|
||||
assert.Equal(t, echo.DELETE, req.Method)
|
||||
|
||||
// Override with query paramter
|
||||
m = MethodOverrideWithConfig(MethodOverrideConfig{Getter: MethodFromQuery("_method")})
|
||||
req = test.NewRequest(echo.POST, "/?_method="+echo.DELETE, nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.POST, "/?_method="+echo.DELETE, nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
m(h)(c)
|
||||
assert.Equal(t, echo.DELETE, req.Method())
|
||||
assert.Equal(t, echo.DELETE, req.Method)
|
||||
|
||||
// Ignore `GET`
|
||||
req = test.NewRequest(echo.GET, "/", nil)
|
||||
req.Header().Set(echo.HeaderXHTTPMethodOverride, echo.DELETE)
|
||||
assert.Equal(t, echo.GET, req.Method())
|
||||
req, _ = http.NewRequest(echo.GET, "/", nil)
|
||||
req.Header.Set(echo.HeaderXHTTPMethodOverride, echo.DELETE)
|
||||
assert.Equal(t, echo.GET, req.Method)
|
||||
}
|
||||
|
@ -3,24 +3,24 @@ package middleware
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRecover(t *testing.T) {
|
||||
e := echo.New()
|
||||
buf := new(bytes.Buffer)
|
||||
e.SetLogOutput(buf)
|
||||
req := test.NewRequest(echo.GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
e.Logger.SetOutput(buf)
|
||||
req, _ := http.NewRequest(echo.GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
h := Recover()(echo.HandlerFunc(func(c echo.Context) error {
|
||||
panic("test")
|
||||
}))
|
||||
h(c)
|
||||
assert.Equal(t, http.StatusInternalServerError, rec.Status())
|
||||
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||
assert.Contains(t, buf.String(), "PANIC RECOVER")
|
||||
}
|
||||
|
@ -52,9 +52,10 @@ func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
host := req.Host()
|
||||
uri := req.URI()
|
||||
if !req.IsTLS() {
|
||||
host := req.Host
|
||||
uri := req.RequestURI
|
||||
println(uri)
|
||||
if !c.IsTLS() {
|
||||
return c.Redirect(config.Code, "https://"+host+uri)
|
||||
}
|
||||
return next(c)
|
||||
@ -88,9 +89,9 @@ func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
host := req.Host()
|
||||
uri := req.URI()
|
||||
if !req.IsTLS() && host[:3] != "www" {
|
||||
host := req.Host
|
||||
uri := req.RequestURI
|
||||
if !c.IsTLS() && host[:3] != "www" {
|
||||
return c.Redirect(http.StatusMovedPermanently, "https://www."+host+uri)
|
||||
}
|
||||
return next(c)
|
||||
@ -124,10 +125,10 @@ func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
scheme := req.Scheme()
|
||||
host := req.Host()
|
||||
scheme := c.Scheme()
|
||||
host := req.Host
|
||||
if host[:3] != "www" {
|
||||
uri := req.URI()
|
||||
uri := req.RequestURI
|
||||
return c.Redirect(http.StatusMovedPermanently, scheme+"://www."+host+uri)
|
||||
}
|
||||
return next(c)
|
||||
@ -160,10 +161,10 @@ func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
scheme := req.Scheme()
|
||||
host := req.Host()
|
||||
scheme := c.Scheme()
|
||||
host := req.Host
|
||||
if host[:3] == "www" {
|
||||
uri := req.URI()
|
||||
uri := req.RequestURI
|
||||
return c.Redirect(http.StatusMovedPermanently, scheme+"://"+host[4:]+uri)
|
||||
}
|
||||
return next(c)
|
||||
|
@ -2,61 +2,61 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHTTPSRedirect(t *testing.T) {
|
||||
func TestRedirectHTTPSRedirect(t *testing.T) {
|
||||
e := echo.New()
|
||||
next := func(c echo.Context) (err error) {
|
||||
return c.NoContent(http.StatusOK)
|
||||
}
|
||||
req := test.NewRequest(echo.GET, "http://labstack.com", nil)
|
||||
res := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "http://labstack.com", nil)
|
||||
res := httptest.NewRecorder()
|
||||
c := e.NewContext(req, res)
|
||||
HTTPSRedirect()(next)(c)
|
||||
assert.Equal(t, http.StatusMovedPermanently, res.Status())
|
||||
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
||||
assert.Equal(t, "https://labstack.com", res.Header().Get(echo.HeaderLocation))
|
||||
}
|
||||
|
||||
func TestHTTPSWWWRedirect(t *testing.T) {
|
||||
func TestRedirectHTTPSWWWRedirect(t *testing.T) {
|
||||
e := echo.New()
|
||||
next := func(c echo.Context) (err error) {
|
||||
return c.NoContent(http.StatusOK)
|
||||
}
|
||||
req := test.NewRequest(echo.GET, "http://labstack.com", nil)
|
||||
res := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "http://labstack.com", nil)
|
||||
res := httptest.NewRecorder()
|
||||
c := e.NewContext(req, res)
|
||||
HTTPSWWWRedirect()(next)(c)
|
||||
assert.Equal(t, http.StatusMovedPermanently, res.Status())
|
||||
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
||||
assert.Equal(t, "https://www.labstack.com", res.Header().Get(echo.HeaderLocation))
|
||||
}
|
||||
|
||||
func TestWWWRedirect(t *testing.T) {
|
||||
func TestRedirectWWWRedirect(t *testing.T) {
|
||||
e := echo.New()
|
||||
next := func(c echo.Context) (err error) {
|
||||
return c.NoContent(http.StatusOK)
|
||||
}
|
||||
req := test.NewRequest(echo.GET, "http://labstack.com", nil)
|
||||
res := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "http://labstack.com", nil)
|
||||
res := httptest.NewRecorder()
|
||||
c := e.NewContext(req, res)
|
||||
WWWRedirect()(next)(c)
|
||||
assert.Equal(t, http.StatusMovedPermanently, res.Status())
|
||||
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
||||
assert.Equal(t, "http://www.labstack.com", res.Header().Get(echo.HeaderLocation))
|
||||
}
|
||||
|
||||
func TestNonWWWRedirect(t *testing.T) {
|
||||
func TestRedirectNonWWWRedirect(t *testing.T) {
|
||||
e := echo.New()
|
||||
next := func(c echo.Context) (err error) {
|
||||
return c.NoContent(http.StatusOK)
|
||||
}
|
||||
req := test.NewRequest(echo.GET, "http://www.labstack.com", nil)
|
||||
res := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "http://www.labstack.com", nil)
|
||||
res := httptest.NewRecorder()
|
||||
c := e.NewContext(req, res)
|
||||
NonWWWRedirect()(next)(c)
|
||||
assert.Equal(t, http.StatusMovedPermanently, res.Status())
|
||||
assert.Equal(t, http.StatusMovedPermanently, res.Code)
|
||||
assert.Equal(t, "http://labstack.com", res.Header().Get(echo.HeaderLocation))
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
|
||||
if config.XFrameOptions != "" {
|
||||
res.Header().Set(echo.HeaderXFrameOptions, config.XFrameOptions)
|
||||
}
|
||||
if (req.IsTLS() || (req.Header().Get(echo.HeaderXForwardedProto) == "https")) && config.HSTSMaxAge != 0 {
|
||||
if (c.IsTLS() || (req.Header.Get(echo.HeaderXForwardedProto) == "https")) && config.HSTSMaxAge != 0 {
|
||||
subdomains := ""
|
||||
if !config.HSTSExcludeSubdomains {
|
||||
subdomains = "; includeSubdomains"
|
||||
|
@ -2,17 +2,17 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSecure(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
h := func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "test")
|
||||
@ -27,8 +27,8 @@ func TestSecure(t *testing.T) {
|
||||
assert.Equal(t, "", rec.Header().Get(echo.HeaderContentSecurityPolicy))
|
||||
|
||||
// Custom
|
||||
req.Header().Set(echo.HeaderXForwardedProto, "https")
|
||||
rec = test.NewResponseRecorder()
|
||||
req.Header.Set(echo.HeaderXForwardedProto, "https")
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
SecureWithConfig(SecureConfig{
|
||||
XSSProtection: "",
|
||||
|
@ -46,9 +46,9 @@ func AddTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
url := req.URL()
|
||||
path := url.Path()
|
||||
qs := url.QueryString()
|
||||
url := req.URL
|
||||
path := url.Path
|
||||
qs := c.QueryString()
|
||||
if path != "/" && path[len(path)-1] != '/' {
|
||||
path += "/"
|
||||
uri := path
|
||||
@ -62,8 +62,8 @@ func AddTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc
|
||||
}
|
||||
|
||||
// Forward
|
||||
req.SetURI(uri)
|
||||
url.SetPath(path)
|
||||
req.RequestURI = uri
|
||||
url.Path = path
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
@ -93,9 +93,9 @@ func RemoveTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFu
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
url := req.URL()
|
||||
path := url.Path()
|
||||
qs := url.QueryString()
|
||||
url := req.URL
|
||||
path := url.Path
|
||||
qs := c.QueryString()
|
||||
l := len(path) - 1
|
||||
if l >= 0 && path != "/" && path[l] == '/' {
|
||||
path = path[:l]
|
||||
@ -110,8 +110,8 @@ func RemoveTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFu
|
||||
}
|
||||
|
||||
// Forward
|
||||
req.SetURI(uri)
|
||||
url.SetPath(path)
|
||||
req.RequestURI = uri
|
||||
url.Path = path
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
|
@ -2,28 +2,28 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAddTrailingSlash(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/add-slash", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "/add-slash", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
h := AddTrailingSlash()(func(c echo.Context) error {
|
||||
return nil
|
||||
})
|
||||
h(c)
|
||||
assert.Equal(t, "/add-slash/", req.URL().Path())
|
||||
assert.Equal(t, "/add-slash/", req.URI())
|
||||
assert.Equal(t, "/add-slash/", req.URL.Path)
|
||||
assert.Equal(t, "/add-slash/", req.RequestURI)
|
||||
|
||||
// With config
|
||||
req = test.NewRequest(echo.GET, "/add-slash?key=value", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.GET, "/add-slash?key=value", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
h = AddTrailingSlashWithConfig(TrailingSlashConfig{
|
||||
RedirectCode: http.StatusMovedPermanently,
|
||||
@ -31,25 +31,25 @@ func TestAddTrailingSlash(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
h(c)
|
||||
assert.Equal(t, http.StatusMovedPermanently, rec.Status())
|
||||
assert.Equal(t, http.StatusMovedPermanently, rec.Code)
|
||||
assert.Equal(t, "/add-slash/?key=value", rec.Header().Get(echo.HeaderLocation))
|
||||
}
|
||||
|
||||
func TestRemoveTrailingSlash(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/remove-slash/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "/remove-slash/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
h := RemoveTrailingSlash()(func(c echo.Context) error {
|
||||
return nil
|
||||
})
|
||||
h(c)
|
||||
assert.Equal(t, "/remove-slash", req.URL().Path())
|
||||
assert.Equal(t, "/remove-slash", req.URI())
|
||||
assert.Equal(t, "/remove-slash", req.URL.Path)
|
||||
assert.Equal(t, "/remove-slash", req.RequestURI)
|
||||
|
||||
// With config
|
||||
req = test.NewRequest(echo.GET, "/remove-slash/?key=value", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.GET, "/remove-slash/?key=value", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
h = RemoveTrailingSlashWithConfig(TrailingSlashConfig{
|
||||
RedirectCode: http.StatusMovedPermanently,
|
||||
@ -57,16 +57,16 @@ func TestRemoveTrailingSlash(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
h(c)
|
||||
assert.Equal(t, http.StatusMovedPermanently, rec.Status())
|
||||
assert.Equal(t, http.StatusMovedPermanently, rec.Code)
|
||||
assert.Equal(t, "/remove-slash?key=value", rec.Header().Get(echo.HeaderLocation))
|
||||
|
||||
// With bare URL
|
||||
req = test.NewRequest(echo.GET, "http://localhost", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.GET, "http://localhost", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
h = RemoveTrailingSlash()(func(c echo.Context) error {
|
||||
return nil
|
||||
})
|
||||
h(c)
|
||||
assert.Equal(t, "", req.URL().Path())
|
||||
assert.Equal(t, "", req.URL.Path)
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
fs := http.Dir(config.Root)
|
||||
p := c.Request().URL().Path()
|
||||
p := c.Request().URL.Path
|
||||
if strings.Contains(c.Path(), "*") { // If serving from a group, e.g. `/static*`.
|
||||
p = c.P(0)
|
||||
}
|
||||
|
@ -2,17 +2,17 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStatic(t *testing.T) {
|
||||
e := echo.New()
|
||||
req := test.NewRequest(echo.GET, "/", nil)
|
||||
rec := test.NewResponseRecorder()
|
||||
req, _ := http.NewRequest(echo.GET, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
h := Static("../_fixture")(func(c echo.Context) error {
|
||||
return echo.ErrNotFound
|
||||
@ -24,8 +24,8 @@ func TestStatic(t *testing.T) {
|
||||
}
|
||||
|
||||
// HTML5 mode
|
||||
req = test.NewRequest(echo.GET, "/client", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.GET, "/client", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
static := StaticWithConfig(StaticConfig{
|
||||
Root: "../_fixture",
|
||||
@ -35,12 +35,12 @@ func TestStatic(t *testing.T) {
|
||||
return echo.ErrNotFound
|
||||
})
|
||||
if assert.NoError(t, h(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Status())
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
}
|
||||
|
||||
// Browse
|
||||
req = test.NewRequest(echo.GET, "/", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.GET, "/", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
static = StaticWithConfig(StaticConfig{
|
||||
Root: "../_fixture/images",
|
||||
@ -54,8 +54,8 @@ func TestStatic(t *testing.T) {
|
||||
}
|
||||
|
||||
// Not found
|
||||
req = test.NewRequest(echo.GET, "/not-found", nil)
|
||||
rec = test.NewResponseRecorder()
|
||||
req, _ = http.NewRequest(echo.GET, "/not-found", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
c = e.NewContext(req, rec)
|
||||
static = StaticWithConfig(StaticConfig{
|
||||
Root: "../_fixture/images",
|
||||
|
99
response.go
Normal file
99
response.go
Normal file
@ -0,0 +1,99 @@
|
||||
package echo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Response wraps an http.ResponseWriter and implements its interface to be used
|
||||
// by an HTTP handler to construct an HTTP response.
|
||||
// See: https://golang.org/pkg/net/http/#ResponseWriter
|
||||
Response struct {
|
||||
writer http.ResponseWriter
|
||||
Status int
|
||||
Size int64
|
||||
Committed bool
|
||||
echo *Echo
|
||||
}
|
||||
)
|
||||
|
||||
// NewResponse creates a new instance of Response.
|
||||
func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) {
|
||||
return &Response{writer: w, echo: e}
|
||||
}
|
||||
|
||||
// SetWriter sets the http.ResponseWriter instance for this Response.
|
||||
func (r *Response) SetWriter(w http.ResponseWriter) {
|
||||
r.writer = w
|
||||
}
|
||||
|
||||
// Writer returns the http.ResponseWriter instance for this Response.
|
||||
func (r *Response) Writer() http.ResponseWriter {
|
||||
return r.writer
|
||||
}
|
||||
|
||||
// Header returns the header map for the writer that will be sent by
|
||||
// WriteHeader. Changing the header after a call to WriteHeader (or Write) has
|
||||
// no effect unless the modified headers were declared as trailers by setting
|
||||
// the "Trailer" header before the call to WriteHeader (see example)
|
||||
// To suppress implicit response headers, set their value to nil.
|
||||
// Example: https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
|
||||
func (r *Response) Header() http.Header {
|
||||
return r.writer.Header()
|
||||
}
|
||||
|
||||
// WriteHeader sends an HTTP response header with status code. If WriteHeader is
|
||||
// not called explicitly, the first call to Write will trigger an implicit
|
||||
// WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly
|
||||
// used to send error codes.
|
||||
func (r *Response) WriteHeader(code int) {
|
||||
if r.Committed {
|
||||
r.echo.Logger.Warn("response already committed")
|
||||
return
|
||||
}
|
||||
r.Status = code
|
||||
r.writer.WriteHeader(code)
|
||||
r.Committed = true
|
||||
}
|
||||
|
||||
// Write writes the data to the connection as part of an HTTP reply.
|
||||
func (r *Response) Write(b []byte) (n int, err error) {
|
||||
if !r.Committed {
|
||||
r.WriteHeader(http.StatusOK)
|
||||
}
|
||||
n, err = r.writer.Write(b)
|
||||
r.Size += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
// Flush implements the http.Flusher interface to allow an HTTP handler to flush
|
||||
// buffered data to the client.
|
||||
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
|
||||
func (r *Response) Flush() {
|
||||
r.writer.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
// Hijack implements the http.Hijacker interface to allow an HTTP handler to
|
||||
// take over the connection.
|
||||
// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
|
||||
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return r.writer.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// CloseNotify implements the http.CloseNotifier interface to allow detecting
|
||||
// when the underlying connection has gone away.
|
||||
// This mechanism can be used to cancel long operations on the server if the
|
||||
// client has disconnected before the response is ready.
|
||||
// See [http.CloseNotifier](https://golang.org/pkg/net/http/#CloseNotifier)
|
||||
func (r *Response) CloseNotify() <-chan bool {
|
||||
return r.writer.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (r *Response) reset(w http.ResponseWriter) {
|
||||
r.writer = w
|
||||
r.Size = 0
|
||||
r.Status = http.StatusOK
|
||||
r.Committed = false
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// Cookie implements `engine.Cookie`.
|
||||
Cookie struct {
|
||||
*http.Cookie
|
||||
}
|
||||
)
|
||||
|
||||
// Name implements `engine.Cookie#Name` function.
|
||||
func (c *Cookie) Name() string {
|
||||
return c.Cookie.Name
|
||||
}
|
||||
|
||||
// Value implements `engine.Cookie#Value` function.
|
||||
func (c *Cookie) Value() string {
|
||||
return c.Cookie.Value
|
||||
}
|
||||
|
||||
// Path implements `engine.Cookie#Path` function.
|
||||
func (c *Cookie) Path() string {
|
||||
return c.Cookie.Path
|
||||
}
|
||||
|
||||
// Domain implements `engine.Cookie#Domain` function.
|
||||
func (c *Cookie) Domain() string {
|
||||
return c.Cookie.Domain
|
||||
}
|
||||
|
||||
// Expires implements `engine.Cookie#Expires` function.
|
||||
func (c *Cookie) Expires() time.Time {
|
||||
return c.Cookie.Expires
|
||||
}
|
||||
|
||||
// Secure implements `engine.Cookie#Secure` function.
|
||||
func (c *Cookie) Secure() bool {
|
||||
return c.Cookie.Secure
|
||||
}
|
||||
|
||||
// HTTPOnly implements `engine.Cookie#HTTPOnly` function.
|
||||
func (c *Cookie) HTTPOnly() bool {
|
||||
return c.Cookie.HttpOnly
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package test
|
||||
|
||||
import "net/http"
|
||||
|
||||
type (
|
||||
Header struct {
|
||||
header http.Header
|
||||
}
|
||||
)
|
||||
|
||||
func (h *Header) Add(key, val string) {
|
||||
h.header.Add(key, val)
|
||||
}
|
||||
|
||||
func (h *Header) Del(key string) {
|
||||
h.header.Del(key)
|
||||
}
|
||||
|
||||
func (h *Header) Get(key string) string {
|
||||
return h.header.Get(key)
|
||||
}
|
||||
|
||||
func (h *Header) Set(key, val string) {
|
||||
h.header.Set(key, val)
|
||||
}
|
||||
|
||||
func (h *Header) Keys() (keys []string) {
|
||||
keys = make([]string, len(h.header))
|
||||
i := 0
|
||||
for k := range h.header {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Header) Contains(key string) bool {
|
||||
_, ok := h.header[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (h *Header) reset(hdr http.Header) {
|
||||
h.header = hdr
|
||||
}
|
176
test/request.go
176
test/request.go
@ -1,176 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/engine"
|
||||
)
|
||||
|
||||
type (
|
||||
Request struct {
|
||||
request *http.Request
|
||||
url engine.URL
|
||||
header engine.Header
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMemory = 32 << 20 // 32 MB
|
||||
)
|
||||
|
||||
func NewRequest(method, url string, body io.Reader) engine.Request {
|
||||
r, _ := http.NewRequest(method, url, body)
|
||||
return &Request{
|
||||
request: r,
|
||||
url: &URL{url: r.URL},
|
||||
header: &Header{r.Header},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) IsTLS() bool {
|
||||
return r.request.TLS != nil
|
||||
}
|
||||
|
||||
func (r *Request) Scheme() string {
|
||||
if r.IsTLS() {
|
||||
return "https"
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (r *Request) Host() string {
|
||||
return r.request.Host
|
||||
}
|
||||
|
||||
func (r *Request) SetHost(host string) {
|
||||
r.request.Host = host
|
||||
}
|
||||
|
||||
func (r *Request) URL() engine.URL {
|
||||
return r.url
|
||||
}
|
||||
|
||||
func (r *Request) Header() engine.Header {
|
||||
return r.header
|
||||
}
|
||||
|
||||
func (r *Request) Referer() string {
|
||||
return r.request.Referer()
|
||||
}
|
||||
|
||||
// func Proto() string {
|
||||
// return r.request.Proto()
|
||||
// }
|
||||
//
|
||||
// func ProtoMajor() int {
|
||||
// return r.request.ProtoMajor()
|
||||
// }
|
||||
//
|
||||
// func ProtoMinor() int {
|
||||
// return r.request.ProtoMinor()
|
||||
// }
|
||||
|
||||
func (r *Request) ContentLength() int64 {
|
||||
return r.request.ContentLength
|
||||
}
|
||||
|
||||
func (r *Request) UserAgent() string {
|
||||
return r.request.UserAgent()
|
||||
}
|
||||
|
||||
func (r *Request) RemoteAddress() string {
|
||||
return r.request.RemoteAddr
|
||||
}
|
||||
|
||||
func (r *Request) RealIP() string {
|
||||
ra := r.RemoteAddress()
|
||||
if ip := r.Header().Get("X-Forwarded-For"); ip != "" {
|
||||
ra = ip
|
||||
} else if ip := r.Header().Get("X-Real-IP"); ip != "" {
|
||||
ra = ip
|
||||
} else {
|
||||
ra, _, _ = net.SplitHostPort(ra)
|
||||
}
|
||||
return ra
|
||||
}
|
||||
|
||||
func (r *Request) Method() string {
|
||||
return r.request.Method
|
||||
}
|
||||
|
||||
func (r *Request) SetMethod(method string) {
|
||||
r.request.Method = method
|
||||
}
|
||||
|
||||
func (r *Request) URI() string {
|
||||
return r.request.RequestURI
|
||||
}
|
||||
|
||||
func (r *Request) SetURI(uri string) {
|
||||
r.request.RequestURI = uri
|
||||
}
|
||||
|
||||
func (r *Request) Body() io.Reader {
|
||||
return r.request.Body
|
||||
}
|
||||
|
||||
func (r *Request) SetBody(reader io.Reader) {
|
||||
r.request.Body = ioutil.NopCloser(reader)
|
||||
}
|
||||
|
||||
func (r *Request) FormValue(name string) string {
|
||||
return r.request.FormValue(name)
|
||||
}
|
||||
|
||||
func (r *Request) FormParams() map[string][]string {
|
||||
if strings.HasPrefix(r.header.Get("Content-Type"), "multipart/form-data") {
|
||||
if err := r.request.ParseMultipartForm(defaultMemory); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
if err := r.request.ParseForm(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return map[string][]string(r.request.Form)
|
||||
}
|
||||
|
||||
func (r *Request) FormFile(name string) (*multipart.FileHeader, error) {
|
||||
_, fh, err := r.request.FormFile(name)
|
||||
return fh, err
|
||||
}
|
||||
|
||||
func (r *Request) MultipartForm() (*multipart.Form, error) {
|
||||
err := r.request.ParseMultipartForm(defaultMemory)
|
||||
return r.request.MultipartForm, err
|
||||
}
|
||||
|
||||
func (r *Request) Cookie(name string) (engine.Cookie, error) {
|
||||
c, err := r.request.Cookie(name)
|
||||
if err != nil {
|
||||
return nil, errors.New("cookie not found")
|
||||
}
|
||||
return &Cookie{c}, nil
|
||||
}
|
||||
|
||||
// Cookies implements `engine.Request#Cookies` function.
|
||||
func (r *Request) Cookies() []engine.Cookie {
|
||||
cs := r.request.Cookies()
|
||||
cookies := make([]engine.Cookie, len(cs))
|
||||
for i, c := range cs {
|
||||
cookies[i] = &Cookie{c}
|
||||
}
|
||||
return cookies
|
||||
}
|
||||
|
||||
func (r *Request) reset(req *http.Request, h engine.Header, u engine.URL) {
|
||||
r.request = req
|
||||
r.header = h
|
||||
r.url = u
|
||||
}
|
103
test/response.go
103
test/response.go
@ -1,103 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/labstack/gommon/log"
|
||||
)
|
||||
|
||||
type (
|
||||
Response struct {
|
||||
response http.ResponseWriter
|
||||
header engine.Header
|
||||
status int
|
||||
size int64
|
||||
committed bool
|
||||
writer io.Writer
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
ResponseRecorder struct {
|
||||
engine.Response
|
||||
Body *bytes.Buffer
|
||||
}
|
||||
)
|
||||
|
||||
func NewResponseRecorder() *ResponseRecorder {
|
||||
rec := httptest.NewRecorder()
|
||||
return &ResponseRecorder{
|
||||
Response: &Response{
|
||||
response: rec,
|
||||
header: &Header{rec.Header()},
|
||||
writer: rec,
|
||||
logger: log.New("test"),
|
||||
},
|
||||
Body: rec.Body,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) Header() engine.Header {
|
||||
return r.header
|
||||
}
|
||||
|
||||
func (r *Response) WriteHeader(code int) {
|
||||
if r.committed {
|
||||
r.logger.Warn("response already committed")
|
||||
return
|
||||
}
|
||||
r.status = code
|
||||
r.response.WriteHeader(code)
|
||||
r.committed = true
|
||||
}
|
||||
|
||||
func (r *Response) Write(b []byte) (n int, err error) {
|
||||
n, err = r.writer.Write(b)
|
||||
r.size += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
// SetCookie implements `engine.Response#SetCookie` function.
|
||||
func (r *Response) SetCookie(c engine.Cookie) {
|
||||
http.SetCookie(r.response, &http.Cookie{
|
||||
Name: c.Name(),
|
||||
Value: c.Value(),
|
||||
Path: c.Path(),
|
||||
Domain: c.Domain(),
|
||||
Expires: c.Expires(),
|
||||
Secure: c.Secure(),
|
||||
HttpOnly: c.HTTPOnly(),
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Response) Status() int {
|
||||
return r.status
|
||||
}
|
||||
|
||||
func (r *Response) Size() int64 {
|
||||
return r.size
|
||||
}
|
||||
|
||||
func (r *Response) Committed() bool {
|
||||
return r.committed
|
||||
}
|
||||
|
||||
func (r *Response) SetWriter(w io.Writer) {
|
||||
r.writer = w
|
||||
}
|
||||
|
||||
func (r *Response) Writer() io.Writer {
|
||||
return r.writer
|
||||
}
|
||||
|
||||
func (r *Response) reset(w http.ResponseWriter, h engine.Header) {
|
||||
r.response = w
|
||||
r.header = h
|
||||
r.status = http.StatusOK
|
||||
r.size = 0
|
||||
r.committed = false
|
||||
r.writer = w
|
||||
}
|
129
test/server.go
129
test/server.go
@ -1,129 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/labstack/gommon/log"
|
||||
)
|
||||
|
||||
type (
|
||||
Server struct {
|
||||
*http.Server
|
||||
config *engine.Config
|
||||
handler engine.Handler
|
||||
pool *Pool
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
Pool struct {
|
||||
request sync.Pool
|
||||
response sync.Pool
|
||||
header sync.Pool
|
||||
url sync.Pool
|
||||
}
|
||||
)
|
||||
|
||||
func New(addr string) *Server {
|
||||
c := &engine.Config{Address: addr}
|
||||
return NewConfig(c)
|
||||
}
|
||||
|
||||
func NewTLS(addr, certFile, keyFile string) *Server {
|
||||
c := &engine.Config{
|
||||
Address: addr,
|
||||
TLSCertFile: certFile,
|
||||
TLSKeyFile: keyFile,
|
||||
}
|
||||
return NewConfig(c)
|
||||
}
|
||||
|
||||
func NewConfig(c *engine.Config) (s *Server) {
|
||||
s = &Server{
|
||||
Server: new(http.Server),
|
||||
config: c,
|
||||
pool: &Pool{
|
||||
request: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Request{}
|
||||
},
|
||||
},
|
||||
response: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Response{logger: s.logger}
|
||||
},
|
||||
},
|
||||
header: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Header{}
|
||||
},
|
||||
},
|
||||
url: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &URL{}
|
||||
},
|
||||
},
|
||||
},
|
||||
handler: engine.HandlerFunc(func(req engine.Request, res engine.Response) {
|
||||
panic("echo: handler not set, use `Server#SetHandler()` to set it.")
|
||||
}),
|
||||
logger: log.New("echo"),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Server) SetHandler(h engine.Handler) {
|
||||
s.handler = h
|
||||
}
|
||||
|
||||
func (s *Server) SetLogger(l *log.Logger) {
|
||||
s.logger = l
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
if s.config.Listener == nil {
|
||||
return s.startDefaultListener()
|
||||
}
|
||||
return s.startCustomListener()
|
||||
}
|
||||
|
||||
func (s *Server) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) startDefaultListener() error {
|
||||
c := s.config
|
||||
if c.TLSCertFile != "" && c.TLSKeyFile != "" {
|
||||
return s.ListenAndServeTLS(c.TLSCertFile, c.TLSKeyFile)
|
||||
}
|
||||
return s.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *Server) startCustomListener() error {
|
||||
return s.Serve(s.config.Listener)
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Request
|
||||
req := s.pool.request.Get().(*Request)
|
||||
reqHdr := s.pool.header.Get().(*Header)
|
||||
reqURL := s.pool.url.Get().(*URL)
|
||||
reqHdr.reset(r.Header)
|
||||
reqURL.reset(r.URL)
|
||||
req.reset(r, reqHdr, reqURL)
|
||||
|
||||
// Response
|
||||
res := s.pool.response.Get().(*Response)
|
||||
resHdr := s.pool.header.Get().(*Header)
|
||||
resHdr.reset(w.Header())
|
||||
res.reset(w, resHdr)
|
||||
|
||||
s.handler.ServeHTTP(req, res)
|
||||
|
||||
s.pool.request.Put(req)
|
||||
s.pool.header.Put(reqHdr)
|
||||
s.pool.url.Put(reqURL)
|
||||
s.pool.response.Put(res)
|
||||
s.pool.header.Put(resHdr)
|
||||
}
|
44
test/url.go
44
test/url.go
@ -1,44 +0,0 @@
|
||||
package test
|
||||
|
||||
import "net/url"
|
||||
|
||||
type (
|
||||
URL struct {
|
||||
url *url.URL
|
||||
query url.Values
|
||||
}
|
||||
)
|
||||
|
||||
func (u *URL) URL() *url.URL {
|
||||
return u.url
|
||||
}
|
||||
|
||||
func (u *URL) SetPath(path string) {
|
||||
u.url.Path = path
|
||||
}
|
||||
|
||||
func (u *URL) Path() string {
|
||||
return u.url.Path
|
||||
}
|
||||
|
||||
func (u *URL) QueryParam(name string) string {
|
||||
if u.query == nil {
|
||||
u.query = u.url.Query()
|
||||
}
|
||||
return u.query.Get(name)
|
||||
}
|
||||
|
||||
func (u *URL) QueryParams() map[string][]string {
|
||||
if u.query == nil {
|
||||
u.query = u.url.Query()
|
||||
}
|
||||
return map[string][]string(u.query)
|
||||
}
|
||||
|
||||
func (u *URL) QueryString() string {
|
||||
return u.url.RawQuery
|
||||
}
|
||||
|
||||
func (u *URL) reset(url *url.URL) {
|
||||
u.url = url
|
||||
}
|
Loading…
Reference in New Issue
Block a user