github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/httputil/httputil.go (about)

     1  /*
     2  Copyright 2011 Google Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package httputil contains a bunch of HTTP utility code, some generic,
    18  // and some Camlistore-specific.
    19  package httputil
    20  
    21  import (
    22  	"encoding/json"
    23  	"fmt"
    24  	"log"
    25  	"net"
    26  	"net/http"
    27  	"net/url"
    28  	"path"
    29  	"strconv"
    30  	"strings"
    31  
    32  	"camlistore.org/pkg/blob"
    33  )
    34  
    35  // IsGet reports whether r.Method is a GET or HEAD request.
    36  func IsGet(r *http.Request) bool {
    37  	return r.Method == "GET" || r.Method == "HEAD"
    38  }
    39  
    40  func ErrorRouting(conn http.ResponseWriter, req *http.Request) {
    41  	http.Error(conn, "Handlers wired up wrong; this path shouldn't be hit", 500)
    42  	log.Printf("Internal routing error on %q", req.URL.Path)
    43  }
    44  
    45  func BadRequestError(conn http.ResponseWriter, errorMessage string, args ...interface{}) {
    46  	conn.WriteHeader(http.StatusBadRequest)
    47  	log.Printf("Bad request: %s", fmt.Sprintf(errorMessage, args...))
    48  	fmt.Fprintf(conn, "%s\n", errorMessage)
    49  }
    50  
    51  func ForbiddenError(conn http.ResponseWriter, errorMessage string, args ...interface{}) {
    52  	conn.WriteHeader(http.StatusForbidden)
    53  	log.Printf("Forbidden: %s", fmt.Sprintf(errorMessage, args...))
    54  	fmt.Fprintf(conn, "<h1>Forbidden</h1>")
    55  }
    56  
    57  func RequestEntityTooLargeError(conn http.ResponseWriter) {
    58  	conn.WriteHeader(http.StatusRequestEntityTooLarge)
    59  	fmt.Fprintf(conn, "<h1>Request entity is too large</h1>")
    60  }
    61  
    62  func ServeError(conn http.ResponseWriter, req *http.Request, err error) {
    63  	conn.WriteHeader(http.StatusInternalServerError)
    64  	if IsLocalhost(req) {
    65  		fmt.Fprintf(conn, "Server error: %s\n", err)
    66  		return
    67  	}
    68  	fmt.Fprintf(conn, "An internal error occured, sorry.")
    69  }
    70  
    71  func ReturnJSON(rw http.ResponseWriter, data interface{}) {
    72  	ReturnJSONCode(rw, 200, data)
    73  }
    74  
    75  func ReturnJSONCode(rw http.ResponseWriter, code int, data interface{}) {
    76  	rw.Header().Set("Content-Type", "text/javascript")
    77  	js, err := json.MarshalIndent(data, "", "  ")
    78  	if err != nil {
    79  		BadRequestError(rw, fmt.Sprintf("JSON serialization error: %v", err))
    80  		return
    81  	}
    82  	rw.Header().Set("Content-Length", strconv.Itoa(len(js)+1))
    83  	rw.WriteHeader(code)
    84  	rw.Write(js)
    85  	rw.Write([]byte("\n"))
    86  }
    87  
    88  // PrefixHandler wraps another Handler and verifies that all requests'
    89  // Path begin with Prefix. If they don't, a 500 error is returned.
    90  // If they do, the headers PathBaseHeader and PathSuffixHeader are set
    91  // on the request before proxying to Handler.
    92  // PathBaseHeader is just the value of Prefix.
    93  // PathSuffixHeader is the part of the path that follows Prefix.
    94  type PrefixHandler struct {
    95  	Prefix  string
    96  	Handler http.Handler
    97  }
    98  
    99  const (
   100  	PathBaseHeader   = "X-Prefixhandler-Pathbase"
   101  	PathSuffixHeader = "X-Prefixhandler-Pathsuffix"
   102  )
   103  
   104  func (p *PrefixHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
   105  	if !strings.HasPrefix(req.URL.Path, p.Prefix) {
   106  		http.Error(rw, "Inconfigured PrefixHandler", 500)
   107  		return
   108  	}
   109  	req.Header.Set(PathBaseHeader, p.Prefix)
   110  	req.Header.Set(PathSuffixHeader, strings.TrimPrefix(req.URL.Path, p.Prefix))
   111  	p.Handler.ServeHTTP(rw, req)
   112  }
   113  
   114  // PathBase returns a Request's base path, if it went via a PrefixHandler.
   115  func PathBase(req *http.Request) string { return req.Header.Get(PathBaseHeader) }
   116  
   117  // PathSuffix returns a Request's suffix path, if it went via a PrefixHandler.
   118  func PathSuffix(req *http.Request) string { return req.Header.Get(PathSuffixHeader) }
   119  
   120  // BaseURL returns the base URL (scheme + host and optional port +
   121  // blobserver prefix) that should be used for requests (and responses)
   122  // subsequent to req. The returned URL does not end in a trailing slash.
   123  // The scheme and host:port are taken from urlStr if present,
   124  // or derived from req otherwise.
   125  // The prefix part comes from urlStr.
   126  func BaseURL(urlStr string, req *http.Request) (string, error) {
   127  	var baseURL string
   128  	defaultURL, err := url.Parse(urlStr)
   129  	if err != nil {
   130  		return baseURL, err
   131  	}
   132  	prefix := path.Clean(defaultURL.Path)
   133  	scheme := "http"
   134  	if req.TLS != nil {
   135  		scheme = "https"
   136  	}
   137  	host := req.Host
   138  	if defaultURL.Host != "" {
   139  		host = defaultURL.Host
   140  	}
   141  	if defaultURL.Scheme != "" {
   142  		scheme = defaultURL.Scheme
   143  	}
   144  	baseURL = scheme + "://" + host + prefix
   145  	return baseURL, nil
   146  }
   147  
   148  // RequestTargetPort returns the port targetted by the client
   149  // in req. If not present, it returns 80, or 443 if TLS is used.
   150  func RequestTargetPort(req *http.Request) int {
   151  	_, portStr, err := net.SplitHostPort(req.Host)
   152  	if err == nil && portStr != "" {
   153  		port, err := strconv.ParseInt(portStr, 0, 64)
   154  		if err == nil {
   155  			return int(port)
   156  		}
   157  	}
   158  	if req.TLS != nil {
   159  		return 443
   160  	}
   161  	return 80
   162  }
   163  
   164  // Recover is meant to be used at the top of handlers with "defer"
   165  // to catch errors from MustGet, etc:
   166  //
   167  //   func handler(rw http.ResponseWriter, req *http.Request) {
   168  //       defer httputil.Recover(rw, req)
   169  //       id := req.MustGet("id")
   170  //       ....
   171  //
   172  // Recover will send the proper HTTP error type and message (e.g.
   173  // a 400 Bad Request for MustGet)
   174  func Recover(rw http.ResponseWriter, req *http.Request) {
   175  	RecoverJSON(rw, req) // TODO: for now. alternate format?
   176  }
   177  
   178  // RecoverJSON is like Recover but returns with a JSON response.
   179  func RecoverJSON(rw http.ResponseWriter, req *http.Request) {
   180  	e := recover()
   181  	if e == nil {
   182  		return
   183  	}
   184  	ServeJSONError(rw, e)
   185  }
   186  
   187  type httpCoder interface {
   188  	HTTPCode() int
   189  }
   190  
   191  // An InvalidMethodError is returned when an HTTP handler is invoked
   192  // with an unsupported method.
   193  type InvalidMethodError struct{}
   194  
   195  func (InvalidMethodError) Error() string { return "invalid method" }
   196  func (InvalidMethodError) HTTPCode() int { return http.StatusMethodNotAllowed }
   197  
   198  // A MissingParameterError represents a missing HTTP parameter.
   199  // The underlying string is the missing parameter name.
   200  type MissingParameterError string
   201  
   202  func (p MissingParameterError) Error() string { return fmt.Sprintf("Missing parameter %q", string(p)) }
   203  func (MissingParameterError) HTTPCode() int   { return http.StatusBadRequest }
   204  
   205  // An InvalidParameterError represents an invalid HTTP parameter.
   206  // The underlying string is the invalid parameter name, not value.
   207  type InvalidParameterError string
   208  
   209  func (p InvalidParameterError) Error() string { return fmt.Sprintf("Invalid parameter %q", string(p)) }
   210  func (InvalidParameterError) HTTPCode() int   { return http.StatusBadRequest }
   211  
   212  // A ServerError is a generic 500 error.
   213  type ServerError string
   214  
   215  func (e ServerError) Error() string { return string(e) }
   216  func (ServerError) HTTPCode() int   { return http.StatusInternalServerError }
   217  
   218  // MustGet returns a non-empty GET (or HEAD) parameter param and panics
   219  // with a special error as caught by a deferred httputil.Recover.
   220  func MustGet(req *http.Request, param string) string {
   221  	if !IsGet(req) {
   222  		panic(InvalidMethodError{})
   223  	}
   224  	v := req.FormValue(param)
   225  	if v == "" {
   226  		panic(MissingParameterError(param))
   227  	}
   228  	return v
   229  }
   230  
   231  // MustGetBlobRef returns a non-nil BlobRef from req, as given by param.
   232  // If it doesn't, it panics with a value understood by Recover or RecoverJSON.
   233  func MustGetBlobRef(req *http.Request, param string) blob.Ref {
   234  	br, ok := blob.Parse(MustGet(req, param))
   235  	if !ok {
   236  		panic(InvalidParameterError(param))
   237  	}
   238  	return br
   239  }
   240  
   241  // OptionalInt returns the integer in req given by param, or 0 if not present.
   242  // If the form value is not an integer, it panics with a a value understood by Recover or RecoverJSON.
   243  func OptionalInt(req *http.Request, param string) int {
   244  	v := req.FormValue(param)
   245  	if v == "" {
   246  		return 0
   247  	}
   248  	i, err := strconv.Atoi(v)
   249  	if err != nil {
   250  		panic(InvalidParameterError(param))
   251  	}
   252  	return i
   253  }
   254  
   255  // ServeJSONError sends a JSON error response to rw for the provided
   256  // error value.
   257  func ServeJSONError(rw http.ResponseWriter, err interface{}) {
   258  	code := 500
   259  	if i, ok := err.(httpCoder); ok {
   260  		code = i.HTTPCode()
   261  	}
   262  	msg := fmt.Sprint(err)
   263  	log.Printf("Sending error %v to client for: %v", code, msg)
   264  	ReturnJSONCode(rw, code, map[string]interface{}{
   265  		"error":     msg,
   266  		"errorType": http.StatusText(code),
   267  	})
   268  }