perkeep/pkg/httputil/httputil.go

338 lines
10 KiB
Go

/*
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package httputil contains a bunch of HTTP utility code, some generic,
// and some Camlistore-specific.
package httputil
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"os"
"path"
"strconv"
"strings"
"camlistore.org/pkg/blob"
)
// IsGet reports whether r.Method is a GET or HEAD request.
func IsGet(r *http.Request) bool {
return r.Method == "GET" || r.Method == "HEAD"
}
func ErrorRouting(rw http.ResponseWriter, req *http.Request) {
http.Error(rw, "Handlers wired up wrong; this path shouldn't be hit", 500)
log.Printf("Internal routing error on %q", req.URL.Path)
}
func BadRequestError(rw http.ResponseWriter, errorMessage string, args ...interface{}) {
rw.WriteHeader(http.StatusBadRequest)
log.Printf("Bad request: %s", fmt.Sprintf(errorMessage, args...))
fmt.Fprintf(rw, "<h1>Bad Request</h1>")
}
func ForbiddenError(rw http.ResponseWriter, errorMessage string, args ...interface{}) {
rw.WriteHeader(http.StatusForbidden)
log.Printf("Forbidden: %s", fmt.Sprintf(errorMessage, args...))
fmt.Fprintf(rw, "<h1>Forbidden</h1>")
}
func RequestEntityTooLargeError(rw http.ResponseWriter) {
rw.WriteHeader(http.StatusRequestEntityTooLarge)
fmt.Fprintf(rw, "<h1>Request entity is too large</h1>")
}
func ServeError(rw http.ResponseWriter, req *http.Request, err error) {
rw.WriteHeader(http.StatusInternalServerError)
if IsLocalhost(req) || os.Getenv("CAMLI_DEV_CAMLI_ROOT") != "" {
fmt.Fprintf(rw, "Server error: %s\n", err)
return
}
fmt.Fprintf(rw, "An internal error occured, sorry.")
}
func ReturnJSON(rw http.ResponseWriter, data interface{}) {
ReturnJSONCode(rw, 200, data)
}
func ReturnJSONCode(rw http.ResponseWriter, code int, data interface{}) {
js, err := json.MarshalIndent(data, "", " ")
if err != nil {
BadRequestError(rw, fmt.Sprintf("JSON serialization error: %v", err))
return
}
rw.Header().Set("Content-Type", "text/javascript")
rw.Header().Set("Content-Length", strconv.Itoa(len(js)+1))
rw.WriteHeader(code)
rw.Write(js)
rw.Write([]byte("\n"))
}
// PrefixHandler wraps another Handler and verifies that all requests'
// Path begin with Prefix. If they don't, a 500 error is returned.
// If they do, the headers PathBaseHeader and PathSuffixHeader are set
// on the request before proxying to Handler.
// PathBaseHeader is just the value of Prefix.
// PathSuffixHeader is the part of the path that follows Prefix.
type PrefixHandler struct {
Prefix string
Handler http.Handler
}
const (
PathBaseHeader = "X-Prefixhandler-Pathbase"
PathSuffixHeader = "X-Prefixhandler-Pathsuffix"
)
func (p *PrefixHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if !strings.HasPrefix(req.URL.Path, p.Prefix) {
http.Error(rw, "Inconfigured PrefixHandler", 500)
return
}
req.Header.Set(PathBaseHeader, p.Prefix)
req.Header.Set(PathSuffixHeader, strings.TrimPrefix(req.URL.Path, p.Prefix))
p.Handler.ServeHTTP(rw, req)
}
// PathBase returns a Request's base path, if it went via a PrefixHandler.
func PathBase(req *http.Request) string { return req.Header.Get(PathBaseHeader) }
// PathSuffix returns a Request's suffix path, if it went via a PrefixHandler.
func PathSuffix(req *http.Request) string { return req.Header.Get(PathSuffixHeader) }
// BaseURL returns the base URL (scheme + host and optional port +
// blobserver prefix) that should be used for requests (and responses)
// subsequent to req. The returned URL does not end in a trailing slash.
// The scheme and host:port are taken from urlStr if present,
// or derived from req otherwise.
// The prefix part comes from urlStr.
func BaseURL(urlStr string, req *http.Request) (string, error) {
var baseURL string
defaultURL, err := url.Parse(urlStr)
if err != nil {
return baseURL, err
}
prefix := path.Clean(defaultURL.Path)
scheme := "http"
if req.TLS != nil {
scheme = "https"
}
host := req.Host
if defaultURL.Host != "" {
host = defaultURL.Host
}
if defaultURL.Scheme != "" {
scheme = defaultURL.Scheme
}
baseURL = scheme + "://" + host + prefix
return baseURL, nil
}
// RequestTargetPort returns the port targetted by the client
// in req. If not present, it returns 80, or 443 if TLS is used.
func RequestTargetPort(req *http.Request) int {
_, portStr, err := net.SplitHostPort(req.Host)
if err == nil && portStr != "" {
port, err := strconv.ParseInt(portStr, 0, 64)
if err == nil {
return int(port)
}
}
if req.TLS != nil {
return 443
}
return 80
}
// Recover is meant to be used at the top of handlers with "defer"
// to catch errors from MustGet, etc:
//
// func handler(rw http.ResponseWriter, req *http.Request) {
// defer httputil.Recover(rw, req)
// id := req.MustGet("id")
// ....
//
// Recover will send the proper HTTP error type and message (e.g.
// a 400 Bad Request for MustGet)
func Recover(rw http.ResponseWriter, req *http.Request) {
RecoverJSON(rw, req) // TODO: for now. alternate format?
}
// RecoverJSON is like Recover but returns with a JSON response.
func RecoverJSON(rw http.ResponseWriter, req *http.Request) {
e := recover()
if e == nil {
return
}
ServeJSONError(rw, e)
}
type httpCoder interface {
HTTPCode() int
}
// An InvalidMethodError is returned when an HTTP handler is invoked
// with an unsupported method.
type InvalidMethodError struct{}
func (InvalidMethodError) Error() string { return "invalid method" }
func (InvalidMethodError) HTTPCode() int { return http.StatusMethodNotAllowed }
// A MissingParameterError represents a missing HTTP parameter.
// The underlying string is the missing parameter name.
type MissingParameterError string
func (p MissingParameterError) Error() string { return fmt.Sprintf("Missing parameter %q", string(p)) }
func (MissingParameterError) HTTPCode() int { return http.StatusBadRequest }
// An InvalidParameterError represents an invalid HTTP parameter.
// The underlying string is the invalid parameter name, not value.
type InvalidParameterError string
func (p InvalidParameterError) Error() string { return fmt.Sprintf("Invalid parameter %q", string(p)) }
func (InvalidParameterError) HTTPCode() int { return http.StatusBadRequest }
// A ServerError is a generic 500 error.
type ServerError string
func (e ServerError) Error() string { return string(e) }
func (ServerError) HTTPCode() int { return http.StatusInternalServerError }
// MustGet returns a non-empty GET (or HEAD) parameter param and panics
// with a special error as caught by a deferred httputil.Recover.
func MustGet(req *http.Request, param string) string {
if !IsGet(req) {
panic(InvalidMethodError{})
}
v := req.FormValue(param)
if v == "" {
panic(MissingParameterError(param))
}
return v
}
// MustGetBlobRef returns a non-nil BlobRef from req, as given by param.
// If it doesn't, it panics with a value understood by Recover or RecoverJSON.
func MustGetBlobRef(req *http.Request, param string) blob.Ref {
br, ok := blob.Parse(MustGet(req, param))
if !ok {
panic(InvalidParameterError(param))
}
return br
}
// OptionalInt returns the integer in req given by param, or 0 if not present.
// If the form value is not an integer, it panics with a a value understood by Recover or RecoverJSON.
func OptionalInt(req *http.Request, param string) int {
v := req.FormValue(param)
if v == "" {
return 0
}
i, err := strconv.Atoi(v)
if err != nil {
panic(InvalidParameterError(param))
}
return i
}
// ServeJSONError sends a JSON error response to rw for the provided
// error value.
func ServeJSONError(rw http.ResponseWriter, err interface{}) {
code := 500
if i, ok := err.(httpCoder); ok {
code = i.HTTPCode()
}
msg := fmt.Sprint(err)
log.Printf("Sending error %v to client for: %v", code, msg)
ReturnJSONCode(rw, code, map[string]interface{}{
"error": msg,
"errorType": http.StatusText(code),
})
}
// TODO: use a sync.Pool if/when Go 1.3 includes it and Camlistore depends on that.
var freeBuf = make(chan *bytes.Buffer, 2)
func getBuf() *bytes.Buffer {
select {
case b := <-freeBuf:
b.Reset()
return b
default:
return new(bytes.Buffer)
}
}
func putBuf(b *bytes.Buffer) {
select {
case freeBuf <- b:
default:
}
}
// DecodeJSON decodes the JSON in res.Body into dest and then closes
// res.Body.
// It defensively caps the JSON at 8 MB for now.
func DecodeJSON(res *http.Response, dest interface{}) error {
defer CloseBody(res.Body)
buf := getBuf()
defer putBuf(buf)
if err := json.NewDecoder(io.TeeReader(io.LimitReader(res.Body, 8<<20), buf)).Decode(dest); err != nil {
return fmt.Errorf("httputil.DecodeJSON: %v, on input: %s", err, buf.Bytes())
}
return nil
}
// CloseBody should be used to close an http.Response.Body.
//
// It does a final little Read to maybe see EOF (to trigger connection
// re-use) before calling Close.
func CloseBody(rc io.ReadCloser) {
// Go 1.2 pseudo-bug: the NewDecoder(res.Body).Decode never
// sees an EOF, so we have to do this 0-byte copy here to
// force the http Transport to see its own EOF and recycle the
// connection. In Go 1.1 at least, the Close would cause it to
// read to EOF and recycle the connection, but in Go 1.2, a
// Close before EOF kills the underlying TCP connection.
//
// Will hopefully be fixed in Go 1.3, at least for bodies with
// Content-Length. Or maybe Go 1.3's Close itself would look
// to see if we're at EOF even if it hasn't been Read.
// TODO: use a bytepool package somewhere for this byte?
// Justification for 3 byte reads: two for up to "\r\n" after
// a JSON/XML document, and then 1 to see EOF if we haven't yet.
buf := make([]byte, 1)
for i := 0; i < 3; i++ {
_, err := rc.Read(buf)
if err != nil {
break
}
}
rc.Close()
}
func IsWebsocketUpgrade(req *http.Request) bool {
return req.Method == "GET" && req.Header.Get("Upgrade") == "websocket"
}