influxdb/http
Jonathan A. Sternberg cbd04f2884
refactor: http error serialization matches the new error schema ()
The http error schema has been changed to simplify the outward facing
API. The `op` and `error` attributes have been dropped because they
confused people. The `error` attribute will likely be readded in some
form in the future, but only as additional context and will not be
required or even suggested for the UI to use.

Errors are now output differently both when they are serialized to JSON
and when they are output as strings. The `op` is no longer used if it is
present. It will only appear as an optional attribute if at all. The
`message` attribute for an error is always output and it will be the
prefix for any nested error. When this is serialized to JSON, the
message is automatically flattened so a nested error such as:

    influxdb.Error{
        Msg: errors.New("something bad happened"),
        Err: io.EOF,
    }

This would be written to the message as:

    something bad happened: EOF

This matches a developers expectations much more easily as most
programmers assume that wrapping an error will act as a prefix for the
inner error.

This is flattened when written out to HTTP in order to make this logic
immaterial to a frontend developer.

The code is still present and plays an important role in categorizing
the error type. On the other hand, the code will not be output as part
of the message as it commonly plays a redundant and confusing role when
humans read it. The human readable message usually gives more context
and a message like with the code acting as a prefix is generally not
desired. But, the code plays a very important role in helping to
identify categories of errors and so it is very important as part of the
return response.
2019-09-19 10:06:47 -05:00
..
influxdb
metric
mock
Makefile
README.md
api_handler.go
api_handler_test.go
assets.go
auth_service.go
auth_test.go
authentication_middleware.go
authentication_test.go
bucket_service.go
bucket_test.go
check_service.go
check_test.go
chronograf_handler.go
client.go
dashboard_service.go
dashboard_test.go
debug.go
document_service.go
document_test.go
duration.go
duration_test.go
errors.go
errors_test.go
handler.go
handler_test.go
health.go
health_test.go
label_service.go
label_test.go
no_assets.go
notification_endpoint.go
notification_endpoint_test.go
notification_rule.go
notification_rule_test.go
onboarding.go
onboarding_test.go
org_service.go
org_test.go
paging.go
paging_test.go
platform_handler.go
query.go
query_handler.go
query_handler_test.go
query_test.go
ready.go
redoc.go
requests.go
requests_test.go
router.go
router_test.go
scraper_service.go
scraper_service_test.go
server.go
session_handler.go
session_test.go
source_proxy_service.go
source_service.go
source_service_test.go
status.go
swagger.go
swagger.yml
swagger_assets.go
swagger_noassets.go
swagger_test.go
task_service.go
task_service_test.go
task_test.go
telegraf.go
telegraf_test.go
tokens.go
tokens_test.go
transport.go
usage_service.go
user_resource_mapping_service.go
user_resource_mapping_test.go
user_service.go
user_test.go
variable_service.go
variable_test.go
write_handler.go
write_handler_test.go

README.md

HTTP Handler Style Guide

HTTP Handler

  • Each handler should implement http.Handler
    • This can be done by embedding a httprouter.Router (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
// 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
// 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.HandlerFuncs)

  • 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
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
// 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