命令你跟我我一起学Hertz!

514 阅读12分钟

Hello,Hertz

 package main
 ​
 import (
     "context"
 ​
     "github.com/cloudwego/hertz/pkg/app"
     "github.com/cloudwego/hertz/pkg/app/server"
     "github.com/cloudwego/hertz/pkg/protocol/consts"
 )
 ​
 func main() {
     // server.Default() creates a Hertz with recovery middleware.
     // If you need a pure hertz, you can use server.New()
     h := server.Default()
 ​
     h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
         c.String(consts.StatusOK, "Hello hertz!")
     })
 ​
     h.Spin()
 }

在主函数中,我们首先创建了一个 Hertz 实例 h,使用 server.Default() 创建的 Hertz 包含了恢复中间件,用于处理请求时的错误恢复。如果你需要一个纯净的 Hertz 实例,可以使用 server.New()

然后,我们使用 h.GET("/hello", ...) 注册了一个路由处理函数,当客户端通过 GET 方法访问 "/hello" 路径时,该处理函数将会被调用。

在处理函数中,我们通过 c.String(consts.StatusOK, "Hello hertz!") 发送一个字符串响应,其中 consts.StatusOK 是 HTTP 状态码 200(表示请求成功)。

最后,通过调用 h.Spin() 启动服务器,开始监听并处理传入的请求。

之后我们运行程序,打开浏览器输入本机的8888端口,那么就会出现响应信息

 Hello hertz!

config

 package main
 ​
 import (
     "context"
 ​
     "github.com/cloudwego/hertz/pkg/app"
     "github.com/cloudwego/hertz/pkg/app/server"
     "github.com/cloudwego/hertz/pkg/network/standard"
     "github.com/cloudwego/hertz/pkg/protocol/consts"
 )
 ​
 func main() {
     // The default listening port is 8888.
     // You can modify it with server.WithHostPorts().
     h := server.Default(
         server.WithHostPorts("127.0.0.1:8080"),
         server.WithMaxRequestBodySize(20<<20),
         server.WithTransport(standard.NewTransporter),
     )
 ​
     h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
         c.String(consts.StatusOK, "Hello hertz!")
     })
 ​
     h.Spin()
 }
 ​

在这个示例中,我们使用了一些额外的配置选项:

  • server.WithHostPorts("127.0.0.1:8080"):设置服务器的监听地址和端口为 127.0.0.1:8080。默认情况下,Hertz 服务器监听在端口 8888,通过这个选项可以修改监听的地址和端口。
  • server.WithMaxRequestBodySize(20<<20):设置请求体的最大限制为 20MB。这个选项可以控制接收请求时的最大请求体大小,当请求体超过设置的限制时,服务器会返回 413 Request Entity Too Large 状态码。
  • server.WithTransport(standard.NewTransporter):使用标准的 HTTP 传输器。Hertz 提供了多种传输器,例如标准的 HTTP 传输器、Fasthttp 传输器等。通过该选项可以选择使用不同的传输器来处理请求。

在主函数中,我们注册了一个路由处理函数,当客户端通过 GET 方法访问 "/hello" 路径时,该处理函数将被调用。处理函数中,我们使用 c.String(consts.StatusOK, "Hello hertz!") 发送一个字符串响应。

最后,通过调用 h.Spin() 来启动服务器,开始监听并处理传入的请求。

这个时候我们之后我们运行程序,打开浏览器输入本机的8080端口,那么就会出现响应信息

 Hello hertz!

config里面可以进行更多的设置。

配置说明 | CloudWeGo

这里面是全部的,我们往往只使用常用的。

配置名称类型说明
WithHostPortsstring指定监听的地址和端口
WithMaxRequestBodySizeint配置最大的请求体大小,默认 4M(4M 对应的填的值是 410241024)
WithRedirectTrailingSlashbool自动根据末尾的 / 转发,例如:如果 router 只有 /foo/,那么 /foo 会重定向到 /foo/ ;如果只有 /foo,那么 /foo/ 会重定向到 /foo。默认开启
WithNetworkstring设置网络协议,可选:tcp,udp,unix(unix domain socket),默认为 tcp
WithReadBufferSizeint设置读缓冲区大小,同时限制 HTTP header 大小。默认值:4 * 1024

不同协议的优点和缺点

HTTP1: 优点:广泛支持,与现有的网络基础设施兼容性好,可以实现简单的请求和响应模型。 缺点:串行传输,每次请求都需要建立新的连接,效率较低;头部信息冗余较多,占用带宽。

TLS: 优点:提供加密通信,确保数据传输的安全性;提供身份验证,防止中间人攻击。 缺点:增加了服务器和客户端的计算负担,可能引起一定的性能损失。

HTTP2: 优点:引入多路复用机制,可以同时处理多个请求,提高传输效率;压缩头部信息,减少带宽占用;支持服务器主动推送。 缺点:对于老旧的网络基础设施支持不好。

HTTP3: 优点:基于QUIC协议,改善了在不稳定网络环境下的表现,提供更好的用户体验;采用UDP传输,可以减少握手延迟。 缺点:相对较新,对于部分服务器和浏览器的支持还不够完善。

Websocket: 优点:双向通信,客户端和服务器可以实时传输数据;减少了传输开销,头部信息较小。 缺点:需要维护长时间的连接,增加服务器负担;不支持跨域访问。

SSE: 优点:服务器可以主动向客户端推送数据,实现实时更新;基于HTTP,兼容性好。 缺点:只能服务器向客户端推送数据,客户端无法主动发起请求;对于大量数据的传输效率较低。

HTTP1

 package main
 ​
 import (
     "context"
 ​
     "github.com/cloudwego/hertz/pkg/app"
     "github.com/cloudwego/hertz/pkg/app/server"
     "github.com/cloudwego/hertz/pkg/common/utils"
     "github.com/cloudwego/hertz/pkg/protocol/consts"
 )
 ​
 func main() {
     h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
 ​
     h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
         ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
     })
 ​
     h.Spin()
 }

无需多言

TLS

 func doTlsRequest() {
     clientCfg := &tls.Config{
         InsecureSkipVerify: true,
     }
     c, err := client.NewClient(
         client.WithTLSConfig(clientCfg),
         client.WithDialer(standard.NewDialer()),
     )
     if err != nil {
         fmt.Println(err.Error())
     }
     req, res := protocol.AcquireRequest(), protocol.AcquireResponse()
     defer func() {
         protocol.ReleaseRequest(req)
         protocol.ReleaseResponse(res)
     }()
     req.SetMethod(consts.MethodGet)                            // set request method
     req.Header.SetContentTypeBytes([]byte("application/json")) // set request header
     req.SetRequestURI("https://localhost:8443/ping")           // set request url
     err = c.Do(context.Background(), req, res)
     if err != nil {
         fmt.Println(err.Error())
     }
     fmt.Printf("%v\n", string(res.Body())) // read response body
     time.Sleep(time.Second)
 }
 ​

该代码片段是一个使用 TLS 进行安全通信的示例。

首先,创建了一个 tls.Config 对象 clientCfg,并将 InsecureSkipVerify 设置为 true。这意味着客户端在进行服务器证书验证时会跳过验证过程,不会校验服务器的证书合法性。请注意,在实际生产环境中,应该设置为 false,以确保通信的安全性。

然后,使用 client.NewClient 创建了一个具有自定义 TLS 配置和标准拨号器的 Client 对象 cclient.WithTLSConfig 用于设置 TLS 配置,client.WithDialer 用于设置拨号器。

接下来,获取请求和响应对象,设置请求方法、请求头和请求 URL。

然后,使用 Client 对象的 Do 方法发送请求,并将响应保存在 res 对象中。如果发生错误,会打印错误信息。

最后,读取响应的内容并打印出来,然后暂停一秒钟。

 func main() {
     cfg := &tls.Config{
         MinVersion:               tls.VersionTLS12,
         CurvePreferences:         []tls.CurveID{tls.X25519, tls.CurveP256},
         PreferServerCipherSuites: true,
         CipherSuites: []uint16{
             tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
             tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
             tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
         },
     }
     cert, err := tls.LoadX509KeyPair("./protocol/tls/server.crt", "./protocol/tls/server.key")
     if err != nil {
         fmt.Println(err.Error())
     }
     cfg.Certificates = append(cfg.Certificates, cert)
 ​
     h := server.Default(server.WithTLS(cfg), server.WithHostPorts(":8443"))
 ​
     h.Use(func(c context.Context, ctx *app.RequestContext) {
         fmt.Fprint(ctx, "Before real handle...\n")
         ctx.Next(c)
         fmt.Fprint(ctx, "After real handle...\n")
     })
 ​
     h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
         ctx.String(consts.StatusOK, "TLS test\n")
     })
 ​
     go func() {
         h.Spin()
     }()
 ​
     time.Sleep(time.Millisecond * 50)
     doTlsRequest()
 }

这段代码是一个完整的示例,展示了使用 Go 语言中的 tlshttp 包实现基于 TLS 的服务器和客户端通信。

首先,创建了一个 tls.Config 对象 cfg,并设置了一些 TLS 相关的配置。MinVersion 指定了最低可接受的 TLS 版本,CurvePreferences 指定了曲线优先级,PreferServerCipherSuites 设置为 true 表示优先选择服务器端加密套件,CipherSuites 则指定了加密套件的优先级列表。

接下来,使用 tls.LoadX509KeyPair 加载了服务器的证书和私钥,并将其添加到 cfg.Certificates 中。

然后,创建了一个基于 cfg 配置的 HTTP 服务器对象 h,并添加了一个中间件函数和一个处理 "/ping" 请求的处理函数。

中间件函数在真正的请求处理之前和之后进行一些处理,这里简单地在请求前后打印了一些信息。

接着,启动了服务器的主协程,并在稍后通过调用 doTlsRequest 函数来发送一个 TLS 请求。

在主协程中,通过 time.Sleep 延迟一段时间,以确保服务器已经启动。

doTlsRequest 函数的实现可以参考之前给出的示例代码。

需要注意的是,这段示例代码假设服务器的证书和私钥文件位于 ./protocol/tls/server.crt./protocol/tls/server.key。在实际使用时,请根据实际情况修改文件路径和名称。

另外,这里只展示了服务器和客户端的基本代码框架,实际使用时还需要根据需求进行适当的修改和完善,例如添加错误处理、身份验证等功能。

HTTP2

 ​
 package main
 ​
 import (
     "context"
     "crypto/tls"
     "encoding/json"
     "fmt"
     "net/http"
     "time"
 ​
     "github.com/cloudwego/hertz/pkg/app"
     "github.com/cloudwego/hertz/pkg/app/client"
     "github.com/cloudwego/hertz/pkg/app/server"
     "github.com/cloudwego/hertz/pkg/network/standard"
     "github.com/cloudwego/hertz/pkg/protocol"
     "github.com/hertz-contrib/http2/config"
     "github.com/hertz-contrib/http2/factory"
 )
 ​
 ​
 ​
 func runClient() {
     c, _ := client.NewClient()
     c.SetClientFactory(factory.NewClientFactory(
         config.WithDialer(standard.NewDialer()),
         config.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}),
         config.WithNoDefaultUserAgent(true)),
     )
 ​
     v, _ := json.Marshal(map[string]string{
         "hello":    "world",
         "protocol": "h2",
     })
 ​
     for {
         time.Sleep(time.Second * 1)
         req, rsp := protocol.AcquireRequest(), protocol.AcquireResponse()
         req.SetMethod("POST")
         req.SetRequestURI("https://127.0.0.1:8888")
         req.SetBody(v)
         err := c.Do(context.Background(), req, rsp)
         if err != nil {
             fmt.Println(err)
             return
         }
         fmt.Printf("client received body: %s\n", string(rsp.Body()))
     }
 }
 ​
 /*
 这是一个名为 "runClient" 的函数,用于运行客户端。
 ​
 首先,创建了一个 Hertz 客户端实例 c,并设置一些配置,如连接器、TLS 配置和默认用户代理。
 然后,将请求体序列化为 JSON 格式,并通过循环发送 POST 请求给服务器。
 请求是一个带有固定 URI 的 HTTPS 请求,并附带了请求体和其他必要参数。
 最后,处理服务器的响应,并打印出响应的主体。
 */
 ​
 func main() {
     cfg := &tls.Config{
         MinVersion:       tls.VersionTLS12,
         CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
         CipherSuites: []uint16{
             tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
             tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
             tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
         },
     }
     cert, err := tls.LoadX509KeyPair("examples/certificate/server.crt", "examples/certificate/server.key")
     if err != nil {
         fmt.Println(err.Error())
     }
     cfg.Certificates = append(cfg.Certificates, cert)
     h := server.New(server.WithHostPorts(":8888"), server.WithALPN(true), server.WithTLS(cfg))
 ​
     // register http2 server factory
     h.AddProtocol("h2", factory.NewServerFactory(
         config.WithReadTimeout(time.Minute),
         config.WithDisableKeepAlive(false)))
     cfg.NextProtos = append(cfg.NextProtos, "h2")
 ​
     h.POST("/", func(c context.Context, ctx *app.RequestContext) {
         var j map[string]string
         _ = json.Unmarshal(ctx.Request.Body(), &j)
         fmt.Printf("server received request: %+v\n", j)
 ​
         r := map[string]string{
             "msg": "hello world",
         }
         for k, v := range j {
             r[k] = v
         }
         ctx.JSON(http.StatusOK, r)
     })
 ​
     go runClient()
 ​
     h.Spin()
 }
 ​
 /*
 这是 main 函数,程序的入口。
 ​
 首先,配置了 TLS 证书并创建了 Hertz 服务器实例 h。
 然后,注册了 HTTP/2 协议,并添加了一些配置项,如读取超时和禁用 Keep-Alive。
 接下来,设置了路由处理函数,处理 POST 请求。在这个示例中,根路径 "/" 即为处理函数。
 在处理函数中,解析请求体为 JSON 格式,并打印出接收到的请求。
 然后,构建响应并返回给客户端。
 最后,启动客户端函数 runClient() 并启动服务器。
 */

HTTP3

 type Test struct {
     A string
     B string
 }
 ​
 func main() {
     run()
 }
 ​
 func run() {
     // 创建一个 Hertz 服务器实例 `h`,并设置 ALPN、TLS、传输器等选项
     h := server.New(server.WithALPN(true), server.WithTLS(testdata.GetTLSConfig()), server.WithTransport(quic.NewTransporter), server.WithAltTransport(netpoll.NewTransporter), server.WithHostPorts("127.0.0.1:8080"))
 ​
     // 添加 HTTP/3 协议和相应的工厂
     h.AddProtocol(suite.HTTP3, factory.NewServerFactory(&http3.Option{}))
 ​
     // 处理 GET /demo/tile 请求,返回一个小图片
     h.GET("/demo/tile", func(c context.Context, ctx *app.RequestContext) {
         // Small 40x40 png
         ctx.Write([]byte{
             0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
             0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28,
             0x01, 0x03, 0x00, 0x00, 0x00, 0xb6, 0x30, 0x2a, 0x2e, 0x00, 0x00, 0x00,
             0x03, 0x50, 0x4c, 0x54, 0x45, 0x5a, 0xc3, 0x5a, 0xad, 0x38, 0xaa, 0xdb,
             0x00, 0x00, 0x00, 0x0b, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0x63, 0x18,
             0x61, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x01, 0xe2, 0xb8, 0x75, 0x22, 0x00,
             0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
         })
     })
 ​
     // 处理 GET /ping 请求,返回一个 JSON 格式的 "ping: pong" 字符串
     h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
         ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
     })
 ​
     // 处理 GET /struct 请求,返回一个 JSON 格式的 Test 结构体
     h.GET("/struct", func(c context.Context, ctx *app.RequestContext) {
         ctx.JSON(consts.StatusOK, &Test{
             A: "aaa",
             B: "bbb",
         })
     })
 ​
     // 分组路由,处理 GET /v1/hello/:name 请求
     v1 := h.Group("/v1")
     {
         v1.GET("/hello/:name", func(c context.Context, ctx *app.RequestContext) {
             // 根据 URL 中的参数 name,构造响应字符串
             fmt.Fprintf(ctx, "Hi %s, this is the response from Hertz.\n", ctx.Param("name"))
         })
     }
 ​
     // 启动服务器
     h.Spin()

这里列出了该程序的一些主要功能:

  • 创建一个 Hertz 服务器实例,用于监听 127.0.0.1:8080 端口,并设置为支持 ALPN、TLS 和 HTTP/3。
  • 添加路由处理函数,如 GET /demo/tile、GET /ping、GET /struct 和 GET /v1/hello/:name。
  • 处理 GET /demo/tile 请求,返回一个小图片。
  • 处理 GET /ping 请求,返回一个 JSON 格式的 "ping: pong" 字符串。
  • 处理 GET /struct 请求,返回一个 JSON 格式的 Test 结构体。
  • 分组路由,处理 GET /v1/hello/:name 请求,根据 URL 中的参数 name,构造响应字符串。
  • 最后,启动服务器。

Websocket

玩转 Go 生态|Hertz WebSocket 扩展简析-CSDN博客

SSH

Hertz 框架可以使用 Server-Sent Events (SSE) 协议来实现服务器向客户端推送数据的功能。下面是一个使用 SSE 协议的示例:

 package main
 ​
 import (
     "fmt"
     "github.com/valyala/fasthttp"
     "github.com/valyala/fasthttp/expvarhandler"
     "github.com/valyala/fasthttp/reuseport"
     "log"
 )
 ​
 func main() {
     ln, err := reuseport.Listen("tcp4", "localhost:8080")
     if err != nil {
         log.Fatalf("error in reuseport listener: %s", err)
     }
 ​
     h := requestHandler
 ​
     server := fasthttp.Server{
         Handler:     h,
         Name:        "Hertz",
         LogAllErrors: true,
     }
 ​
     err = server.Serve(ln)
     if err != nil {
         log.Fatalf("error in fasthttp server: %s", err)
     }
 }
 ​
 func requestHandler(ctx *fasthttp.RequestCtx) {
     switch string(ctx.Path()) {
     case "/events":
         serveEvents(ctx)
     default:
         ctx.Error("Unsupported path", fasthttp.StatusNotFound)
     }
 }
 ​
 func serveEvents(ctx *fasthttp.RequestCtx) {
     ctx.SetContentType("text/event-stream")
 ​
     // 发送第一个事件
     fmt.Fprintf(ctx, "event: message\n")
     fmt.Fprintf(ctx, "data: %s\n\n", "Hello, client!")
 ​
     // 模拟每秒钟发送一个事件
     for i := 0; i < 10; i++ {
         fasthttp.Delay(1e9) // 延迟1秒
         fmt.Fprintf(ctx, "event: message\n")
         fmt.Fprintf(ctx, "data: %s\n\n", fmt.Sprintf("Event %d", i+1))
         ctx.Response.ConnectionClose() // 手动关闭连接以确保消息即时发送
         ctx.Response.Header.Set("X-Accel-Buffering", "no") // 禁用 Nginx 的缓冲
         ctx.Response.Header.Set("Cache-Control", "no-cache")
         ctx.Response.Header.Set("Content-Length", fmt.Sprintf("%d", ctx.Response.ConnectionClose()))
         ctx.Response.Header.Set("Access-Control-Allow-Origin", "*") // 允许跨域请求
         ctx.Response.Header.Set("Access-Control-Expose-Headers", "Content-Length")
 ​
         ctx.Response.Header.Del(fasthttp.HeaderContentType)
         ctx.Response.Header.Add("Content-Type", "text/event-stream")
     }
 ​
     ctx.Response.SetStatusCode(fasthttp.StatusOK)
 }

在上面的示例中,我们定义了一个 serveEvents 函数来处理 /events 路径的请求。当客户端访问 /events 路径时,服务器将使用 SSE 协议向客户端推送事件。

serveEvents 函数中,我们首先设置响应头 Content-Typetext/event-stream,表示服务器将使用 SSE 协议发送数据。然后,我们使用 fmt.Fprintf 发送第一个事件,事件类型为 message,并附带一些数据。

之后,我们使用一个循环模拟每秒发送一个事件,总共发送 10 个事件。在每个事件中,我们设置一些响应头,如 Access-Control-Allow-Origin 允许跨域请求,Cache-Control 禁用缓存等。然后,我们通过 fmt.Fprintf 发送事件数据。需要注意的是,我们在每次发送事件后手动关闭连接以确保消息即时发送。

最后,我们设置响应状态码为 200 并返回响应。客户端可以使用 SSE 相关的 JavaScript API 来处理接收到的事件数据。