aboutsummaryrefslogtreecommitdiffstats
path: root/rpc/http.go
blob: af3d29014c637d3b6f43d64147d61b44bb1b8e0d (plain) (blame)
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
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package rpc

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "strings"

    "io"

    "github.com/rs/cors"
)

const (
    maxHTTPRequestContentLength = 1024 * 128
)

// httpClient connects to a geth RPC server over HTTP.
type httpClient struct {
    endpoint *url.URL // HTTP-RPC server endpoint
    lastRes  []byte   // HTTP requests are synchronous, store last response
}

// NewHTTPClient create a new RPC clients that connection to a geth RPC server
// over HTTP.
func NewHTTPClient(endpoint string) (Client, error) {
    url, err := url.Parse(endpoint)
    if err != nil {
        return nil, err
    }
    return &httpClient{endpoint: url}, nil
}

// Send will serialize the given msg to JSON and sends it to the RPC server.
// Since HTTP is synchronous the response is stored until Recv is called.
func (client *httpClient) Send(msg interface{}) error {
    var body []byte
    var err error

    client.lastRes = nil

    if body, err = json.Marshal(msg); err != nil {
        return err
    }

    httpReq, err := http.NewRequest("POST", client.endpoint.String(), bytes.NewBuffer(body))
    if err != nil {
        return err
    }
    httpReq.Header.Set("Content-Type", "application/json")

    httpClient := http.Client{}
    resp, err := httpClient.Do(httpReq)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode == http.StatusOK {
        client.lastRes, err = ioutil.ReadAll(resp.Body)
        return err
    }

    return fmt.Errorf("unable to handle request")
}

// Recv will try to deserialize the last received response into the given msg.
func (client *httpClient) Recv(msg interface{}) error {
    return json.Unmarshal(client.lastRes, &msg)
}

// Close is not necessary for httpClient
func (client *httpClient) Close() {
}

// SupportedModules will return the collection of offered RPC modules.
func (client *httpClient) SupportedModules() (map[string]string, error) {
    return SupportedModules(client)
}

// httpReadWriteNopCloser wraps a io.Reader and io.Writer with a NOP Close method.
type httpReadWriteNopCloser struct {
    io.Reader
    io.Writer
}

// Close does nothing and returns always nil
func (t *httpReadWriteNopCloser) Close() error {
    return nil
}

// newJSONHTTPHandler creates a HTTP handler that will parse incoming JSON requests,
// send the request to the given API provider and sends the response back to the caller.
func newJSONHTTPHandler(srv *Server) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if r.ContentLength > maxHTTPRequestContentLength {
            http.Error(w,
                fmt.Sprintf("content length too large (%d>%d)", r.ContentLength, maxHTTPRequestContentLength),
                http.StatusRequestEntityTooLarge)
            return
        }

        w.Header().Set("content-type", "application/json")

        // create a codec that reads direct from the request body until
        // EOF and writes the response to w and order the server to process
        // a single request.
        codec := NewJSONCodec(&httpReadWriteNopCloser{r.Body, w})
        defer codec.Close()
        srv.ServeSingleRequest(codec)
    }
}

// NewHTTPServer creates a new HTTP RPC server around an API provider.
func NewHTTPServer(corsString string, srv *Server) *http.Server {
    var allowedOrigins []string
    for _, domain := range strings.Split(corsString, ",") {
        allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain))
    }

    c := cors.New(cors.Options{
        AllowedOrigins: allowedOrigins,
        AllowedMethods: []string{"POST", "GET"},
    })

    handler := c.Handler(newJSONHTTPHandler(srv))

    return &http.Server{
        Handler: handler,
    }
}