response.go 2.78 KB
Newer Older
Jan Winkelmann's avatar
Jan Winkelmann committed
1 2 3 4 5 6
package http

import (
	"errors"
	"io"
	"net/http"
keks's avatar
keks committed
7
	"reflect"
Jan Winkelmann's avatar
Jan Winkelmann committed
8 9
	"strings"

tavit ohanian's avatar
tavit ohanian committed
10
	cmds "gitlab.dms3.io/dms3/public/go-dms3-cmds"
Jan Winkelmann's avatar
Jan Winkelmann committed
11 12
)

13 14 15 16 17 18 19 20
var (
	MIMEEncodings = map[string]cmds.EncodingType{
		"application/json": cmds.JSON,
		"application/xml":  cmds.XML,
		"text/plain":       cmds.Text,
	}
)

Jan Winkelmann's avatar
Jan Winkelmann committed
21 22
type Response struct {
	length uint64
23
	err    error
Jan Winkelmann's avatar
Jan Winkelmann committed
24 25

	res *http.Response
26
	req *cmds.Request
Jan Winkelmann's avatar
Jan Winkelmann committed
27 28 29

	rr  *responseReader
	dec cmds.Decoder
30

Steven Allen's avatar
Steven Allen committed
31
	initErr *cmds.Error
Jan Winkelmann's avatar
Jan Winkelmann committed
32 33
}

34
func (res *Response) Request() *cmds.Request {
Jan Winkelmann's avatar
Jan Winkelmann committed
35
	return res.req
Jan Winkelmann's avatar
Jan Winkelmann committed
36 37
}

Steven Allen's avatar
Steven Allen committed
38
func (res *Response) Error() *cmds.Error {
39
	if res.err == io.EOF || res.err == nil {
keks's avatar
keks committed
40 41 42
		return nil
	}

43
	switch err := res.err.(type) {
Steven Allen's avatar
Steven Allen committed
44
	case *cmds.Error:
45
		return err
Steven Allen's avatar
Steven Allen committed
46
	case cmds.Error:
47 48 49
		return &err
	default:
		// i.e. is a regular error
Steven Allen's avatar
Steven Allen committed
50
		return &cmds.Error{Message: res.err.Error()}
51
	}
Jan Winkelmann's avatar
Jan Winkelmann committed
52 53 54 55 56 57
}

func (res *Response) Length() uint64 {
	return res.length
}

58
func (res *Response) Next() (interface{}, error) {
59
	if res.initErr != nil {
keks's avatar
keks committed
60 61
		return nil, res.initErr
	}
62

keks's avatar
keks committed
63 64
	if res.err != nil {
		return nil, res.err
65 66
	}

67 68 69 70 71 72
	// nil decoder means stream not chunks
	// but only do that once
	if res.dec == nil {
		if res.rr == nil {
			return nil, io.EOF
		}
Steven Allen's avatar
Steven Allen committed
73 74 75
		rr := res.rr
		res.rr = nil
		return rr, nil
76 77
	}

Steven Allen's avatar
Steven Allen committed
78 79 80 81 82 83
	var value interface{}
	if valueType := reflect.TypeOf(res.req.Command.Type); valueType != nil {
		if valueType.Kind() == reflect.Ptr {
			valueType = valueType.Elem()
		}
		value = reflect.New(valueType).Interface()
Alex Cruikshank's avatar
Alex Cruikshank committed
84
	}
keks's avatar
keks committed
85

86
	m := &cmds.MaybeError{Value: value}
keks's avatar
keks committed
87
	err := res.dec.Decode(m)
keks's avatar
keks committed
88
	if err != nil {
89
		if err == io.EOF {
keks's avatar
keks committed
90 91 92
			// handle errors from headers
			errStr := res.res.Header.Get(StreamErrHeader)
			if errStr != "" {
Steven Allen's avatar
Steven Allen committed
93
				err = &cmds.Error{Message: errStr}
keks's avatar
keks committed
94 95
			}

96
			res.err = err
keks's avatar
keks committed
97 98 99
			return nil, err
		} else {
			// wrap all other errors
keks's avatar
keks committed
100
			res.err = err
101
			return nil, res.err
keks's avatar
keks committed
102 103 104 105 106
		}
	}

	v, err := m.Get()
	if err != nil {
Steven Allen's avatar
Steven Allen committed
107
		if e, ok := err.(*cmds.Error); ok {
keks's avatar
keks committed
108 109
			res.err = e
		} else {
Steven Allen's avatar
Steven Allen committed
110
			res.err = &cmds.Error{Message: err.Error()}
keks's avatar
keks committed
111 112 113 114
		}
	}

	return v, err
115 116
}

Jan Winkelmann's avatar
Jan Winkelmann committed
117 118 119 120 121 122 123 124
// responseReader reads from the response body, and checks for an error
// in the http trailer upon EOF, this error if present is returned instead
// of the EOF.
type responseReader struct {
	resp *http.Response
}

func (r *responseReader) Read(b []byte) (int, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
125 126 127 128
	if r == nil || r.resp == nil {
		return 0, io.EOF
	}

Jan Winkelmann's avatar
Jan Winkelmann committed
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
	n, err := r.resp.Body.Read(b)

	// reading on a closed response body is as good as an io.EOF here
	if err != nil && strings.Contains(err.Error(), "read on closed response body") {
		err = io.EOF
	}
	if err == io.EOF {
		_ = r.resp.Body.Close()
		trailerErr := r.checkError()
		if trailerErr != nil {
			return n, trailerErr
		}
	}
	return n, err
}

func (r *responseReader) checkError() error {
	if e := r.resp.Trailer.Get(StreamErrHeader); e != "" {
		return errors.New(e)
	}
	return nil
}

func (r *responseReader) Close() error {
	return r.resp.Body.Close()
}