handler.go 2.39 KB
Newer Older
1 2 3
package http

import (
4
	"errors"
5 6 7 8
	"io"
	"net/http"

	cmds "github.com/jbenet/go-ipfs/commands"
9
	u "github.com/jbenet/go-ipfs/util"
10 11
)

12 13
var log = u.Logger("commands/http")

14
type Handler struct {
15 16 17
	ctx    cmds.Context
	root   *cmds.Command
	origin string
18
}
19

20
var ErrNotFound = errors.New("404 page not found")
21

22 23 24 25
const (
	streamHeader      = "X-Stream-Output"
	contentTypeHeader = "Content-Type"
)
26

27 28 29 30 31 32
var mimeTypes = map[string]string{
	cmds.JSON: "application/json",
	cmds.XML:  "application/xml",
	cmds.Text: "text/plain",
}

33 34 35 36 37 38 39
func NewHandler(ctx cmds.Context, root *cmds.Command, origin string) *Handler {
	// allow whitelisted origins (so we can make API requests from the browser)
	if len(origin) > 0 {
		log.Info("Allowing API requests from origin: " + origin)
	}

	return &Handler{ctx, root, origin}
40 41
}

42
func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
43 44
	log.Debug("Incoming API request: ", r.URL)

45 46 47 48 49
	if len(i.origin) > 0 {
		w.Header().Set("Access-Control-Allow-Origin", i.origin)
	}
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")

50
	req, err := Parse(r, i.root)
51
	if err != nil {
52 53 54 55 56 57
		if err == ErrNotFound {
			w.WriteHeader(http.StatusNotFound)
		} else {
			w.WriteHeader(http.StatusBadRequest)
		}
		w.Write([]byte(err.Error()))
58 59
		return
	}
60
	req.SetContext(i.ctx)
61 62

	// call the command
63
	res := i.root.Call(req)
64 65

	// set the Content-Type based on res output
66
	if _, ok := res.Output().(io.Reader); ok {
67 68 69 70 71 72
		// we don't set the Content-Type for streams, so that browsers can MIME-sniff the type themselves
		// we set this header so clients have a way to know this is an output stream
		// (not marshalled command output)
		// TODO: set a specific Content-Type if the command response needs it to be a certain type
		w.Header().Set(streamHeader, "1")

73
	} else {
74 75
		enc, found, err := req.Option(cmds.EncShort).String()
		if err != nil || !found {
76 77 78
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
79
		mime := mimeTypes[enc]
80
		w.Header().Set(contentTypeHeader, mime)
81 82 83 84 85 86 87 88 89 90 91
	}

	// if response contains an error, write an HTTP error status code
	if e := res.Error(); e != nil {
		if e.Code == cmds.ErrClient {
			w.WriteHeader(http.StatusBadRequest)
		} else {
			w.WriteHeader(http.StatusInternalServerError)
		}
	}

92
	out, err := res.Reader()
93 94
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
95
		w.Header().Set(contentTypeHeader, "text/plain")
96
		w.Write([]byte(err.Error()))
97
		return
98
	}
99 100

	io.Copy(w, out)
101
}