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里面可以进行更多的设置。
这里面是全部的,我们往往只使用常用的。
| 配置名称 | 类型 | 说明 |
|---|---|---|
| WithHostPorts | string | 指定监听的地址和端口 |
| WithMaxRequestBodySize | int | 配置最大的请求体大小,默认 4M(4M 对应的填的值是 410241024) |
| WithRedirectTrailingSlash | bool | 自动根据末尾的 / 转发,例如:如果 router 只有 /foo/,那么 /foo 会重定向到 /foo/ ;如果只有 /foo,那么 /foo/ 会重定向到 /foo。默认开启 |
| WithNetwork | string | 设置网络协议,可选:tcp,udp,unix(unix domain socket),默认为 tcp |
| WithReadBufferSize | int | 设置读缓冲区大小,同时限制 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 对象 c。client.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 语言中的 tls 和 http 包实现基于 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-Type 为 text/event-stream,表示服务器将使用 SSE 协议发送数据。然后,我们使用 fmt.Fprintf 发送第一个事件,事件类型为 message,并附带一些数据。
之后,我们使用一个循环模拟每秒发送一个事件,总共发送 10 个事件。在每个事件中,我们设置一些响应头,如 Access-Control-Allow-Origin 允许跨域请求,Cache-Control 禁用缓存等。然后,我们通过 fmt.Fprintf 发送事件数据。需要注意的是,我们在每次发送事件后手动关闭连接以确保消息即时发送。
最后,我们设置响应状态码为 200 并返回响应。客户端可以使用 SSE 相关的 JavaScript API 来处理接收到的事件数据。