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

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

11 12
	"github.com/rs/cors"

13
	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
14

15 16
	cmds "github.com/ipfs/go-ipfs/commands"
	u "github.com/ipfs/go-ipfs/util"
17 18
)

19 20
var log = u.Logger("commands/http")

21 22 23 24 25 26 27 28
// 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.
29
type Handler struct {
30 31
	internalHandler
	corsHandler http.Handler
32
}
33

34
var ErrNotFound = errors.New("404 page not found")
35

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

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

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

57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
	// 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)}
73 74
}

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

78 79 80 81 82 83 84 85 86 87 88 89 90 91
	// 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
	}

92
	req, err := Parse(r, i.root)
93
	if err != nil {
94 95 96 97 98 99
		if err == ErrNotFound {
			w.WriteHeader(http.StatusNotFound)
		} else {
			w.WriteHeader(http.StatusBadRequest)
		}
		w.Write([]byte(err.Error()))
100 101
		return
	}
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122

	// 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
	}
	ctx, cancel := context.WithCancel(node.Context())
	defer cancel()
	/*
		TODO(cryptix): the next line looks very fishy to me..
		It looks like the the context for the command request beeing prepared here is shared across all incoming requests..

		I assume it really isn't because ServeHTTP() doesn't take a pointer receiver, but it's really subtule..

		Shouldn't the context be just put on the command request?

		ps: take note of the name clash - commands.Context != context.Context
	*/
	i.ctx.Context = ctx
123
	req.SetContext(i.ctx)
124 125

	// call the command
126
	res := i.root.Call(req)
127 128

	// set the Content-Type based on res output
129
	if _, ok := res.Output().(io.Reader); ok {
130 131 132 133 134 135
		// 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")

136
	} else {
137 138
		enc, found, err := req.Option(cmds.EncShort).String()
		if err != nil || !found {
139 140 141
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
142
		mime := mimeTypes[enc]
143
		w.Header().Set(contentTypeHeader, mime)
144 145
	}

146 147 148 149 150
	// set the Content-Length from the response length
	if res.Length() > 0 {
		w.Header().Set(contentLengthHeader, strconv.FormatUint(res.Length(), 10))
	}

151 152 153 154 155 156 157 158 159
	// 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)
		}
	}

160
	out, err := res.Reader()
161
	if err != nil {
162
		w.Header().Set(contentTypeHeader, "text/plain")
163
		w.WriteHeader(http.StatusInternalServerError)
164
		w.Write([]byte(err.Error()))
165
		return
166
	}
167

168 169 170
	// if output is a channel and user requested streaming channels,
	// use chunk copier for the output
	_, isChan := res.Output().(chan interface{})
171 172 173 174
	if !isChan {
		_, isChan = res.Output().(<-chan interface{})
	}

175 176
	streamChans, _, _ := req.Option("stream-channels").Bool()
	if isChan && streamChans {
177 178 179
		// w.WriteString(transferEncodingHeader + ": chunked\r\n")
		// w.Header().Set(channelHeader, "1")
		// w.WriteHeader(200)
180
		err = copyChunks(applicationJson, w, out)
181
		if err != nil {
182
			log.Debug(err)
183 184
		}
		return
185
	}
186

187 188 189
	flushCopy(w, out)
}

190 191 192 193 194
func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Call the CORS handler which wraps the internal handler.
	i.corsHandler.ServeHTTP(w, r)
}

195 196 197 198 199 200 201 202 203
// 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)
	}

	io.Copy(&flushResponse{w}, out)
	return nil
204 205 206 207
}

// Copies from an io.Reader to a http.ResponseWriter.
// Flushes chunks over HTTP stream as they are read (if supported by transport).
208
func copyChunks(contentType string, w http.ResponseWriter, out io.Reader) error {
209 210 211 212 213 214 215 216 217 218 219
	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")
220 221 222
	if contentType != "" {
		writer.WriteString(contentTypeHeader + ": " + contentType + "\r\n")
	}
223 224 225
	writer.WriteString(transferEncodingHeader + ": chunked\r\n")
	writer.WriteString(channelHeader + ": 1\r\n\r\n")

226 227 228 229 230 231
	buf := make([]byte, 32*1024)

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

		if n > 0 {
232 233 234 235
			length := fmt.Sprintf("%x\r\n", n)
			writer.WriteString(length)

			_, err := writer.Write(buf[0:n])
236 237 238 239
			if err != nil {
				return err
			}

240 241
			writer.WriteString("\r\n")
			writer.Flush()
242 243
		}

244
		if err != nil && err != io.EOF {
245 246
			return err
		}
247 248 249
		if err == io.EOF {
			break
		}
250
	}
251 252 253 254

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

255
	return nil
256
}
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272

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
}