Hertz
Hertz is an ultra-large-scale enterprise-level microservice HTTP framework, featuring high ease of use, easy expansion, and low latency etc.
Hertz uses the self-developed high-performance network library Netpoll by default. In some special scenarios, Hertz has certain advantages in QPS and latency compared to go net.
In internal practice, some typical services, such as services with a high proportion of frameworks, gateways and other services, after migrating to Hertz, compared to the Gin framework, the resource usage is significantly reduced, CPU usage is reduced by 30%-60% with the size of the traffic.
For more details, see cloudwego/hertz.
Reverse proxy
In computer networks, a reverse proxy is an application that sits in front of back-end applications and forwards client (e.g. browser) requests to those applications.
Reverse proxies help increase scalability, performance, resilience and security. The resources returned to the client appear as if they originated from the web server itself.
Using reverse proxy in Hertz
Using a reverse proxy with Hertz requires pulling the reverseproxy extension provided by the community.
$ go get github.com/hertz-contrib/reverseproxy
Basic using
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/hertz-contrib/reverseproxy"
)
func main() {
h := server.Default(server.WithHostPorts("127.0.0.1:8000"))
proxy, err := reverseproxy.NewSingleHostReverseProxy("http://127.0.0.1:8000/proxy")
if err != nil {
panic(err)
}
h.GET("/proxy/backend", func(cc context.Context, c *app.RequestContext) {
c.JSON(200, utils.H{
"msg": "proxy success!!",
})
})
h.GET("/backend", proxy.ServeHTTP)
h.Spin()
}
We set the target path of the reverse proxy /proxy
through the NewSingleHostReverseProxy
function. Next, the path of the registered route is the sub-path /proxy/backend
of the target path of the reverse proxy, and finally the reverse proxy service proxy.ServeHTTP
is mapped by registering /backend
. In this way, when we access /backend
through the GET method, we will access the content in /proxy/backend
.
curl 127.0.0.1:8000/backend
{"msg":"proxy success!!"}
Custom configuration
Of course, the extension is not only able to implement a simple reverse proxy, there are many customizable options provided in the reverseproxy extension.
Method | Description |
---|---|
SetDirector |
use to customize protocol.Request |
SetClient |
use to customize client |
SetModifyResponse |
use to customize modify response function |
SetErrorHandler |
use to customize error handler |
SetDirector & SetClient
We practice using SetDirector
and SetClient
by implementing a simple service registration discovery.
Server
package main
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/app/server/registry"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/cloudwego/hertz/pkg/protocol/consts"
"github.com/hertz-contrib/registry/nacos"
)
func main() {
addr := "127.0.0.1:8000"
r, _ := nacos.NewDefaultNacosRegistry()
h := server.Default(
server.WithHostPorts(addr),
server.WithRegistry(r, ®istry.Info{
ServiceName: "demo.hertz-contrib.reverseproxy",
Addr: utils.NewNetAddr("tcp", addr),
Weight: 10,
}),
)
h.GET("/backend", func(cc context.Context, c *app.RequestContext) {
c.JSON(consts.StatusOK, utils.H{"ping": "pong"})
})
h.Spin()
}
The sample code on the server side in the hertz-contrib/registry extension is used here. Since this is not the main content of this article, it will not be expanded. For more information, you can Go to the registry repository.
Client
package main
import (
"github.com/cloudwego/hertz/pkg/app/client"
"github.com/cloudwego/hertz/pkg/app/middlewares/client/sd"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/config"
"github.com/cloudwego/hertz/pkg/protocol"
"github.com/hertz-contrib/registry/nacos"
"github.com/hertz-contrib/reverseproxy"
)
func main() {
cli, err := client.NewClient()
if err != nil {
panic(err)
}
r, err := nacos.NewDefaultNacosResolver()
if err != nil {
panic(err)
}
cli.Use(sd.Discovery(r))
h := server.New(server.WithHostPorts(":8741"))
proxy, _ := reverseproxy.NewSingleHostReverseProxy("http://demo.hertz-contrib.reverseproxy")
proxy.SetClient(cli)
proxy.SetDirector(func(req *protocol.Request) {
req.SetRequestURI(string(reverseproxy.JoinURLPath(req, proxy.Target)))
req.Header.SetHostBytes(req.URI().Host())
req.Options().Apply([]config.RequestOption{config.WithSD(true)})
})
h.GET("/backend", proxy.ServeHTTP)
h.Spin()
}
In the Client section we used a reverse proxy for service discovery. First, specify the client using the service discovery middleware as our forwarding client through SetClient
, and then use SetDirector
to specify our protocol.Request , and configure the use of service discovery in the new Request.
SetModifyResponse & SetErrorHandler
SetModifyResponse
and SetErrorHandler
respectively set the response from the backend and the handling of errors arriving in the backend. SetModifyResponse
is actually modifyResponse
in the reverse proxy extension. If the backend returns any response, no matter what the status code is, this method will be called. If the modifyResponse
method returns an error, the errorHandler
method will be called with the error as an argument.
SetModifyResponse
package main
import (
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/protocol"
"github.com/hertz-contrib/reverseproxy"
)
func main() {
h := server.Default(server.WithHostPorts("127.0.0.1:8000"))
// modify response
proxy, _ := reverseproxy.NewSingleHostReverseProxy("http://127.0.0.1:8000/proxy")
proxy.SetModifyResponse(func(resp *protocol.Response) error {
resp.SetStatusCode(200)
resp.SetBodyRaw([]byte("change response success"))
return nil
})
h.GET("/modifyResponse", proxy.ServeHTTP)
h.Spin()
}
Here, modifyResponse
is modified by SetModifyResponse
to change the processing content of the response.
Test
curl 127.0.0.1:8000/modifyResponse
change response success
SetErrorHandler
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/hertz-contrib/reverseproxy"
)
func main() {
h := server.Default(server.WithHostPorts("127.0.0.1:8002"))
proxy, err := reverseproxy.NewSingleHostReverseProxy("http://127.0.0.1:8000/proxy")
if err != nil {
panic(err)
}
proxy.SetErrorHandler(func(c *app.RequestContext, err error) {
c.Response.SetStatusCode(404)
c.String(404, "fake 404 not found")
})
h.GET("/proxy/backend", func(cc context.Context, c *app.RequestContext) {
c.JSON(200, utils.H{
"msg": "proxy success!!",
})
})
h.GET("/backend", proxy.ServeHTTP)
h.Spin()
}
We use SetErrorHandler
to specify how to handle errors that arrive in the background. When an error arrives in the background or there is an error from modifyResponse
, the specified processing logic will be run.
Test
curl 127.0.0.1:8002/backend
fake 404 not found
Middleware usage
In addition to basic use, Hertz reverse proxy also supports use in middleware.
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/hertz-contrib/reverseproxy"
)
func main() {
r := server.Default(server.WithHostPorts("127.0.0.1:9998"))
r2 := server.Default(server.WithHostPorts("127.0.0.1:9997"))
proxy, err := reverseproxy.NewSingleHostReverseProxy("http://127.0.0.1:9997")
if err != nil {
panic(err)
}
r.Use(func(c context.Context, ctx *app.RequestContext) {
if ctx.Query("country") == "cn" {
proxy.ServeHTTP(c, ctx)
ctx.Response.Header.Set("key", "value")
ctx.Abort()
} else {
ctx.Next(c)
}
})
r.GET("/backend", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(200, utils.H{
"message": "pong1",
})
})
r2.GET("/backend", func(c context.Context, ctx *app.RequestContext) {
ctx.JSON(200, utils.H{
"message": "pong2",
})
})
go r.Spin()
r2.Spin()
}
In this example code, two Hertz instances are initialized first, then NewSingleHostReverseProxy
is used to set the reverse proxy target to port 9997, and finally two routes with the same path are registered for the two instances respectively.
Test1
curl 127.0.0.1:9997/backend
{"message":"pong2"}
curl 127.0.0.1:9998/backend
{"message":"pong1"}
The main part of this code is the middleware usage part. We use the middleware through Use
. In the middleware logic, when the logic of ctx.Query("country") == "cn"
is established, call proxy. ServeHTTP(c, ctx)
uses a reverse proxy, and when you request /backend
through instance r, it will request the content of the reverse proxy target port 9997.
Test2
curl 127.0.0.1:9998/backend?country=cn
{"message":"pong2"}
Tips
- For
NewSingleHostReverseProxy
function, if noconfig.ClientOption
is passed it will use the default globalclient.Client
instance. When passingconfig.ClientOption
it will initialize a localclient.Client
instance. UsingReverseProxy.SetClient
if there is need for shared customizedclient.Client
instance. - The reverse proxy resets the header of the response, any such modifications before the request is made will be discarded.
Top comments (0)