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 }