ZhangZhihui's Blog  

 

复制代码
zzh@ZZHPC:~$ curl -w '\nTime: %{time_total}\n' -d "$BODY" localhost:4000/v1/users
{
    "user": {
        "id": 6,
        "created_at": "2024-11-21T18:33:14+08:00",
        "name": "ZhangZhihui",
        "email": "ZhangZhihuiAAA@126.com",
        "activated": false,
        "version": 1
    }
}

Time: 3.944838
复制代码

 

复制代码
func (app *application) registerUserHandler(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Name     string `json:"name"`
        Email    string `json:"email"`
        Password string `json:"password"`
    }

    err := app.readJSON(w, r, &input)
    if err != nil {
        app.badRequestResponse(w, r, err)
        return
    }

    user := &data.User{
        Name:      input.Name,
        Email:     input.Email,
        Activated: false,
    }

    err = user.Password.Set(input.Password)
    if err != nil {
        app.serverErrorResponse(w, r, err)
        return
    }

    v := validator.New()

    if data.ValidateUser(v, user); !v.Valid() {
        app.failedValidationResponse(w, r, v.Errors)
        return
    }

    // Insert the user data into the database.
    err = app.models.User.Insert(user)
    if err != nil {
        switch {
        case errors.Is(err, data.ErrDuplicateEmail):
            v.AddError("email", "a user with this email address already exists")
            app.failedValidationResponse(w, r, v.Errors)
        default:
            app.serverErrorResponse(w, r, err)
        }
        return
    }

    go func() {
        err = app.emailSender.Send(user.Email, "user_welcome.html", user)
        if err != nil {
            // Importantly, if there is an error sending the email, we use the app.logger.Error() 
            // helper to manage it, instead of the app.serverErrorResponse() helper like before.
            app.logger.Error(err.Error())
        }
    }()

    err = app.writeJSON(w, http.StatusCreated, envelope{"user": user}, nil)
    if err != nil {
        app.serverErrorResponse(w, r, err)
    }
}
复制代码

 

 

复制代码
zzh@ZZHPC:~$ curl -w '\nTime: %{time_total}\n' -d "$BODY" localhost:4000/v1/users
{
    "user": {
        "id": 7,
        "created_at": "2024-11-21T18:53:59+08:00",
        "name": "ZhangZhihui",
        "email": "ZhangZhihuiAAA@126.com",
        "activated": false,
        "version": 1
    }
}

Time: 0.196133
复制代码

 

复制代码
    go func() {
        defer func() {
            if err := recover(); err != nil {
                app.logger.Error(fmt.Sprintf("%v", err))
            }
        }()

        // Send the welcome email.
        err = app.emailSender.Send(user.Email, "user_welcome.html", user)
        if err != nil {
            // Importantly, if there is an error sending the email, we use the app.logger.Error() 
            // helper to manage it, instead of the app.serverErrorResponse() helper like before.
            app.logger.Error(err.Error())
        }
    }()
复制代码

 

复制代码
// The background helper accepts an arbitrary function as a parameter.
func (app *application) background(fn func()) {
    go func() {
        // Recover any panic.
        defer func() {
            if err := recover(); err != nil {
                app.logger.Error(fmt.Sprintf("%v", err))
            }
        }()

        // Execute the arbitrary function received as the parameter.
        fn()
    }()
}
复制代码

 

Now that this is in place, let’s update our registerUserHandler to use it like so:

复制代码
...
    // Send the welcome email in background.
    app.background(func() {
        err = app.emailSender.Send(user.Email, "user_welcome.html", user)
        if err != nil {
            // Importantly, if there is an error sending the email, we use the app.logger.Error() 
            // helper to manage it, instead of the app.serverErrorResponse() helper like before.
            app.logger.Error(err.Error())
        }
    })
...
复制代码

 

 

In main.go:

type application struct {
    config      appConfig
    logger      *slog.Logger
    models      data.Models
    emailSender *mail.EmailSender
    wg          sync.WaitGroup
}

 

In helpers.go:

复制代码
func (app *application) background(fn func()) {
    // Increase the WaitGroup counter.
    app.wg.Add(1)

    go func() {
        // Use defer to decrease the WaitGroup counter before the goroutine returns.
        defer app.wg.Done()

        // Recover any panic.
        defer func() {
            if err := recover(); err != nil {
                app.logger.Error(fmt.Sprintf("%v", err))
            }
        }()

        // Execute the arbitrary function received as the parameter.
        fn()
    }()
}
复制代码

 

复制代码
package main

import (
    "context"
    "errors"
    "log/slog"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func (app *application) serve() error {
    srv := &http.Server{
        Addr:         app.config.serverAddress,
        Handler:      app.routes(),
        IdleTimeout:  time.Minute,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        ErrorLog:     slog.NewLogLogger(app.logger.Handler(), slog.LevelError),
    }

    // The shutdownError channel is used to receive any errors returned by the 
    // graceful Shutdown() function.
    shutdownError := make(chan error)

    // Start a background goroutine to catch signals.
    go func() {
        quit := make(chan os.Signal, 1)

        // Use signal.Notify() to listen for incoming SIGINT and SIGTERM signals and 
        // relay them to the quit channel.
        signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

        // Read the signal from the quit channel. This code will block until a signal is received.
        s := <- quit

        app.logger.Info("shutting down server", "signal", s.String())

        ctx, cancel := context.WithTimeout(context.Background(), 30 * time.Second)
        defer cancel()

        // Call Shutdown() on the server like before, but now we only send on the shutdownError 
        // channel if it returns an error.
        err := srv.Shutdown(ctx)
        if err != nil {
            shutdownError <- err
        }

        // Log a message to say that we're waiting for any background goroutines to complete 
        // their tasks.
        app.logger.Info("waiting for background tasks to complete", "addr", srv.Addr)

        // Call Wait() to block until the WaitGroup counter is zero -- essentially blocking until 
        // the background goroutines have finished. Then we return nil on the shutdownError 
        // channel, to indicate that the shutdown completed without any issues.
        app.wg.Wait()
        shutdownError <- nil
    }()

    app.logger.Info("starting server", "addr", srv.Addr, "env", app.config.env)

    err := srv.ListenAndServe()
    if !errors.Is(err, http.ErrServerClosed) {
        return err
    }

    err = <-shutdownError
    if err != nil {
        return err
    }

    app.logger.Info("stopped server", "addr", srv.Addr)

    return nil
}
复制代码

 

复制代码
zzh@ZZHPC:~$ curl -d "$BODY" localhost:4000/v1/users & pkill -SIGTERM api &
[1] 71854
[2] 71855
zzh@ZZHPC:~$ {
    "user": {
        "id": 8,
        "created_at": "2024-11-21T19:38:52+08:00",
        "name": "ZhangZhihui",
        "email": "ZhangZhihuiAAA@126.com",
        "activated": false,
        "version": 1
    }
}

[1]-  Done                    curl -d "$BODY" localhost:4000/v1/users
[2]+  Done                    pkill -SIGTERM api
复制代码

When you do this, your server logs should look similar to the output below:

zzh@ZZHPC:/zdata/Github/greenlight$ go run ./cmd/api
time=2024-11-21T19:34:10.404+08:00 level=INFO msg="database connection pool established"
time=2024-11-21T19:34:10.405+08:00 level=INFO msg="starting server" addr=:4000 env=development
time=2024-11-21T19:38:52.210+08:00 level=INFO msg="shutting down server" signal=terminated
time=2024-11-21T19:38:52.480+08:00 level=INFO msg="waiting for background tasks to complete" addr=:4000
time=2024-11-21T19:38:56.257+08:00 level=INFO msg="stopped server" addr=:4000

 

posted on   ZhangZhihuiAAA  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2023-11-19 Makefile - What is a Makefile and how does it work?
2023-11-19 Docker - Run PostgreSQL
2023-11-19 GRPC - TLS Credentials
2023-11-19 OpenSSL - Certificate Generation
2023-11-19 Microservice- Resiliency patterns: Circuit Breaker Pattern
2023-11-19 Microservice- Resiliency patterns: Retry Pattern
2023-11-19 Microservice- Resiliency patterns: Timeout Pattern
 
点击右上角即可分享
微信分享提示