1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
package router // import "gopkg.in/juju/charmstore.v5-unstable/internal/router"
14
"github.com/juju/httprequest"
15
"github.com/juju/loggo"
17
"gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
18
"gopkg.in/macaroon-bakery.v1/httpbakery"
21
var logger = loggo.GetLogger("charmstore.internal.router")
23
// WriteError can be used to write an error response.
24
var WriteError = errorToResp.WriteError
26
// JSONHandler represents a handler that returns a JSON value.
27
// The provided header can be used to set response headers.
28
type JSONHandler func(http.Header, *http.Request) (interface{}, error)
30
// ErrorHandler represents a handler that can return an error.
31
type ErrorHandler func(http.ResponseWriter, *http.Request) error
33
// HandleJSON converts from a JSONHandler function to an http.Handler.
34
func HandleJSON(h JSONHandler) http.Handler {
35
// We can't use errorToResp.HandleJSON directly because
36
// we still use old-style handlers in charmstore, so we
37
// insert shim functions to do the conversion.
38
handleJSON := errorToResp.HandleJSON(
39
func(p httprequest.Params) (interface{}, error) {
40
return h(p.Response.Header(), p.Request)
43
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
44
handleJSON(w, req, nil)
48
// HandleJSON converts from a ErrorHandler function to an http.Handler.
49
func HandleErrors(h ErrorHandler) http.Handler {
50
// We can't use errorToResp.HandleErrors directly because
51
// we still use old-style handlers in charmstore, so we
52
// insert shim functions to do the conversion.
53
handleErrors := errorToResp.HandleErrors(
54
func(p httprequest.Params) error {
55
return h(p.Response, p.Request)
58
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
59
handleErrors(w, req, nil)
63
var errorToResp httprequest.ErrorMapper = func(err error) (int, interface{}) {
64
status, body := errorToResp1(err)
65
logger.Infof("error response %d; %s", status, errgo.Details(err))
69
func errorToResp1(err error) (int, interface{}) {
70
// Allow bakery errors to be returned as the bakery would
71
// like them, so that httpbakery.Client.Do will work.
72
if err, ok := errgo.Cause(err).(*httpbakery.Error); ok {
73
return httpbakery.ErrorToResponse(err)
75
errorBody := errorResponseBody(err)
76
status := http.StatusInternalServerError
77
switch errorBody.Code {
78
case params.ErrNotFound, params.ErrMetadataNotFound:
79
status = http.StatusNotFound
80
case params.ErrBadRequest, params.ErrInvalidEntity:
81
status = http.StatusBadRequest
82
case params.ErrForbidden, params.ErrEntityIdNotAllowed:
83
status = http.StatusForbidden
84
case params.ErrUnauthorized:
85
status = http.StatusUnauthorized
86
case params.ErrMethodNotAllowed:
87
// TODO(rog) from RFC 2616, section 4.7: An Allow header
88
// field MUST be present in a 405 (Method Not Allowed)
90
// Perhaps we should not ever return StatusMethodNotAllowed.
91
status = http.StatusMethodNotAllowed
92
case params.ErrServiceUnavailable:
93
status = http.StatusServiceUnavailable
95
return status, errorBody
98
// errorResponse returns an appropriate error
99
// response for the provided error.
100
func errorResponseBody(err error) *params.Error {
102
errResp := ¶ms.Error{
103
Message: err.Error(),
105
cause := errgo.Cause(err)
106
if coder, ok := cause.(errorCoder); ok {
107
errResp.Code = coder.ErrorCode()
109
if infoer, ok := cause.(errorInfoer); ok {
110
errResp.Info = infoer.ErrorInfo()
115
type errorInfoer interface {
116
ErrorInfo() map[string]*params.Error
119
type errorCoder interface {
120
ErrorCode() params.ErrorCode
123
// multiError holds multiple errors.
124
type multiError map[string]error
126
func (err multiError) Error() string {
127
return fmt.Sprintf("multiple (%d) errors", len(err))
130
func (err multiError) ErrorCode() params.ErrorCode {
131
return params.ErrMultipleErrors
134
func (err multiError) ErrorInfo() map[string]*params.Error {
135
m := make(map[string]*params.Error)
136
for key, err := range err {
137
m[key] = errorResponseBody(err)
142
// NotFoundHandler is like http.NotFoundHandler except it
143
// returns a JSON error response.
144
func NotFoundHandler() http.Handler {
145
return HandleErrors(func(w http.ResponseWriter, req *http.Request) error {
146
return errgo.WithCausef(nil, params.ErrNotFound, params.ErrNotFound.Error())
150
func NewServeMux() *ServeMux {
151
return &ServeMux{http.NewServeMux()}
154
// ServeMux is like http.ServeMux but returns
155
// JSON errors when pages are not found.
156
type ServeMux struct {
160
func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, req *http.Request) {
161
if req.RequestURI == "*" {
162
mux.ServeMux.ServeHTTP(w, req)
165
h, pattern := mux.Handler(req)
167
WriteError(w, errgo.WithCausef(nil, params.ErrNotFound, "no handler for %q", req.URL.Path))
173
// RelativeURLPath returns a relative URL path that is lexically
174
// equivalent to targpath when interpreted by url.URL.ResolveReference.
175
// On success, the returned path will always be non-empty and relative
176
// to basePath, even if basePath and targPath share no elements.
178
// An error is returned if basePath or targPath are not absolute paths.
179
func RelativeURLPath(basePath, targPath string) (string, error) {
180
if !strings.HasPrefix(basePath, "/") {
181
return "", errgo.Newf("non-absolute base URL")
183
if !strings.HasPrefix(targPath, "/") {
184
return "", errgo.Newf("non-absolute target URL")
186
baseParts := strings.Split(basePath, "/")
187
targParts := strings.Split(targPath, "/")
189
// For the purposes of dotdot, the last element of
190
// the paths are irrelevant. We save the last part
191
// of the target path for later.
192
lastElem := targParts[len(targParts)-1]
193
baseParts = baseParts[0 : len(baseParts)-1]
194
targParts = targParts[0 : len(targParts)-1]
196
// Find the common prefix between the two paths:
198
for ; i < len(baseParts); i++ {
199
if i >= len(targParts) || baseParts[i] != targParts[i] {
203
dotdotCount := len(baseParts) - i
204
targOnly := targParts[i:]
205
result := make([]string, 0, dotdotCount+len(targOnly)+1)
206
for i := 0; i < dotdotCount; i++ {
207
result = append(result, "..")
209
result = append(result, targOnly...)
210
result = append(result, lastElem)
211
final := strings.Join(result, "/")
213
// If the final result is empty, the last element must
214
// have been empty, so the target was slash terminated
215
// and there were no previous elements, so "."
222
// TODO(mhilton) This is not an ideal place for UnmarshalJSONResponse,
223
// maybe it should be in httprequest somewhere?
225
// UnmarshalJSONResponse unmarshals resp.Body into v. If errorF is not
226
// nil and resp.StatusCode indicates an error has occured (>= 400) then
227
// the result of calling errorF with resp is returned.
228
func UnmarshalJSONResponse(resp *http.Response, v interface{}, errorF func(*http.Response) error) error {
229
if errorF != nil && resp.StatusCode >= http.StatusBadRequest {
230
return errgo.Mask(errorF(resp), errgo.Any)
232
mt, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
234
return errgo.Notef(err, "cannot parse content type")
236
if mt != "application/json" {
237
return errgo.Newf("unexpected content type %q", mt)
239
body, err := ioutil.ReadAll(resp.Body)
241
return errgo.Notef(err, "cannot read response body")
243
if err := json.Unmarshal(body, v); err != nil {
244
return errgo.Notef(err, "cannot unmarshal response")