2024-10-18 14:41:55 +08: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.
package gin
import (
"errors"
2024-10-18 17:21:19 +08:00
"github.com/Superdanda/hade/framework"
2024-10-18 14:41:55 +08:00
"io"
"log"
"math"
"mime/multipart"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/Superdanda/hade/framework/gin/binding"
"github.com/Superdanda/hade/framework/gin/render"
"github.com/gin-contrib/sse"
)
// 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
MIMETOML = binding . MIMETOML
)
// BodyBytesKey indicates a default body bytes key.
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
// ContextKey is the key that a Context returns itself for.
const ContextKey = "_gin-gonic/gin/contextkey"
type ContextKeyType int
const ContextRequestKey ContextKeyType = 0
// abortIndex represents a typical value used in abort functions.
const abortIndex int8 = math . MaxInt8 >> 1
// 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 {
2024-10-18 17:21:19 +08:00
//新增 Hade框架的 容器
container framework . Container
2024-10-18 14:41:55 +08:00
writermem responseWriter
Request * http . Request
Writer ResponseWriter
Params Params
handlers HandlersChain
index int8
fullPath string
engine * Engine
params * Params
skippedNodes * [ ] skippedNode
// This mutex protects Keys map.
mu sync . RWMutex
// Keys is a key/value pair exclusively for the context of each request.
Keys map [ string ] any
// 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
// queryCache caches the query result from c.Request.URL.Query().
queryCache url . Values
// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
// 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 ********/
/************************************/
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) reset ( ) {
ctx . Writer = & ctx . writermem
ctx . Params = ctx . Params [ : 0 ]
ctx . handlers = nil
ctx . index = - 1
2024-10-18 14:41:55 +08:00
2024-10-18 17:21:19 +08:00
ctx . fullPath = ""
ctx . Keys = nil
ctx . Errors = ctx . Errors [ : 0 ]
ctx . Accepted = nil
ctx . queryCache = nil
ctx . formCache = nil
ctx . sameSite = 0
* ctx . params = ( * ctx . params ) [ : 0 ]
* ctx . skippedNodes = ( * ctx . skippedNodes ) [ : 0 ]
2024-10-18 14:41:55 +08: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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Copy ( ) * Context {
2024-10-18 14:41:55 +08:00
cp := Context {
2024-10-18 17:21:19 +08:00
writermem : ctx . writermem ,
Request : ctx . Request ,
engine : ctx . engine ,
2024-10-18 14:41:55 +08:00
}
cp . writermem . ResponseWriter = nil
cp . Writer = & cp . writermem
cp . index = abortIndex
cp . handlers = nil
2024-10-18 17:21:19 +08:00
cp . fullPath = ctx . fullPath
2024-10-18 14:41:55 +08:00
2024-10-18 17:21:19 +08:00
cKeys := ctx . Keys
2024-10-18 14:41:55 +08:00
cp . Keys = make ( map [ string ] any , len ( cKeys ) )
2024-10-18 17:21:19 +08:00
ctx . mu . RLock ( )
2024-10-18 14:41:55 +08:00
for k , v := range cKeys {
cp . Keys [ k ] = v
}
2024-10-18 17:21:19 +08:00
ctx . mu . RUnlock ( )
2024-10-18 14:41:55 +08:00
2024-10-18 17:21:19 +08:00
cParams := ctx . Params
2024-10-18 14:41:55 +08:00
cp . Params = make ( [ ] Param , len ( cParams ) )
copy ( cp . Params , cParams )
return & cp
}
// HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()",
// this function will return "main.handleGetUsers".
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) HandlerName ( ) string {
return nameOfFunction ( ctx . handlers . Last ( ) )
2024-10-18 14:41:55 +08:00
}
// HandlerNames returns a list of all registered handlers for this context in descending order,
// following the semantics of HandlerName()
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) HandlerNames ( ) [ ] string {
hn := make ( [ ] string , 0 , len ( ctx . handlers ) )
for _ , val := range ctx . handlers {
2024-10-18 14:41:55 +08:00
hn = append ( hn , nameOfFunction ( val ) )
}
return hn
}
// Handler returns the main handler.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Handler ( ) HandlerFunc {
return ctx . handlers . Last ( )
2024-10-18 14:41:55 +08:00
}
// FullPath returns a matched route full path. For not found routes
// returns an empty string.
//
// router.GET("/user/:id", func(c *gin.Context) {
// c.FullPath() == "/user/:id" // true
// })
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) FullPath ( ) string {
return ctx . fullPath
2024-10-18 14:41:55 +08:00
}
/************************************/
/*********** 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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Next ( ) {
ctx . index ++
for ctx . index < int8 ( len ( ctx . handlers ) ) {
ctx . handlers [ ctx . index ] ( ctx )
ctx . index ++
2024-10-18 14:41:55 +08:00
}
}
// IsAborted returns true if the current context was aborted.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) IsAborted ( ) bool {
return ctx . index >= abortIndex
2024-10-18 14:41:55 +08:00
}
// 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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Abort ( ) {
ctx . index = abortIndex
2024-10-18 14:41:55 +08:00
}
// 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).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) AbortWithStatus ( code int ) {
ctx . Status ( code )
ctx . Writer . WriteHeaderNow ( )
ctx . Abort ( )
2024-10-18 14:41:55 +08:00
}
// 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".
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) AbortWithStatusJSON ( code int , jsonObj any ) {
ctx . Abort ( )
ctx . JSON ( code , jsonObj )
2024-10-18 14:41:55 +08:00
}
// 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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) AbortWithError ( code int , err error ) * Error {
ctx . AbortWithStatus ( code )
return ctx . Error ( err )
2024-10-18 14:41:55 +08:00
}
/************************************/
/********* 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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Error ( err error ) * Error {
2024-10-18 14:41:55 +08:00
if err == nil {
panic ( "err is nil" )
}
var parsedError * Error
ok := errors . As ( err , & parsedError )
if ! ok {
parsedError = & Error {
Err : err ,
Type : ErrorTypePrivate ,
}
}
2024-10-18 17:21:19 +08:00
ctx . Errors = append ( ctx . Errors , parsedError )
2024-10-18 14:41:55 +08:00
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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Set ( key string , value any ) {
ctx . mu . Lock ( )
defer ctx . mu . Unlock ( )
if ctx . Keys == nil {
ctx . Keys = make ( map [ string ] any )
2024-10-18 14:41:55 +08:00
}
2024-10-18 17:21:19 +08:00
ctx . Keys [ key ] = value
2024-10-18 14:41:55 +08:00
}
// Get returns the value for the given key, ie: (value, true).
// If the value does not exist it returns (nil, false)
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Get ( key string ) ( value any , exists bool ) {
ctx . mu . RLock ( )
defer ctx . mu . RUnlock ( )
value , exists = ctx . Keys [ key ]
2024-10-18 14:41:55 +08:00
return
}
// MustGet returns the value for the given key if it exists, otherwise it panics.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) MustGet ( key string ) any {
if value , exists := ctx . Get ( key ) ; exists {
2024-10-18 14:41:55 +08:00
return value
}
panic ( "Key \"" + key + "\" does not exist" )
}
// GetString returns the value associated with the key as a string.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetString ( key string ) ( s string ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
s , _ = val . ( string )
}
return
}
// GetBool returns the value associated with the key as a boolean.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetBool ( key string ) ( b bool ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
b , _ = val . ( bool )
}
return
}
// GetInt returns the value associated with the key as an integer.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetInt ( key string ) ( i int ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
i , _ = val . ( int )
}
return
}
// GetInt64 returns the value associated with the key as an integer.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetInt64 ( key string ) ( i64 int64 ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
i64 , _ = val . ( int64 )
}
return
}
// GetUint returns the value associated with the key as an unsigned integer.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetUint ( key string ) ( ui uint ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
ui , _ = val . ( uint )
}
return
}
// GetUint64 returns the value associated with the key as an unsigned integer.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetUint64 ( key string ) ( ui64 uint64 ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
ui64 , _ = val . ( uint64 )
}
return
}
// GetFloat64 returns the value associated with the key as a float64.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetFloat64 ( key string ) ( f64 float64 ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
f64 , _ = val . ( float64 )
}
return
}
// GetTime returns the value associated with the key as time.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetTime ( key string ) ( t time . Time ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
t , _ = val . ( time . Time )
}
return
}
// GetDuration returns the value associated with the key as a duration.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetDuration ( key string ) ( d time . Duration ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
d , _ = val . ( time . Duration )
}
return
}
// GetStringSlice returns the value associated with the key as a slice of strings.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetStringSlice ( key string ) ( ss [ ] string ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
ss , _ = val . ( [ ] string )
}
return
}
// GetStringMap returns the value associated with the key as a map of interfaces.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetStringMap ( key string ) ( sm map [ string ] any ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
sm , _ = val . ( map [ string ] any )
}
return
}
// GetStringMapString returns the value associated with the key as a map of strings.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetStringMapString ( key string ) ( sms map [ string ] string ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
sms , _ = val . ( map [ string ] string )
}
return
}
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetStringMapStringSlice ( key string ) ( smss map [ string ] [ ] string ) {
if val , ok := ctx . Get ( key ) ; ok && val != nil {
2024-10-18 14:41:55 +08:00
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)
//
// 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/"
// })
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Param ( key string ) string {
return ctx . Params . ByName ( key )
2024-10-18 14:41:55 +08: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"
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) AddParam ( key , value string ) {
ctx . Params = append ( ctx . Params , Param { Key : key , Value : value } )
2024-10-18 14:41:55 +08: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)`
//
// GET /path?id=1234&name=Manu&value=
// c.Query("id") == "1234"
// c.Query("name") == "Manu"
// c.Query("value") == ""
// c.Query("wtf") == ""
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Query ( key string ) ( value string ) {
value , _ = ctx . GetQuery ( key )
2024-10-18 14:41:55 +08:00
return
}
// DefaultQuery returns the keyed url query value if it exists,
// otherwise it returns the specified defaultValue string.
// See: Query() and GetQuery() for further information.
//
// GET /?name=Manu&lastname=
// c.DefaultQuery("name", "unknown") == "Manu"
// c.DefaultQuery("id", "none") == "none"
// c.DefaultQuery("lastname", "none") == ""
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) DefaultQuery ( key , defaultValue string ) string {
if value , ok := ctx . GetQuery ( key ) ; ok {
2024-10-18 14:41:55 +08:00
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)`
//
// GET /?name=Manu&lastname=
// ("Manu", true) == c.GetQuery("name")
// ("", false) == c.GetQuery("id")
// ("", true) == c.GetQuery("lastname")
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetQuery ( key string ) ( string , bool ) {
if values , ok := ctx . GetQueryArray ( key ) ; ok {
2024-10-18 14:41:55 +08:00
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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) QueryArray ( key string ) ( values [ ] string ) {
values , _ = ctx . GetQueryArray ( key )
2024-10-18 14:41:55 +08:00
return
}
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) initQueryCache ( ) {
if ctx . queryCache == nil {
if ctx . Request != nil {
ctx . queryCache = ctx . Request . URL . Query ( )
2024-10-18 14:41:55 +08:00
} else {
2024-10-18 17:21:19 +08:00
ctx . queryCache = url . Values { }
2024-10-18 14:41:55 +08:00
}
}
}
// 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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetQueryArray ( key string ) ( values [ ] string , ok bool ) {
ctx . initQueryCache ( )
values , ok = ctx . queryCache [ key ]
2024-10-18 14:41:55 +08:00
return
}
// QueryMap returns a map for a given query key.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) QueryMap ( key string ) ( dicts map [ string ] string ) {
dicts , _ = ctx . GetQueryMap ( key )
2024-10-18 14:41:55 +08:00
return
}
// GetQueryMap returns a map for a given query key, plus a boolean value
// whether at least one value exists for the given key.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetQueryMap ( key string ) ( map [ string ] string , bool ) {
ctx . initQueryCache ( )
return ctx . get ( ctx . queryCache , key )
2024-10-18 14:41:55 +08:00
}
// PostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns an empty string `("")`.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) PostForm ( key string ) ( value string ) {
value , _ = ctx . GetPostForm ( key )
2024-10-18 14:41:55 +08:00
return
}
// 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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) DefaultPostForm ( key , defaultValue string ) string {
if value , ok := ctx . GetPostForm ( key ) ; ok {
2024-10-18 14:41:55 +08:00
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:
//
// 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
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetPostForm ( key string ) ( string , bool ) {
if values , ok := ctx . GetPostFormArray ( key ) ; ok {
2024-10-18 14:41:55 +08:00
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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) PostFormArray ( key string ) ( values [ ] string ) {
values , _ = ctx . GetPostFormArray ( key )
2024-10-18 14:41:55 +08:00
return
}
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) initFormCache ( ) {
if ctx . formCache == nil {
ctx . formCache = make ( url . Values )
req := ctx . Request
if err := req . ParseMultipartForm ( ctx . engine . MaxMultipartMemory ) ; err != nil {
2024-10-18 14:41:55 +08:00
if ! errors . Is ( err , http . ErrNotMultipart ) {
debugPrint ( "error on parse multipart form array: %v" , err )
}
}
2024-10-18 17:21:19 +08:00
ctx . formCache = req . PostForm
2024-10-18 14:41:55 +08:00
}
}
// 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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetPostFormArray ( key string ) ( values [ ] string , ok bool ) {
ctx . initFormCache ( )
values , ok = ctx . formCache [ key ]
2024-10-18 14:41:55 +08:00
return
}
// PostFormMap returns a map for a given form key.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) PostFormMap ( key string ) ( dicts map [ string ] string ) {
dicts , _ = ctx . GetPostFormMap ( key )
2024-10-18 14:41:55 +08:00
return
}
// GetPostFormMap returns a map for a given form key, plus a boolean value
// whether at least one value exists for the given key.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetPostFormMap ( key string ) ( map [ string ] string , bool ) {
ctx . initFormCache ( )
return ctx . get ( ctx . formCache , key )
2024-10-18 14:41:55 +08:00
}
// get is an internal method and returns a map which satisfies conditions.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) get ( m map [ string ] [ ] string , key string ) ( map [ string ] string , bool ) {
2024-10-18 14:41:55 +08:00
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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) FormFile ( name string ) ( * multipart . FileHeader , error ) {
if ctx . Request . MultipartForm == nil {
if err := ctx . Request . ParseMultipartForm ( ctx . engine . MaxMultipartMemory ) ; err != nil {
2024-10-18 14:41:55 +08:00
return nil , err
}
}
2024-10-18 17:21:19 +08:00
f , fh , err := ctx . Request . FormFile ( name )
2024-10-18 14:41:55 +08:00
if err != nil {
return nil , err
}
f . Close ( )
return fh , err
}
// MultipartForm is the parsed multipart form, including file uploads.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) MultipartForm ( ) ( * multipart . Form , error ) {
err := ctx . Request . ParseMultipartForm ( ctx . engine . MaxMultipartMemory )
return ctx . Request . MultipartForm , err
2024-10-18 14:41:55 +08:00
}
// SaveUploadedFile uploads the form file to specific dst.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) SaveUploadedFile ( file * multipart . FileHeader , dst string ) error {
2024-10-18 14:41:55 +08:00
src , err := file . Open ( )
if err != nil {
return err
}
defer src . Close ( )
if err = os . MkdirAll ( filepath . Dir ( dst ) , 0750 ) ; err != nil {
return err
}
out , err := os . Create ( dst )
if err != nil {
return err
}
defer out . Close ( )
_ , err = io . Copy ( out , src )
return err
}
// 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:
//
// "application/json" --> JSON binding
// "application/xml" --> XML binding
//
// 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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Bind ( obj any ) error {
b := binding . Default ( ctx . Request . Method , ctx . ContentType ( ) )
return ctx . MustBindWith ( obj , b )
2024-10-18 14:41:55 +08:00
}
// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) BindJSON ( obj any ) error {
return ctx . MustBindWith ( obj , binding . JSON )
2024-10-18 14:41:55 +08:00
}
// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) BindXML ( obj any ) error {
return ctx . MustBindWith ( obj , binding . XML )
2024-10-18 14:41:55 +08:00
}
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) BindQuery ( obj any ) error {
return ctx . MustBindWith ( obj , binding . Query )
2024-10-18 14:41:55 +08:00
}
// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) BindYAML ( obj any ) error {
return ctx . MustBindWith ( obj , binding . YAML )
2024-10-18 14:41:55 +08:00
}
// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) BindTOML ( obj any ) error {
return ctx . MustBindWith ( obj , binding . TOML )
2024-10-18 14:41:55 +08:00
}
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) BindHeader ( obj any ) error {
return ctx . MustBindWith ( obj , binding . Header )
2024-10-18 14:41:55 +08:00
}
// BindUri binds the passed struct pointer using binding.Uri.
// It will abort the request with HTTP 400 if any error occurs.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) BindUri ( obj any ) error {
if err := ctx . ShouldBindUri ( obj ) ; err != nil {
ctx . AbortWithError ( http . StatusBadRequest , err ) . SetType ( ErrorTypeBind ) //nolint: errcheck
2024-10-18 14:41:55 +08: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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) MustBindWith ( obj any , b binding . Binding ) error {
if err := ctx . ShouldBindWith ( obj , b ) ; err != nil {
ctx . AbortWithError ( http . StatusBadRequest , err ) . SetType ( ErrorTypeBind ) //nolint: errcheck
2024-10-18 14:41:55 +08:00
return err
}
return nil
}
// 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:
//
// "application/json" --> JSON binding
// "application/xml" --> XML binding
//
// 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.
// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBind ( obj any ) error {
b := binding . Default ( ctx . Request . Method , ctx . ContentType ( ) )
return ctx . ShouldBindWith ( obj , b )
2024-10-18 14:41:55 +08:00
}
// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindJSON ( obj any ) error {
return ctx . ShouldBindWith ( obj , binding . JSON )
2024-10-18 14:41:55 +08:00
}
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindXML ( obj any ) error {
return ctx . ShouldBindWith ( obj , binding . XML )
2024-10-18 14:41:55 +08:00
}
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindQuery ( obj any ) error {
return ctx . ShouldBindWith ( obj , binding . Query )
2024-10-18 14:41:55 +08:00
}
// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindYAML ( obj any ) error {
return ctx . ShouldBindWith ( obj , binding . YAML )
2024-10-18 14:41:55 +08:00
}
// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindTOML ( obj any ) error {
return ctx . ShouldBindWith ( obj , binding . TOML )
2024-10-18 14:41:55 +08:00
}
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindHeader ( obj any ) error {
return ctx . ShouldBindWith ( obj , binding . Header )
2024-10-18 14:41:55 +08:00
}
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindUri ( obj any ) error {
m := make ( map [ string ] [ ] string , len ( ctx . Params ) )
for _ , v := range ctx . Params {
2024-10-18 14:41:55 +08:00
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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindWith ( obj any , b binding . Binding ) error {
return b . Bind ( ctx . Request , obj )
2024-10-18 14:41:55 +08:00
}
// 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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindBodyWith ( obj any , bb binding . BindingBody ) ( err error ) {
2024-10-18 14:41:55 +08:00
var body [ ] byte
2024-10-18 17:21:19 +08:00
if cb , ok := ctx . Get ( BodyBytesKey ) ; ok {
2024-10-18 14:41:55 +08:00
if cbb , ok := cb . ( [ ] byte ) ; ok {
body = cbb
}
}
if body == nil {
2024-10-18 17:21:19 +08:00
body , err = io . ReadAll ( ctx . Request . Body )
2024-10-18 14:41:55 +08:00
if err != nil {
return err
}
2024-10-18 17:21:19 +08:00
ctx . Set ( BodyBytesKey , body )
2024-10-18 14:41:55 +08:00
}
return bb . BindBody ( body , obj )
}
// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindBodyWithJSON ( obj any ) error {
return ctx . ShouldBindBodyWith ( obj , binding . JSON )
2024-10-18 14:41:55 +08:00
}
// ShouldBindBodyWithXML is a shortcut for c.ShouldBindBodyWith(obj, binding.XML).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindBodyWithXML ( obj any ) error {
return ctx . ShouldBindBodyWith ( obj , binding . XML )
2024-10-18 14:41:55 +08:00
}
// ShouldBindBodyWithYAML is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindBodyWithYAML ( obj any ) error {
return ctx . ShouldBindBodyWith ( obj , binding . YAML )
2024-10-18 14:41:55 +08:00
}
// ShouldBindBodyWithTOML is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ShouldBindBodyWithTOML ( obj any ) error {
return ctx . ShouldBindBodyWith ( obj , binding . TOML )
2024-10-18 14:41:55 +08:00
}
// ClientIP implements one best effort algorithm to return the real client IP.
// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
// 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,
// the remote IP (coming from Request.RemoteAddr) is returned.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ClientIP ( ) string {
2024-10-18 14:41:55 +08:00
// Check if we're running on a trusted platform, continue running backwards if error
2024-10-18 17:21:19 +08:00
if ctx . engine . TrustedPlatform != "" {
2024-10-18 14:41:55 +08:00
// Developers can define their own header of Trusted Platform or use predefined constants
2024-10-18 17:21:19 +08:00
if addr := ctx . requestHeader ( ctx . engine . TrustedPlatform ) ; addr != "" {
2024-10-18 14:41:55 +08:00
return addr
}
}
// Legacy "AppEngine" flag
2024-10-18 17:21:19 +08:00
if ctx . engine . AppEngine {
2024-10-18 14:41:55 +08:00
log . Println ( ` The AppEngine flag is going to be deprecated. Please check issues #2723 and #2739 and use 'TrustedPlatform: gin.PlatformGoogleAppEngine' instead. ` )
2024-10-18 17:21:19 +08:00
if addr := ctx . requestHeader ( "X-Appengine-Remote-Addr" ) ; addr != "" {
2024-10-18 14:41:55 +08:00
return addr
}
}
// 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()
2024-10-18 17:21:19 +08:00
remoteIP := net . ParseIP ( ctx . RemoteIP ( ) )
2024-10-18 14:41:55 +08:00
if remoteIP == nil {
return ""
}
2024-10-18 17:21:19 +08:00
trusted := ctx . engine . isTrustedProxy ( remoteIP )
2024-10-18 14:41:55 +08:00
2024-10-18 17:21:19 +08:00
if trusted && ctx . engine . ForwardedByClientIP && ctx . engine . RemoteIPHeaders != nil {
for _ , headerName := range ctx . engine . RemoteIPHeaders {
ip , valid := ctx . engine . validateHeader ( ctx . requestHeader ( headerName ) )
2024-10-18 14:41:55 +08:00
if valid {
return ip
}
}
}
return remoteIP . String ( )
}
// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) RemoteIP ( ) string {
ip , _ , err := net . SplitHostPort ( strings . TrimSpace ( ctx . Request . RemoteAddr ) )
2024-10-18 14:41:55 +08:00
if err != nil {
return ""
}
return ip
}
// ContentType returns the Content-Type header of the request.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ContentType ( ) string {
return filterFlags ( ctx . requestHeader ( "Content-Type" ) )
2024-10-18 14:41:55 +08:00
}
// IsWebsocket returns true if the request headers indicate that a websocket
// handshake is being initiated by the client.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) IsWebsocket ( ) bool {
if strings . Contains ( strings . ToLower ( ctx . requestHeader ( "Connection" ) ) , "upgrade" ) &&
strings . EqualFold ( ctx . requestHeader ( "Upgrade" ) , "websocket" ) {
2024-10-18 14:41:55 +08:00
return true
}
return false
}
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) requestHeader ( key string ) string {
return ctx . Request . Header . Get ( key )
2024-10-18 14:41:55 +08:00
}
/************************************/
/******** 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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Status ( code int ) {
ctx . Writer . WriteHeader ( code )
2024-10-18 14:41:55 +08:00
}
// Header is an 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)`
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Header ( key , value string ) {
2024-10-18 14:41:55 +08:00
if value == "" {
2024-10-18 17:21:19 +08:00
ctx . Writer . Header ( ) . Del ( key )
2024-10-18 14:41:55 +08:00
return
}
2024-10-18 17:21:19 +08:00
ctx . Writer . Header ( ) . Set ( key , value )
2024-10-18 14:41:55 +08:00
}
// GetHeader returns value from request headers.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetHeader ( key string ) string {
return ctx . requestHeader ( key )
2024-10-18 14:41:55 +08:00
}
// GetRawData returns stream data.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) GetRawData ( ) ( [ ] byte , error ) {
if ctx . Request . Body == nil {
2024-10-18 14:41:55 +08:00
return nil , errors . New ( "cannot read nil body" )
}
2024-10-18 17:21:19 +08:00
return io . ReadAll ( ctx . Request . Body )
2024-10-18 14:41:55 +08:00
}
// SetSameSite with cookie
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) SetSameSite ( samesite http . SameSite ) {
ctx . sameSite = samesite
2024-10-18 14:41:55 +08:00
}
// 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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) SetCookie ( name , value string , maxAge int , path , domain string , secure , httpOnly bool ) {
2024-10-18 14:41:55 +08:00
if path == "" {
path = "/"
}
2024-10-18 17:21:19 +08:00
http . SetCookie ( ctx . Writer , & http . Cookie {
2024-10-18 14:41:55 +08:00
Name : name ,
Value : url . QueryEscape ( value ) ,
MaxAge : maxAge ,
Path : path ,
Domain : domain ,
2024-10-18 17:21:19 +08:00
SameSite : ctx . sameSite ,
2024-10-18 14:41:55 +08:00
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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Cookie ( name string ) ( string , error ) {
cookie , err := ctx . Request . Cookie ( name )
2024-10-18 14:41:55 +08:00
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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Render ( code int , r render . Render ) {
ctx . Status ( code )
2024-10-18 14:41:55 +08:00
if ! bodyAllowedForStatus ( code ) {
2024-10-18 17:21:19 +08:00
r . WriteContentType ( ctx . Writer )
ctx . Writer . WriteHeaderNow ( )
2024-10-18 14:41:55 +08:00
return
}
2024-10-18 17:21:19 +08:00
if err := r . Render ( ctx . Writer ) ; err != nil {
2024-10-18 14:41:55 +08:00
// Pushing error to c.Errors
2024-10-18 17:21:19 +08:00
_ = ctx . Error ( err )
ctx . Abort ( )
2024-10-18 14:41:55 +08: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/
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) HTML ( code int , name string , obj any ) {
instance := ctx . engine . HTMLRender . Instance ( name , obj )
ctx . Render ( code , instance )
2024-10-18 14:41:55 +08:00
}
// 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 using this only for development purposes since printing pretty JSON is
// more CPU and bandwidth consuming. Use Context.JSON() instead.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) IndentedJSON ( code int , obj any ) {
ctx . Render ( code , render . IndentedJSON { Data : obj } )
2024-10-18 14:41:55 +08:00
}
// 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".
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) SecureJSON ( code int , obj any ) {
ctx . Render ( code , render . SecureJSON { Prefix : ctx . engine . secureJSONPrefix , Data : obj } )
2024-10-18 14:41:55 +08:00
}
// 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".
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) JSONP ( code int , obj any ) {
callback := ctx . DefaultQuery ( "callback" , "" )
2024-10-18 14:41:55 +08:00
if callback == "" {
2024-10-18 17:21:19 +08:00
ctx . Render ( code , render . JSON { Data : obj } )
2024-10-18 14:41:55 +08:00
return
}
2024-10-18 17:21:19 +08:00
ctx . Render ( code , render . JsonpJSON { Callback : callback , Data : obj } )
2024-10-18 14:41:55 +08:00
}
// JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json".
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) JSON ( code int , obj any ) {
ctx . Render ( code , render . JSON { Data : obj } )
2024-10-18 14:41:55 +08:00
}
// 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".
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) AsciiJSON ( code int , obj any ) {
ctx . Render ( code , render . AsciiJSON { Data : obj } )
2024-10-18 14:41:55 +08:00
}
// PureJSON serializes the given struct as JSON into the response body.
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) PureJSON ( code int , obj any ) {
ctx . Render ( code , render . PureJSON { Data : obj } )
2024-10-18 14:41:55 +08:00
}
// XML serializes the given struct as XML into the response body.
// It also sets the Content-Type as "application/xml".
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) XML ( code int , obj any ) {
ctx . Render ( code , render . XML { Data : obj } )
2024-10-18 14:41:55 +08:00
}
// YAML serializes the given struct as YAML into the response body.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) YAML ( code int , obj any ) {
ctx . Render ( code , render . YAML { Data : obj } )
2024-10-18 14:41:55 +08:00
}
// TOML serializes the given struct as TOML into the response body.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) TOML ( code int , obj any ) {
ctx . Render ( code , render . TOML { Data : obj } )
2024-10-18 14:41:55 +08:00
}
// ProtoBuf serializes the given struct as ProtoBuf into the response body.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) ProtoBuf ( code int , obj any ) {
ctx . Render ( code , render . ProtoBuf { Data : obj } )
2024-10-18 14:41:55 +08:00
}
// String writes the given string into the response body.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) String ( code int , format string , values ... any ) {
ctx . Render ( code , render . String { Format : format , Data : values } )
2024-10-18 14:41:55 +08:00
}
// Redirect returns an HTTP redirect to the specific location.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Redirect ( code int , location string ) {
ctx . Render ( - 1 , render . Redirect {
2024-10-18 14:41:55 +08:00
Code : code ,
Location : location ,
2024-10-18 17:21:19 +08:00
Request : ctx . Request ,
2024-10-18 14:41:55 +08:00
} )
}
// Data writes some data into the body stream and updates the HTTP code.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Data ( code int , contentType string , data [ ] byte ) {
ctx . Render ( code , render . Data {
2024-10-18 14:41:55 +08:00
ContentType : contentType ,
Data : data ,
} )
}
// DataFromReader writes the specified reader into the body stream and updates the HTTP code.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) DataFromReader ( code int , contentLength int64 , contentType string , reader io . Reader , extraHeaders map [ string ] string ) {
ctx . Render ( code , render . Reader {
2024-10-18 14:41:55 +08:00
Headers : extraHeaders ,
ContentType : contentType ,
ContentLength : contentLength ,
Reader : reader ,
} )
}
// File writes the specified file into the body stream in an efficient way.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) File ( filepath string ) {
http . ServeFile ( ctx . Writer , ctx . Request , filepath )
2024-10-18 14:41:55 +08:00
}
// FileFromFS writes the specified file from http.FileSystem into the body stream in an efficient way.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) FileFromFS ( filepath string , fs http . FileSystem ) {
2024-10-18 14:41:55 +08:00
defer func ( old string ) {
2024-10-18 17:21:19 +08:00
ctx . Request . URL . Path = old
} ( ctx . Request . URL . Path )
2024-10-18 14:41:55 +08:00
2024-10-18 17:21:19 +08:00
ctx . Request . URL . Path = filepath
2024-10-18 14:41:55 +08:00
2024-10-18 17:21:19 +08:00
http . FileServer ( fs ) . ServeHTTP ( ctx . Writer , ctx . Request )
2024-10-18 14:41:55 +08:00
}
var quoteEscaper = strings . NewReplacer ( "\\" , "\\\\" , ` " ` , "\\\"" )
func escapeQuotes ( s string ) string {
return quoteEscaper . Replace ( s )
}
// 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
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) FileAttachment ( filepath , filename string ) {
2024-10-18 14:41:55 +08:00
if isASCII ( filename ) {
2024-10-18 17:21:19 +08:00
ctx . Writer . Header ( ) . Set ( "Content-Disposition" , ` attachment; filename=" ` + escapeQuotes ( filename ) + ` " ` )
2024-10-18 14:41:55 +08:00
} else {
2024-10-18 17:21:19 +08:00
ctx . Writer . Header ( ) . Set ( "Content-Disposition" , ` attachment; filename*=UTF-8'' ` + url . QueryEscape ( filename ) )
2024-10-18 14:41:55 +08:00
}
2024-10-18 17:21:19 +08:00
http . ServeFile ( ctx . Writer , ctx . Request , filepath )
2024-10-18 14:41:55 +08:00
}
// SSEvent writes a Server-Sent Event into the body stream.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) SSEvent ( name string , message any ) {
ctx . Render ( - 1 , sse . Event {
2024-10-18 14:41:55 +08:00
Event : name ,
Data : message ,
} )
}
// Stream sends a streaming response and returns a boolean
// indicates "Is client disconnected in middle of stream"
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Stream ( step func ( w io . Writer ) bool ) bool {
w := ctx . Writer
2024-10-18 14:41:55 +08:00
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
HTMLData any
JSONData any
XMLData any
YAMLData any
Data any
TOMLData any
}
// Negotiate calls different Render according to acceptable Accept format.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Negotiate ( code int , config Negotiate ) {
switch ctx . NegotiateFormat ( config . Offered ... ) {
2024-10-18 14:41:55 +08:00
case binding . MIMEJSON :
data := chooseData ( config . JSONData , config . Data )
2024-10-18 17:21:19 +08:00
ctx . JSON ( code , data )
2024-10-18 14:41:55 +08:00
case binding . MIMEHTML :
data := chooseData ( config . HTMLData , config . Data )
2024-10-18 17:21:19 +08:00
ctx . HTML ( code , config . HTMLName , data )
2024-10-18 14:41:55 +08:00
case binding . MIMEXML :
data := chooseData ( config . XMLData , config . Data )
2024-10-18 17:21:19 +08:00
ctx . XML ( code , data )
2024-10-18 14:41:55 +08:00
case binding . MIMEYAML :
data := chooseData ( config . YAMLData , config . Data )
2024-10-18 17:21:19 +08:00
ctx . YAML ( code , data )
2024-10-18 14:41:55 +08:00
case binding . MIMETOML :
data := chooseData ( config . TOMLData , config . Data )
2024-10-18 17:21:19 +08:00
ctx . TOML ( code , data )
2024-10-18 14:41:55 +08:00
default :
2024-10-18 17:21:19 +08:00
ctx . AbortWithError ( http . StatusNotAcceptable , errors . New ( "the accepted formats are not offered by the server" ) ) //nolint: errcheck
2024-10-18 14:41:55 +08:00
}
}
// NegotiateFormat returns an acceptable Accept format.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) NegotiateFormat ( offered ... string ) string {
2024-10-18 14:41:55 +08:00
assert1 ( len ( offered ) > 0 , "you must provide at least one offer" )
2024-10-18 17:21:19 +08:00
if ctx . Accepted == nil {
ctx . Accepted = parseAccept ( ctx . requestHeader ( "Accept" ) )
2024-10-18 14:41:55 +08:00
}
2024-10-18 17:21:19 +08:00
if len ( ctx . Accepted ) == 0 {
2024-10-18 14:41:55 +08:00
return offered [ 0 ]
}
2024-10-18 17:21:19 +08:00
for _ , accepted := range ctx . Accepted {
2024-10-18 14:41:55 +08:00
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
for ; i < len ( accepted ) && i < len ( offer ) ; i ++ {
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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) SetAccepted ( formats ... string ) {
ctx . Accepted = formats
2024-10-18 14:41:55 +08:00
}
/************************************/
/***** GOLANG.ORG/X/NET/CONTEXT *****/
/************************************/
// hasRequestContext returns whether c.Request has Context and fallback.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) hasRequestContext ( ) bool {
hasFallback := ctx . engine != nil && ctx . engine . ContextWithFallback
hasRequestContext := ctx . Request != nil && ctx . Request . Context ( ) != nil
2024-10-18 14:41:55 +08:00
return hasFallback && hasRequestContext
}
// Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Deadline ( ) ( deadline time . Time , ok bool ) {
if ! ctx . hasRequestContext ( ) {
2024-10-18 14:41:55 +08:00
return
}
2024-10-18 17:21:19 +08:00
return ctx . Request . Context ( ) . Deadline ( )
2024-10-18 14:41:55 +08:00
}
// Done returns nil (chan which will wait forever) when c.Request has no Context.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Done ( ) <- chan struct { } {
if ! ctx . hasRequestContext ( ) {
2024-10-18 14:41:55 +08:00
return nil
}
2024-10-18 17:21:19 +08:00
return ctx . Request . Context ( ) . Done ( )
2024-10-18 14:41:55 +08:00
}
// Err returns nil when c.Request has no Context.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Err ( ) error {
if ! ctx . hasRequestContext ( ) {
2024-10-18 14:41:55 +08:00
return nil
}
2024-10-18 17:21:19 +08:00
return ctx . Request . Context ( ) . Err ( )
2024-10-18 14:41:55 +08: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.
2024-10-18 17:21:19 +08:00
func ( ctx * Context ) Value ( key any ) any {
2024-10-18 14:41:55 +08:00
if key == ContextRequestKey {
2024-10-18 17:21:19 +08:00
return ctx . Request
2024-10-18 14:41:55 +08:00
}
if key == ContextKey {
2024-10-18 17:21:19 +08:00
return ctx
2024-10-18 14:41:55 +08:00
}
if keyAsString , ok := key . ( string ) ; ok {
2024-10-18 17:21:19 +08:00
if val , exists := ctx . Get ( keyAsString ) ; exists {
2024-10-18 14:41:55 +08:00
return val
}
}
2024-10-18 17:21:19 +08:00
if ! ctx . hasRequestContext ( ) {
2024-10-18 14:41:55 +08:00
return nil
}
2024-10-18 17:21:19 +08:00
return ctx . Request . Context ( ) . Value ( key )
2024-10-18 14:41:55 +08:00
}