00634fb648
* api/render: initial implementation of the package * acme/api: refactored to support api/render * authority/admin: refactored to support api/render * ca: refactored to support api/render * api: refactored to support api/render * api/render: implemented Error * api: refactored to support api/render.Error * acme/api: refactored to support api/render.Error * authority/admin: refactored to support api/render.Error * ca: refactored to support api/render.Error * ca: fixed broken tests * api/render, api/log: moved error logging to this package * acme: refactored Error so that it implements render.RenderableError * authority/admin: refactored Error so that it implements render.RenderableError * api/render: implemented RenderableError * api/render: added test coverage for Error * api/render: implemented statusCodeFromError * api: refactored RootsPEM to work with render.Error * acme, authority/admin: fixed pointer receiver name for consistency * api/render, errs: moved StatusCoder & StackTracer to the render package
122 lines
2.8 KiB
Go
122 lines
2.8 KiB
Go
// Package render implements functionality related to response rendering.
|
|
package render
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"github.com/smallstep/certificates/api/log"
|
|
)
|
|
|
|
// JSON is shorthand for JSONStatus(w, v, http.StatusOK).
|
|
func JSON(w http.ResponseWriter, v interface{}) {
|
|
JSONStatus(w, v, http.StatusOK)
|
|
}
|
|
|
|
// JSONStatus marshals v into w. It additionally sets the status code of
|
|
// w to the given one.
|
|
//
|
|
// JSONStatus sets the Content-Type of w to application/json unless one is
|
|
// specified.
|
|
func JSONStatus(w http.ResponseWriter, v interface{}, status int) {
|
|
var b bytes.Buffer
|
|
if err := json.NewEncoder(&b).Encode(v); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
setContentTypeUnlessPresent(w, "application/json")
|
|
w.WriteHeader(status)
|
|
_, _ = b.WriteTo(w)
|
|
|
|
log.EnabledResponse(w, v)
|
|
}
|
|
|
|
// ProtoJSON is shorthand for ProtoJSONStatus(w, m, http.StatusOK).
|
|
func ProtoJSON(w http.ResponseWriter, m proto.Message) {
|
|
ProtoJSONStatus(w, m, http.StatusOK)
|
|
}
|
|
|
|
// ProtoJSONStatus writes the given value into the http.ResponseWriter and the
|
|
// given status is written as the status code of the response.
|
|
func ProtoJSONStatus(w http.ResponseWriter, m proto.Message, status int) {
|
|
b, err := protojson.Marshal(m)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
setContentTypeUnlessPresent(w, "application/json")
|
|
w.WriteHeader(status)
|
|
_, _ = w.Write(b)
|
|
}
|
|
|
|
func setContentTypeUnlessPresent(w http.ResponseWriter, contentType string) {
|
|
const header = "Content-Type"
|
|
|
|
h := w.Header()
|
|
if _, ok := h[header]; !ok {
|
|
h.Set(header, contentType)
|
|
}
|
|
}
|
|
|
|
// RenderableError is the set of errors that implement the basic Render method.
|
|
//
|
|
// Errors that implement this interface will use their own Render method when
|
|
// being rendered into responses.
|
|
type RenderableError interface {
|
|
error
|
|
|
|
Render(http.ResponseWriter)
|
|
}
|
|
|
|
// Error marshals the JSON representation of err to w. In case err implements
|
|
// RenderableError its own Render method will be called instead.
|
|
func Error(w http.ResponseWriter, err error) {
|
|
log.Error(w, err)
|
|
|
|
if e, ok := err.(RenderableError); ok {
|
|
e.Render(w)
|
|
|
|
return
|
|
}
|
|
|
|
JSONStatus(w, err, statusCodeFromError(err))
|
|
}
|
|
|
|
// StatusCodedError is the set of errors that implement the basic StatusCode
|
|
// function.
|
|
//
|
|
// Errors that implement this interface will use the code reported by StatusCode
|
|
// as the HTTP response code when being rendered by this package.
|
|
type StatusCodedError interface {
|
|
error
|
|
|
|
StatusCode() int
|
|
}
|
|
|
|
func statusCodeFromError(err error) (code int) {
|
|
code = http.StatusInternalServerError
|
|
|
|
type causer interface {
|
|
Cause() error
|
|
}
|
|
|
|
for err != nil {
|
|
if sc, ok := err.(StatusCodedError); ok {
|
|
code = sc.StatusCode()
|
|
|
|
break
|
|
}
|
|
|
|
cause, ok := err.(causer)
|
|
if !ok {
|
|
break
|
|
}
|
|
err = cause.Cause()
|
|
}
|
|
|
|
return
|
|
}
|