Skip to content
This repository was archived by the owner on Mar 29, 2025. It is now read-only.

Commit 4f5ec66

Browse files
committed
Initial commit
1 parent 6a80e72 commit 4f5ec66

16 files changed

+833
-0
lines changed

.gitattributes

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Handle line endings automatically for files detected as text
2+
# and leave all files detected as binary untouched.
3+
* text=auto
4+
5+
# Force the following filetypes to have unix eols, so Windows does not break them
6+
*.* text eol=lf
7+
8+
# Windows forced line-endings
9+
/.idea/* text eol=crlf
10+
11+
#
12+
## These files are binary and should be left untouched
13+
#
14+
15+
# (binary is a macro for -text -diff)
16+
*.png binary
17+
*.jpg binary
18+
*.jpeg binary
19+
*.gif binary
20+
*.ico binary
21+
*.mov binary
22+
*.mp4 binary
23+
*.mp3 binary
24+
*.flv binary
25+
*.fla binary
26+
*.swf binary
27+
*.gz binary
28+
*.zip binary
29+
*.7z binary
30+
*.ttf binary
31+
*.eot binary
32+
*.woff binary
33+
*.pyc binary
34+
*.pdf binary
35+
*.ez binary
36+
*.bz2 binary
37+
*.swp binary

.gitignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
*.~*
2+
3+
*.log
4+
*.swp
5+
.idea
6+
.vscode
7+
*.patch
8+
### Go template
9+
# Binaries for programs and plugins
10+
*.exe
11+
*.exe~
12+
*.dll
13+
*.so
14+
*.dylib
15+
16+
# Test binary, build with `go test -c`
17+
*.test
18+
19+
# Output of the go coverage tool, specifically when used with LiteIDE
20+
*.out
21+
.DS_Store
22+
app
23+
demo
24+
25+
vendor/*
26+
27+
/**/*_test.json

LICENSE

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Copyright (C) RandLabs.IO - All Rights Reserved.
2+
Unauthorized copying of this file, via any medium is strictly prohibited.
3+
Proprietary and confidential.
4+
5+
Only explicitly authorized companies or individual can copy, modify,
6+
publish, use, compile, sell, or distribute this software,
7+
either in source code form or as a compiled binary, for any purpose,
8+
commercial or non-commercial, and by any means.

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,71 @@
11
# go-webserver
2+
23
HTTP web server library for Go based on FastHttp
4+
5+
## Usage with example
6+
7+
```golang
8+
package example
9+
10+
import (
11+
"fmt"
12+
"os"
13+
"os/signal"
14+
"syscall"
15+
16+
webserver "github.com/randlabs/go-webserver"
17+
)
18+
19+
func main() {
20+
// Options struct has all the documentation
21+
srvOpts := webserver.Options{
22+
Address: "127.0.0.1",
23+
Port: 3000,
24+
}
25+
srv, err := webserver.Create(srvOpts)
26+
if err != nil {
27+
fmt.Printf("unable to create web server [%v]\n", err)
28+
return
29+
}
30+
31+
// Setup routes
32+
srv.Router.GET("/test", getTestApi)
33+
34+
// Start server
35+
err = srv.Start()
36+
if err != nil {
37+
fmt.Printf("unable to start web server [%v]\n", err)
38+
return
39+
}
40+
41+
fmt.Println("Server running. Press CTRL+C to stop.")
42+
43+
// Wait for CTRL+C
44+
c := make(chan os.Signal, 2)
45+
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
46+
<-c
47+
fmt.Println("Shutting down...")
48+
49+
// Stop web server
50+
srv.Stop()
51+
}
52+
53+
type testApiOutput struct {
54+
Status string `json:"status"`
55+
}
56+
57+
func getTestApi(ctx *webserver.RequestCtx) {
58+
webserver.EnableCORS(ctx)
59+
webserver.DisableCache(ctx)
60+
61+
// Prepare output
62+
output := testApiOutput{}
63+
output.Status = "all systems operational"
64+
65+
// Encode and send output
66+
webserver.SendJSON(ctx, output)
67+
}
68+
```
69+
70+
## Lincese
71+
See `LICENSE` file for details.

go.mod

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module github.com/randlabs/go-webserver
2+
3+
go 1.17
4+
5+
require (
6+
github.com/buaazp/fasthttprouter v0.1.1
7+
github.com/valyala/fasthttp v1.33.0
8+
)
9+
10+
require (
11+
github.com/andybalholm/brotli v1.0.4 // indirect
12+
github.com/klauspost/compress v1.14.1 // indirect
13+
github.com/valyala/bytebufferpool v1.0.0 // indirect
14+
github.com/valyala/tcplisten v1.0.0 // indirect
15+
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect
16+
)

go.sum

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
2+
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
3+
github.com/buaazp/fasthttprouter v0.1.1 h1:4oAnN0C3xZjylvZJdP35cxfclyn4TYkW6Y+DSvS+h8Q=
4+
github.com/buaazp/fasthttprouter v0.1.1/go.mod h1:h/Ap5oRVLeItGKTVBb+heQPks+HdIUtGmI4H5WCYijM=
5+
github.com/klauspost/compress v1.14.1 h1:hLQYb23E8/fO+1u53d02A97a8UnsddcvYzq4ERRU4ds=
6+
github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
7+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
8+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
9+
github.com/valyala/fasthttp v1.33.0 h1:mHBKd98J5NcXuBddgjvim1i3kWzlng1SzLhrnBOU9g8=
10+
github.com/valyala/fasthttp v1.33.0/go.mod h1:KJRK/MXx0J+yd0c5hlR+s1tIHD72sniU8ZJjl97LIw4=
11+
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
12+
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
13+
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
14+
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
15+
golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
16+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
17+
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
18+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
19+
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
20+
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
21+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
22+
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
23+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
24+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

helpers.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package go_webserver
2+
3+
import (
4+
"encoding/json"
5+
6+
"github.com/valyala/fasthttp"
7+
)
8+
9+
// -----------------------------------------------------------------------------
10+
11+
func SendSuccess(ctx *RequestCtx) {
12+
ctx.Response.SetStatusCode(fasthttp.StatusOK)
13+
}
14+
15+
func SendJSON(ctx *RequestCtx, obj interface{}) {
16+
ctx.Response.Header.SetCanonical(strContentType, strApplicationJSON)
17+
ctx.Response.SetStatusCode(fasthttp.StatusOK)
18+
19+
err := json.NewEncoder(ctx).Encode(obj)
20+
if err != nil {
21+
SendInternalServerError(ctx, "")
22+
}
23+
}
24+
25+
func SendBadRequest(ctx *RequestCtx, msg string) {
26+
sendError(ctx, fasthttp.StatusBadRequest, msg)
27+
}
28+
29+
func SendAccessDenied(ctx *RequestCtx, msg string) {
30+
sendError(ctx, fasthttp.StatusForbidden, msg)
31+
}
32+
33+
func SendInternalServerError(ctx *RequestCtx, msg string) {
34+
sendError(ctx, fasthttp.StatusInternalServerError, msg)
35+
}
36+
37+
func EnableCORS(ctx *RequestCtx) {
38+
ctx.Response.Header.Add("Access-Control-Allow-Headers", "Content-Type, X-Access-Token, Authorization")
39+
ctx.Response.Header.Add("Access-Control-Allow-Methods", "GET")
40+
ctx.Response.Header.Add("Access-Control-Allow-Origin", "*")
41+
}
42+
43+
func DisableCache(ctx *RequestCtx) {
44+
ctx.Response.Header.Add(
45+
"Cache-Control",
46+
"private,no-cache,no-store,max-age=0,must-revalidate,pre-check=0,post-check=0",
47+
)
48+
}
49+
50+
// -----------------------------------------------------------------------------
51+
// Private functions
52+
53+
func sendError(ctx *RequestCtx, statusCode int, msg string) {
54+
if len(msg) == 0 {
55+
msg = fasthttp.StatusMessage(statusCode)
56+
}
57+
ctx.Error(msg, statusCode)
58+
}

http_adaptor.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package go_webserver
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/valyala/fasthttp"
7+
"github.com/valyala/fasthttp/fasthttpadaptor"
8+
)
9+
10+
// -----------------------------------------------------------------------------
11+
12+
func FastHttpHandlerFromHttpHandler(handler http.Handler) fasthttp.RequestHandler {
13+
// Create handler
14+
return fasthttpadaptor.NewFastHTTPHandler(handler)
15+
}
16+
17+
func FastHttpHandlerFromHttpHandlerFunc(f http.HandlerFunc) fasthttp.RequestHandler {
18+
return FastHttpHandlerFromHttpHandler(f)
19+
}

listener/connection.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package listener
2+
3+
import (
4+
"net"
5+
)
6+
7+
// -----------------------------------------------------------------------------
8+
9+
type gracefulConn struct {
10+
net.Conn
11+
ln *gracefulListener
12+
}
13+
14+
// -----------------------------------------------------------------------------
15+
16+
func (c *gracefulConn) Close() error {
17+
err := c.Conn.Close()
18+
if err == nil {
19+
c.ln.connClosed()
20+
}
21+
22+
return err
23+
}

listener/listener.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package listener
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"sync/atomic"
7+
"time"
8+
)
9+
10+
// -----------------------------------------------------------------------------
11+
12+
// gracefulListener defines a listener that we can gracefully stop
13+
type gracefulListener struct {
14+
// inner listener
15+
ln net.Listener
16+
17+
// maximum wait time for graceful shutdown
18+
maxShutdownTime time.Duration
19+
20+
// this channel is closed during graceful shutdown on zero open connections.
21+
done chan struct{}
22+
23+
// the number of open connections
24+
activeConnectionsCount uint64
25+
26+
// becomes non-zero when graceful shutdown starts
27+
shutdown uint64
28+
}
29+
30+
// -----------------------------------------------------------------------------
31+
32+
// NewGracefulListener wraps the given listener into 'graceful shutdown' listener.
33+
func NewGracefulListener(ln net.Listener, maxShutdownTime time.Duration) net.Listener {
34+
return &gracefulListener{
35+
ln: ln,
36+
maxShutdownTime: maxShutdownTime,
37+
done: make(chan struct{}),
38+
}
39+
}
40+
41+
// Accept creates a conn
42+
func (ln *gracefulListener) Accept() (net.Conn, error) {
43+
var c net.Conn
44+
var err error
45+
46+
c, err = ln.ln.Accept()
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
atomic.AddUint64(&ln.activeConnectionsCount, 1)
52+
53+
return &gracefulConn{
54+
Conn: c,
55+
ln: ln,
56+
}, nil
57+
}
58+
59+
// Addr returns the listen address
60+
func (ln *gracefulListener) Addr() net.Addr {
61+
return ln.ln.Addr()
62+
}
63+
64+
// Close closes the inner listener and waits until all the pending
65+
// open connections are closed before returning.
66+
func (ln *gracefulListener) Close() error {
67+
var err error
68+
69+
err = ln.ln.Close()
70+
if err != nil {
71+
return err
72+
}
73+
74+
return ln.waitForZeroConns()
75+
}
76+
77+
// -----------------------------------------------------------------------------
78+
// Private methods
79+
80+
func (ln *gracefulListener) waitForZeroConns() error {
81+
atomic.AddUint64(&ln.shutdown, 1)
82+
83+
if atomic.LoadUint64(&ln.activeConnectionsCount) == 0 {
84+
close(ln.done)
85+
return nil
86+
}
87+
88+
select {
89+
case <-ln.done:
90+
return nil
91+
case <-time.After(ln.maxShutdownTime):
92+
return fmt.Errorf("unable to complete graceful shutdown in %s", ln.maxShutdownTime)
93+
}
94+
}
95+
96+
func (ln *gracefulListener) connClosed() {
97+
activeConnectionsCount := atomic.AddUint64(&ln.activeConnectionsCount, ^uint64(0))
98+
99+
if atomic.LoadUint64(&ln.shutdown) != 0 && activeConnectionsCount == 0 {
100+
close(ln.done)
101+
}
102+
}

0 commit comments

Comments
 (0)