2022-09-28 17:30:40 +00:00
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
2021-08-12 19:03:24 +00:00
// 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"
"log"
"math"
"mime/multipart"
"net"
"net/http"
"net/url"
"os"
2023-02-25 12:12:40 +00:00
"path/filepath"
2021-08-12 19:03:24 +00:00
"strings"
"sync"
"time"
"github.com/gin-contrib/sse"
"github.com/gin-gonic/gin/binding"
"github.com/gin-gonic/gin/render"
)
// 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
MIMEYAML = binding . MIMEYAML
2022-09-28 17:30:40 +00:00
MIMETOML = binding . MIMETOML
2021-08-12 19:03:24 +00:00
)
// BodyBytesKey indicates a default body bytes key.
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
2022-09-28 17:30:40 +00:00
// ContextKey is the key that a Context returns itself for.
const ContextKey = "_gin-gonic/gin/contextkey"
// abortIndex represents a typical value used in abort functions.
const abortIndex int8 = math . MaxInt8 >> 1
2021-08-12 19:03:24 +00:00
// 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
fullPath string
2021-11-27 14:26:58 +00:00
engine * Engine
params * Params
skippedNodes * [ ] skippedNode
2021-08-12 19:03:24 +00:00
2022-09-28 17:30:40 +00:00
// This mutex protects Keys map.
2021-08-12 19:03:24 +00:00
mu sync . RWMutex
// Keys is a key/value pair exclusively for the context of each request.
2022-09-28 17:30:40 +00:00
Keys map [ string ] any
2021-08-12 19:03:24 +00:00
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted [ ] string
2022-09-28 17:30:40 +00:00
// queryCache caches the query result from c.Request.URL.Query().
2021-08-12 19:03:24 +00:00
queryCache url . Values
2022-09-28 17:30:40 +00:00
// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
2021-08-12 19:03:24 +00:00
// or PUT body parameters.
formCache url . Values
// SameSite allows a server to define a cookie attribute making it impossible for
// the browser to send this cookie along with cross-site requests.
sameSite http . SameSite
}
/************************************/
/********** CONTEXT CREATION ********/
/************************************/
func ( c * Context ) reset ( ) {
c . Writer = & c . writermem
2022-09-28 17:30:40 +00:00
c . Params = c . Params [ : 0 ]
2021-08-12 19:03:24 +00:00
c . handlers = nil
c . index = - 1
c . fullPath = ""
c . Keys = nil
2022-09-28 17:30:40 +00:00
c . Errors = c . Errors [ : 0 ]
2021-08-12 19:03:24 +00:00
c . Accepted = nil
c . queryCache = nil
c . formCache = nil
2022-09-28 17:30:40 +00:00
c . sameSite = 0
2021-08-12 19:03:24 +00:00
* c . params = ( * c . params ) [ : 0 ]
2021-11-27 14:26:58 +00:00
* c . skippedNodes = ( * c . skippedNodes ) [ : 0 ]
2021-08-12 19:03:24 +00:00
}
// Copy returns a copy of the current context that can be safely used outside the request's scope.
// This has to be used when the context has to be passed to a goroutine.
func ( c * Context ) Copy ( ) * Context {
cp := Context {
writermem : c . writermem ,
Request : c . Request ,
Params : c . Params ,
engine : c . engine ,
}
cp . writermem . ResponseWriter = nil
cp . Writer = & cp . writermem
cp . index = abortIndex
cp . handlers = nil
2022-09-28 17:30:40 +00:00
cp . Keys = map [ string ] any { }
2021-08-12 19:03:24 +00:00
for k , v := range c . Keys {
cp . Keys [ k ] = v
}
paramCopy := make ( [ ] Param , len ( cp . Params ) )
copy ( paramCopy , cp . Params )
cp . Params = paramCopy
return & cp
}
// HandlerName returns the main handler'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 ( ) )
}
// HandlerNames returns a list of all registered handlers for this context in descending order,
// following the semantics of HandlerName()
func ( c * Context ) HandlerNames ( ) [ ] string {
hn := make ( [ ] string , 0 , len ( c . handlers ) )
for _ , val := range c . handlers {
hn = append ( hn , nameOfFunction ( val ) )
}
return hn
}
// Handler returns the main handler.
func ( c * Context ) Handler ( ) HandlerFunc {
return c . handlers . Last ( )
}
// FullPath returns a matched route full path. For not found routes
// returns an empty string.
2023-02-25 12:12:40 +00:00
//
// router.GET("/user/:id", func(c *gin.Context) {
// c.FullPath() == "/user/:id" // true
// })
2021-08-12 19:03:24 +00:00
func ( c * Context ) FullPath ( ) string {
return c . fullPath
}
/************************************/
/*********** 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 ++
for c . index < int8 ( len ( c . handlers ) ) {
c . handlers [ c . index ] ( c )
c . index ++
}
}
// IsAborted returns true if the current context was aborted.
func ( c * Context ) IsAborted ( ) bool {
return c . index >= abortIndex
}
// Abort prevents pending handlers from being called. Note that this will not stop the current handler.
// Let's say you have an authorization middleware that validates that the current request is authorized.
// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers
// for this request are not called.
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 authenticate a request could use: context.AbortWithStatus(401).
func ( c * Context ) AbortWithStatus ( code int ) {
c . Status ( code )
c . Writer . WriteHeaderNow ( )
c . Abort ( )
}
// AbortWithStatusJSON calls `Abort()` and then `JSON` internally.
// This method stops the chain, writes the status code and return a JSON body.
// It also sets the Content-Type as "application/json".
2022-09-28 17:30:40 +00:00
func ( c * Context ) AbortWithStatusJSON ( code int , jsonObj any ) {
2021-08-12 19:03:24 +00:00
c . Abort ( )
c . JSON ( code , jsonObj )
}
// 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 *********/
/************************************/
// Error 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.
// Error will panic if err is nil.
func ( c * Context ) Error ( err error ) * Error {
if err == nil {
panic ( "err is nil" )
}
2022-09-28 17:30:40 +00:00
var parsedError * Error
ok := errors . As ( err , & parsedError )
2021-08-12 19:03:24 +00:00
if ! ok {
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 exclusively for this context.
// It also lazy initializes c.Keys if it was not used previously.
2022-09-28 17:30:40 +00:00
func ( c * Context ) Set ( key string , value any ) {
2021-08-12 19:03:24 +00:00
c . mu . Lock ( )
2023-02-25 12:12:40 +00:00
defer c . mu . Unlock ( )
2021-08-12 19:03:24 +00:00
if c . Keys == nil {
2022-09-28 17:30:40 +00:00
c . Keys = make ( map [ string ] any )
2021-08-12 19:03:24 +00:00
}
c . Keys [ key ] = value
}
// Get returns the value for the given key, ie: (value, true).
2022-09-28 17:30:40 +00:00
// If the value does not exist it returns (nil, false)
func ( c * Context ) Get ( key string ) ( value any , exists bool ) {
2021-08-12 19:03:24 +00:00
c . mu . RLock ( )
2023-02-25 12:12:40 +00:00
defer c . mu . RUnlock ( )
2021-08-12 19:03:24 +00:00
value , exists = c . Keys [ key ]
return
}
// MustGet returns the value for the given key if it exists, otherwise it panics.
2022-09-28 17:30:40 +00:00
func ( c * Context ) MustGet ( key string ) any {
2021-08-12 19:03:24 +00:00
if value , exists := c . Get ( key ) ; exists {
return value
}
panic ( "Key \"" + key + "\" does not exist" )
}
// GetString returns the value associated with the key as a string.
func ( c * Context ) GetString ( key string ) ( s string ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
s , _ = val . ( string )
}
return
}
// GetBool returns the value associated with the key as a boolean.
func ( c * Context ) GetBool ( key string ) ( b bool ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
b , _ = val . ( bool )
}
return
}
// GetInt returns the value associated with the key as an integer.
func ( c * Context ) GetInt ( key string ) ( i int ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
i , _ = val . ( int )
}
return
}
// GetInt64 returns the value associated with the key as an integer.
func ( c * Context ) GetInt64 ( key string ) ( i64 int64 ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
i64 , _ = val . ( int64 )
}
return
}
// GetUint returns the value associated with the key as an unsigned integer.
func ( c * Context ) GetUint ( key string ) ( ui uint ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
ui , _ = val . ( uint )
}
return
}
// GetUint64 returns the value associated with the key as an unsigned integer.
func ( c * Context ) GetUint64 ( key string ) ( ui64 uint64 ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
ui64 , _ = val . ( uint64 )
}
return
}
// GetFloat64 returns the value associated with the key as a float64.
func ( c * Context ) GetFloat64 ( key string ) ( f64 float64 ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
f64 , _ = val . ( float64 )
}
return
}
// GetTime returns the value associated with the key as time.
func ( c * Context ) GetTime ( key string ) ( t time . Time ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
t , _ = val . ( time . Time )
}
return
}
// GetDuration returns the value associated with the key as a duration.
func ( c * Context ) GetDuration ( key string ) ( d time . Duration ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
d , _ = val . ( time . Duration )
}
return
}
// GetStringSlice returns the value associated with the key as a slice of strings.
func ( c * Context ) GetStringSlice ( key string ) ( ss [ ] string ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
ss , _ = val . ( [ ] string )
}
return
}
// GetStringMap returns the value associated with the key as a map of interfaces.
2022-09-28 17:30:40 +00:00
func ( c * Context ) GetStringMap ( key string ) ( sm map [ string ] any ) {
2021-08-12 19:03:24 +00:00
if val , ok := c . Get ( key ) ; ok && val != nil {
2022-09-28 17:30:40 +00:00
sm , _ = val . ( map [ string ] any )
2021-08-12 19:03:24 +00:00
}
return
}
// GetStringMapString returns the value associated with the key as a map of strings.
func ( c * Context ) GetStringMapString ( key string ) ( sms map [ string ] string ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
sms , _ = val . ( map [ string ] string )
}
return
}
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
func ( c * Context ) GetStringMapStringSlice ( key string ) ( smss map [ string ] [ ] string ) {
if val , ok := c . Get ( key ) ; ok && val != nil {
smss , _ = val . ( map [ string ] [ ] string )
}
return
}
/************************************/
/************ INPUT DATA ************/
/************************************/
// Param returns the value of the URL param.
// It is a shortcut for c.Params.ByName(key)
2023-02-25 12:12:40 +00:00
//
// router.GET("/user/:id", func(c *gin.Context) {
// // a GET request to /user/john
// id := c.Param("id") // id == "/john"
// // a GET request to /user/john/
// id := c.Param("id") // id == "/john/"
// })
2021-08-12 19:03:24 +00:00
func ( c * Context ) Param ( key string ) string {
return c . Params . ByName ( key )
}
2022-09-28 17:30:40 +00:00
// AddParam adds param to context and
// replaces path param key with given value for e2e testing purposes
// Example Route: "/user/:id"
// AddParam("id", 1)
// Result: "/user/1"
func ( c * Context ) AddParam ( key , value string ) {
c . Params = append ( c . Params , Param { Key : key , Value : value } )
}
2021-08-12 19:03:24 +00:00
// Query returns the keyed url query value if it exists,
// otherwise it returns an empty string `("")`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
2023-02-25 12:12:40 +00:00
//
// GET /path?id=1234&name=Manu&value=
// c.Query("id") == "1234"
// c.Query("name") == "Manu"
// c.Query("value") == ""
// c.Query("wtf") == ""
2022-09-28 17:30:40 +00:00
func ( c * Context ) Query ( key string ) ( value string ) {
value , _ = c . GetQuery ( key )
return
2021-08-12 19:03:24 +00:00
}
// DefaultQuery returns the keyed url query value if it exists,
// otherwise it returns the specified defaultValue string.
// See: Query() and GetQuery() for further information.
2023-02-25 12:12:40 +00:00
//
// GET /?name=Manu&lastname=
// c.DefaultQuery("name", "unknown") == "Manu"
// c.DefaultQuery("id", "none") == "none"
// c.DefaultQuery("lastname", "none") == ""
2021-08-12 19:03:24 +00:00
func ( c * Context ) DefaultQuery ( key , defaultValue string ) string {
if value , ok := c . GetQuery ( key ) ; ok {
return value
}
return defaultValue
}
// GetQuery is like Query(), it returns the keyed url query value
// if it exists `(value, true)` (even when the value is an empty string),
// otherwise it returns `("", false)`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
2023-02-25 12:12:40 +00:00
//
// GET /?name=Manu&lastname=
// ("Manu", true) == c.GetQuery("name")
// ("", false) == c.GetQuery("id")
// ("", true) == c.GetQuery("lastname")
2021-08-12 19:03:24 +00:00
func ( c * Context ) GetQuery ( key string ) ( string , bool ) {
if values , ok := c . GetQueryArray ( key ) ; ok {
return values [ 0 ] , ok
}
return "" , false
}
// QueryArray returns a slice of strings for a given query key.
// The length of the slice depends on the number of params with the given key.
2022-09-28 17:30:40 +00:00
func ( c * Context ) QueryArray ( key string ) ( values [ ] string ) {
values , _ = c . GetQueryArray ( key )
return
2021-08-12 19:03:24 +00:00
}
func ( c * Context ) initQueryCache ( ) {
if c . queryCache == nil {
if c . Request != nil {
c . queryCache = c . Request . URL . Query ( )
} else {
c . queryCache = url . Values { }
}
}
}
// GetQueryArray returns a slice of strings for a given query key, plus
// a boolean value whether at least one value exists for the given key.
2022-09-28 17:30:40 +00:00
func ( c * Context ) GetQueryArray ( key string ) ( values [ ] string , ok bool ) {
2021-08-12 19:03:24 +00:00
c . initQueryCache ( )
2022-09-28 17:30:40 +00:00
values , ok = c . queryCache [ key ]
return
2021-08-12 19:03:24 +00:00
}
// QueryMap returns a map for a given query key.
2022-09-28 17:30:40 +00:00
func ( c * Context ) QueryMap ( key string ) ( dicts map [ string ] string ) {
dicts , _ = c . GetQueryMap ( key )
return
2021-08-12 19:03:24 +00:00
}
// GetQueryMap returns a map for a given query key, plus a boolean value
// whether at least one value exists for the given key.
func ( c * Context ) GetQueryMap ( key string ) ( map [ string ] string , bool ) {
c . initQueryCache ( )
return c . get ( c . queryCache , key )
}
// PostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns an empty string `("")`.
2022-09-28 17:30:40 +00:00
func ( c * Context ) PostForm ( key string ) ( value string ) {
value , _ = c . GetPostForm ( key )
return
2021-08-12 19:03:24 +00:00
}
// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns the specified defaultValue string.
// See: PostForm() and GetPostForm() for further information.
func ( c * Context ) DefaultPostForm ( key , defaultValue string ) string {
if value , ok := c . GetPostForm ( key ) ; ok {
return value
}
return defaultValue
}
// GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded
// form or multipart form when it exists `(value, true)` (even when the value is an empty string),
// otherwise it returns ("", false).
// For example, during a PATCH request to update the user's email:
2023-02-25 12:12:40 +00:00
//
// email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
// email= --> ("", true) := GetPostForm("email") // set email to ""
// --> ("", false) := GetPostForm("email") // do nothing with email
2021-08-12 19:03:24 +00:00
func ( c * Context ) GetPostForm ( key string ) ( string , bool ) {
if values , ok := c . GetPostFormArray ( key ) ; ok {
return values [ 0 ] , ok
}
return "" , false
}
// PostFormArray returns a slice of strings for a given form key.
// The length of the slice depends on the number of params with the given key.
2022-09-28 17:30:40 +00:00
func ( c * Context ) PostFormArray ( key string ) ( values [ ] string ) {
values , _ = c . GetPostFormArray ( key )
return
2021-08-12 19:03:24 +00:00
}
func ( c * Context ) initFormCache ( ) {
if c . formCache == nil {
c . formCache = make ( url . Values )
req := c . Request
if err := req . ParseMultipartForm ( c . engine . MaxMultipartMemory ) ; err != nil {
2022-09-28 17:30:40 +00:00
if ! errors . Is ( err , http . ErrNotMultipart ) {
2021-08-12 19:03:24 +00:00
debugPrint ( "error on parse multipart form array: %v" , err )
}
}
c . formCache = req . PostForm
}
}
// GetPostFormArray returns a slice of strings for a given form key, plus
// a boolean value whether at least one value exists for the given key.
2022-09-28 17:30:40 +00:00
func ( c * Context ) GetPostFormArray ( key string ) ( values [ ] string , ok bool ) {
2021-08-12 19:03:24 +00:00
c . initFormCache ( )
2022-09-28 17:30:40 +00:00
values , ok = c . formCache [ key ]
return
2021-08-12 19:03:24 +00:00
}
// PostFormMap returns a map for a given form key.
2022-09-28 17:30:40 +00:00
func ( c * Context ) PostFormMap ( key string ) ( dicts map [ string ] string ) {
dicts , _ = c . GetPostFormMap ( key )
return
2021-08-12 19:03:24 +00:00
}
// GetPostFormMap returns a map for a given form key, plus a boolean value
// whether at least one value exists for the given key.
func ( c * Context ) GetPostFormMap ( key string ) ( map [ string ] string , bool ) {
c . initFormCache ( )
return c . get ( c . formCache , key )
}
2023-02-25 12:12:40 +00:00
// get is an internal method and returns a map which satisfies conditions.
2021-08-12 19:03:24 +00:00
func ( c * Context ) get ( m map [ string ] [ ] string , key string ) ( map [ string ] string , bool ) {
dicts := make ( map [ string ] string )
exist := false
for k , v := range m {
if i := strings . IndexByte ( k , '[' ) ; i >= 1 && k [ 0 : i ] == key {
if j := strings . IndexByte ( k [ i + 1 : ] , ']' ) ; j >= 1 {
exist = true
dicts [ k [ i + 1 : ] [ : j ] ] = v [ 0 ]
}
}
}
return dicts , exist
}
// FormFile returns the first file for the provided form key.
func ( c * Context ) FormFile ( name string ) ( * multipart . FileHeader , error ) {
if c . Request . MultipartForm == nil {
if err := c . Request . ParseMultipartForm ( c . engine . MaxMultipartMemory ) ; err != nil {
return nil , err
}
}
f , fh , err := c . Request . FormFile ( name )
if err != nil {
return nil , err
}
f . Close ( )
return fh , err
}
// MultipartForm is the parsed multipart form, including file uploads.
func ( c * Context ) MultipartForm ( ) ( * multipart . Form , error ) {
err := c . Request . ParseMultipartForm ( c . engine . MaxMultipartMemory )
return c . Request . MultipartForm , err
}
// SaveUploadedFile uploads the form file to specific dst.
func ( c * Context ) SaveUploadedFile ( file * multipart . FileHeader , dst string ) error {
src , err := file . Open ( )
if err != nil {
return err
}
defer src . Close ( )
2023-02-25 12:12:40 +00:00
if err = os . MkdirAll ( filepath . Dir ( dst ) , 0750 ) ; err != nil {
return err
}
2021-08-12 19:03:24 +00:00
out , err := os . Create ( dst )
if err != nil {
return err
}
defer out . Close ( )
_ , err = io . Copy ( out , src )
return err
}
2022-09-28 17:30:40 +00:00
// Bind checks the Method and Content-Type to select a binding engine automatically,
// Depending on the "Content-Type" header different bindings are used, for example:
2023-02-25 12:12:40 +00:00
//
// "application/json" --> JSON binding
// "application/xml" --> XML binding
//
2021-08-12 19:03:24 +00:00
// It 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.
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
2022-09-28 17:30:40 +00:00
func ( c * Context ) Bind ( obj any ) error {
2021-08-12 19:03:24 +00:00
b := binding . Default ( c . Request . Method , c . ContentType ( ) )
return c . MustBindWith ( obj , b )
}
// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
2022-09-28 17:30:40 +00:00
func ( c * Context ) BindJSON ( obj any ) error {
2021-08-12 19:03:24 +00:00
return c . MustBindWith ( obj , binding . JSON )
}
// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
2022-09-28 17:30:40 +00:00
func ( c * Context ) BindXML ( obj any ) error {
2021-08-12 19:03:24 +00:00
return c . MustBindWith ( obj , binding . XML )
}
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
2022-09-28 17:30:40 +00:00
func ( c * Context ) BindQuery ( obj any ) error {
2021-08-12 19:03:24 +00:00
return c . MustBindWith ( obj , binding . Query )
}
// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
2022-09-28 17:30:40 +00:00
func ( c * Context ) BindYAML ( obj any ) error {
2021-08-12 19:03:24 +00:00
return c . MustBindWith ( obj , binding . YAML )
}
2022-09-28 17:30:40 +00:00
// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
func ( c * Context ) BindTOML ( obj interface { } ) error {
return c . MustBindWith ( obj , binding . TOML )
}
2021-08-12 19:03:24 +00:00
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
2022-09-28 17:30:40 +00:00
func ( c * Context ) BindHeader ( obj any ) error {
2021-08-12 19:03:24 +00:00
return c . MustBindWith ( obj , binding . Header )
}
// BindUri binds the passed struct pointer using binding.Uri.
// It will abort the request with HTTP 400 if any error occurs.
2022-09-28 17:30:40 +00:00
func ( c * Context ) BindUri ( obj any ) error {
2021-08-12 19:03:24 +00:00
if err := c . ShouldBindUri ( obj ) ; err != nil {
2023-02-25 12:12:40 +00:00
c . AbortWithError ( http . StatusBadRequest , err ) . SetType ( ErrorTypeBind ) //nolint: errcheck
2021-08-12 19:03:24 +00:00
return err
}
return nil
}
// MustBindWith binds the passed struct pointer using the specified binding engine.
// It will abort the request with HTTP 400 if any error occurs.
// See the binding package.
2022-09-28 17:30:40 +00:00
func ( c * Context ) MustBindWith ( obj any , b binding . Binding ) error {
2021-08-12 19:03:24 +00:00
if err := c . ShouldBindWith ( obj , b ) ; err != nil {
2023-02-25 12:12:40 +00:00
c . AbortWithError ( http . StatusBadRequest , err ) . SetType ( ErrorTypeBind ) //nolint: errcheck
2021-08-12 19:03:24 +00:00
return err
}
return nil
}
2022-09-28 17:30:40 +00:00
// ShouldBind checks the Method and Content-Type to select a binding engine automatically,
// Depending on the "Content-Type" header different bindings are used, for example:
2023-02-25 12:12:40 +00:00
//
// "application/json" --> JSON binding
// "application/xml" --> XML binding
//
2021-08-12 19:03:24 +00:00
// It 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.
2022-09-28 17:30:40 +00:00
// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
func ( c * Context ) ShouldBind ( obj any ) error {
2021-08-12 19:03:24 +00:00
b := binding . Default ( c . Request . Method , c . ContentType ( ) )
return c . ShouldBindWith ( obj , b )
}
// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
2022-09-28 17:30:40 +00:00
func ( c * Context ) ShouldBindJSON ( obj any ) error {
2021-08-12 19:03:24 +00:00
return c . ShouldBindWith ( obj , binding . JSON )
}
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
2022-09-28 17:30:40 +00:00
func ( c * Context ) ShouldBindXML ( obj any ) error {
2021-08-12 19:03:24 +00:00
return c . ShouldBindWith ( obj , binding . XML )
}
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
2022-09-28 17:30:40 +00:00
func ( c * Context ) ShouldBindQuery ( obj any ) error {
2021-08-12 19:03:24 +00:00
return c . ShouldBindWith ( obj , binding . Query )
}
// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
2022-09-28 17:30:40 +00:00
func ( c * Context ) ShouldBindYAML ( obj any ) error {
2021-08-12 19:03:24 +00:00
return c . ShouldBindWith ( obj , binding . YAML )
}
2022-09-28 17:30:40 +00:00
// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
func ( c * Context ) ShouldBindTOML ( obj interface { } ) error {
return c . ShouldBindWith ( obj , binding . TOML )
}
2021-08-12 19:03:24 +00:00
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
2022-09-28 17:30:40 +00:00
func ( c * Context ) ShouldBindHeader ( obj any ) error {
2021-08-12 19:03:24 +00:00
return c . ShouldBindWith ( obj , binding . Header )
}
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
2022-09-28 17:30:40 +00:00
func ( c * Context ) ShouldBindUri ( obj any ) error {
2021-08-12 19:03:24 +00:00
m := make ( map [ string ] [ ] string )
for _ , v := range c . Params {
m [ v . Key ] = [ ] string { v . Value }
}
return binding . Uri . BindUri ( m , obj )
}
// ShouldBindWith binds the passed struct pointer using the specified binding engine.
// See the binding package.
2022-09-28 17:30:40 +00:00
func ( c * Context ) ShouldBindWith ( obj any , b binding . Binding ) error {
2021-08-12 19:03:24 +00:00
return b . Bind ( c . Request , obj )
}
// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request
// body into the context, and reuse when it is called again.
//
// NOTE: This method reads the body before binding. So you should use
// ShouldBindWith for better performance if you need to call only once.
2022-09-28 17:30:40 +00:00
func ( c * Context ) ShouldBindBodyWith ( obj any , bb binding . BindingBody ) ( err error ) {
2021-08-12 19:03:24 +00:00
var body [ ] byte
if cb , ok := c . Get ( BodyBytesKey ) ; ok {
if cbb , ok := cb . ( [ ] byte ) ; ok {
body = cbb
}
}
if body == nil {
2023-02-25 12:12:40 +00:00
body , err = io . ReadAll ( c . Request . Body )
2021-08-12 19:03:24 +00:00
if err != nil {
return err
}
c . Set ( BodyBytesKey , body )
}
return bb . BindBody ( body , obj )
}
2021-11-27 14:26:58 +00:00
// ClientIP implements one best effort algorithm to return the real client IP.
2023-02-25 12:12:40 +00:00
// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
2021-11-27 14:26:58 +00:00
// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
// If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy,
2022-09-28 17:30:40 +00:00
// the remote IP (coming from Request.RemoteAddr) is returned.
2021-08-12 19:03:24 +00:00
func ( c * Context ) ClientIP ( ) string {
2021-11-27 14:26:58 +00:00
// Check if we're running on a trusted platform, continue running backwards if error
if c . engine . TrustedPlatform != "" {
// Developers can define their own header of Trusted Platform or use predefined constants
if addr := c . requestHeader ( c . engine . TrustedPlatform ) ; addr != "" {
2021-08-12 19:03:24 +00:00
return addr
}
}
// Legacy "AppEngine" flag
if c . engine . AppEngine {
log . Println ( ` The AppEngine flag is going to be deprecated. Please check issues #2723 and #2739 and use 'TrustedPlatform: gin.PlatformGoogleAppEngine' instead. ` )
if addr := c . requestHeader ( "X-Appengine-Remote-Addr" ) ; addr != "" {
return addr
}
}
2022-09-28 17:30:40 +00:00
// It also checks if the remoteIP is a trusted proxy or not.
// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
// defined by Engine.SetTrustedProxies()
remoteIP := net . ParseIP ( c . RemoteIP ( ) )
2021-08-12 19:03:24 +00:00
if remoteIP == nil {
return ""
}
2022-09-28 17:30:40 +00:00
trusted := c . engine . isTrustedProxy ( remoteIP )
2021-08-12 19:03:24 +00:00
if trusted && c . engine . ForwardedByClientIP && c . engine . RemoteIPHeaders != nil {
for _ , headerName := range c . engine . RemoteIPHeaders {
2021-11-27 14:26:58 +00:00
ip , valid := c . engine . validateHeader ( c . requestHeader ( headerName ) )
2021-08-12 19:03:24 +00:00
if valid {
return ip
}
}
}
return remoteIP . String ( )
}
// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
2022-09-28 17:30:40 +00:00
func ( c * Context ) RemoteIP ( ) string {
2021-08-12 19:03:24 +00:00
ip , _ , err := net . SplitHostPort ( strings . TrimSpace ( c . Request . RemoteAddr ) )
if err != nil {
2022-09-28 17:30:40 +00:00
return ""
2021-08-12 19:03:24 +00:00
}
2022-09-28 17:30:40 +00:00
return ip
2021-08-12 19:03:24 +00:00
}
// ContentType returns the Content-Type header of the request.
func ( c * Context ) ContentType ( ) string {
return filterFlags ( c . requestHeader ( "Content-Type" ) )
}
// IsWebsocket returns true if the request headers indicate that a websocket
// handshake is being initiated by the client.
func ( c * Context ) IsWebsocket ( ) bool {
if strings . Contains ( strings . ToLower ( c . requestHeader ( "Connection" ) ) , "upgrade" ) &&
strings . EqualFold ( c . requestHeader ( "Upgrade" ) , "websocket" ) {
return true
}
return false
}
func ( c * Context ) requestHeader ( key string ) string {
return c . Request . Header . Get ( key )
}
/************************************/
/******** RESPONSE RENDERING ********/
/************************************/
// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.
func bodyAllowedForStatus ( status int ) bool {
switch {
case status >= 100 && status <= 199 :
return false
case status == http . StatusNoContent :
return false
case status == http . StatusNotModified :
return false
}
return true
}
// Status sets the HTTP response code.
func ( c * Context ) Status ( code int ) {
c . Writer . WriteHeader ( code )
}
2022-09-28 17:30:40 +00:00
// Header is an intelligent shortcut for c.Writer.Header().Set(key, value).
2021-08-12 19:03:24 +00:00
// 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 value == "" {
c . Writer . Header ( ) . Del ( key )
return
}
c . Writer . Header ( ) . Set ( key , value )
}
// GetHeader returns value from request headers.
func ( c * Context ) GetHeader ( key string ) string {
return c . requestHeader ( key )
}
2022-09-28 17:30:40 +00:00
// GetRawData returns stream data.
2021-08-12 19:03:24 +00:00
func ( c * Context ) GetRawData ( ) ( [ ] byte , error ) {
2023-02-25 12:12:40 +00:00
return io . ReadAll ( c . Request . Body )
2021-08-12 19:03:24 +00:00
}
// SetSameSite with cookie
func ( c * Context ) SetSameSite ( samesite http . SameSite ) {
c . sameSite = samesite
}
// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.
// The provided cookie must have a valid Name. Invalid cookies may be
// silently dropped.
func ( c * Context ) SetCookie ( name , value string , maxAge int , path , domain string , secure , httpOnly bool ) {
if path == "" {
path = "/"
}
http . SetCookie ( c . Writer , & http . Cookie {
Name : name ,
Value : url . QueryEscape ( value ) ,
MaxAge : maxAge ,
Path : path ,
Domain : domain ,
SameSite : c . sameSite ,
Secure : secure ,
HttpOnly : httpOnly ,
} )
}
// Cookie returns the named cookie provided in the request or
// ErrNoCookie if not found. And return the named cookie is unescaped.
// If multiple cookies match the given name, only one cookie will
// be returned.
func ( c * Context ) Cookie ( name string ) ( string , error ) {
cookie , err := c . Request . Cookie ( name )
if err != nil {
return "" , err
}
val , _ := url . QueryUnescape ( cookie . Value )
return val , nil
}
// Render writes the response headers and calls render.Render to render data.
func ( c * Context ) Render ( code int , r render . Render ) {
c . Status ( code )
if ! bodyAllowedForStatus ( code ) {
r . WriteContentType ( c . Writer )
c . Writer . WriteHeaderNow ( )
return
}
if err := r . Render ( c . Writer ) ; err != nil {
2023-02-25 12:12:40 +00:00
// Pushing error to c.Errors
_ = c . Error ( err )
c . Abort ( )
2021-08-12 19:03:24 +00:00
}
}
// 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/
2022-09-28 17:30:40 +00:00
func ( c * Context ) HTML ( code int , name string , obj any ) {
2021-08-12 19:03:24 +00:00
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".
2022-09-28 17:30:40 +00:00
// WARNING: we recommend using this only for development purposes since printing pretty JSON is
2021-08-12 19:03:24 +00:00
// more CPU and bandwidth consuming. Use Context.JSON() instead.
2022-09-28 17:30:40 +00:00
func ( c * Context ) IndentedJSON ( code int , obj any ) {
2021-08-12 19:03:24 +00:00
c . Render ( code , render . IndentedJSON { Data : obj } )
}
// SecureJSON serializes the given struct as Secure JSON into the response body.
// Default prepends "while(1)," to response body if the given struct is array values.
// It also sets the Content-Type as "application/json".
2022-09-28 17:30:40 +00:00
func ( c * Context ) SecureJSON ( code int , obj any ) {
2021-08-12 19:03:24 +00:00
c . Render ( code , render . SecureJSON { Prefix : c . engine . secureJSONPrefix , Data : obj } )
}
// JSONP serializes the given struct as JSON into the response body.
// It adds padding to response body to request data from a server residing in a different domain than the client.
// It also sets the Content-Type as "application/javascript".
2022-09-28 17:30:40 +00:00
func ( c * Context ) JSONP ( code int , obj any ) {
2021-08-12 19:03:24 +00:00
callback := c . DefaultQuery ( "callback" , "" )
if callback == "" {
c . Render ( code , render . JSON { Data : obj } )
return
}
c . Render ( code , render . JsonpJSON { Callback : callback , Data : obj } )
}
// JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json".
2022-09-28 17:30:40 +00:00
func ( c * Context ) JSON ( code int , obj any ) {
2021-08-12 19:03:24 +00:00
c . Render ( code , render . JSON { Data : obj } )
}
// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.
// It also sets the Content-Type as "application/json".
2022-09-28 17:30:40 +00:00
func ( c * Context ) AsciiJSON ( code int , obj any ) {
2021-08-12 19:03:24 +00:00
c . Render ( code , render . AsciiJSON { Data : obj } )
}
// PureJSON serializes the given struct as JSON into the response body.
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
2022-09-28 17:30:40 +00:00
func ( c * Context ) PureJSON ( code int , obj any ) {
2021-08-12 19:03:24 +00:00
c . Render ( code , render . PureJSON { Data : obj } )
}
// XML serializes the given struct as XML into the response body.
// It also sets the Content-Type as "application/xml".
2022-09-28 17:30:40 +00:00
func ( c * Context ) XML ( code int , obj any ) {
2021-08-12 19:03:24 +00:00
c . Render ( code , render . XML { Data : obj } )
}
// YAML serializes the given struct as YAML into the response body.
2022-09-28 17:30:40 +00:00
func ( c * Context ) YAML ( code int , obj any ) {
2021-08-12 19:03:24 +00:00
c . Render ( code , render . YAML { Data : obj } )
}
2022-09-28 17:30:40 +00:00
// TOML serializes the given struct as TOML into the response body.
func ( c * Context ) TOML ( code int , obj interface { } ) {
c . Render ( code , render . TOML { Data : obj } )
}
2021-08-12 19:03:24 +00:00
// ProtoBuf serializes the given struct as ProtoBuf into the response body.
2022-09-28 17:30:40 +00:00
func ( c * Context ) ProtoBuf ( code int , obj any ) {
2021-08-12 19:03:24 +00:00
c . Render ( code , render . ProtoBuf { Data : obj } )
}
// String writes the given string into the response body.
2022-09-28 17:30:40 +00:00
func ( c * Context ) String ( code int , format string , values ... any ) {
2021-08-12 19:03:24 +00:00
c . Render ( code , render . String { Format : format , Data : values } )
}
2022-09-28 17:30:40 +00:00
// Redirect returns an HTTP redirect to the specific location.
2021-08-12 19:03:24 +00:00
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 ,
} )
}
// DataFromReader writes the specified reader into the body stream and updates the HTTP code.
func ( c * Context ) DataFromReader ( code int , contentLength int64 , contentType string , reader io . Reader , extraHeaders map [ string ] string ) {
c . Render ( code , render . Reader {
Headers : extraHeaders ,
ContentType : contentType ,
ContentLength : contentLength ,
Reader : reader ,
} )
}
// File writes the specified file into the body stream in an efficient way.
func ( c * Context ) File ( filepath string ) {
http . ServeFile ( c . Writer , c . Request , filepath )
}
// FileFromFS writes the specified file from http.FileSystem into the body stream in an efficient way.
func ( c * Context ) FileFromFS ( filepath string , fs http . FileSystem ) {
defer func ( old string ) {
c . Request . URL . Path = old
} ( c . Request . URL . Path )
c . Request . URL . Path = filepath
http . FileServer ( fs ) . ServeHTTP ( c . Writer , c . Request )
}
// FileAttachment writes the specified file into the body stream in an efficient way
// On the client side, the file will typically be downloaded with the given filename
func ( c * Context ) FileAttachment ( filepath , filename string ) {
2022-09-28 17:30:40 +00:00
if isASCII ( filename ) {
c . Writer . Header ( ) . Set ( "Content-Disposition" , ` attachment; filename=" ` + filename + ` " ` )
} else {
c . Writer . Header ( ) . Set ( "Content-Disposition" , ` attachment; filename*=UTF-8'' ` + url . QueryEscape ( filename ) )
}
2021-08-12 19:03:24 +00:00
http . ServeFile ( c . Writer , c . Request , filepath )
}
// SSEvent writes a Server-Sent Event into the body stream.
2022-09-28 17:30:40 +00:00
func ( c * Context ) SSEvent ( name string , message any ) {
2021-08-12 19:03:24 +00:00
c . Render ( - 1 , sse . Event {
Event : name ,
Data : message ,
} )
}
// Stream sends a streaming response and returns a boolean
// indicates "Is client disconnected in middle of stream"
func ( c * Context ) Stream ( step func ( w io . Writer ) bool ) bool {
w := c . Writer
clientGone := w . CloseNotify ( )
for {
select {
case <- clientGone :
return true
default :
keepOpen := step ( w )
w . Flush ( )
if ! keepOpen {
return false
}
}
}
}
/************************************/
/******** CONTENT NEGOTIATION *******/
/************************************/
// Negotiate contains all negotiations data.
type Negotiate struct {
Offered [ ] string
HTMLName string
2022-09-28 17:30:40 +00:00
HTMLData any
JSONData any
XMLData any
YAMLData any
Data any
TOMLData any
2021-08-12 19:03:24 +00:00
}
2022-09-28 17:30:40 +00:00
// Negotiate calls different Render according to acceptable Accept format.
2021-08-12 19:03:24 +00:00
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 )
case binding . MIMEYAML :
data := chooseData ( config . YAMLData , config . Data )
c . YAML ( code , data )
2022-09-28 17:30:40 +00:00
case binding . MIMETOML :
data := chooseData ( config . TOMLData , config . Data )
c . TOML ( code , data )
2021-08-12 19:03:24 +00:00
default :
2023-02-25 12:12:40 +00:00
c . AbortWithError ( http . StatusNotAcceptable , errors . New ( "the accepted formats are not offered by the server" ) ) //nolint: errcheck
2021-08-12 19:03:24 +00:00
}
}
// NegotiateFormat returns an acceptable Accept format.
func ( c * Context ) NegotiateFormat ( offered ... string ) string {
assert1 ( len ( offered ) > 0 , "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 _ , offer := range offered {
// According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers,
// therefore we can just iterate over the string without casting it into []rune
i := 0
2023-02-25 12:12:40 +00:00
for ; i < len ( accepted ) && i < len ( offer ) ; i ++ {
2021-08-12 19:03:24 +00:00
if accepted [ i ] == '*' || offer [ i ] == '*' {
return offer
}
if accepted [ i ] != offer [ i ] {
break
}
}
if i == len ( accepted ) {
return offer
}
}
}
return ""
}
// SetAccepted sets Accept header data.
func ( c * Context ) SetAccepted ( formats ... string ) {
c . Accepted = formats
}
/************************************/
/***** GOLANG.ORG/X/NET/CONTEXT *****/
/************************************/
2022-09-28 17:30:40 +00:00
// Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
2021-08-12 19:03:24 +00:00
func ( c * Context ) Deadline ( ) ( deadline time . Time , ok bool ) {
2022-09-28 17:30:40 +00:00
if ! c . engine . ContextWithFallback || c . Request == nil || c . Request . Context ( ) == nil {
return
}
return c . Request . Context ( ) . Deadline ( )
2021-08-12 19:03:24 +00:00
}
2022-09-28 17:30:40 +00:00
// Done returns nil (chan which will wait forever) when c.Request has no Context.
2021-08-12 19:03:24 +00:00
func ( c * Context ) Done ( ) <- chan struct { } {
2022-09-28 17:30:40 +00:00
if ! c . engine . ContextWithFallback || c . Request == nil || c . Request . Context ( ) == nil {
return nil
}
return c . Request . Context ( ) . Done ( )
2021-08-12 19:03:24 +00:00
}
2022-09-28 17:30:40 +00:00
// Err returns nil when c.Request has no Context.
2021-08-12 19:03:24 +00:00
func ( c * Context ) Err ( ) error {
2022-09-28 17:30:40 +00:00
if ! c . engine . ContextWithFallback || c . Request == nil || c . Request . Context ( ) == nil {
return nil
}
return c . Request . Context ( ) . Err ( )
2021-08-12 19:03:24 +00:00
}
// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
2022-09-28 17:30:40 +00:00
func ( c * Context ) Value ( key any ) any {
2021-08-12 19:03:24 +00:00
if key == 0 {
return c . Request
}
2022-09-28 17:30:40 +00:00
if key == ContextKey {
return c
}
2021-08-12 19:03:24 +00:00
if keyAsString , ok := key . ( string ) ; ok {
2022-09-28 17:30:40 +00:00
if val , exists := c . Get ( keyAsString ) ; exists {
return val
}
2021-08-12 19:03:24 +00:00
}
2022-09-28 17:30:40 +00:00
if ! c . engine . ContextWithFallback || c . Request == nil || c . Request . Context ( ) == nil {
return nil
}
return c . Request . Context ( ) . Value ( key )
2021-08-12 19:03:24 +00:00
}