mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
232 lines
6.8 KiB
Go
232 lines
6.8 KiB
Go
|
// Package api implements an HTTP-based API and server for CFSSL.
|
||
|
package api
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"io/ioutil"
|
||
|
"net/http"
|
||
|
|
||
|
"github.com/cloudflare/cfssl/errors"
|
||
|
"github.com/cloudflare/cfssl/log"
|
||
|
)
|
||
|
|
||
|
// Handler is an interface providing a generic mechanism for handling HTTP requests.
|
||
|
type Handler interface {
|
||
|
Handle(w http.ResponseWriter, r *http.Request) error
|
||
|
}
|
||
|
|
||
|
// HTTPHandler is a wrapper that encapsulates Handler interface as http.Handler.
|
||
|
// HTTPHandler also enforces that the Handler only responds to requests with registered HTTP methods.
|
||
|
type HTTPHandler struct {
|
||
|
Handler // CFSSL handler
|
||
|
Methods []string // The associated HTTP methods
|
||
|
}
|
||
|
|
||
|
// HandlerFunc is similar to the http.HandlerFunc type; it serves as
|
||
|
// an adapter allowing the use of ordinary functions as Handlers. If
|
||
|
// f is a function with the appropriate signature, HandlerFunc(f) is a
|
||
|
// Handler object that calls f.
|
||
|
type HandlerFunc func(http.ResponseWriter, *http.Request) error
|
||
|
|
||
|
// Handle calls f(w, r)
|
||
|
func (f HandlerFunc) Handle(w http.ResponseWriter, r *http.Request) error {
|
||
|
w.Header().Set("Content-Type", "application/json")
|
||
|
return f(w, r)
|
||
|
}
|
||
|
|
||
|
// handleError is the centralised error handling and reporting.
|
||
|
func handleError(w http.ResponseWriter, err error) (code int) {
|
||
|
if err == nil {
|
||
|
return http.StatusOK
|
||
|
}
|
||
|
msg := err.Error()
|
||
|
httpCode := http.StatusInternalServerError
|
||
|
|
||
|
// If it is recognized as HttpError emitted from cfssl,
|
||
|
// we rewrite the status code accordingly. If it is a
|
||
|
// cfssl error, set the http status to StatusBadRequest
|
||
|
switch err := err.(type) {
|
||
|
case *errors.HTTPError:
|
||
|
httpCode = err.StatusCode
|
||
|
code = err.StatusCode
|
||
|
case *errors.Error:
|
||
|
httpCode = http.StatusBadRequest
|
||
|
code = err.ErrorCode
|
||
|
msg = err.Message
|
||
|
}
|
||
|
|
||
|
response := NewErrorResponse(msg, code)
|
||
|
jsonMessage, err := json.Marshal(response)
|
||
|
if err != nil {
|
||
|
log.Errorf("Failed to marshal JSON: %v", err)
|
||
|
} else {
|
||
|
msg = string(jsonMessage)
|
||
|
}
|
||
|
http.Error(w, msg, httpCode)
|
||
|
return code
|
||
|
}
|
||
|
|
||
|
// ServeHTTP encapsulates the call to underlying Handler to handle the request
|
||
|
// and return the response with proper HTTP status code
|
||
|
func (h HTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||
|
var err error
|
||
|
var match bool
|
||
|
// Throw 405 when requested with an unsupported verb.
|
||
|
for _, m := range h.Methods {
|
||
|
if m == r.Method {
|
||
|
match = true
|
||
|
}
|
||
|
}
|
||
|
if match {
|
||
|
err = h.Handle(w, r)
|
||
|
} else {
|
||
|
err = errors.NewMethodNotAllowed(r.Method)
|
||
|
}
|
||
|
status := handleError(w, err)
|
||
|
log.Infof("%s - \"%s %s\" %d", r.RemoteAddr, r.Method, r.URL, status)
|
||
|
}
|
||
|
|
||
|
// readRequestBlob takes a JSON-blob-encoded response body in the form
|
||
|
// map[string]string and returns it, the list of keywords presented,
|
||
|
// and any error that occurred.
|
||
|
func readRequestBlob(r *http.Request) (map[string]string, error) {
|
||
|
var blob map[string]string
|
||
|
|
||
|
body, err := ioutil.ReadAll(r.Body)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
r.Body.Close()
|
||
|
|
||
|
err = json.Unmarshal(body, &blob)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return blob, nil
|
||
|
}
|
||
|
|
||
|
// ProcessRequestOneOf reads a JSON blob for the request and makes
|
||
|
// sure it contains one of a set of keywords. For example, a request
|
||
|
// might have the ('foo' && 'bar') keys, OR it might have the 'baz'
|
||
|
// key. In either case, we want to accept the request; however, if
|
||
|
// none of these sets shows up, the request is a bad request, and it
|
||
|
// should be returned.
|
||
|
func ProcessRequestOneOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) {
|
||
|
blob, err := readRequestBlob(r)
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
var matched []string
|
||
|
for _, set := range keywordSets {
|
||
|
if matchKeywords(blob, set) {
|
||
|
if matched != nil {
|
||
|
return nil, nil, errors.NewBadRequestString("mismatched parameters")
|
||
|
}
|
||
|
matched = set
|
||
|
}
|
||
|
}
|
||
|
if matched == nil {
|
||
|
return nil, nil, errors.NewBadRequestString("no valid parameter sets found")
|
||
|
}
|
||
|
return blob, matched, nil
|
||
|
}
|
||
|
|
||
|
// ProcessRequestFirstMatchOf reads a JSON blob for the request and returns
|
||
|
// the first match of a set of keywords. For example, a request
|
||
|
// might have one of the following combinations: (foo=1, bar=2), (foo=1), and (bar=2)
|
||
|
// By giving a specific ordering of those combinations, we could decide how to accept
|
||
|
// the request.
|
||
|
func ProcessRequestFirstMatchOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) {
|
||
|
blob, err := readRequestBlob(r)
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
for _, set := range keywordSets {
|
||
|
if matchKeywords(blob, set) {
|
||
|
return blob, set, nil
|
||
|
}
|
||
|
}
|
||
|
return nil, nil, errors.NewBadRequestString("no valid parameter sets found")
|
||
|
}
|
||
|
|
||
|
func matchKeywords(blob map[string]string, keywords []string) bool {
|
||
|
for _, keyword := range keywords {
|
||
|
if _, ok := blob[keyword]; !ok {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// ResponseMessage implements the standard for response errors and
|
||
|
// messages. A message has a code and a string message.
|
||
|
type ResponseMessage struct {
|
||
|
Code int `json:"code"`
|
||
|
Message string `json:"message"`
|
||
|
}
|
||
|
|
||
|
// Response implements the CloudFlare standard for API
|
||
|
// responses.
|
||
|
type Response struct {
|
||
|
Success bool `json:"success"`
|
||
|
Result interface{} `json:"result"`
|
||
|
Errors []ResponseMessage `json:"errors"`
|
||
|
Messages []ResponseMessage `json:"messages"`
|
||
|
}
|
||
|
|
||
|
// NewSuccessResponse is a shortcut for creating new successul API
|
||
|
// responses.
|
||
|
func NewSuccessResponse(result interface{}) Response {
|
||
|
return Response{
|
||
|
Success: true,
|
||
|
Result: result,
|
||
|
Errors: []ResponseMessage{},
|
||
|
Messages: []ResponseMessage{},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NewSuccessResponseWithMessage is a shortcut for creating new successul API
|
||
|
// responses that includes a message.
|
||
|
func NewSuccessResponseWithMessage(result interface{}, message string, code int) Response {
|
||
|
return Response{
|
||
|
Success: true,
|
||
|
Result: result,
|
||
|
Errors: []ResponseMessage{},
|
||
|
Messages: []ResponseMessage{{code, message}},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NewErrorResponse is a shortcut for creating an error response for a
|
||
|
// single error.
|
||
|
func NewErrorResponse(message string, code int) Response {
|
||
|
return Response{
|
||
|
Success: false,
|
||
|
Result: nil,
|
||
|
Errors: []ResponseMessage{{code, message}},
|
||
|
Messages: []ResponseMessage{},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SendResponse builds a response from the result, sets the JSON
|
||
|
// header, and writes to the http.ResponseWriter.
|
||
|
func SendResponse(w http.ResponseWriter, result interface{}) error {
|
||
|
response := NewSuccessResponse(result)
|
||
|
w.Header().Set("Content-Type", "application/json")
|
||
|
enc := json.NewEncoder(w)
|
||
|
err := enc.Encode(response)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// SendResponseWithMessage builds a response from the result and the
|
||
|
// provided message, sets the JSON header, and writes to the
|
||
|
// http.ResponseWriter.
|
||
|
func SendResponseWithMessage(w http.ResponseWriter, result interface{}, message string, code int) error {
|
||
|
response := NewSuccessResponseWithMessage(result, message, code)
|
||
|
w.Header().Set("Content-Type", "application/json")
|
||
|
enc := json.NewEncoder(w)
|
||
|
err := enc.Encode(response)
|
||
|
return err
|
||
|
}
|