Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
October 30, 2022 04:16 am GMT

Source Code Analysis for Go HTTP Framework Hertz

CloudWeGo-Hertz

Hertz [hts] is a high-performance, high-usability, extensible HTTP framework for Go. Its designed to simplify building microservices for developers.

Hertz was inspired by other open-source frameworks like fasthttp, gin, and echo, in combination with unique challenges faced by ByteDance, Hertz has become production ready and powered ByteDances internal services over the years.

Nowadays, as Go gain popularity in microservice development, Hertz will be the right choice if you are looking for a customizable, high-performance framework to support a variety of use case.

This article will explain the structure of the Hertz source code and analyze the startup of Hertz.

Next, you need to open the official documentation for Hertz and clone the repository locally, let's get started.

Architecture

image-20220901154829735

Above is an architectural design diagram from the official Hertz documentation. The components in the picture correspond to the package folders within the hertz source code package.

image-20220901175437136

Getting Started

Following the instructions in the documentation, we could initialize a minimal hertz project via the hertz cmd tool.

# Install the command line tool for hertz, used to generate the initial hertz codego install github.com/cloudwego/hertz/cmd/hz@latest# Generate code via the hz tool. If the project created is not in the GOPATH/src path, "-module" needs to be declaredhz new -module hertz-study

image-20220901181637689

Run the project then you can access the default HTTP service by /ping.

curl http://127.0.0.1:8888/ping# response{"message":"pong"}% 

Source Code Analysis

server

Look at the main.go function, which is the startup entry for the hertz service. It initializes a default hertz service, does some registration work, and runs the hertz service (HTTP service).

package mainimport (    "github.com/cloudwego/hertz/pkg/app/server")func main() {    h := server.Default()    register(h)    h.Spin()}

Thinking back to the interface about http://127.0.0.1:8888/ping which was declared by server.Default().

Conversely, if you want to specify custom configurations for HTTP service, you need to pass parameters to the server.Default(), Or using the server.New() method.

server.Default()

// Default creates a hertz instance with default middlewares.func Default(opts ...config.Option) *Hertz {   h := New(opts...)   h.Use(recovery.Recovery())   return h}// 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}

Looking at the Default() method, we find it calls server.New() internally, which accepts an Option array of indeterminate length as a parameter.

// Option is the only struct that can be used to set Options.type Option struct {    F func(o *Options)}// 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}

Then we go to the config.NewOptions method to see how this Option slice will apply our custom content to the Hertz configuration.

func NewOptions(opts []Option) *Options {    options := &Options{        KeepAliveTimeout: defaultKeepAliveTimeout,        ReadTimeout: defaultReadTimeout,        IdleTimeout: defaultReadTimeout,        RedirectTrailingSlash: true,        RedirectFixedPath: false,        HandleMethodNotAllowed: false,        UseRawPath: false,        RemoveExtraSlash: false,        UnescapePathValues: true,        DisablePreParseMultipartForm: false,        Network: defaultNetwork,        Addr: defaultAddr,        MaxRequestBodySize: defaultMaxRequestBodySize,        MaxKeepBodySize: defaultMaxRequestBodySize,        GetOnly: false,        DisableKeepalive: false,        StreamRequestBody: false,        NoDefaultServerHeader: false,        ExitWaitTimeout: defaultWaitExitTimeout,        TLS: nil,        ReadBufferSize: defaultReadBufferSize,        ALPN: false,        H2C: false,        Tracers: []interface{}{},        TraceLevel: new(interface{}),        Registry: registry.NoopRegistry,    }    // apply a custom []Option to Options    options.Apply(opts)    return options}func (o *Options) Apply(opts []Option) {    for _, op := range opts {        op.F(o)    }}

By looking at the config.NewOptions source code, it first initializes an Options structure that holds various initialization information for the Hertz service. The Options properties are fixed by default until the options.Apply(options) method is called to apply the custom configuration.

It passes a pointer to the Options structure created by default as an argument to the F method of each Option you declared. It assigns values to the Options structure via the F method call, which, because it is a pointer, will naturally apply all the assignments to the same Options.

And how the specific Option's F method is defined can be implemented flexibly, which is one of the reasons why Hertz has good scalability.

// Default creates a hertz instance with default middlewares.func Default(opts ...config.Option) *Hertz {    // "Hertz" is the core structure of the framework    h := New(opts...)    h.Use(recovery.Recovery())    return h}

Notice that there is also a h.Use(recovery.Recovery()) method.

// Recovery returns a middleware that recovers from any panic.func Recovery(opts ...Option) app.HandlerFunc {    cfg := newOptions(opts...)    return func(c context.Context, ctx *app.RequestContext) {        defer func() {            if err := recover(); err != nil {                stack := stack(3)                cfg.recoveryHandler(c, ctx, err, stack)            }        }()        ctx.Next(c)    }}

This is a middleware for recovering from panic. By default, it will print the time, content, and stack information of the error and write a 500. Overriding the Config configuration, you can customize the error printing logic.

By the way, if you Initialize a Hertz HTTP service by server.New(), which won't hold the Recovery() method, and you need to inject it yourself.

package mainimport (    "context"    "github.com/cloudwego/hertz/pkg/app"    "github.com/cloudwego/hertz/pkg/app/middlewares/server/recovery"    "github.com/cloudwego/hertz/pkg/app/server"    "github.com/cloudwego/hertz/pkg/common/hlog"    "github.com/cloudwego/hertz/pkg/protocol/consts")func main() {    h := server.New()    h.Use(recovery.Recovery(        recovery.WithRecoveryHandler(func(c context.Context, ctx *app.RequestContext, err interface{}, stack []byte) {            hlog.SystemLogger().CtxErrorf(c, "[Recovery] err=%v
stack=%s", err, stack) ctx.AbortWithStatus(consts.StatusInternalServerError) }))) register(h) h.Spin()}

register()

package mainimport (    "github.com/cloudwego/hertz/pkg/app/server")func main() {    h := server.Default()    register(h)    h.Spin()}

After the above analysis, we know that server.Default() completes the declaration of the Hertz structure. Let's pay more attention to the content of the register(h).

// register registers all routers.func register(r *server.Hertz) {    router.GeneratedRegister(r)    customizedRegister(r)}// GeneratedRegister registers routers generated by IDL.func GeneratedRegister(r *server.Hertz) {    //INSERT_POINT: DO NOT DELETE THIS LINE!}// customizeRegister registers customize routers.func customizedRegister(r *server.Hertz) {    r.GET("/ping", handler.Ping)    // your code ...}

register(h) does two types of route registration internally. The annotation of GeneratedRegister() indicates that this part of the route is generated by the IDL.

customizedRegister() is used to register custom routes which initializes a /ping by default, using it in a very similar way to gin.

Spin()

Finally, let's analyse the third part of the main() method, the h.Spin().

// Spin runs the server until catching os.Signal or error returned by h.Run().func (h *Hertz) Spin() {    errCh := make(chan error)    h.initOnRunHooks(errCh)    go func() {        // core        errCh <- h.Run()    }()    signalWaiter := waitSignal    if h.signalWaiter != nil {        signalWaiter = h.signalWaiter    }    if err := signalWaiter(errCh); err != nil {        hlog.Errorf("HERTZ: Receive close signal: error=%v", err)        if err := h.Engine.Close(); err != nil {            hlog.Errorf("HERTZ: Close error=%v", err)        }        return    }    hlog.Infof("HERTZ: Begin graceful shutdown, wait at most num=%d seconds...", h.GetOptions().ExitWaitTimeout/time.Second)    ctx, cancel := context.WithTimeout(context.Background(), h.GetOptions().ExitWaitTimeout)    defer cancel()    if err := h.Shutdown(ctx); err != nil {        hlog.Errorf("HERTZ: Shutdown error=%v", err)    }}

After a series of initialization and declaration operations, Spin() is responsible for triggering the run of Hertz and handling any errors during the runtime. The most important step is errCh <- h.Run().

func (engine *Engine) Run() (err error) {    if err = engine.Init(); err != nil {        return err    }    if !atomic.CompareAndSwapUint32(&engine.status, statusInitialized, statusRunning) {        return errAlreadyRunning    }    defer atomic.StoreUint32(&engine.status, statusClosed)    // trigger hooks if any    ctx := context.Background()    for i := range engine.OnRun {        if err = engine.OnRun[i](ctx); err != nil {            return err        }    }    return engine.listenAndServe()}

Then you see the engine.listenAndServe() method at the end, which is declared in an interface. Looking at its implementation classes, you find that it can be traced back to the standard and netpoll packages.

image-20220901215952290

As an HTTP framework, the most important thing is to provide network communication capabilities. hertz uses the pluggable network library netpoll to handle network communication and further optimize performance.

The service is now running, and you can send requests via the console:

curl http://127.0.0.1:8888/ping{"message":"pong"}% 

Summary

After generating the simplest Hertz code using the hz tool, this article roughly analyses the contents of the main method, dividing it into three parts, the service configuration declaration Default(), the route registration register() and the HTTP service start Spin().

If you want to know more about Hertz, you could visit the Reference.

Reference List


Original Link: https://dev.to/baize1998/source-code-analysis-for-go-http-framework-hertz-4jgi

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To