https://github.com/josestg/httpkit
HTTP Kit for Go Backend Engineers
https://github.com/josestg/httpkit
Last synced: about 1 month ago
JSON representation
HTTP Kit for Go Backend Engineers
- Host: GitHub
- URL: https://github.com/josestg/httpkit
- Owner: josestg
- License: apache-2.0
- Created: 2023-10-21T11:40:19.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2023-10-21T12:59:51.000Z (over 1 year ago)
- Last Synced: 2025-01-31T06:47:20.318Z (3 months ago)
- Language: Go
- Homepage:
- Size: 25.4 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# HTTP Kit for Go Backend Engineers
This kit is my personal utility, and I have used it in both my professional and hobby projects. I hope you will find it useful as well. I would be happy to receive your feedback and suggestions.
## Features
- [x] Modifies the [httprouter](github.com/julienschmidt/httprouter) to allow the handler to return errors.
- [x] HTTP Server with Graceful Shutdown.
- [x] Helpers for creating middleware for both net/http and the modified httprouter.
- [x] Helpers for request decoding and response encoding.
- [x] A middleware for recording logs. This middleware can be used to implement the [Canonical Log Line](https://stripe.com/blog/canonical-log-lines).## TODOS
- [ ] Add a Content Negotiation Middleware
- [ ] Add more encoders and decoders.
- [ ] Add more common middlewares.
- [ ] Add Open Telemetry support.## Examples
### Creating a Server with Graceful Shutdown
```go
package mainimport (
"fmt"
"github.com/josestg/httpkit"
"log/slog"
"net/http"
"os"
"syscall"
"time"
)func main() {
log := slog.Default()mux := httpkit.NewServeMux(
// Register an error handler that will be called when there is still unresolved error after all the middlewares
// and the handler have been called. We can think this as the last hope when all else fails.
httpkit.Opts.LastResortErrorHandler(LastResortErrorHandler(log)),// Set the global middlewares for the mux. These middlewares will be applied to all the handlers that are
// registered to this mux.
//
// For middlewares that are applied to a specific handler, `mux.Route` takes variadic `httpkit.MuxMiddleware`
// as the last argument, which will be applied to that handler only.
httpkit.Opts.Middleware(GlobalMiddleware()),// And more options are available under `httpkit.Opts` namespace.
)// Using `Route` instead of `HandleFunc` will be more convenient when working with Swagger docs because the swagger
// docs and the route definition will be close to each other. Which will make it easier to keep them in sync.
mux.Route(httpkit.Route{
Method: http.MethodPost,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) error {
data := struct {
Message string `json:"message"`
}{}if err := httpkit.ReadJSON(r.Body, &data); err != nil {
return fmt.Errorf("could not read json: %w", err)
}// do something with the data.
data.Message += " --updated"
return httpkit.WriteJSON(w, data, http.StatusOK)
},
})// this middleware will be applied to the router itself. Meaning that it will be called before and after
// the router calls the handler.
mid := httpkit.ReduceNetMiddleware(
// record both request and response body, along with latency and status code.
httpkit.LogEntryRecorder,// another example of a middleware that will be applied to the router itself.
func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Info("before router")
next.ServeHTTP(w, r)
entry, ok := httpkit.GetLogEntry(w)
if ok {
log.Info("after router", "method", r.Method, "path", r.URL.Path,
"status", entry.StatusCode, "latency", time.Duration(entry.RespondedAt-entry.RequestedAt))
}
})
},
)// apply the middleware to the router.
handler := mid.Then(mux)srv := http.Server{
Addr: "0.0.0.0:8080",
Handler: handler,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}// create a graceful server runner.
run := httpkit.NewGracefulRunner(
&srv,
httpkit.RunOpts.WaitTimeout(10*time.Second), // wait for 10 seconds before force shutdown.
httpkit.RunOpts.Signals(syscall.SIGINT, syscall.SIGTERM), // listen to SIGINT and SIGTERM signals.
httpkit.RunOpts.EventListener(func(evt httpkit.RunEvent, data string) { // listen to events and log them.
switch evt {
default:
log.Info(data)
case httpkit.RunEventAddr:
log.Info("http server listening", "addr", data)
case httpkit.RunEventSignal:
log.Info("http server received shutdown signal", "signal", data)
}
}),
)if err := run.ListenAndServe(); err != nil {
log.Error("http server error", "error", err)
os.Exit(1)
}
}func LastResortErrorHandler(log *slog.Logger) httpkit.LastResortErrorHandler {
return func(w http.ResponseWriter, r *http.Request, err error) {
log.Error("last resort error handler", "error", err)
data := map[string]string{
"message": http.StatusText(http.StatusInternalServerError),
}
if wErr := httpkit.WriteJSON(w, data, http.StatusInternalServerError); wErr != nil {
log.Error("could not write json", "unresolved_error", err, "write_error", wErr)
}
}
}func GlobalMiddleware() httpkit.MuxMiddleware {
return httpkit.ReduceMuxMiddleware(
globalMiddlewareOne,
globalMiddlewareTwo,
)
}func globalMiddlewareOne(next httpkit.Handler) httpkit.Handler {
return httpkit.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
// do something before calling the next handler.
err := next.ServeHTTP(w, r)
// do something after calling the next handler.
return err
})
}func globalMiddlewareTwo(next httpkit.Handler) httpkit.Handler {
return httpkit.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
// do something before calling the next handler.
err := next.ServeHTTP(w, r)
// do something after calling the next handler.
return err
})
}```