英文:
How to write a unit test for Interrupt Signal (Ctrl+C) of an HTTP server?
问题
给定一个小型的HTTP服务器,你如何编写一个单元测试来确保中断被正确处理?
package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func SimpleServer() {
ctx := context.Background()
server := http.Server{
Addr: ":8080",
BaseContext: func(net.Listener) context.Context { return ctx },
}
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "pong at ", time.Now().String())
})
go server.ListenAndServe()
exitChannel := make(chan os.Signal, 1) // reserve with buffer size 1 so the notifiers aren't blocked
defer close(exitChannel)
signal.Notify(exitChannel, os.Interrupt, syscall.SIGTERM) // Docker and Kubernetes will signal with syscall.SIGTERM
<-exitChannel
fmt.Print("\n")
fmt.Println("shutting down server...")
err := server.Shutdown(ctx)
fmt.Println(err)
}
我想知道如何传递这个信号:
exitChannel <- syscall.SIGINT
或者:
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
然后以某种方式被通知服务器已关闭。
更新:
根据Burak的答案,这是一个可行的解决方案:
func exampleCaller() {
exitChannel, serverDownChannel := HTTPServer()
fmt.Println("sending kill signal in...")
for i := 3; i > 0; i-- {
fmt.Println(i)
time.Sleep(1 * time.Second)
}
exitChannel <- syscall.SIGINT
fmt.Println("kill signal sent")
<-serverDownChannel
fmt.Println("server is down!")
}
func HTTPServer() (chan os.Signal, chan struct{}) {
responseDelay := 1000 // 毫秒延迟发送响应消息
ctx := context.Background()
server := http.Server{
Addr: ":8080",
BaseContext: func(net.Listener) context.Context { return ctx },
}
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Duration(responseDelay) * time.Millisecond)
fmt.Fprint(w, "pong at ", time.Now().String())
})
fmt.Println("creating channels...")
exitChannel := make(chan os.Signal, 1) // reserve with buffer size 1 so the notifiers aren't blocked
serverDownChannel := make(chan struct{}) // 注意:我们没有向该通道传递任何数据
signal.Notify(exitChannel, os.Interrupt, syscall.SIGTERM) // Docker and Kubernetes will signal with syscall.SIGTERM
go func() {
<-exitChannel // 等待信号中断
fmt.Print("\n")
fmt.Println("shutting down server...")
err := server.Shutdown(ctx)
fmt.Println(err)
}()
go func() {
server.ListenAndServe() // 启动HTTP服务器
close(serverDownChannel)
}()
return exitChannel, serverDownChannel
}
英文:
Given a small HTTP server, how can you write a unit test to make sure the interrupt is dealt with correctly?
package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func SimpleServer() {
ctx := context.Background()
server := http.Server{
Addr: ":8080",
BaseContext: func(net.Listener) context.Context { return ctx },
}
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "pong at ", time.Now().String())
})
go server.ListenAndServe()
exitChannel := make(chan os.Signal, 1) // reserve with buffer size 1 so the notifiers aren't blocked
defer close(exitChannel)
signal.Notify(exitChannel, os.Interrupt, syscall.SIGTERM) // Docker and Kubernetes will signal with syscall.SIGTERM
<-exitChannel
fmt.Print("\n")
fmt.Println("shutting down server...")
err := server.Shutdown(ctx)
fmt.Println(err)
}
I'm wondering how to pass in this:
exitChannel <- syscall.SIGINT
or:
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
..and then somehow be notified that the server shut down.
Update:
Following Burak's answer below, here's a working solution:
func exampleCaller() {
exitChannel, serverDownChannel := HTTPServer()
fmt.Println("sending kill signal in...")
for i := 3; i > 0; i-- {
fmt.Println(i)
time.Sleep(1 * time.Second)
}
exitChannel <- syscall.SIGINT
fmt.Println("kill signal sent")
<-serverDownChannel
fmt.Println("server is down!")
}
func HTTPServer() (chan os.Signal, chan struct{}) {
responseDelay := 1000 // millisecond delay before sending a response message
ctx := context.Background()
server := http.Server{
Addr: ":8080",
BaseContext: func(net.Listener) context.Context { return ctx },
}
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Duration(responseDelay) * time.Millisecond)
fmt.Fprint(w, "pong at ", time.Now().String())
})
fmt.Println("creating channels...")
exitChannel := make(chan os.Signal, 1) // reserve with buffer size 1 so the notifiers aren't blocked
serverDownChannel := make(chan struct{}) // NOTE: we're not passing any data into this channel
signal.Notify(exitChannel, os.Interrupt, syscall.SIGTERM) // Docker and Kubernetes will signal with syscall.SIGTERM
go func() {
<-exitChannel // wait for the signal interrupt
fmt.Print("\n")
fmt.Println("shutting down server...")
err := server.Shutdown(ctx)
fmt.Println(err)
}()
go func() {
server.ListenAndServe() // start the HTTP server
close(serverDownChannel)
}()
return exitChannel, serverDownChannel
}
答案1
得分: 1
而不是等待信号并关闭,你可以稍微重构一下,根据信号来关闭:
exitChannel := make(chan struct{})
serverDown := make(chan struct{})
go func() {
<-exitChannel
server.Shutdown(ctx)
}()
go func() {
server.ListenAndServe()
close(serverDown)
}()
return exitChannel, serverDown
然后,你可以使用 exitChannel
来在外部终止服务器,使用 serverDown
来检测服务器是否已关闭。在外部连接信号通道,这样当你接收到关闭信号时,可以向 exitChannel
写入数据。
英文:
Instead of waiting for a signal and shutting down, you can refactor a bit to shutdown based on a signal:
exitChannel := make(chan struct{})
serverDown:=make(chan struct{})
go func() {
<-exitChannel
server.Shutdown(ctx)
}()
go func() {
server.ListenAndServe()
close(serverDown)
}()
return exitChannel, serverDown
Then, you can use the exitChannel
to terminate the server externally, and serverDown
to detect if the server shutdown. Hook up the signal channel externally, so when you receive the shutdown signal, you can write to the exitChannel
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论