client.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. // Copyright 2016 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package jsonclient
  15. import (
  16. "bytes"
  17. "context"
  18. "crypto"
  19. "encoding/json"
  20. "errors"
  21. "fmt"
  22. "io/ioutil"
  23. "log"
  24. "math/rand"
  25. "net/http"
  26. "net/url"
  27. "strconv"
  28. "strings"
  29. "time"
  30. ct "github.com/google/certificate-transparency-go"
  31. "github.com/google/certificate-transparency-go/x509"
  32. "golang.org/x/net/context/ctxhttp"
  33. )
  34. const maxJitter = 250 * time.Millisecond
  35. type backoffer interface {
  36. // set adjusts/increases the current backoff interval (typically on retryable failure);
  37. // if the optional parameter is provided, this will be used as the interval if it is greater
  38. // than the currently set interval. Returns the current wait period so that it can be
  39. // logged along with any error message.
  40. set(*time.Duration) time.Duration
  41. // decreaseMultiplier reduces the current backoff multiplier, typically on success.
  42. decreaseMultiplier()
  43. // until returns the time until which the client should wait before making a request,
  44. // it may be in the past in which case it should be ignored.
  45. until() time.Time
  46. }
  47. // JSONClient provides common functionality for interacting with a JSON server
  48. // that uses cryptographic signatures.
  49. type JSONClient struct {
  50. uri string // the base URI of the server. e.g. https://ct.googleapis/pilot
  51. httpClient *http.Client // used to interact with the server via HTTP
  52. Verifier *ct.SignatureVerifier // nil for no verification (e.g. no public key available)
  53. logger Logger // interface to use for logging warnings and errors
  54. backoff backoffer // object used to store and calculate backoff information
  55. }
  56. // Logger is a simple logging interface used to log internal errors and warnings
  57. type Logger interface {
  58. // Printf formats and logs a message
  59. Printf(string, ...interface{})
  60. }
  61. // Options are the options for creating a new JSONClient.
  62. type Options struct {
  63. // Interface to use for logging warnings and errors, if nil the
  64. // standard library log package will be used.
  65. Logger Logger
  66. // PEM format public key to use for signature verification.
  67. PublicKey string
  68. // DER format public key to use for signature verification.
  69. PublicKeyDER []byte
  70. }
  71. // ParsePublicKey parses and returns the public key contained in opts.
  72. // If both opts.PublicKey and opts.PublicKeyDER are set, PublicKeyDER is used.
  73. // If neither is set, nil will be returned.
  74. func (opts *Options) ParsePublicKey() (crypto.PublicKey, error) {
  75. if len(opts.PublicKeyDER) > 0 {
  76. return x509.ParsePKIXPublicKey(opts.PublicKeyDER)
  77. }
  78. if opts.PublicKey != "" {
  79. pubkey, _ /* keyhash */, rest, err := ct.PublicKeyFromPEM([]byte(opts.PublicKey))
  80. if err != nil {
  81. return nil, err
  82. }
  83. if len(rest) > 0 {
  84. return nil, errors.New("extra data found after PEM key decoded")
  85. }
  86. return pubkey, nil
  87. }
  88. return nil, nil
  89. }
  90. type basicLogger struct{}
  91. func (bl *basicLogger) Printf(msg string, args ...interface{}) {
  92. log.Printf(msg, args...)
  93. }
  94. // New constructs a new JSONClient instance, for the given base URI, using the
  95. // given http.Client object (if provided) and the Options object.
  96. // If opts does not specify a public key, signatures will not be verified.
  97. func New(uri string, hc *http.Client, opts Options) (*JSONClient, error) {
  98. pubkey, err := opts.ParsePublicKey()
  99. if err != nil {
  100. return nil, fmt.Errorf("invalid public key: %v", err)
  101. }
  102. var verifier *ct.SignatureVerifier
  103. if pubkey != nil {
  104. var err error
  105. verifier, err = ct.NewSignatureVerifier(pubkey)
  106. if err != nil {
  107. return nil, err
  108. }
  109. }
  110. if hc == nil {
  111. hc = new(http.Client)
  112. }
  113. logger := opts.Logger
  114. if logger == nil {
  115. logger = &basicLogger{}
  116. }
  117. return &JSONClient{
  118. uri: strings.TrimRight(uri, "/"),
  119. httpClient: hc,
  120. Verifier: verifier,
  121. logger: logger,
  122. backoff: &backoff{},
  123. }, nil
  124. }
  125. // BaseURI returns the base URI that the JSONClient makes queries to.
  126. func (c *JSONClient) BaseURI() string {
  127. return c.uri
  128. }
  129. // GetAndParse makes a HTTP GET call to the given path, and attempta to parse
  130. // the response as a JSON representation of the rsp structure. Returns the
  131. // http.Response, the body of the response, and an error. Note that the
  132. // returned http.Response can be non-nil even when an error is returned,
  133. // in particular when the HTTP status is not OK or when the JSON parsing fails.
  134. func (c *JSONClient) GetAndParse(ctx context.Context, path string, params map[string]string, rsp interface{}) (*http.Response, []byte, error) {
  135. if ctx == nil {
  136. return nil, nil, errors.New("context.Context required")
  137. }
  138. // Build a GET request with URL-encoded parameters.
  139. vals := url.Values{}
  140. for k, v := range params {
  141. vals.Add(k, v)
  142. }
  143. fullURI := fmt.Sprintf("%s%s?%s", c.uri, path, vals.Encode())
  144. httpReq, err := http.NewRequest(http.MethodGet, fullURI, nil)
  145. if err != nil {
  146. return nil, nil, err
  147. }
  148. httpRsp, err := ctxhttp.Do(ctx, c.httpClient, httpReq)
  149. if err != nil {
  150. return nil, nil, err
  151. }
  152. // Read everything now so http.Client can reuse the connection.
  153. body, err := ioutil.ReadAll(httpRsp.Body)
  154. httpRsp.Body.Close()
  155. if err != nil {
  156. return httpRsp, body, fmt.Errorf("failed to read response body: %v", err)
  157. }
  158. if httpRsp.StatusCode != http.StatusOK {
  159. return httpRsp, body, fmt.Errorf("got HTTP Status %q", httpRsp.Status)
  160. }
  161. if err := json.NewDecoder(bytes.NewReader(body)).Decode(rsp); err != nil {
  162. return httpRsp, body, err
  163. }
  164. return httpRsp, body, nil
  165. }
  166. // PostAndParse makes a HTTP POST call to the given path, including the request
  167. // parameters, and attempts to parse the response as a JSON representation of
  168. // the rsp structure. Returns the http.Response, the body of the response, and
  169. // an error. Note that the returned http.Response can be non-nil even when an
  170. // error is returned, in particular when the HTTP status is not OK or when the
  171. // JSON parsing fails.
  172. func (c *JSONClient) PostAndParse(ctx context.Context, path string, req, rsp interface{}) (*http.Response, []byte, error) {
  173. if ctx == nil {
  174. return nil, nil, errors.New("context.Context required")
  175. }
  176. // Build a POST request with JSON body.
  177. postBody, err := json.Marshal(req)
  178. if err != nil {
  179. return nil, nil, err
  180. }
  181. fullURI := fmt.Sprintf("%s%s", c.uri, path)
  182. httpReq, err := http.NewRequest(http.MethodPost, fullURI, bytes.NewReader(postBody))
  183. if err != nil {
  184. return nil, nil, err
  185. }
  186. httpReq.Header.Set("Content-Type", "application/json")
  187. httpRsp, err := ctxhttp.Do(ctx, c.httpClient, httpReq)
  188. // Read all of the body, if there is one, so that the http.Client can do Keep-Alive.
  189. var body []byte
  190. if httpRsp != nil {
  191. body, err = ioutil.ReadAll(httpRsp.Body)
  192. httpRsp.Body.Close()
  193. }
  194. if err != nil {
  195. return httpRsp, body, err
  196. }
  197. if httpRsp.StatusCode == http.StatusOK {
  198. if err = json.Unmarshal(body, &rsp); err != nil {
  199. return httpRsp, body, err
  200. }
  201. }
  202. return httpRsp, body, nil
  203. }
  204. // waitForBackoff blocks until the defined backoff interval or context has expired, if the returned
  205. // not before time is in the past it returns immediately.
  206. func (c *JSONClient) waitForBackoff(ctx context.Context) error {
  207. dur := time.Until(c.backoff.until().Add(time.Millisecond * time.Duration(rand.Intn(int(maxJitter.Seconds()*1000)))))
  208. if dur < 0 {
  209. dur = 0
  210. }
  211. backoffTimer := time.NewTimer(dur)
  212. select {
  213. case <-ctx.Done():
  214. return ctx.Err()
  215. case <-backoffTimer.C:
  216. }
  217. return nil
  218. }
  219. // PostAndParseWithRetry makes a HTTP POST call, but retries (with backoff) on
  220. // retriable errors; the caller should set a deadline on the provided context
  221. // to prevent infinite retries. Return values are as for PostAndParse.
  222. func (c *JSONClient) PostAndParseWithRetry(ctx context.Context, path string, req, rsp interface{}) (*http.Response, []byte, error) {
  223. if ctx == nil {
  224. return nil, nil, errors.New("context.Context required")
  225. }
  226. for {
  227. httpRsp, body, err := c.PostAndParse(ctx, path, req, rsp)
  228. if err != nil {
  229. // Don't retry context errors.
  230. if err == context.Canceled || err == context.DeadlineExceeded {
  231. return nil, nil, err
  232. }
  233. wait := c.backoff.set(nil)
  234. c.logger.Printf("Request failed, backing-off for %s: %s", wait, err)
  235. } else {
  236. switch {
  237. case httpRsp.StatusCode == http.StatusOK:
  238. return httpRsp, body, nil
  239. case httpRsp.StatusCode == http.StatusRequestTimeout:
  240. // Request timeout, retry immediately
  241. c.logger.Printf("Request timed out, retrying immediately")
  242. case httpRsp.StatusCode == http.StatusServiceUnavailable:
  243. var backoff *time.Duration
  244. // Retry-After may be either a number of seconds as a int or a RFC 1123
  245. // date string (RFC 7231 Section 7.1.3)
  246. if retryAfter := httpRsp.Header.Get("Retry-After"); retryAfter != "" {
  247. if seconds, err := strconv.Atoi(retryAfter); err == nil {
  248. b := time.Duration(seconds) * time.Second
  249. backoff = &b
  250. } else if date, err := time.Parse(time.RFC1123, retryAfter); err == nil {
  251. b := date.Sub(time.Now())
  252. backoff = &b
  253. }
  254. }
  255. wait := c.backoff.set(backoff)
  256. c.logger.Printf("Request failed, backing-off for %s: got HTTP status %s", wait, httpRsp.Status)
  257. default:
  258. return httpRsp, body, fmt.Errorf("got HTTP Status %q", httpRsp.Status)
  259. }
  260. }
  261. if err := c.waitForBackoff(ctx); err != nil {
  262. return nil, nil, err
  263. }
  264. }
  265. }