git-lfs/httputil/response.go
David Pursehouse 68c0e18d05 Add HTTP 507 (Insufficient Storage) to the list of optional statuses (#1473)
* Add HTTP 507 (Insufficient Storage) to the list of optional statuses

Mention that the server implementation may optionally return status
code 507.

This does not add any specific handling of 507 in either the client or
the test server implementation. If a server returns this status code,
it will be handled by the client as a generic "Server Error".

See #1327 and #1438.

Change-Id: I2de691bb57b3ef44288b91b79226695f768ec44e
Signed-off-by: David Pursehouse <dpursehouse@collab.net>

* Add default error messages for 427, 507 and 509 responses

Change-Id: Ia42731e35f2aa7b918de3d0412205700df968d95
Signed-off-by: David Pursehouse <dpursehouse@collab.net>
2016-08-22 10:37:04 -06:00

137 lines
3.6 KiB
Go

package httputil
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"regexp"
"github.com/github/git-lfs/auth"
"github.com/github/git-lfs/config"
"github.com/github/git-lfs/errors"
)
var (
lfsMediaTypeRE = regexp.MustCompile(`\Aapplication/vnd\.git\-lfs\+json(;|\z)`)
jsonMediaTypeRE = regexp.MustCompile(`\Aapplication/json(;|\z)`)
hiddenHeaders = map[string]bool{
"Authorization": true,
}
defaultErrors = map[int]string{
400: "Client error: %s",
401: "Authorization error: %s\nCheck that you have proper access to the repository",
403: "Authorization error: %s\nCheck that you have proper access to the repository",
404: "Repository or object not found: %s\nCheck that it exists and that you have proper access to it",
429: "Rate limit exceeded: %s",
500: "Server error: %s",
507: "Insufficient server storage: %s",
509: "Bandwidth limit exceeded: %s",
}
)
// DecodeResponse attempts to decode the contents of the response as a JSON object
func DecodeResponse(res *http.Response, obj interface{}) error {
ctype := res.Header.Get("Content-Type")
if !(lfsMediaTypeRE.MatchString(ctype) || jsonMediaTypeRE.MatchString(ctype)) {
return nil
}
err := json.NewDecoder(res.Body).Decode(obj)
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
if err != nil {
return errors.Wrapf(err, "Unable to parse HTTP response for %s", TraceHttpReq(res.Request))
}
return nil
}
// GetDefaultError returns the default text for standard error codes (blank if none)
func GetDefaultError(code int) string {
if s, ok := defaultErrors[code]; ok {
return s
}
return ""
}
// Check the response from a HTTP request for problems
func handleResponse(cfg *config.Configuration, res *http.Response, creds auth.Creds) error {
auth.SaveCredentials(cfg, creds, res)
if res.StatusCode < 400 {
return nil
}
defer func() {
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
}()
cliErr := &ClientError{}
err := DecodeResponse(res, cliErr)
if err == nil {
if len(cliErr.Message) == 0 {
err = defaultError(res)
} else {
err = errors.Wrap(cliErr, "http")
}
}
if res.StatusCode == 401 {
if err == nil {
err = errors.New("api: received status 401")
}
return errors.NewAuthError(err)
}
if res.StatusCode > 499 && res.StatusCode != 501 && res.StatusCode != 507 && res.StatusCode != 509 {
if err == nil {
err = errors.Errorf("api: received status %d", res.StatusCode)
}
return errors.NewFatalError(err)
}
return err
}
func defaultError(res *http.Response) error {
var msgFmt string
if f, ok := defaultErrors[res.StatusCode]; ok {
msgFmt = f
} else if res.StatusCode < 500 {
msgFmt = defaultErrors[400] + fmt.Sprintf(" from HTTP %d", res.StatusCode)
} else {
msgFmt = defaultErrors[500] + fmt.Sprintf(" from HTTP %d", res.StatusCode)
}
return errors.Errorf(msgFmt, res.Request.URL)
}
func SetErrorResponseContext(cfg *config.Configuration, err error, res *http.Response) {
errors.SetContext(err, "Status", res.Status)
setErrorHeaderContext(err, "Request", res.Header)
setErrorRequestContext(cfg, err, res.Request)
}
func setErrorRequestContext(cfg *config.Configuration, err error, req *http.Request) {
errors.SetContext(err, "Endpoint", cfg.Endpoint(auth.GetOperationForRequest(req)).Url)
errors.SetContext(err, "URL", TraceHttpReq(req))
setErrorHeaderContext(err, "Response", req.Header)
}
func setErrorHeaderContext(err error, prefix string, head http.Header) {
for key, _ := range head {
contextKey := fmt.Sprintf("%s:%s", prefix, key)
if _, skip := hiddenHeaders[key]; skip {
errors.SetContext(err, contextKey, "--")
} else {
errors.SetContext(err, contextKey, head.Get(key))
}
}
}