Go Echo 学习笔记(六)Context
Context上下文信息
echo.Context
是Echo中的一个接口,表示的是当前 HTTP 请求所对应的上下文(Request以及Response等相关信息)。
Context介绍
以下是Context接口的定义:
Context interface {
// Request returns `*http.Request`.
Request() *http.Request
// SetRequest sets `*http.Request`.
SetRequest(r *http.Request)
// Response returns `*Response`.
Response() *Response
// IsTLS returns true if HTTP connection is TLS otherwise false.
IsTLS() bool
// Scheme returns the HTTP protocol scheme, `http` or `https`.
Scheme() string
// RealIP returns the client's network address based on `X-Forwarded-For`
// or `X-Real-IP` request header.
RealIP() string
// Path returns the registered path for the handler.
Path() string
// SetPath sets the registered path for the handler.
SetPath(p string)
// Param returns path parameter by name.
Param(name string) string
// ParamNames returns path parameter names.
ParamNames() []string
// SetParamNames sets path parameter names.
SetParamNames(names ...string)
// ParamValues returns path parameter values.
ParamValues() []string
// SetParamValues sets path parameter values.
SetParamValues(values ...string)
// QueryParam returns the query param for the provided name.
QueryParam(name string) string
// QueryParams returns the query parameters as `url.Values`.
QueryParams() url.Values
// QueryString returns the URL query string.
QueryString() string
// FormValue returns the form field value for the provided name.
FormValue(name string) string
// FormParams returns the form parameters as `url.Values`.
FormParams() (url.Values, error)
// FormFile returns the multipart form file for the provided name.
FormFile(name string) (*multipart.FileHeader, error)
// MultipartForm returns the multipart form.
MultipartForm() (*multipart.Form, error)
// Cookie returns the named cookie provided in the request.
Cookie(name string) (*http.Cookie, error)
// SetCookie adds a `Set-Cookie` header in HTTP response.
SetCookie(cookie *http.Cookie)
// Cookies returns the HTTP cookies sent with the request.
Cookies() []*http.Cookie
// Get retrieves data from the context.
Get(key string) interface{}
// Set saves data in the context.
Set(key string, val interface{})
// Bind binds the request body into provided type `i`. The default binder
// does it based on Content-Type header.
Bind(i interface{}) error
// Validate validates provided `i`. It is usually called after `Context#Bind()`.
// Validator must be registered using `Echo#Validator`.
Validate(i interface{}) error
// Render renders a template with data and sends a text/html response with status
// code. Renderer must be registered using `Echo.Renderer`.
Render(code int, name string, data interface{}) error
// HTML sends an HTTP response with status code.
HTML(code int, html string) error
// HTMLBlob sends an HTTP blob response with status code.
HTMLBlob(code int, b []byte) error
// String sends a string response with status code.
String(code int, s string) error
// JSON sends a JSON response with status code.
JSON(code int, i interface{}) error
// JSONPretty sends a pretty-print JSON with status code.
JSONPretty(code int, i interface{}, indent string) error
// JSONBlob sends a JSON blob response with status code.
JSONBlob(code int, b []byte) error
// JSONP sends a JSONP response with status code. It uses `callback` to construct
// the JSONP payload.
JSONP(code int, callback string, i interface{}) error
// JSONPBlob sends a JSONP blob response with status code. It uses `callback`
// to construct the JSONP payload.
JSONPBlob(code int, callback string, b []byte) error
// XML sends an XML response with status code.
XML(code int, i interface{}) error
// XMLPretty sends a pretty-print XML with status code.
XMLPretty(code int, i interface{}, indent string) error
// XMLBlob sends an XML blob response with status code.
XMLBlob(code int, b []byte) error
// Blob sends a blob response with status code and content type.
Blob(code int, contentType string, b []byte) error
// Stream sends a streaming response with status code and content type.
Stream(code int, contentType string, r io.Reader) error
// File sends a response with the content of the file.
File(file string) error
// Attachment sends a response as attachment, prompting client to save the
// file.
Attachment(file string, name string) error
// Inline sends a response as inline, opening the file in the browser.
Inline(file string, name string) error
// NoContent sends a response with no body and a status code.
NoContent(code int) error
// Redirect redirects the request to a provided URL with status code.
Redirect(code int, url string) error
// Error invokes the registered HTTP error handler. Generally used by middleware.
Error(err error)
// Handler returns the matched handler by echo.
Handler() HandlerFunc
// SetHandler sets the matched handler by echo.
SetHandler(h HandlerFunc)
// Logger returns the `Logger` instance with context and bamai log style.
Logger() BamaiLogger
// OriginalLogger return `Logger` without context
OriginalLogger() Logger
// Echo returns the `Echo` instance.
Echo() *Echo
// Reset resets the context after request completes. It must be called along
// with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
// See `Echo#ServeHTTP()`
Reset(r *http.Request, w http.ResponseWriter)
}
利用 Context 可以获取当前请求对应的相关数据,并且将对应的数据在写入到context当中。可以说,Echo中最重要的东西就是 Context,其贯穿在请求的整个生命周期中。
不过在一般的项目中原生的Context并不能很好的满足一些项目的定制化需求,因此Echo也支持利用中间件来自定义化 Context:
// 定义一个CustomContext结构体
type CustomContext struct {
echo.Context // 在原有的Context上进行包装
}
func (c *CustomContext) Foo() {
println("foo")
}
func (c *CustomContext) Bar() {
println("bar")
}
// 利用中间件来扩展当前的CustomContext
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cc := &CustomContext{c}
return next(cc)
}
})
// 在请求中进行使用
e.GET("/", func(c echo.Context) error {
cc := c.(*CustomContext) // 断言验证当前的context类型
cc.Foo()
cc.Bar()
return cc.String(200, "OK")
})
Context 中用的最多的便是获取请求的相关信息,然后进行相应的操作之后返回对应的响应信息。因此接下来介绍 Context 处理请求以及响应的相关方法。
Context-Request
获取请求数据
Context 中的 Request() 方法可以返回一个 *http.Request 指针类型对象,通过该对象可以获取到请求的相关信息:
type Request struct {
// Method specifies the HTTP method (GET, POST, PUT, etc.).
// For client requests, an empty string means GET.
//
// Go's HTTP client does not support sending a request with
// the CONNECT method. See the documentation on Transport for
// details.
Method string
// URL specifies either the URI being requested (for server
// requests) or the URL to access (for client requests).
//
// For server requests, the URL is parsed from the URI
// supplied on the Request-Line as stored in RequestURI. For
// most requests, fields other than Path and RawQuery will be
// empty. (See RFC 7230, Section 5.3)
//
// For client requests, the URL's Host specifies the server to
// connect to, while the Request's Host field optionally
// specifies the Host header value to send in the HTTP
// request.
URL *url.URL
// The protocol version for incoming server requests.
//
// For client requests, these fields are ignored. The HTTP
// client code always uses either HTTP/1.1 or HTTP/2.
// See the docs on Transport for details.
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
// Header contains the request header fields either received
// by the server or to be sent by the client.
//
// If a server received a request with header lines,
//
// Host: example.com
// accept-encoding: gzip, deflate
// Accept-Language: en-us
// fOO: Bar
// foo: two
//
// then
//
// Header = map[string][]string{
// "Accept-Encoding": {"gzip, deflate"},
// "Accept-Language": {"en-us"},
// "Foo": {"Bar", "two"},
// }
//
// For incoming requests, the Host header is promoted to the
// Request.Host field and removed from the Header map.
//
// HTTP defines that header names are case-insensitive. The
// request parser implements this by using CanonicalHeaderKey,
// making the first character and any characters following a
// hyphen uppercase and the rest lowercase.
//
// For client requests, certain headers such as Content-Length
// and Connection are automatically written when needed and
// values in Header may be ignored. See the documentation
// for the Request.Write method.
Header Header
// Body is the request's body.
//
// For client requests, a nil body means the request has no
// body, such as a GET request. The HTTP Client's Transport
// is responsible for calling the Close method.
//
// For server requests, the Request Body is always non-nil
// but will return EOF immediately when no body is present.
// The Server will close the request body. The ServeHTTP
// Handler does not need to.
//
// Body must allow Read to be called concurrently with Close.
// In particular, calling Close should unblock a Read waiting
// for input.
Body io.ReadCloser
// GetBody defines an optional func to return a new copy of
// Body. It is used for client requests when a redirect requires
// reading the body more than once. Use of GetBody still
// requires setting Body.
//
// For server requests, it is unused.
GetBody func() (io.ReadCloser, error)
// ContentLength records the length of the associated content.
// The value -1 indicates that the length is unknown.
// Values >= 0 indicate that the given number of bytes may
// be read from Body.
//
// For client requests, a value of 0 with a non-nil Body is
// also treated as unknown.
ContentLength int64
// TransferEncoding lists the transfer encodings from outermost to
// innermost. An empty list denotes the "identity" encoding.
// TransferEncoding can usually be ignored; chunked encoding is
// automatically added and removed as necessary when sending and
// receiving requests.
TransferEncoding []string
// Close indicates whether to close the connection after
// replying to this request (for servers) or after sending this
// request and reading its response (for clients).
//
// For server requests, the HTTP server handles this automatically
// and this field is not needed by Handlers.
//
// For client requests, setting this field prevents re-use of
// TCP connections between requests to the same hosts, as if
// Transport.DisableKeepAlives were set.
Close bool
// For server requests, Host specifies the host on which the
// URL is sought. For HTTP/1 (per RFC 7230, section 5.4), this
// is either the value of the "Host" header or the host name
// given in the URL itself. For HTTP/2, it is the value of the
// ":authority" pseudo-header field.
// It may be of the form "host:port". For international domain
// names, Host may be in Punycode or Unicode form. Use
// golang.org/x/net/idna to convert it to either format if
// needed.
// To prevent DNS rebinding attacks, server Handlers should
// validate that the Host header has a value for which the
// Handler considers itself authoritative. The included
// ServeMux supports patterns registered to particular host
// names and thus protects its registered Handlers.
//
// For client requests, Host optionally overrides the Host
// header to send. If empty, the Request.Write method uses
// the value of URL.Host. Host may contain an international
// domain name.
Host string
// Form contains the parsed form data, including both the URL
// field's query parameters and the PATCH, POST, or PUT form data.
// This field is only available after ParseForm is called.
// The HTTP client ignores Form and uses Body instead.
Form url.Values
// PostForm contains the parsed form data from PATCH, POST
// or PUT body parameters.
//
// This field is only available after ParseForm is called.
// The HTTP client ignores PostForm and uses Body instead.
PostForm url.Values
// MultipartForm is the parsed multipart form, including file uploads.
// This field is only available after ParseMultipartForm is called.
// The HTTP client ignores MultipartForm and uses Body instead.
MultipartForm *multipart.Form
// Trailer specifies additional headers that are sent after the request
// body.
//
// For server requests, the Trailer map initially contains only the
// trailer keys, with nil values. (The client declares which trailers it
// will later send.) While the handler is reading from Body, it must
// not reference Trailer. After reading from Body returns EOF, Trailer
// can be read again and will contain non-nil values, if they were sent
// by the client.
//
// For client requests, Trailer must be initialized to a map containing
// the trailer keys to later send. The values may be nil or their final
// values. The ContentLength must be 0 or -1, to send a chunked request.
// After the HTTP request is sent the map values can be updated while
// the request body is read. Once the body returns EOF, the caller must
// not mutate Trailer.
//
// Few HTTP clients, servers, or proxies support HTTP trailers.
Trailer Header
// RemoteAddr allows HTTP servers and other software to record
// the network address that sent the request, usually for
// logging. This field is not filled in by ReadRequest and
// has no defined format. The HTTP server in this package
// sets RemoteAddr to an "IP:port" address before invoking a
// handler.
// This field is ignored by the HTTP client.
RemoteAddr string
// RequestURI is the unmodified request-target of the
// Request-Line (RFC 7230, Section 3.1.1) as sent by the client
// to a server. Usually the URL field should be used instead.
// It is an error to set this field in an HTTP client request.
RequestURI string
// TLS allows HTTP servers and other software to record
// information about the TLS connection on which the request
// was received. This field is not filled in by ReadRequest.
// The HTTP server in this package sets the field for
// TLS-enabled connections before invoking a handler;
// otherwise it leaves the field nil.
// This field is ignored by the HTTP client.
TLS *tls.ConnectionState
// Cancel is an optional channel whose closure indicates that the client
// request should be regarded as canceled. Not all implementations of
// RoundTripper may support Cancel.
//
// For server requests, this field is not applicable.
//
// Deprecated: Set the Request's context with NewRequestWithContext
// instead. If a Request's Cancel field and context are both
// set, it is undefined whether Cancel is respected.
Cancel <-chan struct{}
// Response is the redirect response which caused this request
// to be created. This field is only populated during client
// redirects.
Response *Response
// ctx is either the client or server context. It should only
// be modified via copying the whole Request using WithContext.
// It is unexported to prevent people from using Context wrong
// and mutating the contexts held by callers of the same request.
ctx context.Context
}
虽然 Request 对象中也有获取请求数据的方法,但是Context也提供了更加便捷的获取请求数据的方法:
// c为context对象,返回都是字符串
// 获取form表单中的数据
name := c.FormValue("name")
// 获取Get请求参数的数据
name := c.QueryParam("name")
// 获取url路径中的数据
name := c.Param("name")
// 通过FormFile函数获取客户端上传的文件
file, err := c.FormFile("file")
请求数据验证
针对请求的数据是否为必须传入的验证称为请求数据验证操作,Echo 没有内置的数据验证函数,可以自己进行定制然后使用 Echo#Validator
来进行注册,或者使用第三方所提供的(https://github.com/go-playground/validator )。
自定义请求数据验证案例:
// User 用户结构体
type User struct {
Name string `json:"name" form:"name" query:"name" validate:"required"`
Email string `json:"email" form:"email" query:"email" validate:"required,email"`
}
// CustomValidator 自定义数据验证
type CustomValidator struct {
Validator *validator.Validate
}
// Validate CustomValidate接口对应的数据验证方法
func (cv *CustomValidator) Validate(i interface{}) error {
// 对结构体数据进行验证
if err := cv.Validator.Struct(i); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return nil
}
// 设置数据验证器
e.Validator = &model.CustomValidator{Validator: validator.New()}
// 在请求处理函数中进行数据验证
if err := c.Validate(u); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
针对错误的email请求数据会返回相应的错误:
{
"error": "code=400, message=code=500, message=Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag",
"message": "code=500, message=Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag"
}
Context-Response
Echo 支持返回 String、HTML、Json、xml、file等格式的数据。
返回String
要向客户端返回 String,需要用到 Context#String(code int, s string)
方法:
func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
返回HTML
要向客户端返回对应的 HTML页面信息(纯文本),需要用到 Context#HTML(code int, html string)
方法:
func(c echo.Context) error {
return c.HTML(http.StatusOK, "<strong>Hello, World!</strong>")
}
想要输出数据流形式的 HTML 页面信息,需要使用 Context#HTMLBlob(code int, b []byte)
方法。
返回Json
要向客户端返回 Json 数据,需要用到 Context#JSON(code int, i interface{})
方法,该方法会将一个 Go 中的 type 类型的对象编码为 json 并将其配合状态码响应给客户端:
// User
type User struct {
Name string `json:"name" xml:"name"`
Email string `json:"email" xml:"email"`
}
// Handler
func(c echo.Context) error {
u := &User{
Name: "Jon",
Email: "jon@labstack.com",
}
return c.JSON(http.StatusOK, u)
}
Context#JSON(code int, i interface{})
方法在内部使用到了 json.MarShal()
方法来将 type 对象编码为 Json 格式的数据,对于比较复杂的 type 可能会出错,因此可以使用流式 Json 来手动编码 Json:
func(c echo.Context) error {
u := &User{
Name: "Jon",
Email: "jon@labstack.com",
}
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
c.Response().WriteHeader(http.StatusOK)
return json.NewEncoder(c.Response()).Encode(u)
}
同时 Echo 也提供了美观的 Json 响应数据提供方法:Context#JSONPretty(code int, i interface{}, indent string)
,该方法会按照指定的 indent 来对要返回的 Json 数据进行缩进:
func(c echo.Context) error {
u := &User{
Name: "Jon",
Email: "joe@labstack.com",
}
return c.JSONPretty(http.StatusOK, u, " ")
}
// 显示效果
{
"email": "joe@labstack.com",
"name": "Jon"
}
同时 Echo 也支持将已经编码好的 Json 数据返回给对应的客户端,使用的是:Context#JSONBlob(code int, b []byte)
方法:
func(c echo.Context) error {
encodedJSON := []byte{} // Encoded JSON from external source
return c.JSONBlob(http.StatusOK, encodedJSON)
}
对于需要进行跨域服务器调用的场景,Echo 可以使用 JSONP 方法来实现,JSONP是一种允许跨域服务器调用的方法:
func TestJsonP(c echo.Context) error {
callback := c.QueryParam("callback")
var content struct {
Response string `json:"response"`
Timestamp time.Time `json:"timestamp"`
Random int `json:"random"`
}
content.Response = "Sent via JSONP"
content.Timestamp = time.Now().UTC()
content.Random = rand.Intn(1000)
return c.JSONP(http.StatusOK, callback, &content)
}
对应的请求处理:
http://localhost:8080/jsonp?callback=jQuery111106157039283048003_1619404341601&_=1619404341634
返回的结果是:
jQuery111106157039283048003_1619404341601({
"response": "Sent via JSONP",
"timestamp": "2021-04-26T02:39:03.681172Z",
"random": 825
}
);
返回XML
Echo 利用 Context#XML(code int, i interface{})
方法来将一个结构体对象编码为 XML 进行返回。
func(c echo.Context) error {
u := &User{
Name: "Jon",
Email: "jon@labstack.com",
}
return c.XML(http.StatusOK, u)
}
同样的, Context#XML(code int, i interface{})
中使用到了 xml.Marshal()
方法来完成结构体对象到 XML文本的编码过程,对于一些较为复杂的对象也可以使用如下的方式生成原生的 XML 文本:
func(c echo.Context) error {
u := &User{
Name: "Jon",
Email: "jon@labstack.com",
}
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationXMLCharsetUTF8)
c.Response().WriteHeader(http.StatusOK)
return xml.NewEncoder(c.Response()).Encode(u)
}
同时,Context#XMLPretty(code int, i interface{}, indent string)
方法也可以支持对返回的 XML 文本进行缩进排版:
func(c echo.Context) error {
u := &User{
Name: "Jon",
Email: "joe@labstack.com",
}
return c.XMLPretty(http.StatusOK, u, " ")
}
显示效果如下:
<?xml version="1.0" encoding="UTF-8"?>
<User>
<Name>Jon</Name>
<Email>joe@labstack.com</Email>
</User>
对于已经编码好的XML 文本来说,可以使用 Context#XMLBlob(code int, b []byte)
方法来将其返回给客户端。
返回文件
Echo 可以使用 Context#File(file string)
方法来向客户端返回一个文件并自动处理相应的缓存问题。
func(c echo.Context) error {
return c.File("<PATH_TO_YOUR_FILE>")
}
同时 Echo 还提供了 Context#Attachment(file, name string)
方法来将文件按照给定的名称作为附件返回给客户端:
func(c echo.Context) error {
return c.Attachment("<PATH_TO_YOUR_FILE>", "<ATTACHMENT_NAME>")
}
对于多个文件需要排队返回给客户端的情况下,可以使用 Context#Inline(file, name string)
来进行返回:
func(c echo.Context) error {
return c.Inline("<PATH_TO_YOUR_FILE>")
}
返回其他格式文件
对于一些没有专门方法支持的文件类型的返回可以利用 Context#Blob(code int, contentType string, b []byte)
来指定返回的文件类型以及对应的文件数据:
func(c echo.Context) (err error) {
data := []byte(`0306703,0035866,NO_ACTION,06/19/2006
0086003,"0005866",UPDATED,06/19/2006`)
return c.Blob(http.StatusOK, "text/csv", data)
}
返回数据流文件
对于数据流类型的文件来说,可以使用 Context#Stream(code int, contentType string, r io.Reader)
来流式返回给客户端:
func(c echo.Context) error {
f, err := os.Open("<PATH_TO_IMAGE>")
if err != nil {
return err
}
return c.Stream(http.StatusOK, "image/png", f)
}
返回空内容
对于没有内容需要返回的话可以利用 Context#NoContent(code int)
只传递一个对应的状态码就可以:
func(c echo.Context) error {
return c.NoContent(http.StatusOK)
}
重定向
Context#Redirect(code int, url string)
方法可以将请求重定义到一个指定的 url 上:
func(c echo.Context) error {
return c.Redirect(http.StatusMovedPermanently, "<URL>")
}
Hooks钩子方法
Echo 支持在请求的响应被写入的前后过程中注册相应的钩子方法(支持注册多个方法):
Context#Response#Before(func())
:所注册的方法在响应被写入之前调用;Context#Response#After(func())
:所注册的方法在响应被写入之后调用(但假如“ Content-Length”未知,则不会执行after函数。);
func(c echo.Context) error {
c.Response().Before(func() {
println("before response")
})
c.Response().After(func() {
println("after response")
})
return c.NoContent(http.StatusNoContent)
}