2014-08-29 17:49:50 +00:00
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
2014-07-16 18:14:03 +00:00
package gin
import (
"bytes"
"errors"
"fmt"
"github.com/gin-gonic/gin/binding"
"github.com/gin-gonic/gin/render"
"github.com/julienschmidt/httprouter"
"log"
2014-12-21 12:42:48 +00:00
"net"
2014-07-16 18:14:03 +00:00
"net/http"
2014-12-21 12:42:48 +00:00
"strings"
2014-07-16 18:14:03 +00:00
)
const (
ErrorTypeInternal = 1 << iota
ErrorTypeExternal = 1 << iota
ErrorTypeAll = 0xffffffff
)
// Used internally to collect errors that occurred during an http request.
type errorMsg struct {
Err string ` json:"error" `
Type uint32 ` json:"-" `
Meta interface { } ` json:"meta" `
}
type errorMsgs [ ] errorMsg
func ( a errorMsgs ) ByType ( typ uint32 ) errorMsgs {
if len ( a ) == 0 {
return a
}
result := make ( errorMsgs , 0 , len ( a ) )
for _ , msg := range a {
if msg . Type & typ > 0 {
result = append ( result , msg )
}
}
return result
}
func ( a errorMsgs ) String ( ) string {
if len ( a ) == 0 {
return ""
}
var buffer bytes . Buffer
for i , msg := range a {
text := fmt . Sprintf ( "Error #%02d: %s \n Meta: %v\n" , ( i + 1 ) , msg . Err , msg . Meta )
buffer . WriteString ( text )
}
return buffer . String ( )
}
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
2014-07-17 22:10:28 +00:00
writermem responseWriter
Request * http . Request
Writer ResponseWriter
Keys map [ string ] interface { }
Errors errorMsgs
Params httprouter . Params
Engine * Engine
handlers [ ] HandlerFunc
index int8
2014-08-30 20:22:57 +00:00
accepted [ ] string
2014-07-16 18:14:03 +00:00
}
/************************************/
2014-10-08 19:37:26 +00:00
/********** CONTEXT CREATION ********/
2014-07-16 18:14:03 +00:00
/************************************/
func ( engine * Engine ) createContext ( w http . ResponseWriter , req * http . Request , params httprouter . Params , handlers [ ] HandlerFunc ) * Context {
2014-10-08 19:37:26 +00:00
c := engine . pool . Get ( ) . ( * Context )
2014-07-17 22:10:28 +00:00
c . writermem . reset ( w )
2014-07-16 18:14:03 +00:00
c . Request = req
c . Params = params
c . handlers = handlers
c . Keys = nil
c . index = - 1
2014-08-31 16:28:18 +00:00
c . accepted = nil
2014-07-17 22:29:20 +00:00
c . Errors = c . Errors [ 0 : 0 ]
2014-07-16 18:14:03 +00:00
return c
}
2014-10-08 19:37:26 +00:00
func ( engine * Engine ) reuseContext ( c * Context ) {
engine . pool . Put ( c )
}
2014-07-16 18:14:03 +00:00
func ( c * Context ) Copy ( ) * Context {
var cp Context = * c
cp . index = AbortIndex
cp . handlers = nil
return & cp
}
2014-10-08 19:37:26 +00:00
/************************************/
/*************** FLOW ***************/
/************************************/
2014-07-16 18:14:03 +00:00
// Next should be used only in the middlewares.
// It executes the pending handlers in the chain inside the calling handler.
// See example in github.
func ( c * Context ) Next ( ) {
c . index ++
s := int8 ( len ( c . handlers ) )
for ; c . index < s ; c . index ++ {
c . handlers [ c . index ] ( c )
}
}
2014-10-08 19:37:26 +00:00
// Forces the system to do not continue calling the pending handlers in the chain.
func ( c * Context ) Abort ( ) {
2014-07-16 18:14:03 +00:00
c . index = AbortIndex
}
2015-04-26 04:27:04 +00:00
// AbortWithStatus is the same as Abort but also writes the specified response status code.
2014-10-08 19:37:26 +00:00
// For example, the first handler checks if the request is authorized. If it's not, context.AbortWithStatus(401) should be called.
func ( c * Context ) AbortWithStatus ( code int ) {
c . Writer . WriteHeader ( code )
c . Abort ( )
}
/************************************/
/********* ERROR MANAGEMENT *********/
/************************************/
2014-07-16 18:14:03 +00:00
// Fail is the same as Abort plus an error message.
// Calling `context.Fail(500, err)` is equivalent to:
// ```
// context.Error("Operation aborted", err)
2014-10-08 19:37:26 +00:00
// context.AbortWithStatus(500)
2014-07-16 18:14:03 +00:00
// ```
func ( c * Context ) Fail ( code int , err error ) {
c . Error ( err , "Operation aborted" )
2014-10-08 19:37:26 +00:00
c . AbortWithStatus ( code )
2014-07-16 18:14:03 +00:00
}
func ( c * Context ) ErrorTyped ( err error , typ uint32 , meta interface { } ) {
c . Errors = append ( c . Errors , errorMsg {
Err : err . Error ( ) ,
Type : typ ,
Meta : meta ,
} )
}
// Attaches an error to the current context. The error is pushed to a list of errors.
// It's a good idea to call Error for each error that occurred during the resolution of a request.
// A middleware can be used to collect all the errors and push them to a database together, print a log, or append it in the HTTP response.
func ( c * Context ) Error ( err error , meta interface { } ) {
c . ErrorTyped ( err , ErrorTypeExternal , meta )
}
func ( c * Context ) LastError ( ) error {
2014-10-08 19:37:26 +00:00
nuErrors := len ( c . Errors )
if nuErrors > 0 {
return errors . New ( c . Errors [ nuErrors - 1 ] . Err )
2014-07-16 18:14:03 +00:00
} else {
return nil
}
}
/************************************/
/******** METADATA MANAGEMENT********/
/************************************/
// Sets a new pair key/value just for the specified context.
// It also lazy initializes the hashmap.
func ( c * Context ) Set ( key string , item interface { } ) {
if c . Keys == nil {
c . Keys = make ( map [ string ] interface { } )
}
c . Keys [ key ] = item
}
// Get returns the value for the given key or an error if the key does not exist.
func ( c * Context ) Get ( key string ) ( interface { } , error ) {
if c . Keys != nil {
2014-10-08 19:37:26 +00:00
value , ok := c . Keys [ key ]
2014-07-16 18:14:03 +00:00
if ok {
2014-10-08 19:37:26 +00:00
return value , nil
2014-07-16 18:14:03 +00:00
}
}
return nil , errors . New ( "Key does not exist." )
}
// MustGet returns the value for the given key or panics if the value doesn't exist.
func ( c * Context ) MustGet ( key string ) interface { } {
value , err := c . Get ( key )
if err != nil || value == nil {
2014-10-08 19:37:26 +00:00
log . Panicf ( "Key %s doesn't exist" , value )
2014-07-16 18:14:03 +00:00
}
return value
}
2014-12-21 12:42:48 +00:00
func ipInMasks ( ip net . IP , masks [ ] interface { } ) bool {
for _ , proxy := range masks {
var mask * net . IPNet
var err error
switch t := proxy . ( type ) {
case string :
if _ , mask , err = net . ParseCIDR ( t ) ; err != nil {
panic ( err )
}
case net . IP :
mask = & net . IPNet { IP : t , Mask : net . CIDRMask ( len ( t ) * 8 , len ( t ) * 8 ) }
case net . IPNet :
mask = & t
}
if mask . Contains ( ip ) {
return true
}
2014-10-08 23:40:42 +00:00
}
2014-12-21 12:42:48 +00:00
return false
}
// the ForwardedFor middleware unwraps the X-Forwarded-For headers, be careful to only use this
// middleware if you've got servers in front of this server. The list with (known) proxies and
// local ips are being filtered out of the forwarded for list, giving the last not local ip being
// the real client ip.
func ForwardedFor ( proxies ... interface { } ) HandlerFunc {
if len ( proxies ) == 0 {
// default to local ips
2015-02-08 04:44:53 +00:00
var reservedLocalIps = [ ] string { "10.0.0.0/8" , "127.0.0.1/32" , "172.16.0.0/12" , "192.168.0.0/16" }
2014-12-21 12:42:48 +00:00
proxies = make ( [ ] interface { } , len ( reservedLocalIps ) )
for i , v := range reservedLocalIps {
proxies [ i ] = v
}
}
return func ( c * Context ) {
// the X-Forwarded-For header contains an array with left most the client ip, then
// comma separated, all proxies the request passed. The last proxy appears
// as the remote address of the request. Returning the client
// ip to comply with default RemoteAddr response.
// check if remoteaddr is local ip or in list of defined proxies
remoteIp := net . ParseIP ( strings . Split ( c . Request . RemoteAddr , ":" ) [ 0 ] )
if ! ipInMasks ( remoteIp , proxies ) {
return
}
if forwardedFor := c . Request . Header . Get ( "X-Forwarded-For" ) ; forwardedFor != "" {
parts := strings . Split ( forwardedFor , "," )
for i := len ( parts ) - 1 ; i >= 0 ; i -- {
part := parts [ i ]
ip := net . ParseIP ( strings . TrimSpace ( part ) )
if ipInMasks ( ip , proxies ) {
continue
}
// returning remote addr conform the original remote addr format
c . Request . RemoteAddr = ip . String ( ) + ":0"
// remove forwarded for address
c . Request . Header . Set ( "X-Forwarded-For" , "" )
return
}
}
2014-10-08 23:40:42 +00:00
}
2014-12-21 12:42:48 +00:00
}
func ( c * Context ) ClientIP ( ) string {
return c . Request . RemoteAddr
2014-10-08 23:40:42 +00:00
}
2014-07-16 18:14:03 +00:00
/************************************/
2014-10-08 19:37:26 +00:00
/********* PARSING REQUEST **********/
2014-07-16 18:14:03 +00:00
/************************************/
// This function checks the Content-Type to select a binding engine automatically,
// Depending the "Content-Type" header different bindings are used:
// "application/json" --> JSON binding
// "application/xml" --> XML binding
// else --> returns an error
// if Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer.Like ParseBody() but this method also writes a 400 error if the json is not valid.
func ( c * Context ) Bind ( obj interface { } ) bool {
var b binding . Binding
ctype := filterFlags ( c . Request . Header . Get ( "Content-Type" ) )
switch {
case c . Request . Method == "GET" || ctype == MIMEPOSTForm :
b = binding . Form
2015-03-08 14:43:37 +00:00
case ctype == MIMEMultipartPOSTForm :
b = binding . MultipartForm
2014-07-16 18:14:03 +00:00
case ctype == MIMEJSON :
b = binding . JSON
case ctype == MIMEXML || ctype == MIMEXML2 :
b = binding . XML
default :
c . Fail ( 400 , errors . New ( "unknown content-type: " + ctype ) )
return false
}
return c . BindWith ( obj , b )
}
func ( c * Context ) BindWith ( obj interface { } , b binding . Binding ) bool {
if err := b . Bind ( c . Request , obj ) ; err != nil {
c . Fail ( 400 , err )
return false
}
return true
}
2014-10-08 19:37:26 +00:00
/************************************/
/******** RESPONSE RENDERING ********/
/************************************/
2014-07-16 18:14:03 +00:00
func ( c * Context ) Render ( code int , render render . Render , obj ... interface { } ) {
if err := render . Render ( c . Writer , code , obj ... ) ; err != nil {
c . ErrorTyped ( err , ErrorTypeInternal , obj )
2014-10-08 19:37:26 +00:00
c . AbortWithStatus ( 500 )
2014-07-16 18:14:03 +00:00
}
}
// Serializes the given struct as JSON into the response body in a fast and efficient way.
// It also sets the Content-Type as "application/json".
func ( c * Context ) JSON ( code int , obj interface { } ) {
c . Render ( code , render . JSON , obj )
}
// Serializes the given struct as XML into the response body in a fast and efficient way.
// It also sets the Content-Type as "application/xml".
func ( c * Context ) XML ( code int , obj interface { } ) {
c . Render ( code , render . XML , obj )
}
// Renders the HTTP template specified by its file name.
// It also updates the HTTP code and sets the Content-Type as "text/html".
// See http://golang.org/doc/articles/wiki/
func ( c * Context ) HTML ( code int , name string , obj interface { } ) {
c . Render ( code , c . Engine . HTMLRender , name , obj )
}
// Writes the given string into the response body and sets the Content-Type to "text/plain".
func ( c * Context ) String ( code int , format string , values ... interface { } ) {
c . Render ( code , render . Plain , format , values )
}
2015-03-08 16:50:58 +00:00
// Writes the given string into the response body and sets the Content-Type to "text/html" without template.
func ( c * Context ) HTMLString ( code int , format string , values ... interface { } ) {
c . Render ( code , render . HTMLPlain , format , values )
}
2014-07-28 22:51:34 +00:00
// Returns a HTTP redirect to the specific location.
2014-08-02 15:06:09 +00:00
func ( c * Context ) Redirect ( code int , location string ) {
if code >= 300 && code <= 308 {
c . Render ( code , render . Redirect , location )
} else {
panic ( fmt . Sprintf ( "Cannot send a redirect with status code %d" , code ) )
}
2014-07-28 22:48:02 +00:00
}
2014-07-16 18:14:03 +00:00
// Writes some data into the body stream and updates the HTTP code.
func ( c * Context ) Data ( code int , contentType string , data [ ] byte ) {
if len ( contentType ) > 0 {
c . Writer . Header ( ) . Set ( "Content-Type" , contentType )
}
2014-10-08 19:37:26 +00:00
c . Writer . WriteHeader ( code )
2014-07-16 18:14:03 +00:00
c . Writer . Write ( data )
}
2014-07-17 00:01:42 +00:00
// Writes the specified file into the body stream
func ( c * Context ) File ( filepath string ) {
http . ServeFile ( c . Writer , c . Request , filepath )
}
2014-08-30 20:22:57 +00:00
/************************************/
/******** CONTENT NEGOTIATION *******/
/************************************/
2014-08-31 16:28:18 +00:00
2014-08-30 20:22:57 +00:00
type Negotiate struct {
Offered [ ] string
HTMLPath string
2014-08-31 16:28:18 +00:00
HTMLData interface { }
JSONData interface { }
XMLData interface { }
Data interface { }
2014-08-30 20:22:57 +00:00
}
2014-08-31 16:28:18 +00:00
func ( c * Context ) Negotiate ( code int , config Negotiate ) {
2014-08-31 16:41:11 +00:00
switch c . NegotiateFormat ( config . Offered ... ) {
2014-08-30 20:22:57 +00:00
case MIMEJSON :
2014-08-31 16:28:18 +00:00
data := chooseData ( config . JSONData , config . Data )
2014-08-30 20:22:57 +00:00
c . JSON ( code , data )
case MIMEHTML :
2014-08-31 16:28:18 +00:00
data := chooseData ( config . HTMLData , config . Data )
if len ( config . HTMLPath ) == 0 {
panic ( "negotiate config is wrong. html path is needed" )
}
c . HTML ( code , config . HTMLPath , data )
2014-08-30 20:22:57 +00:00
case MIMEXML :
2014-08-31 16:28:18 +00:00
data := chooseData ( config . XMLData , config . Data )
2014-08-30 20:22:57 +00:00
c . XML ( code , data )
2014-08-31 16:28:18 +00:00
2014-08-30 20:22:57 +00:00
default :
2014-08-31 16:41:11 +00:00
c . Fail ( http . StatusNotAcceptable , errors . New ( "the accepted formats are not offered by the server" ) )
2014-08-30 20:22:57 +00:00
}
}
func ( c * Context ) NegotiateFormat ( offered ... string ) string {
2014-08-31 16:41:11 +00:00
if len ( offered ) == 0 {
panic ( "you must provide at least one offer" )
}
2014-08-30 20:22:57 +00:00
if c . accepted == nil {
c . accepted = parseAccept ( c . Request . Header . Get ( "Accept" ) )
}
if len ( c . accepted ) == 0 {
return offered [ 0 ]
} else {
for _ , accepted := range c . accepted {
for _ , offert := range offered {
if accepted == offert {
return offert
}
}
}
return ""
}
}
func ( c * Context ) SetAccepted ( formats ... string ) {
c . accepted = formats
}