You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

210 regels
5.6 KiB

  1. // Copyright 2018 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package acme
  5. import (
  6. "context"
  7. "fmt"
  8. "io/ioutil"
  9. "net/http"
  10. "net/http/httptest"
  11. "reflect"
  12. "strings"
  13. "testing"
  14. "time"
  15. )
  16. func TestDefaultBackoff(t *testing.T) {
  17. tt := []struct {
  18. nretry int
  19. retryAfter string // Retry-After header
  20. out time.Duration // expected min; max = min + jitter
  21. }{
  22. {-1, "", time.Second}, // verify the lower bound is 1
  23. {0, "", time.Second}, // verify the lower bound is 1
  24. {100, "", 10 * time.Second}, // verify the ceiling
  25. {1, "3600", time.Hour}, // verify the header value is used
  26. {1, "", 1 * time.Second},
  27. {2, "", 2 * time.Second},
  28. {3, "", 4 * time.Second},
  29. {4, "", 8 * time.Second},
  30. }
  31. for i, test := range tt {
  32. r := httptest.NewRequest("GET", "/", nil)
  33. resp := &http.Response{Header: http.Header{}}
  34. if test.retryAfter != "" {
  35. resp.Header.Set("Retry-After", test.retryAfter)
  36. }
  37. d := defaultBackoff(test.nretry, r, resp)
  38. max := test.out + time.Second // + max jitter
  39. if d < test.out || max < d {
  40. t.Errorf("%d: defaultBackoff(%v) = %v; want between %v and %v", i, test.nretry, d, test.out, max)
  41. }
  42. }
  43. }
  44. func TestErrorResponse(t *testing.T) {
  45. s := `{
  46. "status": 400,
  47. "type": "urn:acme:error:xxx",
  48. "detail": "text"
  49. }`
  50. res := &http.Response{
  51. StatusCode: 400,
  52. Status: "400 Bad Request",
  53. Body: ioutil.NopCloser(strings.NewReader(s)),
  54. Header: http.Header{"X-Foo": {"bar"}},
  55. }
  56. err := responseError(res)
  57. v, ok := err.(*Error)
  58. if !ok {
  59. t.Fatalf("err = %+v (%T); want *Error type", err, err)
  60. }
  61. if v.StatusCode != 400 {
  62. t.Errorf("v.StatusCode = %v; want 400", v.StatusCode)
  63. }
  64. if v.ProblemType != "urn:acme:error:xxx" {
  65. t.Errorf("v.ProblemType = %q; want urn:acme:error:xxx", v.ProblemType)
  66. }
  67. if v.Detail != "text" {
  68. t.Errorf("v.Detail = %q; want text", v.Detail)
  69. }
  70. if !reflect.DeepEqual(v.Header, res.Header) {
  71. t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header)
  72. }
  73. }
  74. func TestPostWithRetries(t *testing.T) {
  75. var count int
  76. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  77. count++
  78. w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count))
  79. if r.Method == "HEAD" {
  80. // We expect the client to do 2 head requests to fetch
  81. // nonces, one to start and another after getting badNonce
  82. return
  83. }
  84. head, err := decodeJWSHead(r)
  85. switch {
  86. case err != nil:
  87. t.Errorf("decodeJWSHead: %v", err)
  88. case head.Nonce == "":
  89. t.Error("head.Nonce is empty")
  90. case head.Nonce == "nonce1":
  91. // Return a badNonce error to force the call to retry.
  92. w.Header().Set("Retry-After", "0")
  93. w.WriteHeader(http.StatusBadRequest)
  94. w.Write([]byte(`{"type":"urn:ietf:params:acme:error:badNonce"}`))
  95. return
  96. }
  97. // Make client.Authorize happy; we're not testing its result.
  98. w.WriteHeader(http.StatusCreated)
  99. w.Write([]byte(`{"status":"valid"}`))
  100. }))
  101. defer ts.Close()
  102. client := &Client{Key: testKey, dir: &Directory{AuthzURL: ts.URL}}
  103. // This call will fail with badNonce, causing a retry
  104. if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
  105. t.Errorf("client.Authorize 1: %v", err)
  106. }
  107. if count != 4 {
  108. t.Errorf("total requests count: %d; want 4", count)
  109. }
  110. }
  111. func TestRetryErrorType(t *testing.T) {
  112. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  113. w.Header().Set("Replay-Nonce", "nonce")
  114. w.WriteHeader(http.StatusTooManyRequests)
  115. w.Write([]byte(`{"type":"rateLimited"}`))
  116. }))
  117. defer ts.Close()
  118. client := &Client{
  119. Key: testKey,
  120. RetryBackoff: func(n int, r *http.Request, res *http.Response) time.Duration {
  121. // Do no retries.
  122. return 0
  123. },
  124. dir: &Directory{AuthzURL: ts.URL},
  125. }
  126. t.Run("post", func(t *testing.T) {
  127. testRetryErrorType(t, func() error {
  128. _, err := client.Authorize(context.Background(), "example.com")
  129. return err
  130. })
  131. })
  132. t.Run("get", func(t *testing.T) {
  133. testRetryErrorType(t, func() error {
  134. _, err := client.GetAuthorization(context.Background(), ts.URL)
  135. return err
  136. })
  137. })
  138. }
  139. func testRetryErrorType(t *testing.T, callClient func() error) {
  140. t.Helper()
  141. err := callClient()
  142. if err == nil {
  143. t.Fatal("client.Authorize returned nil error")
  144. }
  145. acmeErr, ok := err.(*Error)
  146. if !ok {
  147. t.Fatalf("err is %v (%T); want *Error", err, err)
  148. }
  149. if acmeErr.StatusCode != http.StatusTooManyRequests {
  150. t.Errorf("acmeErr.StatusCode = %d; want %d", acmeErr.StatusCode, http.StatusTooManyRequests)
  151. }
  152. if acmeErr.ProblemType != "rateLimited" {
  153. t.Errorf("acmeErr.ProblemType = %q; want 'rateLimited'", acmeErr.ProblemType)
  154. }
  155. }
  156. func TestRetryBackoffArgs(t *testing.T) {
  157. const resCode = http.StatusInternalServerError
  158. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  159. w.Header().Set("Replay-Nonce", "test-nonce")
  160. w.WriteHeader(resCode)
  161. }))
  162. defer ts.Close()
  163. // Canceled in backoff.
  164. ctx, cancel := context.WithCancel(context.Background())
  165. var nretry int
  166. backoff := func(n int, r *http.Request, res *http.Response) time.Duration {
  167. nretry++
  168. if n != nretry {
  169. t.Errorf("n = %d; want %d", n, nretry)
  170. }
  171. if nretry == 3 {
  172. cancel()
  173. }
  174. if r == nil {
  175. t.Error("r is nil")
  176. }
  177. if res.StatusCode != resCode {
  178. t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, resCode)
  179. }
  180. return time.Millisecond
  181. }
  182. client := &Client{
  183. Key: testKey,
  184. RetryBackoff: backoff,
  185. dir: &Directory{AuthzURL: ts.URL},
  186. }
  187. if _, err := client.Authorize(ctx, "example.com"); err == nil {
  188. t.Error("err is nil")
  189. }
  190. if nretry != 3 {
  191. t.Errorf("nretry = %d; want 3", nretry)
  192. }
  193. }