-
Notifications
You must be signed in to change notification settings - Fork 11
/
http.go
154 lines (139 loc) · 3.8 KB
/
http.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package rbxmk
import (
"bytes"
"context"
"fmt"
"net/http"
"github.com/anaminus/rbxmk/rtypes"
)
// HttpRequest performs and HTTP request with a promise-like API.
type HttpRequest struct {
global rtypes.Global
cancel context.CancelFunc
respch chan *http.Response
resp *rtypes.HttpResponse
errch chan error
err error
fmt Format
sel rtypes.FormatSelector
}
// Type returns a string identifying the type of the value.
func (*HttpRequest) Type() string {
return rtypes.T_HttpRequest
}
// do concurrently begins the request.
func (r *HttpRequest) do(client *Client, req *http.Request) {
defer close(r.respch)
defer close(r.errch)
resp, err := client.Do(req)
if err != nil {
r.errch <- err
return
}
r.respch <- resp
}
// Resolve blocks until the request resolves.
func (r *HttpRequest) Resolve() (*rtypes.HttpResponse, error) {
if r.resp != nil || r.err != nil {
return r.resp, r.err
}
select {
case resp := <-r.respch:
defer resp.Body.Close()
headers := rtypes.HttpHeaders(resp.Header)
r.resp = &rtypes.HttpResponse{
Success: 200 <= resp.StatusCode && resp.StatusCode < 300,
StatusCode: resp.StatusCode,
StatusMessage: resp.Status,
Headers: headers,
Cookies: headers.RetrieveSetCookies(),
}
if r.fmt.Name != "" {
if r.resp.Body, r.err = r.fmt.Decode(r.global, r.sel, resp.Body); r.err != nil {
return nil, r.err
}
}
return r.resp, nil
case r.err = <-r.errch:
return nil, r.err
}
}
// Cancel cancels the request.
func (r *HttpRequest) Cancel() {
if r.resp != nil || r.err != nil {
return
}
r.cancel()
defer close(r.respch)
defer close(r.errch)
r.err = <-r.errch
}
// BeginHttpRequest begins an HTTP request according to the given options, in
// the context of the given world.
//
// The request starts immediately, and can either be resolved or canceled.
func BeginHttpRequest(w *World, options rtypes.HttpOptions) (request *HttpRequest, err error) {
var buf *bytes.Buffer
if options.RequestFormat.Format != "" {
reqfmt := w.Format(options.RequestFormat.Format)
if reqfmt.Encode == nil {
return nil, fmt.Errorf("cannot encode with format %s", reqfmt.Name)
}
if options.Body != nil {
buf = new(bytes.Buffer)
if err := reqfmt.Encode(w.Global, options.RequestFormat, buf, options.Body); err != nil {
return nil, fmt.Errorf("encode body: %w", err)
}
}
}
var respfmt Format
if options.ResponseFormat.Format != "" {
respfmt = w.Format(options.ResponseFormat.Format)
if respfmt.Decode == nil {
return nil, fmt.Errorf("cannot decode with format %s", respfmt.Name)
}
}
// Create request.
ctx, cancel := context.WithCancel(context.TODO())
var req *http.Request
if buf != nil {
// Use of *bytes.Buffer guarantees that req.GetBody will be set.
req, err = http.NewRequestWithContext(ctx, options.Method, options.URL, buf)
} else {
req, err = http.NewRequestWithContext(ctx, options.Method, options.URL, nil)
}
if err != nil {
cancel()
return nil, err
}
if options.Headers == nil {
options.Headers = rtypes.HttpHeaders{}
}
req.Header = http.Header(options.Headers.AppendCookies(options.Cookies))
// Push request object.
request = &HttpRequest{
global: w.Global,
cancel: cancel,
respch: make(chan *http.Response),
errch: make(chan error),
fmt: respfmt,
sel: options.ResponseFormat,
}
go request.do(w.Client, req)
return request, nil
}
// DoHttpRequest begins and resolves an HttpRequest. Returns an error if the
// reponse did not return a successful status.
func DoHttpRequest(w *World, options rtypes.HttpOptions) (resp *rtypes.HttpResponse, err error) {
request, err := BeginHttpRequest(w, options)
if err != nil {
return nil, err
}
if resp, err = request.Resolve(); err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("%s", resp.StatusMessage)
}
return resp, nil
}