responseemitter.go 4.16 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
package http

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"

	cmds "github.com/ipfs/go-ipfs/commands"
	"github.com/ipfs/go-ipfs/repo/config"
)

var (
	HeadRequest = fmt.Errorf("HEAD request")
)

// NewResponeEmitter returns a new ResponseEmitter.
func NewResponseEmitter(w http.ResponseWriter, encType cmds.EncodingType, method string) *ResponseEmitter {
	re := &ResponseEmitter{
		w:       w,
		encType: encType,
		enc:     cmds.Encoders[encType](w),
		method:  method,
	}
	return re
}

type ResponseEmitter struct {
Jan Winkelmann's avatar
Jan Winkelmann committed
30 31 32 33 34 35 36 37 38 39
	w http.ResponseWriter

	enc     cmds.Encoder
	encType cmds.EncodingType

	length uint64
	err    *Error

	hasEmitted bool
	method     string
40 41 42 43 44 45 46
}

func (re *ResponseEmitter) Emit(value interface{}) error {
	var err error

	if !re.hasEmitted {
		re.hasEmitted = true
Jan Winkelmann's avatar
Jan Winkelmann committed
47
		re.preamble(value)
48 49
	}

50 51 52 53 54
	// ignore those
	if value == nil {
		return nil
	}

55
	// return immediately if this is a head request
Jan Winkelmann's avatar
Jan Winkelmann committed
56
	if re.method == "HEAD" {
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
		return nil
	}

	// Special case: if text encoding and an error, just print it out.
	// TODO review question: its like that in response.go, should we keep that?
	if re.encType == cmds.Text && re.err != nil {
		value = re.err
	}

	switch v := value.(type) {
	case io.Reader:
		_, err = io.Copy(re.w, v)
	default:
		err = re.enc.Encode(value)
	}

	return err
}

func (re *ResponseEmitter) SetLength(l uint64) {
	re.length = l
}

func (re *ResponseEmitter) Close() error {
	// can't close HTTP connections
	return nil
}

func (re *ResponseEmitter) SetError(err interface{}, code cmds.ErrorType) {
Jan Winkelmann's avatar
Jan Winkelmann committed
86
	re.err = &cmds.Error{Message: fmt.Sprint(err), Code: code}
87

Jan Winkelmann's avatar
Jan Winkelmann committed
88 89 90
	// force send of preamble
	// TODO is this the right thing to do?
	re.Emit(nil)
91 92
}

Jan Winkelmann's avatar
Jan Winkelmann committed
93
// Flush the http connection
94
func (re *ResponseEmitter) Flush() {
Jan Winkelmann's avatar
Jan Winkelmann committed
95 96 97 98 99
	if !re.hasEmitted {
		re.hasEmitted = true
		re.preamble(value)
	}

100 101 102
	re.w.(http.Flusher).Flush()
}

Jan Winkelmann's avatar
Jan Winkelmann committed
103
func (re *ResponseEmitter) preamble(value interface{}) {
104 105 106 107 108 109 110 111 112 113 114 115
	h := re.w.Header()
	// Expose our agent to allow identification
	h.Set("Server", "go-ipfs/"+config.CurrentVersionNumber)

	status := http.StatusOK
	// if response contains an error, write an HTTP error status code
	if e := re.err; e != nil {
		if e.(cmds.Error).Code == cmds.ErrClient {
			status = http.StatusBadRequest
		} else {
			status = http.StatusInternalServerError
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
116
		// NOTE: The error will actually be written out below
117 118
	}

Jan Winkelmann's avatar
Jan Winkelmann committed
119 120 121 122 123 124 125 126
	// write error to connection
	if re.err != nil {
		if re.err.Code == ErrClient {
			http.Error(re.w, err.Error(), http.StatusInternalServerError)
		}

		return
	}
127 128 129 130 131 132 133 134 135 136 137 138

	// Set up our potential trailer
	h.Set("Trailer", StreamErrHeader)

	if re.Length() > 0 {
		h.Set("X-Content-Length", strconv.FormatUint(re.Length(), 10))
	}

	if _, ok := value.(io.Reader); ok {
		// set streams output type to text to avoid issues with browsers rendering
		// html pages on priveleged api ports
		h.Set(streamHeader, "1")
Jan Winkelmann's avatar
Jan Winkelmann committed
139
	} else {
140 141
		h.Set(channelHeader, "1")
	}
Jan Winkelmann's avatar
Jan Winkelmann committed
142 143 144

	// lookup mime type from map
	mime := mimeTypes[re.encType]
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159

	// catch-all, set to text as default
	if mime == "" {
		mime = "text/plain"
	}

	h.Set(contentTypeHeader, mime)

	// set 'allowed' headers
	h.Set("Access-Control-Allow-Headers", AllowedExposedHeaders)
	// expose those headers
	h.Set("Access-Control-Expose-Headers", AllowedExposedHeaders)

	re.w.WriteHeader(status)
}
Jan Winkelmann's avatar
Jan Winkelmann committed
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209

// NewTeeEmitter returns a ResponseEmitter that can Flush. It will forward that flush to re1 and, if it can flush, re2.
func NewTeeEmitter(re1 *ResponseEmitter, re2 cmds.ResponseEmitter) *ResponseEmitter {
	return *teeEmitter{
		re1, re2,
	}
}

type teeEmitter struct {
	*ResponseEmitter

	re cmds.ResponseEmitter
}

func (re *teeEmitter) Close() error {
	err1 := re.ResponseEmitter.Close()
	err2 := re.re.Close()

	if err1 != nil {
		return err1
	}

	// XXX we drop the second error if both fail
	return err2
}

func (re *teeEmitter) Emit(v interface{}) error {
	err1 := re.ResponseEmitter.Emit()
	err2 := re.re.Emit()

	if err1 != nil {
		return err1
	}

	// XXX we drop the second error if both fail
	return err2
}

func (re *teeEmitter) SetError(err interface{}, code cmds.ErrorType) {
	re.ResponseEmitter.SetError(err, code)
	re.re.SetError(err, code)
}

func (re *teeEmitter) Flush() {
	re.ResponseEmitter.Flush()

	if hre, ok := re.re.(*ResponseEmitter); ok {
		hre.Flush()
	}
}