responseemitter.go 3.39 KB
Newer Older
1 2 3 4 5 6 7 8
package http

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

9 10 11
	cmds "github.com/ipfs/go-ipfs-cmds"
	"github.com/ipfs/go-ipfs-cmds/cmdsutil"

12 13 14 15 16 17 18 19
	"github.com/ipfs/go-ipfs/repo/config"
)

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

// NewResponeEmitter returns a new ResponseEmitter.
20 21
func NewResponseEmitter(w http.ResponseWriter, encType cmds.EncodingType, method string) ResponseEmitter {
	re := &responseEmitter{
22 23 24 25 26 27 28 29
		w:       w,
		encType: encType,
		enc:     cmds.Encoders[encType](w),
		method:  method,
	}
	return re
}

30 31 32 33 34 35
type ResponseEmitter interface {
	cmds.ResponseEmitter
	http.Flusher
}

type responseEmitter struct {
Jan Winkelmann's avatar
Jan Winkelmann committed
36 37 38 39 40 41
	w http.ResponseWriter

	enc     cmds.Encoder
	encType cmds.EncodingType

	length uint64
42
	err    *cmdsutil.Error
Jan Winkelmann's avatar
Jan Winkelmann committed
43 44 45

	hasEmitted bool
	method     string
46 47
}

48
func (re *responseEmitter) Emit(value interface{}) error {
49 50 51 52
	var err error

	if !re.hasEmitted {
		re.hasEmitted = true
Jan Winkelmann's avatar
Jan Winkelmann committed
53
		re.preamble(value)
54 55
	}

56 57 58 59 60
	// ignore those
	if value == nil {
		return nil
	}

61
	// return immediately if this is a head request
Jan Winkelmann's avatar
Jan Winkelmann committed
62
	if re.method == "HEAD" {
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
		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
}

82
func (re *responseEmitter) SetLength(l uint64) {
83 84 85
	re.length = l
}

86
func (re *responseEmitter) Close() error {
87 88 89 90
	// can't close HTTP connections
	return nil
}

91
func (re *responseEmitter) SetError(err interface{}, code cmdsutil.ErrorType) {
92
	re.err = &cmdsutil.Error{Message: fmt.Sprint(err), Code: code}
93

Jan Winkelmann's avatar
Jan Winkelmann committed
94 95 96
	// force send of preamble
	// TODO is this the right thing to do?
	re.Emit(nil)
97 98
}

Jan Winkelmann's avatar
Jan Winkelmann committed
99
// Flush the http connection
100
func (re *responseEmitter) Flush() {
Jan Winkelmann's avatar
Jan Winkelmann committed
101 102
	if !re.hasEmitted {
		re.hasEmitted = true
103 104 105

		// setting this to nil means that it sends channel/chunked-encoding headers
		re.preamble(nil)
Jan Winkelmann's avatar
Jan Winkelmann committed
106 107
	}

108 109 110
	re.w.(http.Flusher).Flush()
}

111
func (re *responseEmitter) preamble(value interface{}) {
112 113 114 115 116 117 118
	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 {
119
		if e.Code == cmdsutil.ErrClient {
120 121 122 123
			status = http.StatusBadRequest
		} else {
			status = http.StatusInternalServerError
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
124
		// NOTE: The error will actually be written out below
125 126
	}

Jan Winkelmann's avatar
Jan Winkelmann committed
127 128
	// write error to connection
	if re.err != nil {
129 130
		if re.err.Code == cmdsutil.ErrClient {
			http.Error(re.w, re.err.Error(), http.StatusInternalServerError)
Jan Winkelmann's avatar
Jan Winkelmann committed
131 132 133 134
		}

		return
	}
135 136 137 138

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

139 140
	if re.length > 0 {
		h.Set("X-Content-Length", strconv.FormatUint(re.length, 10))
141 142 143 144 145 146
	}

	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
147
	} else {
148 149
		h.Set(channelHeader, "1")
	}
Jan Winkelmann's avatar
Jan Winkelmann committed
150 151 152

	// lookup mime type from map
	mime := mimeTypes[re.encType]
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167

	// 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)
}