-
Notifications
You must be signed in to change notification settings - Fork 11
/
http.go
145 lines (123 loc) · 3.4 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
package jsonrpc2
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
)
const httpContentType = "application/json"
var _ http.Handler = &HTTPServer{}
// HTTPServer provides a JSONRPC2 server over HTTP by implementing http.Handler.
type HTTPServer struct {
Server
// MaxContentLength is the request size limit (optional)
MaxContentLength int64
}
func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// TODO: Convert http.Error(...) output into actual JSONRPC error responses?
if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" {
// Ignore empty GET requests
return
}
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if h.MaxContentLength > 0 && r.ContentLength > h.MaxContentLength {
http.Error(w, "request too large", http.StatusRequestEntityTooLarge)
return
}
var body io.Reader = r.Body
if h.MaxContentLength > 0 {
body = io.LimitReader(r.Body, h.MaxContentLength)
}
codec := &jsonCodec{
rwc: rwc{body, w, r.Body},
remoteAddr: r.RemoteAddr,
}
defer codec.Close()
msg, err := codec.ReadMessage()
if err != nil {
err = &ErrResponse{
Code: ErrCodeParse,
Message: fmt.Sprintf("failed to parse request: %s", err),
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("content-type", httpContentType)
resp := h.Server.Handle(r.Context(), msg)
err = codec.WriteMessage(resp)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
var _ Service = &HTTPService{}
type HTTPService struct {
Client
HTTPClient http.Client
// Endpoint is the HTTP URL to dial for RPC calls.
Endpoint string
// MaxContentLength is the response size limit (optional)
MaxContentLength int64
}
func (service *HTTPService) Call(ctx context.Context, result interface{}, method string, params ...interface{}) error {
msg, err := service.Client.Request(method, params...)
if err != nil {
return err
}
body, err := json.Marshal(msg)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, service.Endpoint, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", httpContentType)
req.Header.Set("Accept", httpContentType)
req = req.WithContext(ctx)
resp, err := service.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return HTTPRequestError{
Response: resp,
Reason: fmt.Sprintf("bad status code: %d", resp.StatusCode),
}
}
if service.MaxContentLength > 0 && resp.ContentLength > service.MaxContentLength {
return HTTPRequestError{
Response: resp,
Reason: "response too large",
}
}
var r io.Reader = resp.Body
if service.MaxContentLength > 0 {
r = io.LimitReader(resp.Body, service.MaxContentLength)
}
var respMsg Message
if err := json.NewDecoder(r).Decode(&respMsg); err != nil {
return err
}
if respMsg.Response == nil {
return HTTPRequestError{
Response: resp,
Reason: "missing response in RPC message",
}
}
return respMsg.Response.UnmarshalResult(result)
}
// HTTPRequestError is used when RPC over HTTP encounters an error during transport.
type HTTPRequestError struct {
Response *http.Response
Reason string
}
func (err HTTPRequestError) Error() string {
return fmt.Sprintf("http rpc request error: %s", err.Reason)
}