mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat: trace httpapi.{Read,Write} (#4134)
This commit is contained in:
@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
|
||||
"github.com/coder/coder/coderd/tracing"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
@ -49,16 +50,19 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience error functions don't take contexts since their responses are
|
||||
// static, it doesn't make much sense to trace them.
|
||||
|
||||
// ResourceNotFound is intentionally vague. All 404 responses should be identical
|
||||
// to prevent leaking existence of resources.
|
||||
func ResourceNotFound(rw http.ResponseWriter) {
|
||||
Write(rw, http.StatusNotFound, codersdk.Response{
|
||||
Write(context.Background(), rw, http.StatusNotFound, codersdk.Response{
|
||||
Message: "Resource not found or you do not have access to this resource",
|
||||
})
|
||||
}
|
||||
|
||||
func Forbidden(rw http.ResponseWriter) {
|
||||
Write(rw, http.StatusForbidden, codersdk.Response{
|
||||
Write(context.Background(), rw, http.StatusForbidden, codersdk.Response{
|
||||
Message: "Forbidden.",
|
||||
})
|
||||
}
|
||||
@ -69,20 +73,29 @@ func InternalServerError(rw http.ResponseWriter, err error) {
|
||||
details = err.Error()
|
||||
}
|
||||
|
||||
Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Write(context.Background(), rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "An internal server error occurred.",
|
||||
Detail: details,
|
||||
})
|
||||
}
|
||||
|
||||
func RouteNotFound(rw http.ResponseWriter) {
|
||||
Write(rw, http.StatusNotFound, codersdk.Response{
|
||||
Write(context.Background(), rw, http.StatusNotFound, codersdk.Response{
|
||||
Message: "Route not found.",
|
||||
})
|
||||
}
|
||||
|
||||
// Write outputs a standardized format to an HTTP response body.
|
||||
func Write(rw http.ResponseWriter, status int, response interface{}) {
|
||||
// Write outputs a standardized format to an HTTP response body. ctx is used for
|
||||
// tracing and can be nil for tracing to be disabled. Tracing this function is
|
||||
// helpful because JSON marshaling can sometimes take a non-insignificant amount
|
||||
// of time, and could help us catch outliers. Additionally, we can enrich span
|
||||
// data a bit more since we have access to the actual interface{} we're
|
||||
// marshaling, such as the number of elements in an array, which could help us
|
||||
// spot routes that need to be paginated.
|
||||
func Write(ctx context.Context, rw http.ResponseWriter, status int, response interface{}) {
|
||||
_, span := tracing.StartSpan(ctx)
|
||||
defer span.End()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(true)
|
||||
@ -100,12 +113,17 @@ func Write(rw http.ResponseWriter, status int, response interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Read decodes JSON from the HTTP request into the value provided.
|
||||
// It uses go-validator to validate the incoming request body.
|
||||
func Read(rw http.ResponseWriter, r *http.Request, value interface{}) bool {
|
||||
// Read decodes JSON from the HTTP request into the value provided. It uses
|
||||
// go-validator to validate the incoming request body. ctx is used for tracing
|
||||
// and can be nil. Although tracing this function isn't likely too helpful, it
|
||||
// was done to be consistent with Write.
|
||||
func Read(ctx context.Context, rw http.ResponseWriter, r *http.Request, value interface{}) bool {
|
||||
ctx, span := tracing.StartSpan(ctx)
|
||||
defer span.End()
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(value)
|
||||
if err != nil {
|
||||
Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Request body must be valid JSON.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
@ -121,14 +139,14 @@ func Read(rw http.ResponseWriter, r *http.Request, value interface{}) bool {
|
||||
Detail: fmt.Sprintf("Validation failed for tag %q with value: \"%v\"", validationError.Tag(), validationError.Value()),
|
||||
})
|
||||
}
|
||||
Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Validation failed.",
|
||||
Validations: apiErrors,
|
||||
})
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error validating request body payload.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
Reference in New Issue
Block a user