mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-15 13:02:14 +08:00
http.Request.RemoteAddr contains the client's ip address and port number. The reverse proxy headers X-Real-Ip and X-Forwarded-For usually only contain a client's ip address. This change ensures that the ClientIP method behaves the same in both situations. The additional logic results in one more allocation. BenchmarkLoggerMiddleware is affected and goes from 14 to 15 allocations.
509 lines
14 KiB
Go
509 lines
14 KiB
Go
// 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.
|
|
|
|
package gin
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"math"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin/binding"
|
|
"github.com/gin-gonic/gin/render"
|
|
"github.com/manucorporat/sse"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// Content-Type MIME of the most common data formats
|
|
const (
|
|
MIMEJSON = binding.MIMEJSON
|
|
MIMEHTML = binding.MIMEHTML
|
|
MIMEXML = binding.MIMEXML
|
|
MIMEXML2 = binding.MIMEXML2
|
|
MIMEPlain = binding.MIMEPlain
|
|
MIMEPOSTForm = binding.MIMEPOSTForm
|
|
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
|
)
|
|
|
|
const abortIndex int8 = math.MaxInt8 / 2
|
|
|
|
// 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 {
|
|
writermem responseWriter
|
|
Request *http.Request
|
|
Writer ResponseWriter
|
|
|
|
Params Params
|
|
handlers HandlersChain
|
|
index int8
|
|
|
|
engine *Engine
|
|
Keys map[string]interface{}
|
|
Errors errorMsgs
|
|
Accepted []string
|
|
}
|
|
|
|
var _ context.Context = &Context{}
|
|
|
|
/************************************/
|
|
/********** CONTEXT CREATION ********/
|
|
/************************************/
|
|
|
|
func (c *Context) reset() {
|
|
c.Writer = &c.writermem
|
|
c.Params = c.Params[0:0]
|
|
c.handlers = nil
|
|
c.index = -1
|
|
c.Keys = nil
|
|
c.Errors = c.Errors[0:0]
|
|
c.Accepted = nil
|
|
}
|
|
|
|
// Copy returns a copy of the current context that can be safely used outside the request's scope.
|
|
// This have to be used then the context has to be passed to a goroutine.
|
|
func (c *Context) Copy() *Context {
|
|
var cp Context = *c
|
|
cp.writermem.ResponseWriter = nil
|
|
cp.Writer = &cp.writermem
|
|
cp.index = abortIndex
|
|
cp.handlers = nil
|
|
return &cp
|
|
}
|
|
|
|
// HandlerName returns the main handle's name. For example if the handler is "handleGetUsers()", this
|
|
// function will return "main.handleGetUsers"
|
|
func (c *Context) HandlerName() string {
|
|
return nameOfFunction(c.handlers.Last())
|
|
}
|
|
|
|
/************************************/
|
|
/*********** FLOW CONTROL ***********/
|
|
/************************************/
|
|
|
|
// Next should be used only inside middleware.
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
// IsAborted returns true if the currect context was aborted.
|
|
func (c *Context) IsAborted() bool {
|
|
return c.index >= abortIndex
|
|
}
|
|
|
|
// Abort stops the system to continue calling the pending handlers in the chain.
|
|
// Let's say you have an authorization middleware that validates if the request is authorized
|
|
// if the authorization fails (the password does not match). This method (Abort()) should be called
|
|
// in order to stop the execution of the actual handler.
|
|
func (c *Context) Abort() {
|
|
c.index = abortIndex
|
|
}
|
|
|
|
// AbortWithStatus calls `Abort()` and writes the headers with the specified status code.
|
|
// For example, a failed attempt to authentificate a request could use: context.AbortWithStatus(401).
|
|
func (c *Context) AbortWithStatus(code int) {
|
|
c.Writer.WriteHeader(code)
|
|
c.Abort()
|
|
}
|
|
|
|
// AbortWithError calls `AbortWithStatus()` and `Error()` internally. This method stops the chain, writes the status code and
|
|
// pushes the specified error to `c.Errors`.
|
|
// See Context.Error() for more details.
|
|
func (c *Context) AbortWithError(code int, err error) *Error {
|
|
c.AbortWithStatus(code)
|
|
return c.Error(err)
|
|
}
|
|
|
|
/************************************/
|
|
/********* ERROR MANAGEMENT *********/
|
|
/************************************/
|
|
|
|
// 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) *Error {
|
|
var parsedError *Error
|
|
switch err.(type) {
|
|
case *Error:
|
|
parsedError = err.(*Error)
|
|
default:
|
|
parsedError = &Error{
|
|
Err: err,
|
|
Type: ErrorTypePrivate,
|
|
}
|
|
}
|
|
c.Errors = append(c.Errors, parsedError)
|
|
return parsedError
|
|
}
|
|
|
|
/************************************/
|
|
/******** METADATA MANAGEMENT********/
|
|
/************************************/
|
|
|
|
// Set is used to store a new key/value pair exclusivelly for this context.
|
|
// It also lazy initializes c.Keys if it was not used previously.
|
|
func (c *Context) Set(key string, value interface{}) {
|
|
if c.Keys == nil {
|
|
c.Keys = make(map[string]interface{})
|
|
}
|
|
c.Keys[key] = value
|
|
}
|
|
|
|
// Get returns the value for the given key, ie: (value, true).
|
|
// If the value does not exists it returns (nil, false)
|
|
func (c *Context) Get(key string) (value interface{}, exists bool) {
|
|
if c.Keys != nil {
|
|
value, exists = c.Keys[key]
|
|
}
|
|
return
|
|
}
|
|
|
|
// Returns the value for the given key if it exists, otherwise it panics.
|
|
func (c *Context) MustGet(key string) interface{} {
|
|
if value, exists := c.Get(key); exists {
|
|
return value
|
|
}
|
|
panic("Key \"" + key + "\" does not exist")
|
|
}
|
|
|
|
/************************************/
|
|
/************ INPUT DATA ************/
|
|
/************************************/
|
|
|
|
// Query is a shortcut for c.Request.URL.Query().Get(key)
|
|
// It is used to return the url query values.
|
|
// ?id=1234&name=Manu
|
|
// c.Query("id") == "1234"
|
|
// c.Query("name") == "Manu"
|
|
// c.Query("wtf") == ""
|
|
func (c *Context) Query(key string) (va string) {
|
|
va, _ = c.query(key)
|
|
return
|
|
}
|
|
|
|
// PostForm is a shortcut for c.Request.PostFormValue(key)
|
|
func (c *Context) PostForm(key string) (va string) {
|
|
va, _ = c.postForm(key)
|
|
return
|
|
}
|
|
|
|
// Param is a shortcut for c.Params.ByName(key)
|
|
func (c *Context) Param(key string) string {
|
|
return c.Params.ByName(key)
|
|
}
|
|
|
|
func (c *Context) DefaultPostForm(key, defaultValue string) string {
|
|
if va, ok := c.postForm(key); ok {
|
|
return va
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
// DefaultQuery returns the keyed url query value if it exists, othewise it returns the
|
|
// specified defaultValue.
|
|
// ```
|
|
// /?name=Manu
|
|
// c.DefaultQuery("name", "unknown") == "Manu"
|
|
// c.DefaultQuery("id", "none") == "none"
|
|
// ```
|
|
func (c *Context) DefaultQuery(key, defaultValue string) string {
|
|
if va, ok := c.query(key); ok {
|
|
return va
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func (c *Context) query(key string) (string, bool) {
|
|
req := c.Request
|
|
if values, ok := req.URL.Query()[key]; ok && len(values) > 0 {
|
|
return values[0], true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func (c *Context) postForm(key string) (string, bool) {
|
|
req := c.Request
|
|
req.ParseMultipartForm(32 << 20) // 32 MB
|
|
if values := req.PostForm[key]; len(values) > 0 {
|
|
return values[0], true
|
|
}
|
|
if req.MultipartForm != nil && req.MultipartForm.File != nil {
|
|
if values := req.MultipartForm.Value[key]; len(values) > 0 {
|
|
return values[0], true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// Bind 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
|
|
// otherwise --> 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{}) error {
|
|
b := binding.Default(c.Request.Method, c.ContentType())
|
|
return c.BindWith(obj, b)
|
|
}
|
|
|
|
// BindJSON is a shortcut for c.BindWith(obj, binding.JSON)
|
|
func (c *Context) BindJSON(obj interface{}) error {
|
|
return c.BindWith(obj, binding.JSON)
|
|
}
|
|
|
|
// BindWith binds the passed struct pointer using the specified binding engine.
|
|
// See the binding package.
|
|
func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
|
|
if err := b.Bind(c.Request, obj); err != nil {
|
|
c.AbortWithError(400, err).SetType(ErrorTypeBind)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClientIP implements a best effort algorithm to return the real client IP, it parses
|
|
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
|
|
func (c *Context) ClientIP() string {
|
|
if c.engine.ForwardedByClientIP {
|
|
clientIP := strings.TrimSpace(c.requestHeader("X-Real-Ip"))
|
|
if len(clientIP) > 0 {
|
|
return clientIP
|
|
}
|
|
clientIP = c.requestHeader("X-Forwarded-For")
|
|
if index := strings.IndexByte(clientIP, ','); index >= 0 {
|
|
clientIP = clientIP[0:index]
|
|
}
|
|
clientIP = strings.TrimSpace(clientIP)
|
|
if len(clientIP) > 0 {
|
|
return clientIP
|
|
}
|
|
}
|
|
|
|
// http.Request.RemoteAddr also contains the connection's port number; only return the client's ip
|
|
host, _, err := net.SplitHostPort(c.Request.RemoteAddr)
|
|
if err != nil {
|
|
host = c.Request.RemoteAddr
|
|
}
|
|
return strings.TrimSpace(host)
|
|
}
|
|
|
|
// ContentType returns the Content-Type header of the request.
|
|
func (c *Context) ContentType() string {
|
|
return filterFlags(c.requestHeader("Content-Type"))
|
|
}
|
|
|
|
func (c *Context) requestHeader(key string) string {
|
|
if values, _ := c.Request.Header[key]; len(values) > 0 {
|
|
return values[0]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
/************************************/
|
|
/******** RESPONSE RENDERING ********/
|
|
/************************************/
|
|
|
|
// Header is a intelligent shortcut for c.Writer.Header().Set(key, value)
|
|
// It writes a header in the response.
|
|
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
|
|
func (c *Context) Header(key, value string) {
|
|
if len(value) == 0 {
|
|
c.Writer.Header().Del(key)
|
|
} else {
|
|
c.Writer.Header().Set(key, value)
|
|
}
|
|
}
|
|
|
|
func (c *Context) Render(code int, r render.Render) {
|
|
c.writermem.WriteHeader(code)
|
|
if err := r.Render(c.Writer); err != nil {
|
|
c.renderError(err)
|
|
}
|
|
}
|
|
|
|
func (c *Context) renderError(err error) {
|
|
debugPrintError(err)
|
|
c.AbortWithError(500, err).SetType(ErrorTypeRender)
|
|
}
|
|
|
|
// HTML 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{}) {
|
|
instance := c.engine.HTMLRender.Instance(name, obj)
|
|
c.Render(code, instance)
|
|
}
|
|
|
|
// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.
|
|
// It also sets the Content-Type as "application/json".
|
|
// WARNING: we recommend to use this only for development propuses since printing pretty JSON is
|
|
// more CPU and bandwidth consuming. Use Context.JSON() instead.
|
|
func (c *Context) IndentedJSON(code int, obj interface{}) {
|
|
c.Render(code, render.IndentedJSON{Data: obj})
|
|
}
|
|
|
|
// JSON serializes the given struct as JSON into the response body.
|
|
// It also sets the Content-Type as "application/json".
|
|
func (c *Context) JSON(code int, obj interface{}) {
|
|
c.writermem.WriteHeader(code)
|
|
if err := render.WriteJSON(c.Writer, obj); err != nil {
|
|
c.renderError(err)
|
|
}
|
|
}
|
|
|
|
// XML serializes the given struct as XML into the response body.
|
|
// It also sets the Content-Type as "application/xml".
|
|
func (c *Context) XML(code int, obj interface{}) {
|
|
c.Render(code, render.XML{Data: obj})
|
|
}
|
|
|
|
// String writes the given string into the response body.
|
|
func (c *Context) String(code int, format string, values ...interface{}) {
|
|
c.writermem.WriteHeader(code)
|
|
render.WriteString(c.Writer, format, values)
|
|
}
|
|
|
|
// Redirect returns a HTTP redirect to the specific location.
|
|
func (c *Context) Redirect(code int, location string) {
|
|
c.Render(-1, render.Redirect{
|
|
Code: code,
|
|
Location: location,
|
|
Request: c.Request,
|
|
})
|
|
}
|
|
|
|
// Data writes some data into the body stream and updates the HTTP code.
|
|
func (c *Context) Data(code int, contentType string, data []byte) {
|
|
c.Render(code, render.Data{
|
|
ContentType: contentType,
|
|
Data: data,
|
|
})
|
|
}
|
|
|
|
// File writes the specified file into the body stream in a efficient way.
|
|
func (c *Context) File(filepath string) {
|
|
http.ServeFile(c.Writer, c.Request, filepath)
|
|
}
|
|
|
|
// SSEvent writes a Server-Sent Event into the body stream.
|
|
func (c *Context) SSEvent(name string, message interface{}) {
|
|
c.Render(-1, sse.Event{
|
|
Event: name,
|
|
Data: message,
|
|
})
|
|
}
|
|
|
|
func (c *Context) Stream(step func(w io.Writer) bool) {
|
|
w := c.Writer
|
|
clientGone := w.CloseNotify()
|
|
for {
|
|
select {
|
|
case <-clientGone:
|
|
return
|
|
default:
|
|
keepopen := step(w)
|
|
w.Flush()
|
|
if !keepopen {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************/
|
|
/******** CONTENT NEGOTIATION *******/
|
|
/************************************/
|
|
|
|
type Negotiate struct {
|
|
Offered []string
|
|
HTMLName string
|
|
HTMLData interface{}
|
|
JSONData interface{}
|
|
XMLData interface{}
|
|
Data interface{}
|
|
}
|
|
|
|
func (c *Context) Negotiate(code int, config Negotiate) {
|
|
switch c.NegotiateFormat(config.Offered...) {
|
|
case binding.MIMEJSON:
|
|
data := chooseData(config.JSONData, config.Data)
|
|
c.JSON(code, data)
|
|
|
|
case binding.MIMEHTML:
|
|
data := chooseData(config.HTMLData, config.Data)
|
|
c.HTML(code, config.HTMLName, data)
|
|
|
|
case binding.MIMEXML:
|
|
data := chooseData(config.XMLData, config.Data)
|
|
c.XML(code, data)
|
|
|
|
default:
|
|
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server"))
|
|
}
|
|
}
|
|
|
|
func (c *Context) NegotiateFormat(offered ...string) string {
|
|
if len(offered) == 0 {
|
|
panic("you must provide at least one offer")
|
|
}
|
|
if c.Accepted == nil {
|
|
c.Accepted = parseAccept(c.requestHeader("Accept"))
|
|
}
|
|
if len(c.Accepted) == 0 {
|
|
return offered[0]
|
|
}
|
|
for _, accepted := range c.Accepted {
|
|
for _, offert := range offered {
|
|
if accepted == offert {
|
|
return offert
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (c *Context) SetAccepted(formats ...string) {
|
|
c.Accepted = formats
|
|
}
|
|
|
|
/************************************/
|
|
/***** GOLANG.ORG/X/NET/CONTEXT *****/
|
|
/************************************/
|
|
|
|
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
|
return
|
|
}
|
|
|
|
func (c *Context) Done() <-chan struct{} {
|
|
return nil
|
|
}
|
|
|
|
func (c *Context) Err() error {
|
|
return nil
|
|
}
|
|
|
|
func (c *Context) Value(key interface{}) interface{} {
|
|
if key == 0 {
|
|
return c.Request
|
|
}
|
|
if keyAsString, ok := key.(string); ok {
|
|
val, _ := c.Get(keyAsString)
|
|
return val
|
|
}
|
|
return nil
|
|
}
|