23/12/2019 - GO
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.
.
├── cmd
│ └── client
│ └── main.go
├── go.mod
└── internal
├── app
│ ├── client.go
│ ├── handler.go
│ └── server.go
└── user
└── get.go
"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.
Kodun tamamı tek bir dosyada saklanabilir veya farklı bir şekilde organize edilebilir, bu nedenle onu geliştirmek/düzenlemek size kalmış.
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")
}
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)
}
package app
import (
"net/http"
"internal/user"
)
func Handler() http.Handler {
handler := http.NewServeMux()
handler.HandleFunc("/client/api/v1/users", user.Get)
return handler
}
package app
import (
"net/http"
)
func Server(handler http.Handler) *http.Server {
return &http.Server{
Addr: ":8080",
Handler: handler,
}
}
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)))
}