如何为HTTP服务器编写一个关于中断信号(Ctrl+C)的单元测试?

huangapple go评论73阅读模式
英文:

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 (
&quot;context&quot;
&quot;fmt&quot;
&quot;net&quot;
&quot;net/http&quot;
&quot;os&quot;
&quot;os/signal&quot;
&quot;syscall&quot;
&quot;time&quot;
)
func SimpleServer() {
ctx := context.Background()
server := http.Server{
Addr:        &quot;:8080&quot;,
BaseContext: func(net.Listener) context.Context { return ctx },
}
http.HandleFunc(&quot;/ping&quot;, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, &quot;pong at &quot;, time.Now().String())
})
go server.ListenAndServe()
exitChannel := make(chan os.Signal, 1) // reserve with buffer size 1 so the notifiers aren&#39;t blocked
defer close(exitChannel)
signal.Notify(exitChannel, os.Interrupt, syscall.SIGTERM) // Docker and Kubernetes will signal with syscall.SIGTERM
&lt;-exitChannel
fmt.Print(&quot;\n&quot;)
fmt.Println(&quot;shutting down server...&quot;)
err := server.Shutdown(ctx)
fmt.Println(err)
}

I'm wondering how to pass in this:

exitChannel &lt;- 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(&quot;sending kill signal in...&quot;)
for i := 3; i &gt; 0; i-- {
fmt.Println(i)
time.Sleep(1 * time.Second)
}
exitChannel &lt;- syscall.SIGINT
fmt.Println(&quot;kill signal sent&quot;)
&lt;-serverDownChannel
fmt.Println(&quot;server is down!&quot;)
}
func HTTPServer() (chan os.Signal, chan struct{}) {
responseDelay := 1000 // millisecond delay before sending a response message
ctx := context.Background()
server := http.Server{
Addr:        &quot;:8080&quot;,
BaseContext: func(net.Listener) context.Context { return ctx },
}
http.HandleFunc(&quot;/ping&quot;, func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Duration(responseDelay) * time.Millisecond)
fmt.Fprint(w, &quot;pong at &quot;, time.Now().String())
})
fmt.Println(&quot;creating channels...&quot;)
exitChannel := make(chan os.Signal, 1)                    // reserve with buffer size 1 so the notifiers aren&#39;t blocked
serverDownChannel := make(chan struct{})                  // NOTE: we&#39;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() {
&lt;-exitChannel // wait for the signal interrupt
fmt.Print(&quot;\n&quot;)
fmt.Println(&quot;shutting down server...&quot;)
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() {
&lt;-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.

huangapple
  • 本文由 发表于 2022年7月28日 07:17:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/73145574.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定