Skip to content

Commit fa62a96

Browse files
authored
Merge pull request #1 from ryodocx/dev
first release
2 parents 86691ef + 8aafb4e commit fa62a96

File tree

4 files changed

+171
-0
lines changed

4 files changed

+171
-0
lines changed

.github/workflows/docker.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
on:
2+
push:
3+
branches-ignore:
4+
- "**"
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v3
13+
- uses: docker/setup-qemu-action@v2
14+
- uses: docker/setup-buildx-action@v2
15+
- uses: docker/metadata-action@v3
16+
id: meta
17+
with:
18+
images: ghcr.io/ryodocx/testserver
19+
tags: |
20+
type=semver,pattern={{version}}
21+
type=semver,pattern={{major}}.{{minor}}
22+
- uses: docker/login-action@v2
23+
with:
24+
registry: ghcr.io
25+
username: ryodocx
26+
password: ${{ secrets.GITHUB_TOKEN }}
27+
- uses: docker/build-push-action@v3
28+
with:
29+
context: .
30+
push: true
31+
platforms: linux/amd64,linux/arm64
32+
tags: ${{ steps.meta.outputs.tags }}
33+
labels: ${{ steps.meta.outputs.labels }}

Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM golang:1.18.3-alpine
2+
COPY *.go .
3+
ENV CGO_ENABLED=0
4+
RUN go build -o server *.go
5+
6+
FROM scratch
7+
ENV LISTEN_ADDR=0.0.0.0:8080
8+
COPY --from=0 /go/server .
9+
CMD [ "/server" ]

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,22 @@
11
# testserver
22
HTTP Server for graceful-shutdown testing
3+
4+
## Usage
5+
6+
```sh
7+
# go install
8+
go install github.com/ryodocx/testserver@latest
9+
10+
# docker
11+
docker run --rm -it -p 8080:8080 ghcr.io/ryodocx/testserver
12+
```
13+
14+
## Environment variables
15+
16+
| env | default | example | description |
17+
|----------------|--------------------------|-------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------|
18+
| LISTEN_ADDR | `0.0.0.0:8080` | `127.0.0.1:8080` | Listen address |
19+
| RESPONSE_BODY | `I'm a testserver` | `hello world` | HTTP response body |
20+
| RESPONSE_SLEEP | `50ms` | `0` (without sleep) <br> `200ms` `5s` `0.01h` | Sleep time during HTTP response |
21+
| TRAP_SIGNALS | `[interrupt terminated]` | `0` (disable graceful shutdown) <br> `1,2,15` (enable graceful shutdown for SIGHUP/SIGINT/SIGTERM at Linux) | Trapped Signals for graceful shutdown |
22+
| GRACE_PERIOD | `1s` | `0` (no wait) <br> `5s` `1m` | Grace period before starting shutdown (ignored when `TRAP_SIGNALS=0`) |

main.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"net/http"
8+
"os"
9+
"os/signal"
10+
"strconv"
11+
"strings"
12+
"syscall"
13+
"time"
14+
)
15+
16+
// default config
17+
var listenAddr = "127.0.0.1:8080"
18+
var responseBody []byte = []byte("I'm a testserver")
19+
var responseSleep time.Duration = 50 * time.Millisecond
20+
var trapSignals []os.Signal = []os.Signal{syscall.SIGINT, syscall.SIGTERM}
21+
var gracePeriod time.Duration = 1 * time.Second
22+
23+
func init() {
24+
// override default config
25+
if v := os.Getenv("LISTEN_ADDR"); v != "" {
26+
listenAddr = v
27+
}
28+
if v := os.Getenv("RESPONSE_BODY"); v != "" {
29+
responseBody = []byte(v)
30+
}
31+
if v, err := time.ParseDuration(os.Getenv("RESPONSE_SLEEP")); err == nil {
32+
responseSleep = v
33+
}
34+
if v := os.Getenv("TRAP_SIGNALS"); v != "" {
35+
trapSignals = []os.Signal{}
36+
if v == "0" {
37+
// disable graceful shutdown
38+
} else {
39+
// enable graceful shutdown
40+
for _, s := range strings.Split(v, ",") {
41+
i, err := strconv.Atoi(s)
42+
if err != nil {
43+
log.Fatalln("invalid 'TRAP_SIGNALS':", s)
44+
}
45+
trapSignals = append(trapSignals, syscall.Signal(i))
46+
}
47+
}
48+
}
49+
if v, err := time.ParseDuration(os.Getenv("GRACE_PERIOD")); err == nil {
50+
gracePeriod = v
51+
}
52+
}
53+
54+
func handler(w http.ResponseWriter, req *http.Request) {
55+
time.Sleep(responseSleep)
56+
w.Write(responseBody)
57+
}
58+
59+
func main() {
60+
fmt.Println("pid =", os.Getpid())
61+
fmt.Println("############# Configuration #############")
62+
print := func(key string, val interface{}) { fmt.Printf("%-19s%v\n", key, val) }
63+
print("LISTEN_ADDR", listenAddr)
64+
print("RESPONSE_SLEEP", responseSleep)
65+
print("TRAP_SIGNALS", trapSignals)
66+
print("GRACE_PERIOD", gracePeriod)
67+
fmt.Println("#########################################")
68+
69+
var srv http.Server
70+
srv.Addr = listenAddr
71+
http.HandleFunc("/", handler)
72+
73+
idleConnsClosed := make(chan struct{})
74+
go func() {
75+
76+
// signal monitoring
77+
for {
78+
sigChan := make(chan os.Signal, 1)
79+
signal.Notify(sigChan)
80+
signal.Ignore(syscall.SIGURG) // https://golang.hateblo.jp/entry/golang-signal-urgent-io-condition
81+
recievedSignal := <-sigChan
82+
log.Println("signal recieved:", fmt.Sprintf("%d(%s)", recievedSignal, recievedSignal.String()))
83+
84+
for _, s := range trapSignals {
85+
if recievedSignal == s {
86+
goto shutdown
87+
}
88+
}
89+
}
90+
91+
// graceful shutdown
92+
shutdown:
93+
log.Println("waiting for shutdown:", gracePeriod)
94+
time.Sleep(gracePeriod)
95+
log.Println("shutting down...")
96+
if err := srv.Shutdown(context.Background()); err != nil {
97+
log.Printf("HTTP server Shutdown: %v", err)
98+
}
99+
close(idleConnsClosed)
100+
}()
101+
102+
// start
103+
log.Println("start servering")
104+
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
105+
log.Fatalf("HTTP server ListenAndServe: %v", err)
106+
}
107+
108+
<-idleConnsClosed
109+
}

0 commit comments

Comments
 (0)