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

import (
4
	"errors"
5
	"fmt"
6 7
	"io"
	"net/http"
8
	"strconv"
9
	"strings"
10

David Braun's avatar
David Braun committed
11
	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/rs/cors"
12

13 14
	cmds "github.com/ipfs/go-ipfs/commands"
	u "github.com/ipfs/go-ipfs/util"
15 16
)

17 18
var log = u.Logger("commands/http")

19 20 21 22 23 24 25 26
// the internal handler for the API
type internalHandler struct {
	ctx  cmds.Context
	root *cmds.Command
}

// The Handler struct is funny because we want to wrap our internal handler
// with CORS while keeping our fields.
27
type Handler struct {
28 29
	internalHandler
	corsHandler http.Handler
30
}
31

32
var ErrNotFound = errors.New("404 page not found")
33

34
const (
35 36 37 38 39
	streamHeader           = "X-Stream-Output"
	channelHeader          = "X-Chunked-Output"
	contentTypeHeader      = "Content-Type"
	contentLengthHeader    = "Content-Length"
	transferEncodingHeader = "Transfer-Encoding"
40
	applicationJson        = "application/json"
41
)
42

43 44 45 46 47 48
var mimeTypes = map[string]string{
	cmds.JSON: "application/json",
	cmds.XML:  "application/xml",
	cmds.Text: "text/plain",
}

49
func NewHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Handler {
50
	// allow whitelisted origins (so we can make API requests from the browser)
51 52
	if len(allowedOrigin) > 0 {
		log.Info("Allowing API requests from origin: " + allowedOrigin)
53 54
	}

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
	// Create a handler for the API.
	internal := internalHandler{ctx, root}

	// Create a CORS object for wrapping the internal handler.
	c := cors.New(cors.Options{
		AllowedMethods: []string{"GET", "POST", "PUT"},

		// use AllowOriginFunc instead of AllowedOrigins because we want to be
		// restrictive by default.
		AllowOriginFunc: func(origin string) bool {
			return (allowedOrigin == "*") || (origin == allowedOrigin)
		},
	})

	// Wrap the internal handler with CORS handling-middleware.
	return &Handler{internal, c.Handler(internal)}
71 72
}

73
func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
74 75
	log.Debug("Incoming API request: ", r.URL)

76 77 78 79 80 81 82 83 84 85 86 87 88 89
	// error on external referers (to prevent CSRF attacks)
	referer := r.Referer()
	scheme := r.URL.Scheme
	if len(scheme) == 0 {
		scheme = "http"
	}
	host := fmt.Sprintf("%s://%s/", scheme, r.Host)
	// empty string means the user isn't following a link (they are directly typing in the url)
	if referer != "" && !strings.HasPrefix(referer, host) {
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte("403 - Forbidden"))
		return
	}

90
	req, err := Parse(r, i.root)
91
	if err != nil {
92 93 94 95 96 97
		if err == ErrNotFound {
			w.WriteHeader(http.StatusNotFound)
		} else {
			w.WriteHeader(http.StatusBadRequest)
		}
		w.Write([]byte(err.Error()))
98 99
		return
	}
100 101 102 103 104 105 106 107 108

	// get the node's context to pass into the commands.
	node, err := i.ctx.GetNode()
	if err != nil {
		err = fmt.Errorf("cmds/http: couldn't GetNode(): %s", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

Jeromy's avatar
Jeromy committed
109 110 111
	//ps: take note of the name clash - commands.Context != context.Context
	req.SetInvocContext(i.ctx)
	err = req.SetRootContext(node.Context())
112 113 114 115
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
116

117
	// call the command
118
	res := i.root.Call(req)
119 120

	// set the Content-Type based on res output
121
	if _, ok := res.Output().(io.Reader); ok {
122 123 124 125 126 127
		// 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")

128
	} else {
129 130
		enc, found, err := req.Option(cmds.EncShort).String()
		if err != nil || !found {
131 132 133
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
134
		mime := mimeTypes[enc]
135
		w.Header().Set(contentTypeHeader, mime)
136 137
	}

138 139 140 141 142
	// set the Content-Length from the response length
	if res.Length() > 0 {
		w.Header().Set(contentLengthHeader, strconv.FormatUint(res.Length(), 10))
	}

143 144 145 146 147 148 149 150 151
	// 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)
		}
	}

152
	out, err := res.Reader()
153
	if err != nil {
154
		w.Header().Set(contentTypeHeader, "text/plain")
155
		w.WriteHeader(http.StatusInternalServerError)
156
		w.Write([]byte(err.Error()))
157
		return
158
	}
159

160 161 162
	// if output is a channel and user requested streaming channels,
	// use chunk copier for the output
	_, isChan := res.Output().(chan interface{})
163 164 165 166
	if !isChan {
		_, isChan = res.Output().(<-chan interface{})
	}

167 168
	streamChans, _, _ := req.Option("stream-channels").Bool()
	if isChan && streamChans {
169 170 171
		// w.WriteString(transferEncodingHeader + ": chunked\r\n")
		// w.Header().Set(channelHeader, "1")
		// w.WriteHeader(200)
172
		err = copyChunks(applicationJson, w, out)
173
		if err != nil {
174
			log.Debug("copy chunks error: ", err)
175 176
		}
		return
177
	}
178

179 180 181 182
	err = flushCopy(w, out)
	if err != nil {
		log.Debug("Flush copy returned an error: ", err)
	}
183 184
}

185 186 187 188 189
func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Call the CORS handler which wraps the internal handler.
	i.corsHandler.ServeHTTP(w, r)
}

190 191 192 193 194 195 196
// flushCopy Copies from an io.Reader to a http.ResponseWriter.
// Flushes chunks over HTTP stream as they are read (if supported by transport).
func flushCopy(w http.ResponseWriter, out io.Reader) error {
	if _, ok := w.(http.Flusher); !ok {
		return copyChunks("", w, out)
	}

197 198
	_, err := io.Copy(&flushResponse{w}, out)
	return err
199 200 201 202
}

// Copies from an io.Reader to a http.ResponseWriter.
// Flushes chunks over HTTP stream as they are read (if supported by transport).
203
func copyChunks(contentType string, w http.ResponseWriter, out io.Reader) error {
204 205 206 207 208 209 210 211 212 213 214
	hijacker, ok := w.(http.Hijacker)
	if !ok {
		return errors.New("Could not create hijacker")
	}
	conn, writer, err := hijacker.Hijack()
	if err != nil {
		return err
	}
	defer conn.Close()

	writer.WriteString("HTTP/1.1 200 OK\r\n")
215 216 217
	if contentType != "" {
		writer.WriteString(contentTypeHeader + ": " + contentType + "\r\n")
	}
218 219 220
	writer.WriteString(transferEncodingHeader + ": chunked\r\n")
	writer.WriteString(channelHeader + ": 1\r\n\r\n")

221 222 223 224 225 226
	buf := make([]byte, 32*1024)

	for {
		n, err := out.Read(buf)

		if n > 0 {
227 228 229 230
			length := fmt.Sprintf("%x\r\n", n)
			writer.WriteString(length)

			_, err := writer.Write(buf[0:n])
231 232 233 234
			if err != nil {
				return err
			}

235 236
			writer.WriteString("\r\n")
			writer.Flush()
237 238
		}

239
		if err != nil && err != io.EOF {
240 241
			return err
		}
242 243 244
		if err == io.EOF {
			break
		}
245
	}
246 247 248 249

	writer.WriteString("0\r\n\r\n")
	writer.Flush()

250
	return nil
251
}
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267

type flushResponse struct {
	W http.ResponseWriter
}

func (fr *flushResponse) Write(buf []byte) (int, error) {
	n, err := fr.W.Write(buf)
	if err != nil {
		return n, err
	}

	if flusher, ok := fr.W.(http.Flusher); ok {
		flusher.Flush()
	}
	return n, err
}