forked from moby/moby
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement basic interfaces to write custom routers that can be plugged to the server. Remove server coupling with the daemon. Signed-off-by: David Calavera <david.calavera@gmail.com>
- Loading branch information
Showing
32 changed files
with
809 additions
and
591 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package httputils | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"path/filepath" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
// BoolValue transforms a form value in different formats into a boolean type. | ||
func BoolValue(r *http.Request, k string) bool { | ||
s := strings.ToLower(strings.TrimSpace(r.FormValue(k))) | ||
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") | ||
} | ||
|
||
// BoolValueOrDefault returns the default bool passed if the query param is | ||
// missing, otherwise it's just a proxy to boolValue above | ||
func BoolValueOrDefault(r *http.Request, k string, d bool) bool { | ||
if _, ok := r.Form[k]; !ok { | ||
return d | ||
} | ||
return BoolValue(r, k) | ||
} | ||
|
||
// Int64ValueOrZero parses a form value into a int64 type. | ||
// It returns 0 if the parsing fails. | ||
func Int64ValueOrZero(r *http.Request, k string) int64 { | ||
val, err := strconv.ParseInt(r.FormValue(k), 10, 64) | ||
if err != nil { | ||
return 0 | ||
} | ||
return val | ||
} | ||
|
||
// ArchiveOptions stores archive information for different operations. | ||
type ArchiveOptions struct { | ||
Name string | ||
Path string | ||
} | ||
|
||
// ArchiveFormValues parses form values and turns them into ArchiveOptions. | ||
// It fails if the archive name and path are not in the request. | ||
func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) { | ||
if vars == nil { | ||
return ArchiveOptions{}, fmt.Errorf("Missing parameter") | ||
} | ||
if err := ParseForm(r); err != nil { | ||
return ArchiveOptions{}, err | ||
} | ||
|
||
name := vars["name"] | ||
path := filepath.FromSlash(r.Form.Get("path")) | ||
|
||
switch { | ||
case name == "": | ||
return ArchiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty") | ||
case path == "": | ||
return ArchiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty") | ||
} | ||
|
||
return ArchiveOptions{name, path}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
package httputils | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"strings" | ||
|
||
"golang.org/x/net/context" | ||
|
||
"github.com/Sirupsen/logrus" | ||
"github.com/docker/distribution/registry/api/errcode" | ||
"github.com/docker/docker/api" | ||
"github.com/docker/docker/pkg/version" | ||
"github.com/docker/docker/utils" | ||
) | ||
|
||
// APIVersionKey is the client's requested API version. | ||
const APIVersionKey = "api-version" | ||
|
||
// APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints. | ||
// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion). | ||
type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error | ||
|
||
// HijackConnection interrupts the http response writer to get the | ||
// underlying connection and operate with it. | ||
func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { | ||
conn, _, err := w.(http.Hijacker).Hijack() | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
// Flush the options to make sure the client sets the raw mode | ||
conn.Write([]byte{}) | ||
return conn, conn, nil | ||
} | ||
|
||
// CloseStreams ensures that a list for http streams are properly closed. | ||
func CloseStreams(streams ...interface{}) { | ||
for _, stream := range streams { | ||
if tcpc, ok := stream.(interface { | ||
CloseWrite() error | ||
}); ok { | ||
tcpc.CloseWrite() | ||
} else if closer, ok := stream.(io.Closer); ok { | ||
closer.Close() | ||
} | ||
} | ||
} | ||
|
||
// CheckForJSON makes sure that the request's Content-Type is application/json. | ||
func CheckForJSON(r *http.Request) error { | ||
ct := r.Header.Get("Content-Type") | ||
|
||
// No Content-Type header is ok as long as there's no Body | ||
if ct == "" { | ||
if r.Body == nil || r.ContentLength == 0 { | ||
return nil | ||
} | ||
} | ||
|
||
// Otherwise it better be json | ||
if api.MatchesContentType(ct, "application/json") { | ||
return nil | ||
} | ||
return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct) | ||
} | ||
|
||
// ParseForm ensures the request form is parsed even with invalid content types. | ||
// If we don't do this, POST method without Content-type (even with empty body) will fail. | ||
func ParseForm(r *http.Request) error { | ||
if r == nil { | ||
return nil | ||
} | ||
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// ParseMultipartForm ensure the request form is parsed, even with invalid content types. | ||
func ParseMultipartForm(r *http.Request) error { | ||
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// WriteError decodes a specific docker error and sends it in the response. | ||
func WriteError(w http.ResponseWriter, err error) { | ||
if err == nil || w == nil { | ||
logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling") | ||
return | ||
} | ||
|
||
statusCode := http.StatusInternalServerError | ||
errMsg := err.Error() | ||
|
||
// Based on the type of error we get we need to process things | ||
// slightly differently to extract the error message. | ||
// In the 'errcode.*' cases there are two different type of | ||
// error that could be returned. errocode.ErrorCode is the base | ||
// type of error object - it is just an 'int' that can then be | ||
// used as the look-up key to find the message. errorcode.Error | ||
// extends errorcode.Error by adding error-instance specific | ||
// data, like 'details' or variable strings to be inserted into | ||
// the message. | ||
// | ||
// Ideally, we should just be able to call err.Error() for all | ||
// cases but the errcode package doesn't support that yet. | ||
// | ||
// Additionally, in both errcode cases, there might be an http | ||
// status code associated with it, and if so use it. | ||
switch err.(type) { | ||
case errcode.ErrorCode: | ||
daError, _ := err.(errcode.ErrorCode) | ||
statusCode = daError.Descriptor().HTTPStatusCode | ||
errMsg = daError.Message() | ||
|
||
case errcode.Error: | ||
// For reference, if you're looking for a particular error | ||
// then you can do something like : | ||
// import ( derr "github.com/docker/docker/errors" ) | ||
// if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... } | ||
|
||
daError, _ := err.(errcode.Error) | ||
statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode | ||
errMsg = daError.Message | ||
|
||
default: | ||
// This part of will be removed once we've | ||
// converted everything over to use the errcode package | ||
|
||
// FIXME: this is brittle and should not be necessary. | ||
// If we need to differentiate between different possible error types, | ||
// we should create appropriate error types with clearly defined meaning | ||
errStr := strings.ToLower(err.Error()) | ||
for keyword, status := range map[string]int{ | ||
"not found": http.StatusNotFound, | ||
"no such": http.StatusNotFound, | ||
"bad parameter": http.StatusBadRequest, | ||
"conflict": http.StatusConflict, | ||
"impossible": http.StatusNotAcceptable, | ||
"wrong login/password": http.StatusUnauthorized, | ||
"hasn't been activated": http.StatusForbidden, | ||
} { | ||
if strings.Contains(errStr, keyword) { | ||
statusCode = status | ||
break | ||
} | ||
} | ||
} | ||
|
||
if statusCode == 0 { | ||
statusCode = http.StatusInternalServerError | ||
} | ||
|
||
logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": utils.GetErrorMessage(err)}).Error("HTTP Error") | ||
http.Error(w, errMsg, statusCode) | ||
} | ||
|
||
// WriteJSON writes the value v to the http response stream as json with standard json encoding. | ||
func WriteJSON(w http.ResponseWriter, code int, v interface{}) error { | ||
w.Header().Set("Content-Type", "application/json") | ||
w.WriteHeader(code) | ||
return json.NewEncoder(w).Encode(v) | ||
} | ||
|
||
// VersionFromContext returns an API version from the context using APIVersionKey. | ||
// It panics if the context value does not have version.Version type. | ||
func VersionFromContext(ctx context.Context) (ver version.Version) { | ||
if ctx == nil { | ||
return | ||
} | ||
val := ctx.Value(APIVersionKey) | ||
if val == nil { | ||
return | ||
} | ||
return val.(version.Version) | ||
} |
Oops, something went wrong.