mirror of https://github.com/perkeep/perkeep.git
211 lines
5.9 KiB
Go
211 lines
5.9 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
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"camlistore.org/pkg/auth"
|
|
"camlistore.org/pkg/blobref"
|
|
)
|
|
|
|
func ErrorRouting(conn http.ResponseWriter, req *http.Request) {
|
|
http.Error(conn, "Handlers wired up wrong; this path shouldn't be hit", 500)
|
|
log.Printf("Internal routing error on %q", req.URL.Path)
|
|
}
|
|
|
|
func BadRequestError(conn http.ResponseWriter, errorMessage string, args ...interface{}) {
|
|
conn.WriteHeader(http.StatusBadRequest)
|
|
log.Printf("Bad request: %s", fmt.Sprintf(errorMessage, args...))
|
|
fmt.Fprintf(conn, "%s\n", errorMessage)
|
|
}
|
|
|
|
func ForbiddenError(conn http.ResponseWriter, errorMessage string, args ...interface{}) {
|
|
conn.WriteHeader(http.StatusForbidden)
|
|
log.Printf("Forbidden: %s", fmt.Sprintf(errorMessage, args...))
|
|
fmt.Fprintf(conn, "<h1>Forbidden</h1>")
|
|
}
|
|
|
|
func RequestEntityTooLargeError(conn http.ResponseWriter) {
|
|
conn.WriteHeader(http.StatusRequestEntityTooLarge)
|
|
fmt.Fprintf(conn, "<h1>Request entity is too large</h1>")
|
|
}
|
|
|
|
func ServerError(conn http.ResponseWriter, req *http.Request, err error) {
|
|
conn.WriteHeader(http.StatusInternalServerError)
|
|
if auth.IsLocalhost(req) {
|
|
fmt.Fprintf(conn, "Server error: %s\n", err)
|
|
return
|
|
}
|
|
fmt.Fprintf(conn, "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{}) {
|
|
rw.Header().Set("Content-Type", "text/javascript")
|
|
rw.WriteHeader(code)
|
|
js, err := json.MarshalIndent(data, "", " ")
|
|
if err != nil {
|
|
BadRequestError(rw, fmt.Sprintf("JSON serialization error: %v", err))
|
|
return
|
|
}
|
|
rw.Header().Set("Content-Length", strconv.Itoa(len(js)+1))
|
|
rw.Write(js)
|
|
rw.Write([]byte("\n"))
|
|
}
|
|
|
|
type PrefixHandler struct {
|
|
Prefix string
|
|
Handler http.Handler
|
|
}
|
|
|
|
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("X-PrefixHandler-PathBase", p.Prefix)
|
|
req.Header.Set("X-PrefixHandler-PathSuffix", req.URL.Path[len(p.Prefix):])
|
|
p.Handler.ServeHTTP(rw, req)
|
|
}
|
|
|
|
// 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?
|
|
}
|
|
|
|
func RecoverJSON(rw http.ResponseWriter, req *http.Request) {
|
|
e := recover()
|
|
if e == nil {
|
|
return
|
|
}
|
|
code := 500
|
|
if i, ok := e.(httpCoder); ok {
|
|
code = i.HTTPCode()
|
|
}
|
|
msg := fmt.Sprint(e)
|
|
log.Printf("Sending error %v to client for: %v", code, msg)
|
|
ReturnJSONCode(rw, code, map[string]interface{}{
|
|
"error": msg,
|
|
"errorType": http.StatusText(code),
|
|
})
|
|
}
|
|
|
|
type httpCoder interface {
|
|
HTTPCode() int
|
|
}
|
|
|
|
type InvalidMethodError struct{}
|
|
|
|
func (InvalidMethodError) Error() string { return "invalid method" }
|
|
func (InvalidMethodError) HTTPCode() int { return http.StatusMethodNotAllowed }
|
|
|
|
type MissingParameterError string
|
|
|
|
func (p MissingParameterError) Error() string { return fmt.Sprintf("Missing parameter %q", string(p)) }
|
|
func (MissingParameterError) HTTPCode() int { return http.StatusBadRequest }
|
|
|
|
type InvalidParameterError string
|
|
|
|
func (p InvalidParameterError) Error() string { return fmt.Sprintf("Invalid parameter %q", string(p)) }
|
|
func (InvalidParameterError) HTTPCode() int { return http.StatusBadRequest }
|
|
|
|
// MustGet returns the 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 req.Method != "GET" && req.Method != "HEAD" {
|
|
panic(InvalidMethodError{})
|
|
}
|
|
v := req.FormValue(param)
|
|
if v == "" {
|
|
panic(MissingParameterError(param))
|
|
}
|
|
return v
|
|
}
|
|
|
|
func MustGetBlobRef(req *http.Request, param string) *blobref.BlobRef {
|
|
br := blobref.Parse(MustGet(req, param))
|
|
if br == nil {
|
|
panic(InvalidParameterError(param))
|
|
}
|
|
return br
|
|
}
|