diff --git a/content/en/docs/hertz/tutorials/basic-feature/context/request.md b/content/en/docs/hertz/tutorials/basic-feature/context/request.md index 4edf13a282..bd9cf09bbc 100644 --- a/content/en/docs/hertz/tutorials/basic-feature/context/request.md +++ b/content/en/docs/hertz/tutorials/basic-feature/context/request.md @@ -906,7 +906,7 @@ h.POST("/user", func(c context.Context, ctx *app.RequestContext) { ### SetFormValueFunc -Set the FormValue function. +If the default method provided by the [FormValue](#formvalue) function to obtain the value of the key does not meet the requirements, users can use this function to customize the method of obtaining the value of the key. Function Signature: @@ -1346,7 +1346,9 @@ h.Use(func(c context.Context, ctx *app.RequestContext) { ### SetClientIPFunc -Set the address of the client IP. +If the default method provided by the [ClientIP](#clientip) function does not meet the requirements, users can use this function to customize the way to obtain the client ip. + +This function can be used in scenarios where you want to obtain an ip from the `X-Forwarded-For` or `X-Real-IP` header even if a remote ip exists (multiple proxies, want to obtain the initial ip from the `X-Forwarded-For` or `X-Real-IP` header). Function Signature: diff --git a/content/en/docs/hertz/tutorials/basic-feature/context/response.md b/content/en/docs/hertz/tutorials/basic-feature/context/response.md index b33d09f67d..4f89b62a5b 100644 --- a/content/en/docs/hertz/tutorials/basic-feature/context/response.md +++ b/content/en/docs/hertz/tutorials/basic-feature/context/response.md @@ -367,7 +367,7 @@ func (ctx *RequestContext) AbortWithStatusJSON(code int, jsonObj interface{}) ### SetBodyStream -Set Body Stream and optional Body Size. +Set Body Stream and optional Body Size. This function is used for streaming processing on Hertz Server, as detailed in [Streaming](/docs/hertz/tutorials/basic-feature/engine/#streaming). > Note: When the bodySize is less than 0, all data is written. When it is greater than or equal to 0, data is written based on the set bodySize size. diff --git a/content/en/docs/hertz/tutorials/basic-feature/engine.md b/content/en/docs/hertz/tutorials/basic-feature/engine.md index 22b0fc22f2..a71ae93ca1 100644 --- a/content/en/docs/hertz/tutorials/basic-feature/engine.md +++ b/content/en/docs/hertz/tutorials/basic-feature/engine.md @@ -1,319 +1,230 @@ --- title: "Engine" -date: 2023-04-10 +date: 2023-08-18 weight: 1 description: > --- -The important methods of registering routes, middleware, starting and stopping services for Hertz are all included in the **core** type `server.Hertz`. It is composed of `route.Engine` and a `signalWaiter`. Here's the definition: +`Server.Hertz` is the core type of `Hertz`, consisting of `route.Engine` and `signalWaiter`. The important methods for starting, registering routes, registering middleware, and exiting the `Hertz` server are all included in `server.Hertz`. The following is the definition of `server.Hertz`: ```go -// Hertz is the core struct of hertz. type Hertz struct { - *route.Engine - // Used to receive signals for graceful shutdown - signalWaiter func(err chan error) error +    *route.Engine +    // used to receive signal for elegant exit +    signalWaiter func (err chan error) error } ``` -## server.Hertz - -### Initializing the Server - -Hertz provides two functions in the `server` package to initialize servers: `New` and `Default`. - -The default function `Default` uses the middleware called `Recovery` to prevent service crashes caused by panicking -during runtime. +`Route.Engine` is an important component of `server.Hertz`, and the definition of `Engine` is located in [Engine](https://github.com/cloudwego/hertz/blob/main/pkg/route/engine.go). + +## Client Config + +| **Option** | **Default** | **Description** | +| :---- | :---- | :---- | +| WithTransport | network.NewTransporter | Replace the transport | +| WithHostPorts | `:8888` | Specify the listening address and port | +| WithKeepAliveTimeout | 1min | Set the keep-alive time of tcp persistent connection, generally no need to modify it, you should more pay attention to idleTimeout rather than modifying it | +| WithReadTimeout | 3min | The timeout of data reading | +| WithIdleTimeout | 3min | The free timeout of the request link for persistent connection | +| WithMaxRequestBodySize | 4 * 1024 * 1024 | Max body size of a request | +| WithRedirectTrailingSlash | true | Whether to redirect with the / which is at the end of the router automatically. For example: If there is only /foo/ in the router, /foo will be redirected to /foo/. And if there is only /foo in the router, /foo/ will be redirected to /foo | +| WithRemoveExtraSlash | false | RemoveExtraSlash makes the parameter still valid when it contains an extra /. For example, if WithRemoveExtraSlash is true user//xiaoming can match the user/:name router | +| WithUnescapePathValues | true | If true, the request path will be escaped automatically (eg. '%2F' -> '/'). If UseRawPath is false (the default), UnescapePathValues is true, because URI().Path() will be used and it is already escaped. To set WithUnescapePathValues to false, you need to set WithUseRawPath to true | +| WithUseRawPath | false | If true, the original path will be used to match the route | +| WithHandleMethodNotAllowed | false | If true when the current path cannot match any method, the server will check whether other methods are registered with the route of the current path, and if exist other methods, it will respond "Method Not Allowed" and return the status code 405; if not, it will use the handler of NotFound to handle it | +| WithDisablePreParseMultipartForm | false | If true, the multipart form will not be preprocessed. The body can be obtained via ctx.Request.Body() and then can be processed by user | +| WithStreamBody | false | If true, the body will be handled by stream processing | +| WithNetwork | "tcp" | Set the network protocol, optional: tcp,udp,unix(unix domain socket) | +| WithExitWaitTime | 5s | Set the graceful exit time. the Server will stop connection establishment for new requests and set the Connection: Close header for each request after closing. When the set time is reached, Server will to be closed. the Server can be closed early when all connections have been closed | +| WithTLS | nil | Configuring server tls capabilities, For detailed information, please refer to [TLS](/zh/docs/hertz/tutorials/basic-feature/protocol/tls/) | +| WithListenConfig | nil | Set the listener configuration. Can be used to set whether to allow reuse ports, etc.| +| WithALPN | false | Whether to enable ALPN | +| WithTracer | []interface{}{} | Inject tracer implementation, if not inject Tracer, default: close. | +| WithTraceLevel | LevelDetailed | Set trace level | +| WithWriteTimeout | infinite | The timeout of data writing | +| WithRedirectFixedPath | false | If enabled, if the current request path does not match, the server will try to repair the request path and re-match, if the match is successful and the request is a GET request, it will return status code 301 for redirect, other requests will return 308 for redirect | +| WithBasePath | `/` | Set the base path, which must be prefixed and suffixed with `/` | +| WithMaxKeepBodySize | 4 * 1024 * 1024 | Sets the maximum size of the request body and response body to be retained during reclaim. Unit: Byte | +| WithGetOnly | false | If enabled, only GET requests are accepted | +| WithKeepAlive | true | If enabled, use HTTP keepalive | +| WithAltTransport | network.NewTransporter | Set up the alternate transport | +| WithH2C | false | Sets whether H2C is enabled | +| WithReadBufferSize | 4 * 1024 | Set the read buffer size while limiting the HTTP header size | +| WithRegistry | registry.NoopRegistry, nil | Setup registry configuration, service registration information | +| WithAutoReloadRender | false, 0 | Set up the automatic reload rendering configuration | +| WithDisablePrintRoute | false | Sets whether debugPrintRoute is disabled | +| WithOnAccept | nil | Set the callback function when a new connection is accepted but cannot receive data in netpoll. In go net, it will be called before converting tls connection | +| WithOnConnect | nil | Set the onConnect function. It can received data from connection in netpoll. In go net, it will be called after converting tls connection | + +Server Connection limitation: + +* If you are using the standard network library, there is no such restriction. +* If netpoll is used, the maximum number of connections is 10000 (this is + the [gopool](https://github.com/bytedance/gopkg/blob/b9c1c36b51a6837cef4c2223e11522e3a647460c/util/gopool/gopool.go#L46)) + used at the bottom of netpoll. Yes, the modification method is also very simple, just call the function provided by + gopool: `gopool.SetCap(xxx)` (you can call it once in main.go). + +The configuration items on the server side are initialized using the `server.WithXXX` method, such as: ```go -// New creates a hertz instance without any default config. -func New(opts ...config.Option) *Hertz { - options := config.NewOptions(opts) - h := &Hertz{Engine: route.NewEngine(options)} - return h +func main() { + h := server.New(server.WithXXXX()) + ... } ``` +## Initial Service + ```go -// Default creates a hertz instance with default middlewares. -func Default(opts ...config.Option) *Hertz { - h := New(opts...) - // Uses built-in Recovery middleware based on New method - h.Use(recovery.Recovery()) - return h -} +func Default(opts ...config.Option) *Hertz +func New(opts ...config.Option) *Hertz ``` -For more information on optional configurations, refer to [Configuration Options](https://www.cloudwego.io/docs/hertz/reference/config/). +### Default + +`Default` is used to initialize the service, and the `Recovery` middleware is used by default to ensure that the service will not crash during runtime due to `panic`. -Example Code +Function Signature: ```go -package main +func Default(opts ...config.Option) *Hertz +``` -import ( - "github.com/cloudwego/hertz/pkg/app/server" -) +Example Code: +```go func main() { - h := server.New() - // Use Default - // h := server.Default() + h := server.Default() h.Spin() } ``` -### Run Service - -`Hertz` provides the `Spin` function to start the server. +### New -Unlike the `Run` provided in `route.Engine`, it is generally recommended to use `Spin` unless you have **special** needs when running services. +`New` is used to initialize service and does not use the default `Recovery` middleware. -When using [Service Registration and Discovery](http://localhost:1313/docs/hertz/tutorials/service-governance/service_discovery/), `Spin` -will register the service into a registry center when starting up, and use `signalWaiter` to monitor service exceptions. Only by using `Spin` can we support graceful shutdown. +Function Signature: ```go -package main - -import ( - "github.com/cloudwego/hertz/pkg/app/server" -) - -func main() { - h := server.New() - // We usually recommend using Spin - h.Spin() -} +func New(opts ...config.Option) *Hertz ``` -```go -package main - -import ( - "github.com/cloudwego/hertz/pkg/app/server" -) +Example Code: +```go func main() { h := server.New() - // Start with Run function - if err:= h.Run (); err != Nil { - //... - panic(err) - } + h.Spin() } ``` -## route.Engine - -`route.Engine` is an important part of `server.Hertz`, which contains a large number of commonly used methods in development, and is particularly **important**. +## Service Run and Exit ```go -type Engine struct { - noCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used - - // engine name - Name string - serverName atomic.Value - - // Options for route and protocol server - options *config.Options - - // route - RouterGroup - trees MethodTrees - - maxParams uint16 - - allNoMethod app.HandlersChain - allNoRoute app.HandlersChain - noRoute app.HandlersChain - noMethod app.HandlersChain - - // For render HTML - delims render.Delims - funcMap template.FuncMap - htmlRender render.HTMLRender - - // NoHijackConnPool will control whether invite pool to acquire/release the hijackConn or not. - // If it is difficult to guarantee that hijackConn will not be closed repeatedly, set it to true. - NoHijackConnPool bool - hijackConnPool sync.Pool - // KeepHijackedConns is an opt-in disable of connection - // close by hertz after connections' HijackHandler returns. - // This allows to save goroutines, e.g. when hertz used to upgrade - // http connections to WS and connection goes to another handler, - // which will close it when needed. - KeepHijackedConns bool - - // underlying transport - transport network.Transporter - - // trace - tracerCtl tracer.Controller - enableTrace bool - - // protocol layer management - protocolSuite *suite.Config - protocolServers map[string]protocol.Server - protocolStreamServers map[string]protocol.StreamServer - - // RequestContext pool - ctxPool sync.Pool +func (h *Hertz) Spin() +func (engine *Engine) Run() (err error) +func (h *Hertz) SetCustomSignalWaiter(f func(err chan error) error) +``` - // Function to handle panics recovered from http handlers. - // It should be used to generate an error page and return the http error code - // 500 (Internal Server Error). - // The handler can be used to keep your server from crashing because of - // unrecovered panics. - PanicHandler app.HandlerFunc +### Spin - // ContinueHandler is called after receiving the Expect 100 Continue Header - // - // https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3 - // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.1.1 - // Using ContinueHandler a server can make decisioning on whether or not - // to read a potentially large request body based on the headers - // - // The default is to automatically read request bodies of Expect 100 Continue requests - // like they are normal requests - ContinueHandler func(header *protocol.RequestHeader) bool +The `Spin` function is used to run the Hertz server and can exit the service upon receiving an exit signal. - // Indicates the engine status (Init/Running/Shutdown/Closed). - status uint32 +This function supports graceful shutdown of services. For detailed information on graceful shutdown, please refer to [graceful-shutdown](https://www.cloudwego.io/docs/hertz/tutorials/basic-feature/graceful-shutdown/). - // Hook functions get triggered sequentially when engine start - OnRun []CtxErrCallback +When using the function of [service_discovery](https://www.cloudwego.io/docs/hertz/tutorials/service-governance/service_discovery/), `Spin` will register the service into the registry when it is started, and use `signalWaiter` to monitor service exceptions. - // Hook functions get triggered simultaneously when engine shutdown - OnShutdown []CtxCallback +Function Signature: - // Custom Functions - clientIPFunc app.ClientIP - formValueFunc app.FormValueFunc -} +```go +func (h *Hertz) Spin() ``` -### Set ServiceName - -Sample code: +Example Code: ```go -package main - func main() { - h := server.New() - // Used to set the Server field in response header, default is Hertz. - h.Name = "" + h := server.Default() + h.Spin() } ``` -### Rendering template - -The engine provides methods such as `Delims`, `SetFuncMap`, `LoadHTMLGlob`, `LoadHTMLFiles`, `SetHTMLTemplate`, and `SetAutoReloadHTMLTemplate` for rendering HTML or template files. - -#### Delims - -Used to set the delimiter of the templates. - -Function signature: - -```go -func (engine *Engine) Delims(left, right string) *Engine -``` +### Run -#### SetFuncMap +The `Run` function is used to run the Hertz server and can exit the service upon receiving an exit signal. -Used to set data sources for templates. +This function does not support graceful shutdown of service. Unless there are **special** requirements, the [Spin](#spin) function is generally used to run service. -Function signature: +Function Signature: ```go -type FuncMap map[string]interface{} - -func (engine *Engine) SetFuncMap(funcMap template.FuncMap) +func (engine *Engine) Run() (err error) ``` -Example usage: +Example Code: ```go -package main - func main() { - h := server.New() - h.SetFuncMap(template.FuncMap{ - "time": time.Now.String(), - }) + h := server.Default() + if err := h.Run(); err != nil { + // ... + panic(err) + } } ``` -#### LoadHTMLGlob +### SetCustomSignalWaiter -Used for global loading of template files. The `*` wildcard can be used to specify the template folder. +The `SetCustomimSignalWaiter` function is used to customize the processing function of the server after receiving signals. If no custom function is set, Hertz uses the `waitSignal` function as the default implementation method for signal processing. For more details, please refer to [graceful-shutdown](https://www.cloudwego.io/docs/hertz/tutorials/basic-feature/graceful-shutdown/). -Function signature: +Function Signature: ```go -// LoadHTMLGlob loads HTML files identified by glob pattern -// and associates the result with HTML renderer. -func (engine *Engine) LoadHTMLGlob(pattern string) +func (h *Hertz) SetCustomSignalWaiter(f func(err chan error) error) ``` -Example code: +Example Code: ```go -// Loads all html template files in render/html directory -h.LoadHTMLGlob("render/html/*") - -// Loads index.tmpl template file -h.LoadHTMLGlob("index.tmpl") +func main() { + h := server.New() + h.SetCustomSignalWaiter(func(err chan error) error { + return nil + }) + h.Spin() +} ``` -#### LoadHTMLFiles - -Used to load specified template files as a string slice. - -Function signature: +## Middleware ```go -// LoadHTMLFiles loads a slice of HTML files -// and associates the result with HTML renderer. -func (engine *Engine) LoadHTMLFiles(files ...string) +func (engine *Engine) Use(middleware ...app.HandlerFunc) IRoutes ``` -#### SetHTMLTemplate/SetAutoReloadHTMLTemplate - -These two methods are used internally in rendering logic and are not recommended for direct use. - -### Register Middleware +### Use -Hertz provides `Use` function for registering middleware into routes. +The `Use` function is used to register the middleware into the router. -We support user-defined middleware, and at the same time we also provide some commonly used middleware implementations, -See details [hertz-contrib](https://github.com/hertz-contrib). +Hertz supports user-defined middleware, and has implemented some commonly used middleware. Please refer to [hertz contrib](https://github.com/hertz-contrib) for details. -"At the same time, although the common usage of middleware is to register it globally, we also support registration -at the routing level. For more details, [please see](https://www.cloudwego.io/docs/hertz/tutorials/basic-feature/middleware/#group-level-middleware) +The usage methods of middleware supported by Hertz include **global registration**, **routing group** level, and **single routing** level registration. For details, please refer to [server-side-middleware](/docs/hertz/tutorials/basic-feature/middleware/#server-side-middleware). -The parameter type of `middleware` in the `Use` function must be a http processing function of `app.HandlerFunc`. +The formal parameter of `middleware` in the `Use` function must be the http processing function of `app.HandlerFunc`: ```go type HandlerFunc func (ctx context.Context, c *app.RequestContext) ``` -Function signature: +Function Signature: ```go func (engine *Engine) Use(middleware ...app.HandlerFunc) IRoutes ``` -Sample code: +Example Code: ```go -package main - -// ... - func main() { h := server.New() // Register built-in Recovery middleware into routes. @@ -334,103 +245,223 @@ func exampleMiddleware() app.handlerFunc { } ``` -For more examples, see [repository](https://github.com/cloudwego/hertz-examples/tree/main/middleware). +## Streaming -### Service Shutdown +Hertz supports server streaming processing, including streaming read and streaming write. -hertz provides the `Shutdown` function for graceful shutdown. +> Note: Due to the different triggering modes between netpoll and go net, netpoll streaming is "pseudo" (due to LT triggering, data will be read into the buffer of the network library by the network library). In scenarios with large packets (such as uploading files), there may be memory issues, and it is recommended to use go net. -If you are using service registration and discovery, the corresponding data will also be deregistered from the registry when the service exits. +### Streaming Write -Function signature: - -```go -func (engine *Engine) Shutdown(ctx context.Context) (err error) -``` +Hertz Server supports streaming read request content. -Example code: +Example Code: ```go -package main +func main() { + h := server.Default(server.WithHostPorts("127.0.0.1:8080"), server.WithStreamBody(true), server.WithTransport(standard.NewTransporter)) -import ( - //... - "github.com/cloudwego/hertz/pkg/app/server" -) + h.POST("/bodyStream", handler) -func main() { - h := server.New() - // When accessing this path, it triggers the shutdown function to go offline - h.GET("/shutdown", func(ctx context.Context, c *app.RequestContext) { - h.ShutDown(ctx) - }) - h.Spin() + h.Spin() +} + +func handler(ctx context.Context, c *app.RequestContext) { + // Acquire body streaming + bodyStream := c.RequestBodyStream() + // Read half of body bytes + p := make([]byte, c.Request.Header.ContentLength()/2) + r, err := bodyStream.Read(p) + if err != nil { + panic(err) + } + left, _ := ioutil.ReadAll(bodyStream) + c.String(consts.StatusOK, "bytes streaming_read: %d\nbytes left: %d\n", r, len(left)) } ``` -For more information on graceful shutdown see [documentation](https://www.cloudwego.io/docs/hertz/tutorials/basic-feature/graceful-shutdown/) +### Streaming Read -### Set Hook Function +Hertz Server supports streaming write responses. -The corresponding hook functions will be triggered when Engine starts up and shuts down. +Two methods are provided: -`OnRun` and `OnShutdown` are two slices of hook functions used to store hook functions. To avoid affecting existing hook functions, -you need to use the `append` function to add new hooks into slices. +1. The user passes in a `io.Reader` through the `ctx.SetBodyStream` function in the handler, and then reads and writes data in blocks in a similar manner to the example code (using channel to control data partitioning and read/write order) **Note that data needs to be written asynchronously**. + + If the user knows the total length of the transmitted data in advance, they can pass in the length in the `ctx.SetBodyStream` function for streaming writing, as shown in the example code `/streamWrite1`. -For detailed configuration methods, see [Hooks](https://www.cloudwego.io/docs/hertz/tutorials/basic-feature/hooks/). + If the user does not know the total length of the transmitted data in advance, they can pass in -1 in the `ctx.SetBodyStream` function to write the stream in the form of `Transfer-Encoding: chunked`, as shown in the example code `/streamWrite2`. -Function signature: + Example Code: -```go -type CtxCallback func (ctx context.Context) + ```go + func main() { + h := server.Default(server.WithHostPorts("127.0.0.1:8080"), server.WithStreamBody(true), server.WithTransport(standard.NewTransporter)) -type CtxErrCallback func (ctx context.Context) error + h.GET("/streamWrite1", func(c context.Context, ctx *app.RequestContext) { + rw := newChunkReader() + line := []byte("line\r\n") + ctx.SetBodyStream(rw, 500*len(line)) -// Hook Functions triggered during engine startup. -OnRun []CtxErrCallback + go func() { + for i := 1; i <= 500; i++ { + // For each streaming_write, the upload_file prints + rw.Write(line) + fmt.Println(i) + time.Sleep(10 * time.Millisecond) + } + rw.Close() + }() -// Hook Functions triggered during engine shutdown. -OnShutdown []CtxCallback -``` + go func() { + <-ctx.Finished() + fmt.Println("request process end") + }() + }) -Example code: + h.GET("/streamWrite2", func(c context.Context, ctx *app.RequestContext) { + rw := newChunkReader() + // Content-Length may be negative: + // -1 means Transfer-Encoding: chunked. + ctx.SetBodyStream(rw, -1) + + go func() { + for i := 1; i < 1000; i++ { + // For each streaming_write, the upload_file prints + rw.Write([]byte(fmt.Sprintf("===%d===\n", i))) + fmt.Println(i) + time.Sleep(100 * time.Millisecond) + } + rw.Close() + }() + + go func() { + <-ctx.Finished() + fmt.Println("request process end") + }() + }) -```go -package main + h.Spin() + } -import ( - "github.com/cloudwego/hertz/pkg/app/server" -) + type ChunkReader struct { + rw bytes.Buffer + w2r chan struct{} + r2w chan struct{} + } -func main() { - h := server.New() - h.OnRun = append(h.OnRun, func(ctx context.Context) error { - return nil - }) + func newChunkReader() *ChunkReader { + var rw bytes.Buffer + w2r := make(chan struct{}) + r2w := make(chan struct{}) + cr := &ChunkReader{rw, w2r, r2w} + return cr + } + + var closeOnce = new(sync.Once) + + func (cr *ChunkReader) Read(p []byte) (n int, err error) { + for { + _, ok := <-cr.w2r + if !ok { + closeOnce.Do(func() { + close(cr.r2w) + }) + n, err = cr.rw.Read(p) + return + } + + n, err = cr.rw.Read(p) + + cr.r2w <- struct{}{} + + if n == 0 { + continue + } + return + } + } + + func (cr *ChunkReader) Write(p []byte) (n int, err error) { + n, err = cr.rw.Write(p) + cr.w2r <- struct{}{} + <-cr.r2w + return + } + + func (cr *ChunkReader) Close() { + close(cr.w2r) + } - h.OnShutdown = append(h.OnShutdown, func(ctx context.Context) { - //... - }) -} + ``` + +2. Users can use the `NewChunkedBodyWriter` method provided under `pkg/protocol/http1/resp/writer` in the handler to hijack the response writer, and then use the `ctx.Write` function to write the partitioned data to the body and immediately send it to the client using the `ctx.Flush` function. + + Example Code: + + ```go + h.GET("/flush/chunk", func(c context.Context, ctx *app.RequestContext) { + // Hijack the writer of response + ctx.Response.HijackWriter(resp.NewChunkedBodyWriter(&ctx.Response, ctx.GetWriter())) + + for i := 0; i < 10; i++ { + ctx.Write([]byte(fmt.Sprintf("chunk %d: %s", i, strings.Repeat("hi~", i)))) // nolint: errcheck + ctx.Flush() // nolint: errcheck + time.Sleep(200 * time.Millisecond) + } + }) + ``` + +**The difference between these two methods: the first method sends the data to the client in blocks after executing the handler logic, and the second method can send the partitioned data out in the handler logic.** + +For more example code, please refer to [example](/docs/hertz/tutorials/example/#streaming-readwrite). + +## Register Custom Protocol + +```go +func (engine *Engine) AddProtocol(protocol string, factory interface{}) ``` -### Error Handlers +Detailed information can be found in [registration-of-custom-protocol-server-into-hertz](/docs/hertz/tutorials/framework-exten/protocol/#registration-of-custom-protocol-server-into-hertz). + +## SetClientIPFunc + +The parameter f of this function will be passed to the `RequestContext.SetClientIPFunc` function. The function and example code are shown in [SetClientIPFunc](/docs/hertz/tutorials/basic-feature/context/request/#setclientipfunc). -#### PanicHandler +Function Signature: -Used to set the handling function when a program panics, default is `nil`. +```go +func (engine *Engine) SetClientIPFunc(f app.ClientIP) +``` + +## SetFormValueFunc -**Note**: If both `PanicHandler` and `Recovery` middleware are set at the same time, then the logic in `Recovery` middleware will override that of `PanicHandler`. +The parameter f of this function will be passed to the `RequestContext.SetFormValueFunc` function. The function and example code are shown in [SetFormValueFunc](/docs/hertz/tutorials/basic-feature/context/request/#setformvaluefunc). -Example code: +Function Signature: ```go -package main +func (engine *Engine) SetFormValueFunc(f app.FormValueFunc) +``` + +## Hooks +Hook function is a general concept that represents the operations that accompany an event when it is triggered. + +Hertz provides global Hook injection capabilities for injecting its own processing logic after service triggering and before exiting. For detailed information, please refer to [Hooks](/docs/hertz/tutorials/basic-feature/hooks/). + +## PanicHandler + +Used to set the handler function when panic occurs in the program, default to `nil`. + +> Note: If both `PanicHandler` and `Recovery` middleware are set, the `Recovery` middleware will override the handler logic of `PanicHandler`. + +Example Code: + +```go func main() { h := server.New() - // When panic occurs, the function in PanicHandler will be triggered, - // returning a 500 status code with error message. + // When in Panic, the function in PanicHandler will be triggered, returning a 500 status code and carrying error information h.PanicHandler = func(c context.Context, ctx *app.RequestContext) { ctx.JSON(500, utils.H{ "message": "panic", @@ -443,189 +474,147 @@ func main() { } ``` -### Hijack +## ContinueHandler -#### NoHijackConnPool +Call ContinueHandler after receiving the Expect 100 Continue header sent by the client. Using ContinueHandler, the server can decide whether to read potentially large request bodies, which will be read by default. -> The hijacked connection used during Hertz connection hijacking is managed by pool management. Therefore, when the hijacked connection is used for websockets it does not support asynchronous operations. +Example Code: -The hijacked connection can only be closed once; closing it a second time will result in a null pointer exception. +```go +h := server.Default() +h.ContinueHandler = func(header *protocol.RequestHeader) bool { + return false +} +``` -NoHijackConnPool controls whether to use cache pools to obtain/release hijacked connections. Using pools improves performance of memory resource allocation but cannot prevent exceptions caused by closing connections twice. +## Rendering Template -If it is difficult to ensure that hijackConn won't be closed repeatedly, it can be set as true. +Hertz provides methods such as `Delims`, `SetFuncMap`, `LoadHTMLGlob`, and `LoadHTMLFiles` for rendering HTML or template files. For detailed information, please refer to [HTML](/docs/hertz/tutorials/basic-feature/render/#html). -Example code: +## Using NoRoute and NoMethod -```go -package main +Hertz provides `NoRoute` and `NoMethod` methods for global processing of HTTP 404 and 405 requests. For detailed information, please refer to [NoRoute And NoMethod](/docs/hertz/tutorials/basic-feature/route/#noroute-and-nomethod). -func main() { - // https://github.com/cloudwego/hertz/issues/121 - h.NoHijackConnPool = true -} +## Get Route Information + +```go +func (engine *Engine) Routes() (routes RoutesInfo) ``` -### Get route info +### Routes -Hertz provides `Routes` to get the registered route information. +The `Routes` function returns a slice divided by HTTP methods that contains routing information (HTTP method name, routing path, request handler function name). -Route information struct: +Function Signature: ```go -// RouteInfo represents a request route's specification which contains method and path and its handler. -type RouteInfo struct { - Method string // http method - Path string // url path - Handler string // handler name - HandlerFunc app.HandlerFunc -} - -// RoutesInfo defines a RouteInfo array. -type RoutesInfo []RouteInfo +func (engine *Engine) Routes() (routes RoutesInfo) ``` -Sample Code: +Example Code: ```go -package main +func getHandler() app.HandlerFunc { + return func(c context.Context, ctx *app.RequestContext) { + ctx.String(consts.StatusOK, "get handler") + } +} -import ( - "context" - "github.com/cloudwego/hertz/pkg/app" - "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/common/hlog" - "github.com/cloudwego/hertz/pkg/common/utils" - "github.com/cloudwego/hertz/pkg/protocol/consts" -) +func postHandler() app.HandlerFunc { + return func(c context.Context, ctx *app.RequestContext) { + ctx.String(consts.StatusOK, "post handler") + } +} func main() { h := server.Default() - h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) - }) - routeInfo := h.Routes() - hlog.Info(routeInfo) - h.Spin() + h.GET("/get", getHandler()) + h.POST("/post", postHandler()) + routesInfo := h.Routes() + fmt.Printf("%v\n", routesInfo) + // [{GET /get main.getHandler.func1 0xb2afa0} {POST /post main.postHandler.func1 0xb2b060}] } ``` -## Configuration +## Transporter -Here is a collection of configuration options that can be used for the engine. - -### Use - -Please see [here](#register-middleware). - -### NoHijackConnPool - -Please see [here](#nohijackconnpool). - -### OnRun/OnShutdown - -Please see [here](#set-hook-function). - -### PanicHandler - -Please see [here](#panichandler). +```go +func (engine *Engine) GetTransporterName() (tName string) +func SetTransporter(transporter func (options *config.Options) network.Transporter) +``` ### GetTransporterName -Get the name of the current network library being used. There are currently two options: Go's native `net` and `netpoll`. Linux uses `netpoll` by default, while Windows can only use Go's native `net`. +Obtain the name of the currently used network library, which now has two native options: `go net` and `netpoll`. + +Linux uses `netpoll` by default, while Windows can only use `go net`. -If you're unsure about how to use one of these network libraries, please refer to [this page](https://www.cloudwego.io/docs/hertz/tutorials/basic-feature/network-lib/) +If you have any doubts about how to use the corresponding network library, please refer to [here](https://www.cloudwego.io/docs/hertz/tutorials/basic-feature/network-lib/). -Function signature: +Function Signature: ```go func (engine *Engine) GetTransporterName() (tName string) - -// Deprecated: This only get the global default transporter - may not be the real one used by the engine. -// Use engine.GetTransporterName for the real transporter used. -func GetTransporterName() (tName string) ``` -### SetTransporter - -`SetTransporter` sets only Engine's global defaults. When initializing Engine, use WithTransporter to set your network library instead. - -Function signature: +Example Code: ```go -func SetTransporter(transporter func(options *config.Options) network.Transporter) +h := server.New() +tName := h.GetTransporterName() ``` -### IsRunning +### SetTransporter + +`SetTransporter` is used to set network library. -Check whether or not Engine has been started. +> Note: `SetTransporter` only sets the global default values of the Engine, so using `WithTransporter` to set the network library when initializing the Engine will overwrite the settings of `SetTransporter`. -Function signature: +Function Signature: ```go -func (engine *Engine) IsRunning() bool +func SetTransporter(transporter func (options *config.Options) network.Transporter) ``` -Code example: +Example Code: ```go -package main - -func main() { - h := server.New() - // Check if service is running via /live interface - h.GET("/live", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(200, utils.H{ - "isLive": h.IsRunning(), - }) - }) - h.Spin() -} +route.SetTransporter(standard.NewTransporter) ``` -### IsTraceEnable +## Tracing -Check if tracing is enabled. +Hertz provides the capability of link tracking and also supports user-defined link tracking. For details, please refer to [tracking](/docs/hertz/tutorials/observability/tracing/). -Function signature: +## Hijack -```go -func (engine *Engine) IsTraceEnable() bool -``` +### NoHijackConnPool -### GetCtxPool +> The hijacked connection used during Hertz connection hijacking is managed by pool management. Therefore, when the hijacked connection is used for websockets it does not support asynchronous operations. -Get the current Engine's ctxPool. +The hijacked connection can only be closed once; closing it a second time will result in a null pointer exception. -Function signature: +NoHijackConnPool controls whether to use cache pools to obtain/release hijacked connections. Using pools improves performance of memory resource allocation but cannot prevent exceptions caused by closing connections twice. -```go -func (engine *Engine) GetCtxPool() *sync.Pool -``` +If it is difficult to ensure that hijackConn won't be closed repeatedly, it can be set as true. -Code example: +Example Code: ```go -h := server.New() -// Retrieve a ctx from the ctxPool -h.GetCtxPool().Get().(*app.RequestContext) +package main -// Return a ctx to the pool -h.GetCtxPool().Put(ctx) +func main() { + // https://github.com/cloudwego/hertz/issues/121 + h.NoHijackConnPool = true +} ``` -### Delims +### HijackConnHandle -Please see [here](#delims) +Set the Hijack connection processing function. -### SetFuncMap +Function Signature: -Please see [here](#setfuncmap) - -### LoadHTMLGlob - -Please see [here](#loadhtmlglob) - -### LoadHTMLFiles - -Please see [here](#loadhtmlfiles) +```go +func (engine *Engine) HijackConnHandle(c network.Conn, h app.HijackHandler) +``` diff --git a/content/zh/docs/hertz/tutorials/basic-feature/context/request.md b/content/zh/docs/hertz/tutorials/basic-feature/context/request.md index 5a278ec825..267433c134 100644 --- a/content/zh/docs/hertz/tutorials/basic-feature/context/request.md +++ b/content/zh/docs/hertz/tutorials/basic-feature/context/request.md @@ -906,7 +906,7 @@ h.POST("/user", func(c context.Context, ctx *app.RequestContext) { ### SetFormValueFunc -设置 FormValue 函数。 +若 [FormValue](#formvalue) 函数提供的默认获取 key 的值的方式不满足需求,用户可以使用该函数自定义获取 key 的值的方式。 函数签名: @@ -1346,7 +1346,9 @@ h.Use(func(c context.Context, ctx *app.RequestContext) { ### SetClientIPFunc -设置获取客户端 IP 的地址的函数。 +若 [ClientIP](#clientip) 函数提供的默认方式不满足需求,用户可以使用该函数自定义获取客户端 ip 的方式。 + +该函数可用于即使 remote ip 存在,也希望从 `X-Forwarded-For` 或 `X-Real-IP` Header 获取 ip 的场景(多重代理,想从 `X-Forwarded-For` 或 `X-Real-IP` Header 获得最初的 ip)。 函数签名: diff --git a/content/zh/docs/hertz/tutorials/basic-feature/context/response.md b/content/zh/docs/hertz/tutorials/basic-feature/context/response.md index 1bbcd3bf46..962586fa2f 100644 --- a/content/zh/docs/hertz/tutorials/basic-feature/context/response.md +++ b/content/zh/docs/hertz/tutorials/basic-feature/context/response.md @@ -367,7 +367,7 @@ func (ctx *RequestContext) AbortWithStatusJSON(code int, jsonObj interface{}) ### SetBodyStream -设置 Body Stream 和可选的 Body 大小。 +设置 Body Stream 和可选的 Body 大小。该函数用于 Hertz Server 的流式处理,详情可见 [流式处理](/zh/docs/hertz/tutorials/basic-feature/engine/#流式处理)。 > 注意:bodySize 小于 0 时数据全部写入,大于等于 0 时根据设置的 bodySize 大小写入数据。 diff --git a/content/zh/docs/hertz/tutorials/basic-feature/engine.md b/content/zh/docs/hertz/tutorials/basic-feature/engine.md index c0809bac89..db60c34e7f 100644 --- a/content/zh/docs/hertz/tutorials/basic-feature/engine.md +++ b/content/zh/docs/hertz/tutorials/basic-feature/engine.md @@ -1,315 +1,229 @@ --- title: "Engine" -date: 2023-04-24 +date: 2023-08-18 weight: 1 description: > --- -`Hertz` 的路由、中间件的注册,服务启动,退出等重要方法都是包含在 `server.Hertz` 这个**核心**类型之中的。 -它由 `route.Engine` 以及 `signalWaiter` 组成。以下是 `Hertz` 的定义: +`server.Hertz` 是 `Hertz` 的核心类型,它由 `route.Engine` 以及 `signalWaiter` 组成,`Hertz` 服务器的启动、路由注册、中间件注册以及退出等重要方法均包含在 `server.Hertz` 中。以下是 `server.Hertz` 的定义: ```go -// Hertz is the core struct of hertz. type Hertz struct { - *route.Engine - // 用于接收信号以实现优雅退出 - signalWaiter func (err chan error) error +    *route.Engine +    // 用于接收信号以实现优雅退出 +    signalWaiter func (err chan error) error } ``` -## server.Hertz +`route.Engine` 为 `server.Hertz` 的重要组成部分,`Engine` 的定义位于 [Engine](https://github.com/cloudwego/hertz/blob/main/pkg/route/engine.go)。 -### 初始化服务 - -Hertz 在 `server` 包中提供了 `New` 和 `Default` 函数用于初始化服务。 +## 配置 -`Default` 默认使用了 `Recovery` 中间件以保证服务在运行时不会因为 `panic` 导致服务崩溃。 +| 配置项 | 默认值 | 说明 | +| :---- | :---- | :---- | +| WithTransport | network.NewTransporter | 更换底层 transport | +| WithHostPorts | `:8888` | 指定监听的地址和端口 | +| WithKeepAliveTimeout | 1min | tcp 长连接保活时间,一般情况下不用修改,更应该关注 idleTimeout | +| WithReadTimeout | 3min | 底层读取数据超时时间 | +| WithIdleTimeout | 3min | 长连接请求链接空闲超时时间 | +| WithMaxRequestBodySize | 4 * 1024 * 1024 | 配置最大的请求体大小 | +| WithRedirectTrailingSlash | true | 自动根据末尾的 / 转发,例如:如果 router 只有 /foo/,那么 /foo 会重定向到 /foo/ ;如果只有 /foo,那么 /foo/ 会重定向到 /foo | +| WithRemoveExtraSlash | false | RemoveExtraSlash 当有额外的 / 时也可以当作参数。如:user/:name,如果开启该选项 user//xiaoming 也可匹配上参数 | +| WithUnescapePathValues | true | 如果开启,请求路径会被自动转义(eg. '%2F' -> '/')。如果 UseRawPath 为 false(默认情况),则 UnescapePathValues 实际上为 true,因为 .URI().Path() 将被使用,它已经是转义后的。设置该参数为 false,需要配合 WithUseRawPath(true) | +| WithUseRawPath | false | 如果开启,会使用原始 path 进行路由匹配 | +| WithHandleMethodNotAllowed | false | 如果开启,当当前路径不能被匹配上时,server 会去检查其他方法是否注册了当前路径的路由,如果存在则会响应"Method Not Allowed",并返回状态码 405; 如果没有,则会用 NotFound 的 handler 进行处理 | +| WithDisablePreParseMultipartForm | false | 如果开启,则不会预处理 multipart form。可以通过 ctx.Request.Body() 获取到 body 后由用户处理 | +| WithStreamBody | false | 如果开启,则会使用流式处理 body | +| WithNetwork | "tcp" | 设置网络协议,可选:tcp,udp,unix(unix domain socket),默认为 tcp | +| WithExitWaitTime | 5s | 设置优雅退出时间。Server 会停止建立新的连接,并对关闭后的每一个请求设置 Connection: Close 的 header,当到达设定的时间关闭 Server。当所有连接已经关闭时,Server 可以提前关闭 | +| WithTLS | nil | 配置 server tls 能力,详情可见 [TLS](/zh/docs/hertz/tutorials/basic-feature/protocol/tls/) | +| WithListenConfig | nil | 设置监听器配置,可用于设置是否允许 reuse port 等 | +| WithALPN | false | 是否开启 ALPN | +| WithTracer | []interface{}{} | 注入 tracer 实现,如不注入 Tracer 实现,默认关闭 | +| WithTraceLevel | LevelDetailed | 设置 trace level | +| WithWriteTimeout | 无限长 | 写入数据超时时间 | +| WithRedirectFixedPath | false | 如果开启,当当前请求路径不能匹配上时,server 会尝试修复请求路径并重新进行匹配,如果成功匹配并且为 GET 请求则会返回状态码 301 进行重定向,其他请求方式返回 308 进行重定向 | +| WithBasePath | `/` | 设置基本路径,前缀和后缀必须为 `/` | +| WithMaxKeepBodySize | 4 * 1024 * 1024 | 设置回收时保留的请求体和响应体的最大大小。单位:字节 | +| WithGetOnly | false | 如果开启则只接受 GET 请求 | +| WithKeepAlive | true | 如果开启则使用 HTTP 长连接 | +| WithAltTransport | network.NewTransporter | 设置备用 transport | +| WithH2C | false | 设置是否开启 H2C | +| WithReadBufferSize | 4 * 1024 | 设置读缓冲区大小,同时限制 HTTP header 大小 | +| WithRegistry | registry.NoopRegistry, nil | 设置注册中心配置,服务注册信息 | +| WithAutoReloadRender | false, 0 | 设置自动重载渲染配置 | +| WithDisablePrintRoute | false | 设置是否禁用 debugPrintRoute | +| WithOnAccept | nil | 设置在 netpoll 中当一个连接被接受但不能接收数据时的回调函数,在 go net 中在转换 TLS 连接之前被调用 | +| WithOnConnect | nil | 设置 onConnect 函数。它可以接收来自 netpoll 连接的数据。在 go net 中,它将在转换 TLS 连接后被调用 | + +Server Connection 数量限制: + +* 如果是使用标准网络库,无此限制 +* 如果是使用 netpoll,最大连接数为 10000 + (这个是 netpoll 底层使用的 [gopool](https://github.com/bytedance/gopkg/blob/b9c1c36b51a6837cef4c2223e11522e3a647460c/util/gopool/gopool.go#L46) + )控制的,修改方式也很简单,调用 gopool 提供的函数即可:`gopool.SetCap(xxx)`(main.go 中调用一次即可)。 + +Server 侧的配置项均在初始化 Server 时采用 `server.WithXXX` 的方式,如: ```go -// New creates a hertz instance without any default config. -func New(opts ...config.Option) *Hertz { - options := config.NewOptions(opts) - h := &Hertz{Engine: route.NewEngine(options)} - return h +func main() { + h := server.New(server.WithXXXX()) + ... } ``` +## 初始化服务 + ```go -// Default creates a hertz instance with default middlewares. -func Default(opts ...config.Option) *Hertz { - h := New(opts...) - // 在 New 的基础上使用了内置的 Recovery 中间件 - h.Use(recovery.Recovery()) - return h -} +func Default(opts ...config.Option) *Hertz +func New(opts ...config.Option) *Hertz ``` -若想详细地了解可选的配置项,可以在 [配置说明](https://www.cloudwego.io/zh/docs/hertz/reference/config/) 中查看。 +### Default + +`Default` 用于初始化服务,默认使用了 `Recovery` 中间件以保证服务在运行时不会因为 `panic` 导致服务崩溃。 -示例代码 +函数签名: ```go -package main +func Default(opts ...config.Option) *Hertz +``` -import ( - "github.com/cloudwego/hertz/pkg/app/server" -) +示例代码: +```go func main() { - h := server.New() - // 使用 Default - // h := server.Default() + h := server.Default() h.Spin() } ``` -### 运行服务 - -`Hertz` 提供了 `Spin` 函数用于启动服务器。 +### New -和 `route.Engine` 中提供的 `Run` 不同,除非有**特殊**需求,不然一般使用 `Spin` 函数用于运行服务。 +`New` 用于初始化服务,没有使用默认的 `Recovery` 中间件。 -在使用 [服务注册发现](https://www.cloudwego.io/zh/docs/hertz/tutorials/service-governance/service_discovery/) 的功能时,`Spin` -会在服务启动时将服务注册进入注册中心,并使用 `signalWaiter` 监测服务异常。 -只有使用 `Spin` 来启动服务才能支持 [优雅退出](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/graceful-shutdown/) 的特性。 +函数签名: ```go -package main +func New(opts ...config.Option) *Hertz +``` -import ( - "github.com/cloudwego/hertz/pkg/app/server" -) +示例代码: +```go func main() { h := server.New() - // 我们通常推荐使用 Spin h.Spin() } ``` +## 服务运行与退出 + ```go -package main +func (h *Hertz) Spin() +func (engine *Engine) Run() (err error) +func (h *Hertz) SetCustomSignalWaiter(f func(err chan error) error) +``` -import ( - "github.com/cloudwego/hertz/pkg/app/server" -) +### Spin -func main() { - h := server.New() - // 使用 Run 函数启动 - if err := h.Run(); err != nil { - // ... - panic(err) - } -} -``` +`Spin` 函数用于运行 Hertz 服务器,接收到退出信号后可退出服务。 -## route.Engine +该函数支持服务的优雅退出,优雅退出的详细内容请看 [优雅退出](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/graceful-shutdown/)。 -`route.Engine` 为 `server.Hertz` 的重要组成部分,其中拥有大量的在开发中常用的方法,尤为 **重要** +在使用 [服务注册发现](https://www.cloudwego.io/zh/docs/hertz/tutorials/service-governance/service_discovery/) 的功能时,`Spin` 会在服务启动时将服务注册进入注册中心,并使用 `signalWaiter` 监测服务异常。 -```go -package route +函数签名: -type Engine struct { - noCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is used - - // Engine 名称 - Name string - serverName atomic.Value - - // 路由和协议服务器的选项 - options *config.Options - - // router 前缀树 - RouterGroup - trees MethodTrees - maxParams uint16 - - allNoMethod app.HandlersChain - allNoRoute app.HandlersChain - noRoute app.HandlersChain - noMethod app.HandlersChain - - // 用于渲染 HTML - delims render.Delims - funcMap template.FuncMap - htmlRender render.HTMLRender - - // NoHijackConnPool 将控制是否使用缓存池来获取/释放劫持连接 - // 如果很难保证 hijackConn 不会重复关闭,请将其设置为 true - NoHijackConnPool bool - hijackConnPool sync.Pool - - // KeepHijackedConns 是一个可选择的禁用连接的选项 - // 在连接的 HijackHandler 返回后由 Hertz 关闭。 - // 这的选项允许保存在 goroutine 中 - // 例如当 hertz 将 http 连接升级为 websocket 时, - // 连接会转到另一个处理程序,该处理程序会在需要时关闭它 - KeepHijackedConns bool - - // 底层传输的网络库,现在有 go net 和 netpoll 两个选择 - transport network.Transporter - - // 用于链路追踪 - tracerCtl tracer.Controller - enableTrace bool - - // 用于管理协议层 - protocolSuite *suite.Config - protocolServers map[string]protocol.Server - protocolStreamServers map[string]protocol.StreamServer - - // RequestContext 连接池 - ctxPool sync.Pool - - // 处理从 http 处理程序中恢复的 panic 的函数 - // 它应该用于生成错误页面并返回 http 错误代码 500(内部服务器错误) - // 处理程序可用于防止服务器因未恢复的 panic 而崩溃 - PanicHandler app.HandlerFunc - - // 在收到 Expect 100 Continue Header 后调用 ContinueHandler。 - // - // https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3 - // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.1.1 - // 使用 ContinueHandler,服务器可以基于头信息决定是否读取可能较大的请求正文。 - // - // 默认情况下就像它们是普通请求一样,自动读取 Expect 100 Continue 请求的请求正文, - ContinueHandler func(header *protocol.RequestHeader) bool - - // 用于表示 Engine 状态(Init/Running/Shutdown/Closed)。 - status uint32 - - // Engine 启动时依次触发的 hook 函数 - OnRun []CtxErrCallback - - // Engine 关闭时同时触发的 hook 函数 - OnShutdown []CtxCallback - - clientIPFunc app.ClientIP - formValueFunc app.FormValueFunc -} +```go +func (h *Hertz) Spin() ``` -### 设置服务器名 - -示例代码 +示例代码: ```go -package main - func main() { - h := server.New() - // 用于设置 response header 中的 Server 字段,默认为 Hertz - h.Name = "" + h := server.Default() + h.Spin() } ``` -### 渲染 template +### Run -engine 提供了 `Delims`, `SetFuncMap`, `LoadHTMLGlob`, `LoadHTMLFiles`, `SetHTMLTemplate`, `SetAutoReloadHTMLTemplate` -等方法用于渲染 HTML 或模板文件。 +`Run` 函数用于运行 Hertz 服务器,接收到退出信号后可退出服务。 -#### Delims +该函数不支持服务的优雅退出,除非有**特殊**需求,不然一般使用 [Spin](#spin) 函数用于运行服务。 -用于设置 template 的分隔符 - -函数签名: +函数签名: ```go -func (engine *Engine) Delims(left, right string) *Engine +func (engine *Engine) Run() (err error) ``` -#### SetFuncMap - -用于设置 template 的数据源 - -函数签名: - -```go -type FuncMap map[string]interface{} - -func (engine *Engine) SetFuncMap(funcMap template.FuncMap) -``` +示例代码: ```go -package main - func main() { - h := server.New() - h.SetFuncMap(template.FuncMap{ - "time": time.Now.String(), - }) + h := server.Default() + if err := h.Run(); err != nil { + // ... + panic(err) + } } ``` -#### LoadHTMLGlob +### SetCustomSignalWaiter -用于全局加载 template 文件,可以使用 `*` 通配符来指定模板文件夹 +`SetCustomSignalWaiter` 函数用于自定义服务器接收信号后的处理函数,若没有设置自定义函数,Hertz 使用 `waitSignal` 函数作为信号处理的默认实现方式,详细内容请看[优雅退出](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/graceful-shutdown/)。 -函数签名: +函数签名: ```go -// LoadHTMLGlob loads HTML files identified by glob pattern -// and associates the result with HTML renderer. -func (engine *Engine) LoadHTMLGlob(pattern string) +func (h *Hertz) SetCustomSignalWaiter(f func(err chan error) error) ``` -示例代码: +示例代码: ```go -// 加载 render/html 目录下的所有 html 模板文件 -h.LoadHTMLGlob("render/html/*") - -// 加载 render/html/index.tmpl 模板文件 -h.LoadHTMLGlob("index.tmpl") +func main() { + h := server.New() + h.SetCustomSignalWaiter(func(err chan error) error { + return nil + }) + h.Spin() +} ``` -#### LoadHTMLFiles - -用于加载指定的 template 文件,参数为 string 切片 - -函数签名: +## 中间件 ```go -// LoadHTMLFiles loads a slice of HTML files -// and associates the result with HTML renderer. -func (engine *Engine) LoadHTMLFiles(files ...string) +func (engine *Engine) Use(middleware ...app.HandlerFunc) IRoutes ``` -#### SetHTMLTemplate/SetAutoReloadHTMLTemplate - -这两个方法在渲染的内部逻辑使用,不推荐直接使用 - -### 注册中间件 +### Use -Hertz 提供 `Use` 函数用于将中间件注册进入路由。 +`Use` 函数用于将中间件注册进入路由。 -我们支持用户自定义中间件,与此同时我们也提供了一些常用的中间件实现, -详情见 [hertz-contrib](https://github.com/hertz-contrib) +Hertz 支持用户自定义中间件,Hertz 已经实现了一些常用的中间件,详情见 [hertz-contrib](https://github.com/hertz-contrib)。 -于此同时,虽然常见的中间件的使用方法为**全局注册**, 但是我们也支持**路由组**级别和**单一路由**级别的注册,[详见](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/middleware/#%E8%B7%AF%E7%94%B1%E7%BB%84%E7%BA%A7%E5%88%AB%E4%B8%AD%E9%97%B4%E4%BB%B6) +Hertz 支持的中间件的使用方法包括**全局注册**、**路由组**级别和**单一路由**级别的注册,详情见 [服务端中间件](/zh/docs/hertz/tutorials/basic-feature/middleware/#服务端中间件)。 -`Use` 函数中 `middleware`的形参必须为 `app.HandlerFunc` 的 http 处理函数。 +`Use` 函数中 `middleware` 的形参必须为 `app.HandlerFunc` 的 http 处理函数: ```go type HandlerFunc func (ctx context.Context, c *app.RequestContext) ``` -函数签名: +函数签名: ```go func (engine *Engine) Use(middleware ...app.HandlerFunc) IRoutes ``` -示例代码 +示例代码: ```go -package main - -// ... - func main() { h := server.New() // 将内置的 Recovery 中间件注册进入路由 @@ -330,355 +244,376 @@ func exampleMiddleware() app.handlerFunc { } ``` -更多示例详见 [仓库](https://github.com/cloudwego/hertz-examples/tree/main/middleware) - -### 服务退出 +## 流式处理 -hertz 提供 `Shutdown` 函数用于进行优雅退出。 +Hertz 支持 Server 的流式处理,包括流式读和流式写。 -若是使用了 [服务注册与发现](https://www.cloudwego.io/zh/docs/hertz/tutorials/service-governance/service_discovery/) 的功能,在服务退出发生时也会从注册中心下线相应数据。 +> 注意:由于 netpoll 和 go net 触发模式不同,netpoll 流式为 “伪” 流式(由于 LT 触发,会由网络库将数据读取到网络库的 buffer 中),在大包的场景下(如:上传文件等)可能会有内存问题,推荐使用 go net。 -函数签名: +### 流式读 -```go -func (engine *Engine) Shutdown(ctx context.Context) (err error) -``` +Hertz Server 支持流式读取请求内容。 -示例代码: +示例代码: ```go -package main +func main() { + h := server.Default(server.WithHostPorts("127.0.0.1:8080"), server.WithStreamBody(true), server.WithTransport(standard.NewTransporter)) -// ... + h.POST("/bodyStream", handler) -func main() { - h := server.New() - // 在访问该路径时,会触发 shutdown 函数触发下线 - h.GET("/shutdown", func(ctx context.Context, c *app.RequestContext) { - h.ShutDown(ctx) - }) - h.Spin() + h.Spin() +} + +func handler(ctx context.Context, c *app.RequestContext) { + // Acquire body streaming + bodyStream := c.RequestBodyStream() + // Read half of body bytes + p := make([]byte, c.Request.Header.ContentLength()/2) + r, err := bodyStream.Read(p) + if err != nil { + panic(err) + } + left, _ := ioutil.ReadAll(bodyStream) + c.String(consts.StatusOK, "bytes streaming_read: %d\nbytes left: %d\n", r, len(left)) } ``` -关于优雅退出的更多的信息见 [文档](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/graceful-shutdown/) +### 流式写 -### 设置 hook 函数 +Hertz Server 支持流式写入响应。 -在 Engine 启动和关闭时会触发相应的 hook 函数. +提供了两种方式: -`OnRun` 和 `OnShutdown` 是两个 hook 函数的切片,用于存储 hook 函数,为了不影响原有的 hook 函数, -在使用时需要使用 `append` 函数将新的 hook 函数添加进入切片. +1. 用户在 handler 中通过 `ctx.SetBodyStream` 函数传入一个 `io.Reader`,然后按与示例代码(利用 channel 控制数据分块及读写顺序)类似的方式分块读写数据。**注意,数据需异步写入。** + + 若用户事先知道传输数据的总长度,可以在 `ctx.SetBodyStream` 函数中传入该长度进行流式写,示例代码如 `/streamWrite1`。 -详细设置方式见 [Hooks](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/hooks/) + 若用户事先不知道传输数据的总长度,可以在 `ctx.SetBodyStream` 函数中传入 -1 以 `Transfer-Encoding: chunked` 的方式进行流式写,示例代码如 `/streamWrite2`。 -函数签名: + 示例代码: -```go -type CtxCallback func (ctx context.Context) + ```go + func main() { + h := server.Default(server.WithHostPorts("127.0.0.1:8080"), server.WithStreamBody(true), server.WithTransport(standard.NewTransporter)) -type CtxErrCallback func (ctx context.Context) error + h.GET("/streamWrite1", func(c context.Context, ctx *app.RequestContext) { + rw := newChunkReader() + line := []byte("line\r\n") + ctx.SetBodyStream(rw, 500*len(line)) -// Engine 启动时依次触发的 hook 函数 -OnRun []CtxErrCallback + go func() { + for i := 1; i <= 500; i++ { + // For each streaming_write, the upload_file prints + rw.Write(line) + fmt.Println(i) + time.Sleep(10 * time.Millisecond) + } + rw.Close() + }() -// Engine 关闭时同时触发的 hook 函数 -OnShutdown []CtxCallback -``` + go func() { + <-ctx.Finished() + fmt.Println("request process end") + }() + }) -示例代码: + h.GET("/streamWrite2", func(c context.Context, ctx *app.RequestContext) { + rw := newChunkReader() + // Content-Length may be negative: + // -1 means Transfer-Encoding: chunked. + ctx.SetBodyStream(rw, -1) + + go func() { + for i := 1; i < 1000; i++ { + // For each streaming_write, the upload_file prints + rw.Write([]byte(fmt.Sprintf("===%d===\n", i))) + fmt.Println(i) + time.Sleep(100 * time.Millisecond) + } + rw.Close() + }() + + go func() { + <-ctx.Finished() + fmt.Println("request process end") + }() + }) -```go -package main + h.Spin() + } -func main() { - h := server.New() - h.OnRun = append(h.OnRun, func(ctx context.Context) error { - return nil - }) - h.OnShutdown = append(h.OnShutdown, func(ctx context.Context) { - //... - }) -} -``` + type ChunkReader struct { + rw bytes.Buffer + w2r chan struct{} + r2w chan struct{} + } -### 错误处理器 + func newChunkReader() *ChunkReader { + var rw bytes.Buffer + w2r := make(chan struct{}) + r2w := make(chan struct{}) + cr := &ChunkReader{rw, w2r, r2w} + return cr + } -#### PanicHandler + var closeOnce = new(sync.Once) -用于设置当程序发生 panic 时的处理函数,默认为 `nil`. + func (cr *ChunkReader) Read(p []byte) (n int, err error) { + for { + _, ok := <-cr.w2r + if !ok { + closeOnce.Do(func() { + close(cr.r2w) + }) + n, err = cr.rw.Read(p) + return + } -**注意**: 如果同时设置了 `PanicHandler` 和 `Recovery` 中间件,则 `Recovery` 中间件会覆盖 `PanicHandler` 的处理逻辑. + n, err = cr.rw.Read(p) -示例代码: + cr.r2w <- struct{}{} -```go -package main + if n == 0 { + continue + } + return + } + } -func main() { - h := server.New() - // 在 panic 时,会触发 PanicHandler 中的函数,返回 500 状态码并携带错误信息 - h.PanicHandler = func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(500, utils.H{ - "message": "panic", - }) + func (cr *ChunkReader) Write(p []byte) (n int, err error) { + n, err = cr.rw.Write(p) + cr.w2r <- struct{}{} + <-cr.r2w + return } - h.GET("/hello", func(c context.Context, ctx *app.RequestContext) { - panic("panic") - }) - h.Spin() -} -``` -### Hijack + func (cr *ChunkReader) Close() { + close(cr.w2r) + } + + ``` -#### NoHijackConnPool +2. 用户可以在 handler 中使用 `pkg/protocol/http1/resp/writer` 下提供的 `NewChunkedBodyWriter` 方法劫持 response 的 writer,然后使用 `ctx.Write` 函数将分块数据写入 Body 并将分块数据使用 `ctx.Flush` 函数立即发送给客户端。 -> Hertz 连接劫持时所使用的 hijack conn 是池化管理的,因此被劫持的连接在 websocket 中使用的时候,不支持异步操作。 + 示例代码: -劫持的连接仅能被关闭一次,第二次关闭会导致空指针异常。 + ```go + h.GET("/flush/chunk", func(c context.Context, ctx *app.RequestContext) { + // Hijack the writer of response + ctx.Response.HijackWriter(resp.NewChunkedBodyWriter(&ctx.Response, ctx.GetWriter())) -NoHijackConnPool 将控制是否使用缓存池来获取/释放劫持连接。如果使用池,将提升内存资源分配的性能,但无法避免二次关闭连接导致的异常。 + for i := 0; i < 10; i++ { + ctx.Write([]byte(fmt.Sprintf("chunk %d: %s", i, strings.Repeat("hi~", i)))) // nolint: errcheck + ctx.Flush() // nolint: errcheck + time.Sleep(200 * time.Millisecond) + } + }) + ``` -如果很难保证 hijackConn 不会被反复关闭,可以将其设置为 true。 +**这两种方式的区别:第一种在执行完 handler 逻辑后再将数据按分块发送给客户端,第二种在 handler 逻辑中就可以将分块数据发送出去。** -示例代码: +更多示例代码可参考 [example](/zh/docs/hertz/tutorials/example/#流式读写)。 -```go -package main +## 注册自定义协议 -func main() { - // https://github.com/cloudwego/hertz/issues/121 - h.NoHijackConnPool = true -} +```go +func (engine *Engine) AddProtocol(protocol string, factory interface{}) ``` -### 获取路由信息 - -Hertz 提供了 `Routes` 获取注册的路由信息供用户使用。 +详细信息可见 [注册自定义协议](/zh/docs/hertz/tutorials/framework-exten/protocol/#注册自定义协议-server-到-hertz-中)。 -路由信息结构: +## SetClientIPFunc -```go -// RouteInfo represents a request route's specification which contains method and path and its handler. -type RouteInfo struct { - Method string // http method - Path string // url path - Handler string // handler name - HandlerFunc app.HandlerFunc -} - -// RoutesInfo defines a RouteInfo array. -type RoutesInfo []RouteInfo -``` +该函数的参数 f 会被传递到 `RequestContext.SetClientIPFunc` 函数中,作用及示例代码见 [SetClientIPFunc](/zh/docs/hertz/tutorials/basic-feature/context/request/#setclientipfunc)。 -示例代码: +函数签名: ```go -package main - -import ( - //... - "github.com/cloudwego/hertz/pkg/app/server" - //... -) - -func main() { - h := server.Default() - h.GET("/ping", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"}) - }) - routeInfo := h.Routes() - hlog.Info(routeInfo) - h.Spin() -} +func (engine *Engine) SetClientIPFunc(f app.ClientIP) ``` -## 配置 +## SetFormValueFunc -这里是 engine 所涉及的可以使用的配置项集合 +该函数的参数 f 会被传递到 `RequestContext.SetFormValueFunc` 函数中,作用及示例代码见 [SetFormValueFunc](/zh/docs/hertz/tutorials/basic-feature/context/request/#setformvaluefunc)。 -### Use +函数签名: -请查看 [此处](#注册中间件) +```go +func (engine *Engine) SetFormValueFunc(f app.FormValueFunc) +``` -### NoHijackConnPool +## 钩子函数 -请查看 [此处](#nohijackconnpool) +钩子函数(Hooks)是一个通用的概念,表示某事件触发时所伴随的操作。 -### OnRun/OnShutdown +Hertz 提供了全局的 Hook 注入能力,用于在服务触发启动后和退出前注入自己的处理逻辑,详细信息可见 [Hooks](/zh/docs/hertz/tutorials/basic-feature/hooks/)。 -请查看 [此处](#设置-hook-函数) +## Panic 处理函数 -### PanicHandler +用于设置当程序发生 panic 时的处理函数,默认为 `nil`。 -请查看 [此处](#panichandler) +>注意: 如果同时设置了 `PanicHandler` 和 `Recovery` 中间件,则 `Recovery` 中间件会覆盖 `PanicHandler` 的处理逻辑。 -### GetTransporterName +示例代码: -获取当前使用的网络库名称,现在有原生的 `go net` 和 `netpoll` 两种. +```go +func main() { + h := server.New() + // 在 panic 时,会触发 PanicHandler 中的函数,返回 500 状态码并携带错误信息 + h.PanicHandler = func(c context.Context, ctx *app.RequestContext) { + ctx.JSON(500, utils.H{ + "message": "panic", + }) + } + h.GET("/hello", func(c context.Context, ctx *app.RequestContext) { + panic("panic") + }) + h.Spin() +} +``` -linux 默认使用 `netpoll`, windows 只能使用 `go net`. +## ContinueHandler -如果对如何使用对应的网络库有疑惑,请查看 [此处](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/network-lib/) +在接收到客户端发来的 Expect 100 Continue 头之后调用 ContinueHandler。使用 ContinueHandler,服务器可以决定是否读取可能很大的请求正文,默认情况下会读取。 -函数签名: +示例代码: ```go -func (engine *Engine) GetTransporterName() (tName string) - -// Deprecated: This only get the global default transporter - may not be the real one used by the engine. -// Use engine.GetTransporterName for the real transporter used. -func GetTransporterName() (tName string) +h := server.Default() +h.ContinueHandler = func(header *protocol.RequestHeader) bool { + return false +} ``` -### SetTransporter +## 渲染 template -`SetTransporter` 只设置 Engine 的全局默认值。 -所以具体在初始化 Engine 时使用 WithTransporter 来设置网络库时会覆盖掉 SetTransporter 的设置。 +Hertz 提供了 `Delims`, `SetFuncMap`, `LoadHTMLGlob`, `LoadHTMLFiles` 等方法用于渲染 HTML 或模板文件,详细内容可参考 [HTML](/zh/docs/hertz/tutorials/basic-feature/render/#html)。 -函数签名: +## NoRoute 与 NoMethod 使用 + +Hertz 提供了 `NoRoute` 与 `NoMethod` 方法用于全局处理 HTTP 404 与 405 请求,详细内容可参考 [NoRoute 与 NoMethod 使用](/zh/docs/hertz/tutorials/basic-feature/route/#noroute-与-nomethod-使用)。 + +## 获取路由信息 ```go -func SetTransporter(transporter func (options *config.Options) network.Transporter) +func (engine *Engine) Routes() (routes RoutesInfo) ``` -### IsRunning +### Routes -判断当前 Engine 是否已经启动. +`Routes` 函数返回一个按 HTTP 方法划分的包含路由信息(HTTP 方法名,路由路径,请求处理函数名)的切片。 -函数签名: +函数签名: ```go -func (engine *Engine) IsRunning() bool +func (engine *Engine) Routes() (routes RoutesInfo) ``` -代码示例: +示例代码: ```go -package main +func getHandler() app.HandlerFunc { + return func(c context.Context, ctx *app.RequestContext) { + ctx.String(consts.StatusOK, "get handler") + } +} + +func postHandler() app.HandlerFunc { + return func(c context.Context, ctx *app.RequestContext) { + ctx.String(consts.StatusOK, "post handler") + } +} func main() { - h := server.New() - // 可以通过 /live 接口来判断当前服务的运行状态 - h.GET("/live", func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(200, utils.H{ - "isLive": h.IsRunning(), - }) - }) - h.Spin() + h := server.Default() + h.GET("/get", getHandler()) + h.POST("/post", postHandler()) + routesInfo := h.Routes() + fmt.Printf("%v\n", routesInfo) + // [{GET /get main.getHandler.func1 0xb2afa0} {POST /post main.postHandler.func1 0xb2b060}] } ``` -### IsTraceEnable - -判断是否启用了 trace 功能. - -函数签名: +## 底层网络库 ```go -func (engine *Engine) IsTraceEnable() bool +func (engine *Engine) GetTransporterName() (tName string) +func SetTransporter(transporter func (options *config.Options) network.Transporter) ``` -### GetCtxPool +### GetTransporterName + +获取当前使用的网络库名称,现在有原生的 `go net` 和 `netpoll` 两种。 + +linux 默认使用 `netpoll`, windows 只能使用 `go net`。 -获取当前 Engine 的 ctxPool. +如果对如何使用对应的网络库有疑惑,请查看 [此处](https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/network-lib/)。 函数签名: ```go -func (engine *Engine) GetCtxPool() *sync.Pool +func (engine *Engine) GetTransporterName() (tName string) ``` -代码示例: +示例代码: ```go h := server.New() -// 从 ctxPool 中获取一个 ctx -h.GetCtxPool().Get().(*app.RequestContext) - -// 将 ctx 放回 ctxPool -h.GetCtxPool().Put(ctx) +tName := h.GetTransporterName() ``` -### GetServiceName +### SetTransporter + +`SetTransporter` 用于设置网络库。 -获取当前 Engine 的服务名. +>注意:`SetTransporter` 只设置 Engine 的全局默认值,所以在初始化 Engine 时使用 `WithTransporter` 来设置网络库会覆盖掉 `SetTransporter` 的设置。 函数签名: ```go -func (engine *Engine) GetServerName() []byte +func SetTransporter(transporter func (options *config.Options) network.Transporter) ``` -### NoRoute - -用于设置当请求的路由不存在时的处理函数,默认返回 404 状态码 - -函数签名: +示例代码: ```go -// NoRoute adds handlers for NoRoute. It returns a 404 code by default. -func (engine *Engine) NoRoute(handlers ...app.HandlerFunc) +route.SetTransporter(standard.NewTransporter) ``` -示例代码: +## 链路追踪 -```go -package main +Hertz 提供了链路追踪的能力,也支持用户自定义链路跟踪,详情可参考 [链路追踪](/zh/docs/hertz/tutorials/observability/tracing/)。 -func main() { - h := server.New() - h.NoRoute(func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(404, utils.H{ - "msg": "cannot found resource", - }) - }) - h.Spin() -} -``` +## Hijack -### NoMethod +### NoHijackConnPool -用于设置当请求的方法不存在时的处理函数,它默认返回一个 405 状态码 -> 当使用 NoMethod 时需要与 server.WithHandleMethodNotAllowed 配合使用 +> Hertz 连接劫持时所使用的 hijack conn 是池化管理的,因此被劫持的连接在 websocket 中使用的时候,不支持异步操作。 -函数签名: +劫持的连接仅能被关闭一次,第二次关闭会导致空指针异常。 -```go -// NoMethod adds handlers for NoMethod. It returns a 405 code by default. -// NoMethod sets the handlers called when the HTTP method does not match. -func (engine *Engine) NoMethod(handlers ...app.HandlerFunc) -``` +NoHijackConnPool 将控制是否使用缓存池来获取/释放劫持连接。如果使用池,将提升内存资源分配的性能,但无法避免二次关闭连接导致的异常。 -示例代码: +如果很难保证 hijackConn 不会被反复关闭,可以将其设置为 true。 + +示例代码: ```go package main func main() { - h := server.New() - h.NoRoute(func(c context.Context, ctx *app.RequestContext) { - ctx.JSON(405, utils.H{ - "msg": "cannot match HTTP method", - }) - }) - h.Spin() + // https://github.com/cloudwego/hertz/issues/121 + h.NoHijackConnPool = true } ``` -### Delims - -请查看 [此处](#delims) - -### SetFuncMap +### HijackConnHandle -请查看 [此处](#setfuncmap) +设置 Hijack 连接处理函数。 -### LoadHTMLGlob +函数签名: -请查看 [此处](#loadhtmlglob) - -### LoadHTMLFiles - -请查看 [此处](#loadhtmlfiles) +```go +func (engine *Engine) HijackConnHandle(c network.Conn, h app.HijackHandler) +```