Bu örnekte, Go uygulamalarında HTTP sunucusunun kapatılmasını dikkatli bir şekilde nasıl ele alabileceğinizi göstereceğim. Buradaki önemli nokta, tüm "canlı" istekleri/bağlantıları sadece hemen kapatarak öldürmek yerine, sunucuyu kapatmadan önce belirli bir süre bekletmektir. Kod satırlarının ve fonksiyonların tam olarak ne yaptıklarını anlamak için kod içi yorumlarını okuyun. Dikkatlice sunucu kapatmayı ele almanın farklı yolları vardır ama buradaki çoğunlukla Golang Shutdown belgelerine dayanan bir örnektir. Ben örneği mümkün olduğunca alakalı tutmak için sadece gerekli kodu tutacağım.


Yapı


.
├── cmd
│   └── client
│   └── main.go
├── go.mod
└── internal
├── app
│   ├── client.go
│   ├── handler.go
│   └── server.go
└── user
└── get.go

Mantık


"Beklendik" bir kapatma sinyali oluşuyor. Örneğin: Ctrl-C, kill PID, docker stop veya docker down. Eğer o anda "canlı" istek/bağlantı yoksa, gelen yeni istekler engellenir ve 10 saniyeyi hiç beklemeden sunucu kapatılır. Ama eğer bir veya birden fazla "canlı" istek/bağlantı var ise, gelen yeni istekler engellenir ve mevcut olan "canlı" istek/bağlantılara işlerini bitirmeleri için 10 saniye verildikten sonra sunucu kapatılır. Bu süre içinde bitmeyen işler kesilir.


Dosyalar


Kodun tamamı tek bir dosyada saklanabilir veya farklı bir şekilde organize edilebilir, bu nedenle onu geliştirmek/düzenlemek size kalmış.


main.go


package main

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

"internal/app"
)

func main() {
// Create the application instance.
application := app.App{
Server: app.Server(app.Handler()),
}

// idleChan channel is dedicated for shutting down all active connections.
// Once actual shutdown occurred by closing this channel, the main goroutine
// is shutdown.
idleChan := make(chan struct{})

go func() {
// signChan channel is used to transmit signal notifications.
signChan := make(chan os.Signal, 1)
// Catch and relay certain signal(s) to signChan channel.
signal.Notify(signChan, os.Interrupt, syscall.SIGTERM)
// Blocking until a signal is sent over signChan channel.
sig := <-signChan

log.Println("shutdown:", sig)

// Create a new context with a timeout duration. It helps allowing
// timeout duration to all active connections in order for them to
// finish their job. Any connections that wont complete within the
// allowed timeout duration gets halted.
ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
defer cancel()

if err := application.Stop(ctx); err == context.DeadlineExceeded {
log.Println("shutdown: halted active connections")
}

// Actual shutdown trigger.
close(idleChan)
}()

if err := application.Start(); err == http.ErrServerClosed {
log.Println("shutdown: started")
}

// Blocking until the shutdown to complete then inform the main goroutine.
<-idleChan

log.Println("shutdown: completed")
}

client.go


package app

import (
"context"
"net/http"
)

type App struct {
Server *http.Server
}

func (a App) Start() error {
return a.Server.ListenAndServe()
}

func (a App) Stop(ctx context.Context) error {
return a.Server.Shutdown(ctx)
}

handler.go


package app

import (
"net/http"

"internal/user"
)

func Handler() http.Handler {
handler := http.NewServeMux()
handler.HandleFunc("/client/api/v1/users", user.Get)

return handler
}

server.go


package app

import (
"net/http"
)

func Server(handler http.Handler) *http.Server {
return &http.Server{
Addr: ":8080",
Handler: handler,
}
}

get.go


package user

import (
"fmt"
"net/http"
"time"
)

func Get(w http.ResponseWriter, _ *http.Request) {
time.Sleep(15 * time.Second)

_, _ = w.Write([]byte(fmt.Sprintf("Response: %d", 1)))
}