1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-24 20:14:31 +02:00

Encapsulated fields and exposed public functions.

Signed-off-by: Vishal Rana <vr@labstack.com>
This commit is contained in:
Vishal Rana 2015-05-22 04:40:01 -07:00
parent df924ffc11
commit bf85c56b08
13 changed files with 108 additions and 82 deletions

View File

@ -11,9 +11,9 @@ type (
// Context represents context for the current request. It holds request and // Context represents context for the current request. It holds request and
// response objects, path parameters, data and registered handler. // response objects, path parameters, data and registered handler.
Context struct { Context struct {
Request *http.Request request *http.Request
Response *Response response *Response
Socket *websocket.Conn socket *websocket.Conn
pnames []string pnames []string
pvalues []string pvalues []string
store store store store
@ -24,8 +24,8 @@ type (
func NewContext(req *http.Request, res *Response, e *Echo) *Context { func NewContext(req *http.Request, res *Response, e *Echo) *Context {
return &Context{ return &Context{
Request: req, request: req,
Response: res, response: res,
echo: e, echo: e,
pnames: make([]string, e.maxParam), pnames: make([]string, e.maxParam),
pvalues: make([]string, e.maxParam), pvalues: make([]string, e.maxParam),
@ -33,6 +33,21 @@ func NewContext(req *http.Request, res *Response, e *Echo) *Context {
} }
} }
// Request returns *http.Request.
func (c *Context) Request() *http.Request {
return c.request
}
// Response returns *Response.
func (c *Context) Response() *Response {
return c.response
}
// Socket returns *websocket.Conn.
func (c *Context) Socket() *websocket.Conn {
return c.socket
}
// P returns path parameter by index. // P returns path parameter by index.
func (c *Context) P(i uint8) (value string) { func (c *Context) P(i uint8) (value string) {
l := uint8(len(c.pnames)) l := uint8(len(c.pnames))
@ -57,7 +72,7 @@ func (c *Context) Param(name string) (value string) {
// Bind binds the request body into specified type v. Default binder does it // Bind binds the request body into specified type v. Default binder does it
// based on Content-Type header. // based on Content-Type header.
func (c *Context) Bind(i interface{}) error { func (c *Context) Bind(i interface{}) error {
return c.echo.binder(c.Request, i) return c.echo.binder(c.request, i)
} }
// Render invokes the registered HTML template renderer and sends a text/html // Render invokes the registered HTML template renderer and sends a text/html
@ -66,37 +81,37 @@ func (c *Context) Render(code int, name string, data interface{}) error {
if c.echo.renderer == nil { if c.echo.renderer == nil {
return RendererNotRegistered return RendererNotRegistered
} }
c.Response.Header().Set(ContentType, TextHTML+"; charset=utf-8") c.response.Header().Set(ContentType, TextHTML+"; charset=utf-8")
c.Response.WriteHeader(code) c.response.WriteHeader(code)
return c.echo.renderer.Render(c.Response, name, data) return c.echo.renderer.Render(c.response, name, data)
} }
// JSON sends an application/json response with status code. // JSON sends an application/json response with status code.
func (c *Context) JSON(code int, i interface{}) error { func (c *Context) JSON(code int, i interface{}) error {
c.Response.Header().Set(ContentType, ApplicationJSON+"; charset=utf-8") c.response.Header().Set(ContentType, ApplicationJSON+"; charset=utf-8")
c.Response.WriteHeader(code) c.response.WriteHeader(code)
return json.NewEncoder(c.Response).Encode(i) return json.NewEncoder(c.response).Encode(i)
} }
// String sends a text/plain response with status code. // String sends a text/plain response with status code.
func (c *Context) String(code int, s string) error { func (c *Context) String(code int, s string) error {
c.Response.Header().Set(ContentType, TextPlain+"; charset=utf-8") c.response.Header().Set(ContentType, TextPlain+"; charset=utf-8")
c.Response.WriteHeader(code) c.response.WriteHeader(code)
_, err := c.Response.Write([]byte(s)) _, err := c.response.Write([]byte(s))
return err return err
} }
// HTML sends a text/html response with status code. // HTML sends a text/html response with status code.
func (c *Context) HTML(code int, html string) error { func (c *Context) HTML(code int, html string) error {
c.Response.Header().Set(ContentType, TextHTML+"; charset=utf-8") c.response.Header().Set(ContentType, TextHTML+"; charset=utf-8")
c.Response.WriteHeader(code) c.response.WriteHeader(code)
_, err := c.Response.Write([]byte(html)) _, err := c.response.Write([]byte(html))
return err return err
} }
// NoContent sends a response with no body and a status code. // NoContent sends a response with no body and a status code.
func (c *Context) NoContent(code int) error { func (c *Context) NoContent(code int) error {
c.Response.WriteHeader(code) c.response.WriteHeader(code)
return nil return nil
} }
@ -117,11 +132,11 @@ func (c *Context) Set(key string, val interface{}) {
// Redirect redirects the request using http.Redirect with status code. // Redirect redirects the request using http.Redirect with status code.
func (c *Context) Redirect(code int, url string) { func (c *Context) Redirect(code int, url string) {
http.Redirect(c.Response, c.Request, url, code) http.Redirect(c.response, c.request, url, code)
} }
func (c *Context) reset(w http.ResponseWriter, r *http.Request, e *Echo) { func (c *Context) reset(w http.ResponseWriter, r *http.Request, e *Echo) {
c.Request = r c.request = r
c.Response.reset(w) c.response.reset(w)
c.echo = e c.echo = e
} }

View File

@ -91,26 +91,26 @@ func TestContext(t *testing.T) {
// JSON // JSON
r.Header.Set(Accept, ApplicationJSON) r.Header.Set(Accept, ApplicationJSON)
c.Response.committed = false c.response.committed = false
if he := c.JSON(http.StatusOK, u1); he != nil { if he := c.JSON(http.StatusOK, u1); he != nil {
t.Errorf("json %#v", he) t.Errorf("json %#v", he)
} }
// String // String
r.Header.Set(Accept, TextPlain) r.Header.Set(Accept, TextPlain)
c.Response.committed = false c.response.committed = false
if he := c.String(http.StatusOK, "Hello, World!"); he != nil { if he := c.String(http.StatusOK, "Hello, World!"); he != nil {
t.Errorf("string %#v", he.Error) t.Errorf("string %#v", he.Error)
} }
// HTML // HTML
r.Header.Set(Accept, TextHTML) r.Header.Set(Accept, TextHTML)
c.Response.committed = false c.response.committed = false
if he := c.HTML(http.StatusOK, "Hello, <strong>World!</strong>"); he != nil { if he := c.HTML(http.StatusOK, "Hello, <strong>World!</strong>"); he != nil {
t.Errorf("html %v", he.Error) t.Errorf("html %v", he.Error)
} }
// Redirect // Redirect
c.Response.committed = false c.response.committed = false
c.Redirect(http.StatusMovedPermanently, "http://labstack.github.io/echo") c.Redirect(http.StatusMovedPermanently, "http://labstack.github.io/echo")
} }

58
echo.go
View File

@ -13,9 +13,9 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/bradfitz/http2"
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
"github.com/bradfitz/http2"
) )
type ( type (
@ -34,8 +34,8 @@ type (
debug bool debug bool
} }
HTTPError struct { HTTPError struct {
Code int code int
Message string message string
} }
Middleware interface{} Middleware interface{}
MiddlewareFunc func(HandlerFunc) HandlerFunc MiddlewareFunc func(HandlerFunc) HandlerFunc
@ -123,15 +123,21 @@ var (
) )
func NewHTTPError(code int, msg ...string) *HTTPError { func NewHTTPError(code int, msg ...string) *HTTPError {
he := &HTTPError{Code: code, Message: http.StatusText(code)} he := &HTTPError{code: code, message: http.StatusText(code)}
for _, m := range msg { for _, m := range msg {
he.Message = m he.message = m
} }
return he return he
} }
// Code returns code.
func (e *HTTPError) Code() int {
return e.code
}
// Error returns message.
func (e *HTTPError) Error() string { func (e *HTTPError) Error() string {
return e.Message return e.message
} }
// New creates an Echo instance. // New creates an Echo instance.
@ -157,13 +163,13 @@ func New() (e *Echo) {
code := http.StatusInternalServerError code := http.StatusInternalServerError
msg := http.StatusText(code) msg := http.StatusText(code)
if he, ok := err.(*HTTPError); ok { if he, ok := err.(*HTTPError); ok {
code = he.Code code = he.code
msg = he.Message msg = he.message
} }
if e.Debug() { if e.Debug() {
msg = err.Error() msg = err.Error()
} }
http.Error(c.Response, msg, code) http.Error(c.response, msg, code)
}) })
e.SetBinder(func(r *http.Request, v interface{}) error { e.SetBinder(func(r *http.Request, v interface{}) error {
ct := r.Header.Get(ContentType) ct := r.Header.Get(ContentType)
@ -283,12 +289,12 @@ func (e *Echo) WebSocket(path string, h HandlerFunc) {
e.Get(path, func(c *Context) (err error) { e.Get(path, func(c *Context) (err error) {
wss := websocket.Server{ wss := websocket.Server{
Handler: func(ws *websocket.Conn) { Handler: func(ws *websocket.Conn) {
c.Socket = ws c.socket = ws
c.Response.status = http.StatusSwitchingProtocols c.response.status = http.StatusSwitchingProtocols
err = h(c) err = h(c)
}, },
} }
wss.ServeHTTP(c.Response.writer, c.Request) wss.ServeHTTP(c.response.writer, c.request)
return err return err
}) })
} }
@ -313,7 +319,7 @@ func (e *Echo) Favicon(file string) {
func (e *Echo) Static(path, root string) { func (e *Echo) Static(path, root string) {
fs := http.StripPrefix(path, http.FileServer(http.Dir(root))) fs := http.StripPrefix(path, http.FileServer(http.Dir(root)))
e.Get(path+"*", func(c *Context) error { e.Get(path+"*", func(c *Context) error {
fs.ServeHTTP(c.Response, c.Request) fs.ServeHTTP(c.response, c.request)
return nil return nil
}) })
} }
@ -321,7 +327,7 @@ func (e *Echo) Static(path, root string) {
// ServeFile serves a file. // ServeFile serves a file.
func (e *Echo) ServeFile(path, file string) { func (e *Echo) ServeFile(path, file string) {
e.Get(path, func(c *Context) error { e.Get(path, func(c *Context) error {
http.ServeFile(c.Response, c.Request, file) http.ServeFile(c.response, c.request, file)
return nil return nil
}) })
} }
@ -399,17 +405,17 @@ func (e *Echo) RunTLSServer(srv *http.Server, certFile, keyFile string) {
e.run(srv, certFile, keyFile) e.run(srv, certFile, keyFile)
} }
func (e *Echo) run(s *http.Server, f ...string) { func (e *Echo) run(s *http.Server, files ...string) {
s.Handler = e s.Handler = e
if e.http2 { if e.http2 {
http2.ConfigureServer(s, nil) http2.ConfigureServer(s, nil)
} }
if len(f) == 0 { if len(files) == 0 {
log.Fatal(s.ListenAndServe()) log.Fatal(s.ListenAndServe())
} else if len(f) == 2 { } else if len(files) == 2 {
log.Fatal(s.ListenAndServeTLS(f[0], f[1])) log.Fatal(s.ListenAndServeTLS(files[0], files[1]))
} else { } else {
log.Fatal("echo: invalid TLS configuration") log.Fatal("echo => invalid TLS configuration")
} }
} }
@ -428,10 +434,10 @@ func wrapMiddleware(m Middleware) MiddlewareFunc {
return func(h HandlerFunc) HandlerFunc { return func(h HandlerFunc) HandlerFunc {
return func(c *Context) (err error) { return func(c *Context) (err error) {
m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Response.writer = w c.response.writer = w
c.Request = r c.request = r
err = h(c) err = h(c)
})).ServeHTTP(c.Response.writer, c.Request) })).ServeHTTP(c.response.writer, c.request)
return return
} }
} }
@ -462,8 +468,8 @@ func wrapHandlerFuncMW(m HandlerFunc) MiddlewareFunc {
func wrapHTTPHandlerFuncMW(m http.HandlerFunc) MiddlewareFunc { func wrapHTTPHandlerFuncMW(m http.HandlerFunc) MiddlewareFunc {
return func(h HandlerFunc) HandlerFunc { return func(h HandlerFunc) HandlerFunc {
return func(c *Context) error { return func(c *Context) error {
if !c.Response.committed { if !c.response.committed {
m.ServeHTTP(c.Response.writer, c.Request) m.ServeHTTP(c.response.writer, c.request)
} }
return h(c) return h(c)
} }
@ -479,12 +485,12 @@ func wrapHandler(h Handler) HandlerFunc {
return h return h
case http.Handler, http.HandlerFunc: case http.Handler, http.HandlerFunc:
return func(c *Context) error { return func(c *Context) error {
h.(http.Handler).ServeHTTP(c.Response, c.Request) h.(http.Handler).ServeHTTP(c.response, c.request)
return nil return nil
} }
case func(http.ResponseWriter, *http.Request): case func(http.ResponseWriter, *http.Request):
return func(c *Context) error { return func(c *Context) error {
h(c.Response, c.Request) h(c.response, c.request)
return nil return nil
} }
default: default:

View File

@ -252,7 +252,7 @@ func TestEchoMethod(t *testing.T) {
func TestWebSocket(t *testing.T) { func TestWebSocket(t *testing.T) {
e := New() e := New()
e.WebSocket("/ws", func(c *Context) error { e.WebSocket("/ws", func(c *Context) error {
c.Socket.Write([]byte("test")) c.socket.Write([]byte("test"))
return nil return nil
}) })
srv := httptest.NewServer(e) srv := httptest.NewServer(e)

View File

@ -21,7 +21,7 @@ const (
// For invalid credentials, it sends "401 - Unauthorized" response. // For invalid credentials, it sends "401 - Unauthorized" response.
func BasicAuth(fn AuthFunc) echo.HandlerFunc { func BasicAuth(fn AuthFunc) echo.HandlerFunc {
return func(c *echo.Context) error { return func(c *echo.Context) error {
auth := c.Request.Header.Get(echo.Authorization) auth := c.Request().Header.Get(echo.Authorization)
i := 0 i := 0
code := http.StatusBadRequest code := http.StatusBadRequest

View File

@ -48,8 +48,8 @@ func TestBasicAuth(t *testing.T) {
he := ba(c).(*echo.HTTPError) he := ba(c).(*echo.HTTPError)
if ba(c) == nil { if ba(c) == nil {
t.Error("expected `fail`, with incorrect password.") t.Error("expected `fail`, with incorrect password.")
} else if he.Code != http.StatusUnauthorized { } else if he.Code() != http.StatusUnauthorized {
t.Errorf("expected status `401`, got %d", he.Code) t.Errorf("expected status `401`, got %d", he.Code())
} }
// Empty Authorization header // Empty Authorization header
@ -58,8 +58,8 @@ func TestBasicAuth(t *testing.T) {
he = ba(c).(*echo.HTTPError) he = ba(c).(*echo.HTTPError)
if he == nil { if he == nil {
t.Error("expected `fail`, with empty Authorization header.") t.Error("expected `fail`, with empty Authorization header.")
} else if he.Code != http.StatusBadRequest { } else if he.Code() != http.StatusBadRequest {
t.Errorf("expected status `400`, got %d", he.Code) t.Errorf("expected status `400`, got %d", he.Code())
} }
// Invalid Authorization header // Invalid Authorization header
@ -69,8 +69,8 @@ func TestBasicAuth(t *testing.T) {
he = ba(c).(*echo.HTTPError) he = ba(c).(*echo.HTTPError)
if he == nil { if he == nil {
t.Error("expected `fail`, with invalid Authorization header.") t.Error("expected `fail`, with invalid Authorization header.")
} else if he.Code != http.StatusBadRequest { } else if he.Code() != http.StatusBadRequest {
t.Errorf("expected status `400`, got %d", he.Code) t.Errorf("expected status `400`, got %d", he.Code())
} }
// Invalid scheme // Invalid scheme
@ -80,7 +80,7 @@ func TestBasicAuth(t *testing.T) {
he = ba(c).(*echo.HTTPError) he = ba(c).(*echo.HTTPError)
if he == nil { if he == nil {
t.Error("expected `fail`, with invalid scheme.") t.Error("expected `fail`, with invalid scheme.")
} else if he.Code != http.StatusBadRequest { } else if he.Code() != http.StatusBadRequest {
t.Errorf("expected status `400`, got %d", he.Code) t.Errorf("expected status `400`, got %d", he.Code())
} }
} }

View File

@ -6,17 +6,18 @@ import (
"strings" "strings"
"github.com/labstack/echo" "github.com/labstack/echo"
"net/http"
) )
type ( type (
gzipWriter struct { gzipWriter struct {
io.Writer io.Writer
*echo.Response http.ResponseWriter
} }
) )
func (g gzipWriter) Write(b []byte) (int, error) { func (w gzipWriter) Write(b []byte) (int, error) {
return g.Writer.Write(b) return w.Writer.Write(b)
} }
// Gzip returns a middleware which compresses HTTP response using gzip compression // Gzip returns a middleware which compresses HTTP response using gzip compression
@ -26,12 +27,12 @@ func Gzip() echo.MiddlewareFunc {
return func(h echo.HandlerFunc) echo.HandlerFunc { return func(h echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error { return func(c *echo.Context) error {
if strings.Contains(c.Request.Header.Get(echo.AcceptEncoding), scheme) { if strings.Contains(c.Request().Header.Get(echo.AcceptEncoding), scheme) {
w := gzip.NewWriter(c.Response.Writer()) w := gzip.NewWriter(c.Response().Writer())
defer w.Close() defer w.Close()
gw := gzipWriter{Writer: w, Response: c.Response} gw := gzipWriter{Writer: w, ResponseWriter: c.Response().Writer()}
c.Response.Header().Set(echo.ContentEncoding, scheme) c.Response().Header().Set(echo.ContentEncoding, scheme)
c.Response = echo.NewResponse(gw) c.Response().SetWriter(gw)
} }
return h(c) return h(c)
} }

View File

@ -29,7 +29,7 @@ func TestGzip(t *testing.T) {
// Content-Encoding header // Content-Encoding header
req.Header.Set(echo.AcceptEncoding, "gzip") req.Header.Set(echo.AcceptEncoding, "gzip")
w = httptest.NewRecorder() w = httptest.NewRecorder()
c.Response = echo.NewResponse(w) c.Response().SetWriter(w)
Gzip()(h)(c) Gzip()(h)(c)
ce := w.Header().Get(echo.ContentEncoding) ce := w.Header().Get(echo.ContentEncoding)
if ce != "gzip" { if ce != "gzip" {

View File

@ -16,14 +16,14 @@ func Logger() echo.MiddlewareFunc {
c.Error(err) c.Error(err)
} }
end := time.Now() end := time.Now()
method := c.Request.Method method := c.Request().Method
path := c.Request.URL.Path path := c.Request().URL.Path
if path == "" { if path == "" {
path = "/" path = "/"
} }
size := c.Response.Size() size := c.Response().Size()
n := c.Response.Status() n := c.Response().Status()
code := color.Green(n) code := color.Green(n)
switch { switch {
case n >= 500: case n >= 500:

View File

@ -21,14 +21,14 @@ func TestLogger(t *testing.T) {
Logger()(h)(c) Logger()(h)(c)
// Status 4xx // Status 4xx
c.Response = echo.NewResponse(w) c = echo.NewContext(req, echo.NewResponse(w), e)
h = func(c *echo.Context) error { h = func(c *echo.Context) error {
return c.String(http.StatusNotFound, "test") return c.String(http.StatusNotFound, "test")
} }
Logger()(h)(c) Logger()(h)(c)
// Status 5xx // Status 5xx
c.Response = echo.NewResponse(w) c = echo.NewContext(req, echo.NewResponse(w), e)
h = func(c *echo.Context) error { h = func(c *echo.Context) error {
return c.String(http.StatusInternalServerError, "test") return c.String(http.StatusInternalServerError, "test")
} }

View File

@ -15,10 +15,10 @@ type (
// path. // path.
func StripTrailingSlash() echo.HandlerFunc { func StripTrailingSlash() echo.HandlerFunc {
return func(c *echo.Context) error { return func(c *echo.Context) error {
p := c.Request.URL.Path p := c.Request().URL.Path
l := len(p) l := len(p)
if p[l-1] == '/' { if p[l-1] == '/' {
c.Request.URL.Path = p[:l-1] c.Request().URL.Path = p[:l-1]
} }
return nil return nil
} }
@ -36,7 +36,7 @@ func RedirectToSlash(opts ...RedirectToSlashOptions) echo.HandlerFunc {
} }
return func(c *echo.Context) error { return func(c *echo.Context) error {
p := c.Request.URL.Path p := c.Request().URL.Path
l := len(p) l := len(p)
if p[l-1] != '/' { if p[l-1] != '/' {
c.Redirect(code, p+"/") c.Redirect(code, p+"/")

View File

@ -13,7 +13,7 @@ func TestStripTrailingSlash(t *testing.T) {
res := echo.NewResponse(httptest.NewRecorder()) res := echo.NewResponse(httptest.NewRecorder())
c := echo.NewContext(req, res, echo.New()) c := echo.NewContext(req, res, echo.New())
StripTrailingSlash()(c) StripTrailingSlash()(c)
p := c.Request.URL.Path p := c.Request().URL.Path
if p != "/users" { if p != "/users" {
t.Errorf("expected path `/users` got, %s.", p) t.Errorf("expected path `/users` got, %s.", p)
} }
@ -31,7 +31,7 @@ func TestRedirectToSlash(t *testing.T) {
} }
// Location header // Location header
l := c.Response.Header().Get("Location") l := c.Response().Header().Get("Location")
if l != "/users/" { if l != "/users/" {
t.Errorf("expected Location header `/users/`, got %s.", l) t.Errorf("expected Location header `/users/`, got %s.", l)
} }

View File

@ -26,6 +26,10 @@ func (r *Response) Header() http.Header {
return r.writer.Header() return r.writer.Header()
} }
func (r *Response) SetWriter(w http.ResponseWriter) {
r.writer = w
}
func (r *Response) Writer() http.ResponseWriter { func (r *Response) Writer() http.ResponseWriter {
return r.writer return r.writer
} }