17
17
package comms
18
18
19
19
import (
20
+ "encoding/json"
20
21
"fmt"
22
+ "net"
21
23
"net/http"
22
24
"strings"
25
+ "sync"
26
+ "time"
23
27
24
28
"bytes"
29
+ "io"
25
30
"io/ioutil"
26
31
27
32
"github.com/ethereum/go-ethereum/logger"
@@ -31,10 +36,15 @@ import (
31
36
"github.com/rs/cors"
32
37
)
33
38
39
+ const (
40
+ serverIdleTimeout = 10 * time .Second // idle keep-alive connections
41
+ serverReadTimeout = 15 * time .Second // per-request read timeout
42
+ serverWriteTimeout = 15 * time .Second // per-request read timeout
43
+ )
44
+
34
45
var (
35
- // main HTTP rpc listener
36
- httpListener * stoppableTCPListener
37
- listenerStoppedError = fmt .Errorf ("Listener has stopped" )
46
+ httpServerMu sync.Mutex
47
+ httpServer * stopServer
38
48
)
39
49
40
50
type HttpConfig struct {
@@ -43,42 +53,171 @@ type HttpConfig struct {
43
53
CorsDomain string
44
54
}
45
55
56
+ // stopServer augments http.Server with idle connection tracking.
57
+ // Idle keep-alive connections are shut down when Close is called.
58
+ type stopServer struct {
59
+ * http.Server
60
+ l net.Listener
61
+ // connection tracking state
62
+ mu sync.Mutex
63
+ shutdown bool // true when Stop has returned
64
+ idle map [net.Conn ]struct {}
65
+ }
66
+
67
+ type handler struct {
68
+ codec codec.Codec
69
+ api shared.EthereumApi
70
+ }
71
+
72
+ // StartHTTP starts listening for RPC requests sent via HTTP.
46
73
func StartHttp (cfg HttpConfig , codec codec.Codec , api shared.EthereumApi ) error {
47
- if httpListener != nil {
48
- if fmt .Sprintf ("%s:%d" , cfg .ListenAddress , cfg .ListenPort ) != httpListener .Addr ().String () {
49
- return fmt .Errorf ("RPC service already running on %s " , httpListener .Addr ().String ())
74
+ httpServerMu .Lock ()
75
+ defer httpServerMu .Unlock ()
76
+
77
+ addr := fmt .Sprintf ("%s:%d" , cfg .ListenAddress , cfg .ListenPort )
78
+ if httpServer != nil {
79
+ if addr != httpServer .Addr {
80
+ return fmt .Errorf ("RPC service already running on %s " , httpServer .Addr )
50
81
}
51
82
return nil // RPC service already running on given host/port
52
83
}
53
-
54
- l , err := newStoppableTCPListener (fmt .Sprintf ("%s:%d" , cfg .ListenAddress , cfg .ListenPort ))
84
+ // Set up the request handler, wrapping it with CORS headers if configured.
85
+ handler := http .Handler (& handler {codec , api })
86
+ if len (cfg .CorsDomain ) > 0 {
87
+ opts := cors.Options {
88
+ AllowedMethods : []string {"POST" },
89
+ AllowedOrigins : strings .Split (cfg .CorsDomain , " " ),
90
+ }
91
+ handler = cors .New (opts ).Handler (handler )
92
+ }
93
+ // Start the server.
94
+ s , err := listenHTTP (addr , handler )
55
95
if err != nil {
56
96
glog .V (logger .Error ).Infof ("Can't listen on %s:%d: %v" , cfg .ListenAddress , cfg .ListenPort , err )
57
97
return err
58
98
}
59
- httpListener = l
99
+ httpServer = s
100
+ return nil
101
+ }
60
102
61
- var handler http.Handler
62
- if len (cfg .CorsDomain ) > 0 {
63
- var opts cors.Options
64
- opts .AllowedMethods = []string {"POST" }
65
- opts .AllowedOrigins = strings .Split (cfg .CorsDomain , " " )
103
+ func (h * handler ) ServeHTTP (w http.ResponseWriter , req * http.Request ) {
104
+ w .Header ().Set ("Content-Type" , "application/json" )
66
105
67
- c := cors .New (opts )
68
- handler = newStoppableHandler (c .Handler (gethHttpHandler (codec , api )), l .stop )
69
- } else {
70
- handler = newStoppableHandler (gethHttpHandler (codec , api ), l .stop )
106
+ // Limit request size to resist DoS
107
+ if req .ContentLength > maxHttpSizeReqLength {
108
+ err := fmt .Errorf ("Request too large" )
109
+ response := shared .NewRpcErrorResponse (- 1 , shared .JsonRpcVersion , - 32700 , err )
110
+ sendJSON (w , & response )
111
+ return
71
112
}
72
113
73
- go http .Serve (l , handler )
114
+ defer req .Body .Close ()
115
+ payload , err := ioutil .ReadAll (req .Body )
116
+ if err != nil {
117
+ err := fmt .Errorf ("Could not read request body" )
118
+ response := shared .NewRpcErrorResponse (- 1 , shared .JsonRpcVersion , - 32700 , err )
119
+ sendJSON (w , & response )
120
+ return
121
+ }
74
122
75
- return nil
123
+ c := h .codec .New (nil )
124
+ var rpcReq shared.Request
125
+ if err = c .Decode (payload , & rpcReq ); err == nil {
126
+ reply , err := h .api .Execute (& rpcReq )
127
+ res := shared .NewRpcResponse (rpcReq .Id , rpcReq .Jsonrpc , reply , err )
128
+ sendJSON (w , & res )
129
+ return
130
+ }
131
+
132
+ var reqBatch []shared.Request
133
+ if err = c .Decode (payload , & reqBatch ); err == nil {
134
+ resBatch := make ([]* interface {}, len (reqBatch ))
135
+ resCount := 0
136
+ for i , rpcReq := range reqBatch {
137
+ reply , err := h .api .Execute (& rpcReq )
138
+ if rpcReq .Id != nil { // this leaves nil entries in the response batch for later removal
139
+ resBatch [i ] = shared .NewRpcResponse (rpcReq .Id , rpcReq .Jsonrpc , reply , err )
140
+ resCount += 1
141
+ }
142
+ }
143
+ // make response omitting nil entries
144
+ sendJSON (w , resBatch [:resCount ])
145
+ return
146
+ }
147
+
148
+ // invalid request
149
+ err = fmt .Errorf ("Could not decode request" )
150
+ res := shared .NewRpcErrorResponse (- 1 , shared .JsonRpcVersion , - 32600 , err )
151
+ sendJSON (w , res )
76
152
}
77
153
154
+ func sendJSON (w io.Writer , v interface {}) {
155
+ if glog .V (logger .Detail ) {
156
+ if payload , err := json .MarshalIndent (v , "" , "\t " ); err == nil {
157
+ glog .Infof ("Sending payload: %s" , payload )
158
+ }
159
+ }
160
+ if err := json .NewEncoder (w ).Encode (v ); err != nil {
161
+ glog .V (logger .Error ).Infoln ("Error sending JSON:" , err )
162
+ }
163
+ }
164
+
165
+ // Stop closes all active HTTP connections and shuts down the server.
78
166
func StopHttp () {
79
- if httpListener != nil {
80
- httpListener .Stop ()
81
- httpListener = nil
167
+ httpServerMu .Lock ()
168
+ defer httpServerMu .Unlock ()
169
+ if httpServer != nil {
170
+ httpServer .Close ()
171
+ httpServer = nil
172
+ }
173
+ }
174
+
175
+ func listenHTTP (addr string , h http.Handler ) (* stopServer , error ) {
176
+ l , err := net .Listen ("tcp" , addr )
177
+ if err != nil {
178
+ return nil , err
179
+ }
180
+ s := & stopServer {l : l , idle : make (map [net.Conn ]struct {})}
181
+ s .Server = & http.Server {
182
+ Addr : addr ,
183
+ Handler : h ,
184
+ ReadTimeout : serverReadTimeout ,
185
+ WriteTimeout : serverWriteTimeout ,
186
+ ConnState : s .connState ,
187
+ }
188
+ go s .Serve (l )
189
+ return s , nil
190
+ }
191
+
192
+ func (s * stopServer ) connState (c net.Conn , state http.ConnState ) {
193
+ s .mu .Lock ()
194
+ defer s .mu .Unlock ()
195
+ // Close c immediately if we're past shutdown.
196
+ if s .shutdown {
197
+ if state != http .StateClosed {
198
+ c .Close ()
199
+ }
200
+ return
201
+ }
202
+ if state == http .StateIdle {
203
+ s .idle [c ] = struct {}{}
204
+ } else {
205
+ delete (s .idle , c )
206
+ }
207
+ }
208
+
209
+ func (s * stopServer ) Close () {
210
+ s .mu .Lock ()
211
+ defer s .mu .Unlock ()
212
+ // Shut down the acceptor. No new connections can be created.
213
+ s .l .Close ()
214
+ // Drop all idle connections. Non-idle connections will be
215
+ // closed by connState as soon as they become idle.
216
+ s .shutdown = true
217
+ for c := range s .idle {
218
+ glog .V (logger .Detail ).Infof ("closing idle connection %v" , c .RemoteAddr ())
219
+ c .Close ()
220
+ delete (s .idle , c )
82
221
}
83
222
}
84
223
0 commit comments