chore(SimpleRouter)!: promoted from unstable

This commit is contained in:
2026-03-09 20:31:43 +01:00
parent 5acd5e01eb
commit d2f2558fdf
3 changed files with 0 additions and 0 deletions

View File

@@ -1,224 +0,0 @@
package SimpleRouter
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
Type "github.com/lbatuska/goutils/type"
)
func (rg *RouteGroup) StartServer(addr string, config Type.Optional[ServerConfig]) *http.Server {
var server *http.Server
if config.HasValue() {
serverConfig := config.Unwrap()
server = &http.Server{
Addr: addr,
Handler: rg,
ReadTimeout: serverConfig.ReadTimeout,
ReadHeaderTimeout: serverConfig.ReadHeaderTimeout,
WriteTimeout: serverConfig.WriteTimeout,
}
} else {
server = &http.Server{
Addr: addr,
Handler: rg,
}
}
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
panic(fmt.Sprintf("HTTP server error: %v", err))
}
}()
return server
}
func (rg *RouteGroup) StartWithGracefulShutdown(addr string, config Type.Optional[ServerConfig]) {
var server *http.Server
if config.HasValue() {
serverConfig := config.Unwrap()
server = &http.Server{
Addr: addr,
Handler: rg,
ReadTimeout: serverConfig.ReadTimeout,
ReadHeaderTimeout: serverConfig.ReadHeaderTimeout,
WriteTimeout: serverConfig.WriteTimeout,
}
} else {
server = &http.Server{
Addr: addr,
Handler: rg,
}
}
// Start the server in a separate goroutine
go func() {
fmt.Println("Starting server on", addr)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
panic(fmt.Sprintf("HTTP server error: %v", err))
}
}()
// Set up signal catching for graceful shutdown (Ctrl+C, SIGINT, SIGTERM)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// Block until a signal is received
<-sigChan
fmt.Println("Shutting down server...")
// Set a timeout for the shutdown process (10 seconds)
shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second)
defer shutdownRelease()
// Perform graceful shutdown
if err := server.Shutdown(shutdownCtx); err != nil {
panic(fmt.Sprintf("HTTP shutdown error: %v", err))
}
fmt.Println("Graceful shutdown complete.")
}
// http.ListenAndServe takes a Handler interface defined as: ServeHTTP(ResponseWriter, *Request)
func (rg *RouteGroup) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rg.mux.ServeHTTP(w, r)
})
mwlen := len(*rg.global_middlewares) - 1
for i := range *rg.global_middlewares {
handler = (*rg.global_middlewares)[mwlen-i](handler)
}
handler.ServeHTTP(w, r)
}
// / Handler returns the handler to use for the given request, ... propagate call to http library
func (rg *RouteGroup) Handler(r *http.Request) (h http.Handler, pattern string) {
return rg.mux.Handler(r)
}
// Creates a new SimpleRouter with the basePath of "/"
func SimpleRouter() *RouteGroup {
return &RouteGroup{mux: http.NewServeMux(), basePath: "/", global_middlewares: &[]Middleware{}}
}
// Puts middlewares on top of existing ones in order
func (rg *RouteGroup) PushMiddleware(first Middleware, others ...Middleware) *RouteGroup {
rg.middlewares = append(rg.middlewares, first)
// append supposedly keeps the order of elements, so no extra work is needed
rg.middlewares = append(rg.middlewares, others...)
return rg
}
// Removes the top middleware and returns it to the caller
func (rg *RouteGroup) PopMiddleware() Middleware {
if len(rg.middlewares) == 0 {
return nil
}
lastIndex := len(rg.middlewares) - 1
lastMiddleware := rg.middlewares[lastIndex]
rg.middlewares = rg.middlewares[:lastIndex]
return lastMiddleware
}
// this vs PushMiddleware ? what's the diff?
func (rg *RouteGroup) PushGlobalMiddleware(first Middleware, others ...Middleware) *RouteGroup {
*rg.global_middlewares = append(*rg.global_middlewares, first)
*rg.global_middlewares = append(*rg.global_middlewares, others...)
return rg
}
func (rg *RouteGroup) PopGlobalMiddleware() Middleware {
if len(*rg.global_middlewares) == 0 {
return nil
}
lastIndex := len(*rg.global_middlewares) - 1
lastMiddleware := (*rg.global_middlewares)[lastIndex]
*rg.global_middlewares = (*rg.global_middlewares)[:lastIndex]
return lastMiddleware
}
// applies the array of middlewares on the handler
func (rg *RouteGroup) applyMiddlewares(handler http.Handler) http.Handler {
mwlen := len(rg.middlewares) - 1 // We need to start from the last element going to 0 for correct ordering
for i := range rg.middlewares {
handler = rg.middlewares[mwlen-i](handler)
}
return handler
}
func (rg *RouteGroup) registerRoute(method string, path string, handler http.HandlerFunc) {
if path == "/" {
path = rg.basePath[:len(rg.basePath)-1]
} else {
path = rg.basePath + path
}
fullPath := method + " " + path
rg.mux.HandleFunc(fullPath, rg.applyMiddlewares(handler).ServeHTTP)
}
func (rg *RouteGroup) SubPath(path string) *RouteGroup {
rgc := &RouteGroup{
mux: rg.mux,
basePath: rg.basePath + path + "/",
global_middlewares: rg.global_middlewares,
}
middlewares := make([]Middleware, len(rg.middlewares))
copy(middlewares, rg.middlewares)
rgc.middlewares = middlewares
return rgc
}
func (rg *RouteGroup) HandleFunc(method string, path string, handler http.HandlerFunc) {
rg.registerRoute(method, path, handler)
}
func (rg *RouteGroup) Handle(method string, path string, handler http.Handler) {
rg.HandleFunc(method, path, handler.ServeHTTP)
}
func (rg *RouteGroup) SubpathHandle(path string, handler http.Handler) {
rg.Handle("", path, handler)
}
func (rg *RouteGroup) GET(path string, handler http.Handler) {
rg.Handle(http.MethodGet, path, handler)
}
func (rg *RouteGroup) HEAD(path string, handler http.Handler) {
rg.Handle(http.MethodHead, path, handler)
}
func (rg *RouteGroup) OPTIONS(path string, handler http.Handler) {
rg.Handle(http.MethodOptions, path, handler)
}
func (rg *RouteGroup) POST(path string, handler http.Handler) {
rg.Handle(http.MethodPost, path, handler)
}
func (rg *RouteGroup) PUT(path string, handler http.Handler) {
rg.Handle(http.MethodPut, path, handler)
}
func (rg *RouteGroup) PATCH(path string, handler http.Handler) {
rg.Handle(http.MethodPatch, path, handler)
}
func (rg *RouteGroup) DELETE(path string, handler http.Handler) {
rg.Handle(http.MethodDelete, path, handler)
}

View File

@@ -1,422 +0,0 @@
package SimpleRouter
import (
"net/http"
"net/http/httptest"
"sync"
"syscall"
"testing"
"time"
Testing "github.com/lbatuska/goutils/testing"
Type "github.com/lbatuska/goutils/type"
)
func setupServer(wg *sync.WaitGroup) {
defer wg.Done()
simpleRouter := SimpleRouter()
simpleRouter.GET("test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Second * 5)
w.WriteHeader(200)
}))
simpleRouter.GET("test2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}))
simpleRouter.StartWithGracefulShutdown("0.0.0.0:8080", Type.None[ServerConfig]())
}
func sendKillSignal(wg *sync.WaitGroup) {
defer wg.Done()
time.Sleep(time.Second * 4)
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
}
func Test_gracefulShutdown(t *testing.T) {
var wg sync.WaitGroup
wg.Add(2)
go setupServer(&wg)
go sendKillSignal(&wg)
time.Sleep(time.Second * 2)
res, err := http.Get("http://localhost:8080/test")
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusOK, res.StatusCode)
wg.Wait()
}
func Test_middlewares(t *testing.T) {
counter := 0
mw1 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter += 1
next.ServeHTTP(w, r)
})
}
mw2 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter = counter * 2
next.ServeHTTP(w, r)
})
}
simpleRouter := SimpleRouter()
simpleRouter.PushMiddleware(mw1)
simpleRouter.PushMiddleware(mw2)
simpleRouter.GET("test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
ts := httptest.NewServer(simpleRouter)
req, _ := http.NewRequest("GET", ts.URL+"/test", nil)
_, err := http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, 2, counter)
ts.Close()
counter = 0
simpleRouter.PopMiddleware()
simpleRouter.PopMiddleware()
simpleRouter.PushMiddleware(mw2)
simpleRouter.PushMiddleware(mw1)
simpleRouter.GET("test2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
ts = httptest.NewServer(simpleRouter)
req, _ = http.NewRequest("GET", ts.URL+"/test2", nil)
_, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, 1, counter)
ts.Close()
counter = 0
simpleRouter.PopMiddleware()
simpleRouter.PopMiddleware()
simpleRouter.PushMiddleware(mw1)
simpleRouter.PushMiddleware(mw2)
simpleRouter.PushMiddleware(mw1)
simpleRouter.PushMiddleware(mw2)
simpleRouter.GET("test3", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
ts = httptest.NewServer(simpleRouter)
defer ts.Close()
req, _ = http.NewRequest("GET", ts.URL+"/test3", nil)
_, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, 6, counter)
ts.Close()
}
func Test_middlewares2(t *testing.T) {
counter := 0
mw1 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter += 1
next.ServeHTTP(w, r)
})
}
mw2 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter = counter * 2
next.ServeHTTP(w, r)
})
}
simpleRouter := SimpleRouter()
simpleRouter.PushMiddleware(mw1, mw2)
simpleRouter.GET("test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
ts := httptest.NewServer(simpleRouter)
req, _ := http.NewRequest("GET", ts.URL+"/test", nil)
_, err := http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, 2, counter)
ts.Close()
counter = 0
simpleRouter.PopMiddleware()
simpleRouter.PopMiddleware()
simpleRouter.PushMiddleware(mw2, mw1)
simpleRouter.GET("test2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
ts = httptest.NewServer(simpleRouter)
req, _ = http.NewRequest("GET", ts.URL+"/test2", nil)
_, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, 1, counter)
ts.Close()
counter = 0
simpleRouter.PopMiddleware()
simpleRouter.PopMiddleware()
simpleRouter.PushMiddleware(mw1, mw2, mw1, mw2)
simpleRouter.GET("test3", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
ts = httptest.NewServer(simpleRouter)
defer ts.Close()
req, _ = http.NewRequest("GET", ts.URL+"/test3", nil)
_, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, 6, counter)
ts.Close()
}
func Test_globalMiddlewares(t *testing.T) {
counter := 0
mw1 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter += 1
next.ServeHTTP(w, r)
})
}
mw2 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter = counter * 2
next.ServeHTTP(w, r)
})
}
gmw1 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter += 3
next.ServeHTTP(w, r)
})
}
gmw2 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter = counter * 4
next.ServeHTTP(w, r)
})
}
simpleRouter := SimpleRouter()
simpleRouter2 := simpleRouter.SubPath("2")
simpleRouter.PushGlobalMiddleware(gmw1)
simpleRouter.PushGlobalMiddleware(gmw2)
simpleRouter.PushMiddleware(mw1, mw2)
simpleRouter.GET("test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
simpleRouter.GET("test2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
// Subpath
simpleRouter2.GET("test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
_ts := httptest.NewServer(simpleRouter2)
_req, _ := http.NewRequest("GET", _ts.URL+"/2/test", nil)
_, _err := http.DefaultClient.Do(_req)
Testing.AssertNotError(t, _err)
Testing.AssertEqual(t, 12, counter)
counter = 0
// Subpath
ts := httptest.NewServer(simpleRouter)
req, _ := http.NewRequest("GET", ts.URL+"/test", nil)
_, err := http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, 26, counter)
counter = 0
req2, _ := http.NewRequest("GET", ts.URL+"/test2", nil)
_, err2 := http.DefaultClient.Do(req2)
Testing.AssertNotError(t, err2)
Testing.AssertEqual(t, 26, counter)
ts.Close()
counter = 0
simpleRouter.PopGlobalMiddleware()
simpleRouter.PopGlobalMiddleware()
simpleRouter.PushGlobalMiddleware(gmw2)
simpleRouter.PushGlobalMiddleware(gmw1)
ts = httptest.NewServer(simpleRouter)
req, _ = http.NewRequest("GET", ts.URL+"/test", nil)
_, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, 8, counter)
counter = 0
req2, _ = http.NewRequest("GET", ts.URL+"/test2", nil)
_, err2 = http.DefaultClient.Do(req2)
Testing.AssertNotError(t, err2)
Testing.AssertEqual(t, 8, counter)
ts.Close()
counter = 0
}
func Test_globalMiddlewares2(t *testing.T) {
counter := 0
mw1 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter += 1
next.ServeHTTP(w, r)
})
}
mw2 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter = counter * 2
next.ServeHTTP(w, r)
})
}
gmw1 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter += 3
next.ServeHTTP(w, r)
})
}
gmw2 := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter = counter * 4
next.ServeHTTP(w, r)
})
}
simpleRouter := SimpleRouter()
simpleRouter.PushGlobalMiddleware(gmw1, gmw2)
simpleRouter.PushMiddleware(mw1, mw2)
simpleRouter.GET("test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
simpleRouter.GET("test2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
ts := httptest.NewServer(simpleRouter)
req, _ := http.NewRequest("GET", ts.URL+"/test", nil)
_, err := http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, 26, counter)
counter = 0
req2, _ := http.NewRequest("GET", ts.URL+"/test2", nil)
_, err2 := http.DefaultClient.Do(req2)
Testing.AssertNotError(t, err2)
Testing.AssertEqual(t, 26, counter)
ts.Close()
counter = 0
simpleRouter.PopGlobalMiddleware()
simpleRouter.PopGlobalMiddleware()
simpleRouter.PushGlobalMiddleware(gmw2, gmw1)
ts = httptest.NewServer(simpleRouter)
req, _ = http.NewRequest("GET", ts.URL+"/test", nil)
_, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, 8, counter)
counter = 0
req2, _ = http.NewRequest("GET", ts.URL+"/test2", nil)
_, err2 = http.DefaultClient.Do(req2)
Testing.AssertNotError(t, err2)
Testing.AssertEqual(t, 8, counter)
ts.Close()
counter = 0
}
func Test_handle(t *testing.T) {
counter := 0
subpathHandler := func() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter = 1
})
}
regularHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
counter = 2
})
simpleRouter := SimpleRouter()
simpleRouter.SubpathHandle("test/", subpathHandler())
simpleRouter.Handle("GET", "test2", regularHandler)
ts := httptest.NewServer(simpleRouter)
defer ts.Close()
req, _ := http.NewRequest("GET", ts.URL+"/test/1", nil)
res, err := http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusOK, res.StatusCode)
Testing.AssertEqual(t, 1, counter)
req, _ = http.NewRequest("GET", ts.URL+"/test/2", nil)
res, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusOK, res.StatusCode)
Testing.AssertEqual(t, 1, counter)
req, _ = http.NewRequest("GET", ts.URL+"/test2", nil)
res, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusOK, res.StatusCode)
Testing.AssertEqual(t, 2, counter)
}
func Test_methods(t *testing.T) {
simpleRouter := SimpleRouter()
simpleRouter.GET("test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
simpleRouter.POST("test2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
simpleRouter.PATCH("test3", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
simpleRouter.PUT("test4", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
simpleRouter.DELETE("test5", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
simpleRouter.OPTIONS("test6", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
simpleRouter.HEAD("test7", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
ts := httptest.NewServer(simpleRouter)
defer ts.Close()
req, _ := http.NewRequest("GET", ts.URL+"/test", nil)
req2, _ := http.NewRequest("POST", ts.URL+"/test", nil)
res, err := http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusOK, res.StatusCode)
res2, err := http.DefaultClient.Do(req2)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusMethodNotAllowed, res2.StatusCode)
req, _ = http.NewRequest("POST", ts.URL+"/test2", nil)
req2, _ = http.NewRequest("PATCH", ts.URL+"/test2", nil)
res, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusOK, res.StatusCode)
res2, err = http.DefaultClient.Do(req2)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusMethodNotAllowed, res2.StatusCode)
req, _ = http.NewRequest("PATCH", ts.URL+"/test3", nil)
req2, _ = http.NewRequest("PUT", ts.URL+"/test3", nil)
res, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusOK, res.StatusCode)
res2, err = http.DefaultClient.Do(req2)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusMethodNotAllowed, res2.StatusCode)
req, _ = http.NewRequest("PUT", ts.URL+"/test4", nil)
req2, _ = http.NewRequest("DELETE", ts.URL+"/test4", nil)
res, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusOK, res.StatusCode)
res2, err = http.DefaultClient.Do(req2)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusMethodNotAllowed, res2.StatusCode)
req, _ = http.NewRequest("DELETE", ts.URL+"/test5", nil)
req2, _ = http.NewRequest("OPTIONS", ts.URL+"/test5", nil)
res, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusOK, res.StatusCode)
res2, err = http.DefaultClient.Do(req2)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusMethodNotAllowed, res2.StatusCode)
req, _ = http.NewRequest("OPTIONS", ts.URL+"/test6", nil)
req2, _ = http.NewRequest("HEAD", ts.URL+"/test6", nil)
res, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusOK, res.StatusCode)
res2, err = http.DefaultClient.Do(req2)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusMethodNotAllowed, res2.StatusCode)
req, _ = http.NewRequest("HEAD", ts.URL+"/test7", nil)
req2, _ = http.NewRequest("GET", ts.URL+"/test7", nil)
res, err = http.DefaultClient.Do(req)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusOK, res.StatusCode)
res2, err = http.DefaultClient.Do(req2)
Testing.AssertNotError(t, err)
Testing.AssertEqual(t, http.StatusMethodNotAllowed, res2.StatusCode)
}

View File

@@ -1,21 +0,0 @@
package SimpleRouter
import (
"net/http"
"time"
)
type Middleware func(http.Handler) http.Handler
type RouteGroup struct {
mux *http.ServeMux // A ponter to the underlying ServeMux, this allows us to call ListenAndServe on any instance
basePath string // The current path we are defining handlers on / appending to
middlewares []Middleware // Stack of middlewares that will be applied on a handler in order
global_middlewares *[]Middleware
}
type ServerConfig struct {
ReadTimeout time.Duration
ReadHeaderTimeout time.Duration
WriteTimeout time.Duration
}