90 lines
2.4 KiB
Markdown
90 lines
2.4 KiB
Markdown
# HTTP Handler Style Guide
|
|
|
|
### HTTP Handler
|
|
* Each handler should implement `http.Handler`
|
|
- This can be done by embedding a [`httprouter.Router`](https://github.com/julienschmidt/httprouter)
|
|
(a light weight HTTP router that supports variables in the routing pattern and matches against the request method)
|
|
* Required services should be exported on the struct
|
|
|
|
```go
|
|
// ThingHandler represents an HTTP API handler for things.
|
|
type ThingHandler struct {
|
|
// embedded httprouter.Router as a lazy way to implement http.Handler
|
|
*httprouter.Router
|
|
|
|
ThingService platform.ThingService
|
|
AuthorizationService platform.AuthorizationService
|
|
|
|
Logger *zap.Logger
|
|
}
|
|
```
|
|
|
|
### HTTP Handler Constructor
|
|
|
|
* Routes should be declared in the constructor
|
|
|
|
```go
|
|
// NewThingHandler returns a new instance of ThingHandler.
|
|
func NewThingHandler() *ThingHandler {
|
|
h := &ThingHandler{
|
|
Router: httprouter.New(),
|
|
Logger: zap.Nop(),
|
|
}
|
|
|
|
h.HandlerFunc("POST", "/api/v2/things", h.handlePostThing)
|
|
h.HandlerFunc("GET", "/api/v2/things", h.handleGetThings)
|
|
|
|
return h
|
|
}
|
|
```
|
|
|
|
### Route handlers (`http.HandlerFunc`s)
|
|
|
|
* Each route handler should have an associated request struct and decode function
|
|
* The decode function should take a `context.Context` and an `*http.Request` and return the associated route request struct
|
|
|
|
```go
|
|
type postThingRequest struct {
|
|
Thing *platform.Thing
|
|
}
|
|
|
|
func decodePostThingRequest(ctx context.Context, r *http.Request) (*postThingRequest, error) {
|
|
t := &platform.Thing{}
|
|
if err := json.NewDecoder(r.Body).Decode(t); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &postThingRequest{
|
|
Thing: t,
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
* Route `http.HandlerFuncs` should separate the decoding and encoding of HTTP requests/response from actual handler logic
|
|
|
|
```go
|
|
// handlePostThing is the HTTP handler for the POST /api/v2/things route.
|
|
func (h *ThingHandler) handlePostThing(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
req, err := decodePostThingRequest(ctx, r)
|
|
if err != nil {
|
|
EncodeError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
// Do stuff here
|
|
if err := h.ThingService.CreateThing(ctx, req.Thing); err != nil {
|
|
EncodeError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusCreated, req.Thing); err != nil {
|
|
h.Logger.Info("encoding response failed", zap.Error(err))
|
|
return
|
|
}
|
|
}
|
|
```
|
|
|
|
* `http.HandlerFunc`'s that require particular encoding of http responses should implement an encode response function
|