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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!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