forked from bpicode/fritzctl
-
Notifications
You must be signed in to change notification settings - Fork 1
/
httpread.go
140 lines (124 loc) · 3.54 KB
/
httpread.go
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package httpread
import (
"bytes"
"encoding/csv"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"github.com/bpicode/fritzctl/logger"
"github.com/pkg/errors"
)
var (
httpStatusBuzzwords = map[string]int{"500 Internal Server Error": 500}
)
type stringDecoder struct {
reader io.Reader
}
func (s *stringDecoder) Decode(v interface{}) error {
bytesRead, err := ioutil.ReadAll(s.reader)
if err != nil {
return err
}
sp, ok := v.(*string)
if !ok {
return errors.New("cannot decode into string, call with string pointer")
}
*sp = string(bytesRead)
return nil
}
// String reads a http response into a string.
// The response is checked for its status code and the http.Response.Body is closed.
func String(f func() (*http.Response, error)) (string, error) {
body := ""
err := readDecode(f, func(r io.Reader) decoder {
return &stringDecoder{reader: r}
}, &body)
if err != nil {
return "", err
}
sc, sp := guessStatusCode(body)
if sc >= 400 {
return "", fmt.Errorf("HTTP status code error (%d, guessed): remote replied with '%s'", sc, sp)
}
return body, nil
}
type csvDecoder struct {
reader io.Reader
comma rune
}
func (c *csvDecoder) Decode(v interface{}) error {
cr := csv.NewReader(c.reader)
cr.Comma = c.comma
cr.FieldsPerRecord = -1
records, err := cr.ReadAll()
if err != nil {
return err
}
t, ok := v.(*[][]string)
if !ok {
return errors.New("cannot decode into csv, call with *[][]string slice")
}
*t = records
return nil
}
// Csv reads a http response into a [][]string.
// The response is checked for its status code and the http.Response.Body is closed.
func Csv(f func() (*http.Response, error), comma rune) ([][]string, error) {
var records [][]string
err := readDecode(f, func(r io.Reader) decoder {
return &csvDecoder{reader: r, comma: comma}
}, &records)
return records, err
}
func guessStatusCode(body string) (int, string) {
// There are web servers that send the wrong status code, but provide some hint in the text/html.
for k, v := range httpStatusBuzzwords {
if strings.Contains(strings.ToLower(body), strings.ToLower(k)) {
return v, k
}
}
return 0, ""
}
type decoder interface {
Decode(v interface{}) error
}
type decoderFactory func(io.Reader) decoder
// XML reads a http response into a data container using an XML decoder.
// The response is checked for its status code and the http.Response.Body is closed.
func XML(f func() (*http.Response, error), v interface{}) error {
return readDecode(f, func(r io.Reader) decoder {
return xml.NewDecoder(r)
}, v)
}
// JSON reads a http response into a data container using a json decoder.
// The response is checked for its status code and the http.Response.Body is closed.
func JSON(f func() (*http.Response, error), v interface{}) error {
return readDecode(f, func(r io.Reader) decoder {
return json.NewDecoder(r)
}, v)
}
func readDecode(f func() (*http.Response, error), df decoderFactory, v interface{}) error {
response, err := f()
if err != nil {
return errors.Wrap(err, "error obtaining HTTP response from remote")
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("HTTP status code error (%d): remote replied with '%s'", response.StatusCode, response.Status)
}
return decode(response.Body, df, v)
}
func decode(r io.Reader, df decoderFactory, v interface{}) error {
buf := new(bytes.Buffer)
tee := io.TeeReader(r, buf)
defer func() { logger.Debug("DATA:", buf) }()
err := df(tee).Decode(v)
if err != nil {
return errors.Wrapf(err, "unable to decode remote response")
}
return nil
}