patient_http 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +8 -7
  3. data/CHANGELOG.md +29 -0
  4. data/README.md +539 -515
  5. data/VERSION +1 -1
  6. data/lib/patient_http/callback_args.rb +53 -48
  7. data/lib/patient_http/callback_validator.rb +11 -7
  8. data/lib/patient_http/class_helper.rb +6 -7
  9. data/lib/patient_http/client.rb +28 -22
  10. data/lib/patient_http/client_pool.rb +134 -39
  11. data/lib/patient_http/completion_executor.rb +20 -20
  12. data/lib/patient_http/configuration.rb +367 -119
  13. data/lib/patient_http/connection_endpoint.rb +150 -0
  14. data/lib/patient_http/encryptor.rb +28 -18
  15. data/lib/patient_http/error.rb +24 -18
  16. data/lib/patient_http/external_storage.rb +42 -38
  17. data/lib/patient_http/http_error.rb +30 -26
  18. data/lib/patient_http/http_headers.rb +35 -29
  19. data/lib/patient_http/immediate_retries.rb +98 -0
  20. data/lib/patient_http/inline_task_handler.rb +15 -10
  21. data/lib/patient_http/lifecycle_manager.rb +39 -40
  22. data/lib/patient_http/outgoing_request.rb +25 -23
  23. data/lib/patient_http/payload.rb +28 -26
  24. data/lib/patient_http/payload_store/active_record_store.rb +31 -34
  25. data/lib/patient_http/payload_store/base.rb +42 -46
  26. data/lib/patient_http/payload_store/file_store.rb +22 -26
  27. data/lib/patient_http/payload_store/redis_store.rb +28 -34
  28. data/lib/patient_http/payload_store/s3_store.rb +25 -28
  29. data/lib/patient_http/payload_store.rb +2 -0
  30. data/lib/patient_http/processor.rb +111 -79
  31. data/lib/patient_http/processor_observer.rb +65 -59
  32. data/lib/patient_http/rails/engine.rb +13 -8
  33. data/lib/patient_http/redirect_error.rb +50 -41
  34. data/lib/patient_http/redirect_helper.rb +38 -38
  35. data/lib/patient_http/request.rb +70 -46
  36. data/lib/patient_http/request_error.rb +47 -42
  37. data/lib/patient_http/request_helper.rb +142 -119
  38. data/lib/patient_http/request_preparer.rb +13 -10
  39. data/lib/patient_http/request_task.rb +113 -84
  40. data/lib/patient_http/request_template.rb +87 -64
  41. data/lib/patient_http/response.rb +58 -52
  42. data/lib/patient_http/response_reader.rb +66 -65
  43. data/lib/patient_http/secret_manager.rb +34 -30
  44. data/lib/patient_http/secret_reference.rb +33 -26
  45. data/lib/patient_http/synchronous_executor.rb +67 -95
  46. data/lib/patient_http/task_handler.rb +23 -19
  47. data/lib/patient_http/time_helper.rb +8 -8
  48. data/lib/patient_http.rb +311 -186
  49. data/patient_http.gemspec +3 -2
  50. metadata +21 -5
@@ -1,31 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PatientHttp
4
- # Mixin that provides a compact API for scheduling async HTTP requests.
4
+ # Adds methods that make async HTTP requests to a class.
5
5
  #
6
- # Include this module in your class to get instance-level and class-level helpers for building
7
- # requests and dispatching them through a registered handler.
6
+ # Include this module to get the `async_get`, `async_head`, `async_post`,
7
+ # `async_put`, `async_patch`, `async_delete`, `async_query`, and
8
+ # `async_request` methods. The methods are available as instance methods and
9
+ # as class methods. They send each request to the registered request handler,
10
+ # so your code doesn't depend on the job system.
8
11
  #
9
- # This module allows you to use the same interface for making HTTP requests while swapping out
10
- # the underlying queueing mechanism for handling responses asynchronously. By registering a
11
- # custom handler, you can integrate with any job queue system (Sidekiq, Solid Queue, etc.)
12
- # without changing your application code that makes HTTP requests. This decouples your request
13
- # interface from your async processing infrastructure.
12
+ # To use the module:
14
13
  #
15
- # The common workflow is:
16
- # 1. Register a global request handler with {PatientHttp.register_handler}.
14
+ # 1. Load a job system integration gem, or register a request handler with
15
+ # {PatientHttp.register_handler}.
17
16
  # 2. Include this module in a class.
18
- # 3. Optionally configure defaults with {.request_template}.
19
- # 4. Call `async_get`, `async_head`, `async_post`, `async_put`, `async_patch`,
20
- # `async_delete`, `async_query`, or `async_request`.
17
+ # 3. Optional: Set shared request options with `request_template`.
18
+ # 4. Call the `async_*` methods.
21
19
  #
22
- # @example Register a handler
23
- # PatientHttp.register_handler do |request:, callback:, callback_args: nil, raise_error_responses: nil|
24
- # # Dispatch the request through your app-specific task/enqueue operation
25
- # # and return the request id
26
- # end
27
- #
28
- # @example Include in a class and enqueue requests
20
+ # @example Include the module and make requests
29
21
  # class ApiClient
30
22
  # include PatientHttp::RequestHelper
31
23
  #
@@ -39,11 +31,9 @@ module PatientHttp
39
31
  extend self
40
32
 
41
33
  class << self
42
- # Hooks helper behavior into the including class.
34
+ # Adds the class methods to a class that includes this module.
43
35
  #
44
- # Extends the class with {.ClassMethods} and initializes template storage.
45
- #
46
- # @param base [Class] class including this module
36
+ # @param base [Class] The class that includes this module.
47
37
  # @return [void]
48
38
  def included(base)
49
39
  base.extend(ClassMethods)
@@ -51,94 +41,107 @@ module PatientHttp
51
41
  end
52
42
  end
53
43
 
44
+ # The `async_*` methods for each HTTP method. They're available as instance
45
+ # methods and as class methods.
54
46
  module HttpMethodHelpers
55
- # Enqueues an asynchronous HTTP GET request.
47
+ # Makes an async GET request.
56
48
  #
57
- # @param uri [String] absolute URL or path (when using a request template)
58
- # @param callback [Class, String] callback class to handle the response
59
- # @param kwargs [Hash] forwarded to `async_request`
60
- # @return [Object] return value from the registered request handler
49
+ # @param uri [String] The absolute URL, or a path relative to the base URL of
50
+ # the request template.
51
+ # @param callback [Class, String] The callback service class, or its name.
52
+ # @param kwargs [Hash] The request options. See `async_request`.
53
+ # @return [Object] The value that the request handler returns.
61
54
  def async_get(uri, callback:, **kwargs)
62
55
  async_request(:get, uri, callback: callback, **kwargs)
63
56
  end
64
57
 
65
- # Enqueues an asynchronous HTTP HEAD request.
58
+ # Makes an async HEAD request.
66
59
  #
67
- # @param uri [String] absolute URL or path (when using a request template)
68
- # @param callback [Class, String] callback class to handle the response
69
- # @param kwargs [Hash] forwarded to `async_request`
70
- # @return [Object] return value from the registered request handler
60
+ # @param uri [String] The absolute URL, or a path relative to the base URL of
61
+ # the request template.
62
+ # @param callback [Class, String] The callback service class, or its name.
63
+ # @param kwargs [Hash] The request options. See `async_request`.
64
+ # @return [Object] The value that the request handler returns.
71
65
  def async_head(uri, callback:, **kwargs)
72
66
  async_request(:head, uri, callback: callback, **kwargs)
73
67
  end
74
68
 
75
- # Enqueues an asynchronous HTTP POST request.
69
+ # Makes an async POST request.
76
70
  #
77
- # @param uri [String] absolute URL or path (when using a request template)
78
- # @param callback [Class, String] callback class to handle the response
79
- # @param kwargs [Hash] forwarded to `async_request`
80
- # @return [Object] return value from the registered request handler
71
+ # @param uri [String] The absolute URL, or a path relative to the base URL of
72
+ # the request template.
73
+ # @param callback [Class, String] The callback service class, or its name.
74
+ # @param kwargs [Hash] The request options. See `async_request`.
75
+ # @return [Object] The value that the request handler returns.
81
76
  def async_post(uri, callback:, **kwargs)
82
77
  async_request(:post, uri, callback: callback, **kwargs)
83
78
  end
84
79
 
85
- # Enqueues an asynchronous HTTP PUT request.
80
+ # Makes an async PUT request.
86
81
  #
87
- # @param uri [String] absolute URL or path (when using a request template)
88
- # @param callback [Class, String] callback class to handle the response
89
- # @param kwargs [Hash] forwarded to `async_request`
90
- # @return [Object] return value from the registered request handler
82
+ # @param uri [String] The absolute URL, or a path relative to the base URL of
83
+ # the request template.
84
+ # @param callback [Class, String] The callback service class, or its name.
85
+ # @param kwargs [Hash] The request options. See `async_request`.
86
+ # @return [Object] The value that the request handler returns.
91
87
  def async_put(uri, callback:, **kwargs)
92
88
  async_request(:put, uri, callback: callback, **kwargs)
93
89
  end
94
90
 
95
- # Enqueues an asynchronous HTTP PATCH request.
91
+ # Makes an async PATCH request.
96
92
  #
97
- # @param uri [String] absolute URL or path (when using a request template)
98
- # @param callback [Class, String] callback class to handle the response
99
- # @param kwargs [Hash] forwarded to `async_request`
100
- # @return [Object] return value from the registered request handler
93
+ # @param uri [String] The absolute URL, or a path relative to the base URL of
94
+ # the request template.
95
+ # @param callback [Class, String] The callback service class, or its name.
96
+ # @param kwargs [Hash] The request options. See `async_request`.
97
+ # @return [Object] The value that the request handler returns.
101
98
  def async_patch(uri, callback:, **kwargs)
102
99
  async_request(:patch, uri, callback: callback, **kwargs)
103
100
  end
104
101
 
105
- # Enqueues an asynchronous HTTP DELETE request.
102
+ # Makes an async DELETE request.
106
103
  #
107
- # @param uri [String] absolute URL or path (when using a request template)
108
- # @param callback [Class, String] callback class to handle the response
109
- # @param kwargs [Hash] forwarded to `async_request`
110
- # @return [Object] return value from the registered request handler
104
+ # @param uri [String] The absolute URL, or a path relative to the base URL of
105
+ # the request template.
106
+ # @param callback [Class, String] The callback service class, or its name.
107
+ # @param kwargs [Hash] The request options. See `async_request`.
108
+ # @return [Object] The value that the request handler returns.
111
109
  def async_delete(uri, callback:, **kwargs)
112
110
  async_request(:delete, uri, callback: callback, **kwargs)
113
111
  end
114
112
 
115
- # Enqueues an asynchronous HTTP QUERY request.
113
+ # Makes an async QUERY request.
116
114
  #
117
- # @param uri [String] absolute URL or path (when using a request template)
118
- # @param callback [Class, String] callback class to handle the response
119
- # @param kwargs [Hash] forwarded to `async_request`
120
- # @return [Object] return value from the registered request handler
115
+ # @param uri [String] The absolute URL, or a path relative to the base URL of
116
+ # the request template.
117
+ # @param callback [Class, String] The callback service class, or its name.
118
+ # @param kwargs [Hash] The request options. See `async_request`.
119
+ # @return [Object] The value that the request handler returns.
121
120
  def async_query(uri, callback:, **kwargs)
122
121
  async_request(:query, uri, callback: callback, **kwargs)
123
122
  end
124
123
  end
125
124
 
125
+ # The class methods that this module adds to a class.
126
126
  module ClassMethods
127
127
  include HttpMethodHelpers
128
128
 
129
- # Defines a default request template for this class.
129
+ # Sets the default request options for this class and its subclasses.
130
130
  #
131
- # Requests created with the helper methods merge these defaults unless explicitly overridden.
131
+ # The `async_*` methods build each request from this template. Options
132
+ # passed to a method override the template.
132
133
  #
133
- # @param base_url [String, nil] optional base URL used to resolve relative request URLs
134
- # @param headers [Hash] default headers for requests
135
- # @param params [Hash, nil] default query parameters for requests
136
- # @param timeout [Float] default timeout in seconds
137
- # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] default names of
138
- # preprocessors registered on the configuration to apply to requests
139
- # @param processor [String, Symbol, nil] default processor name for requests
134
+ # @param base_url [String, nil] The base URL that relative paths are joined
135
+ # with.
136
+ # @param headers [Hash] The default request headers.
137
+ # @param params [Hash, nil] The default query parameters.
138
+ # @param timeout [Numeric, nil] The default request timeout in seconds. If
139
+ # `nil`, the configured `request_timeout` applies.
140
+ # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] The names
141
+ # of the default preprocessors.
142
+ # @param processor [String, Symbol, nil] The name of the default processor.
140
143
  # @return [void]
141
- def request_template(base_url: nil, headers: {}, params: nil, timeout: 30, preprocessors: nil, processor: nil)
144
+ def request_template(base_url: nil, headers: {}, params: nil, timeout: nil, preprocessors: nil, processor: nil)
142
145
  @patient_http_request_template = RequestTemplate.new(
143
146
  base_url: base_url,
144
147
  headers: headers,
@@ -149,31 +152,39 @@ module PatientHttp
149
152
  )
150
153
  end
151
154
 
152
- # Builds and dispatches an asynchronous HTTP request.
155
+ # Makes an async HTTP request.
153
156
  #
154
- # When a request template is configured, the request is built from the template. Otherwise,
155
- # it is built directly from the provided arguments.
157
+ # If the class has a request template, the template builds the request.
156
158
  #
157
- # @param method [Symbol] HTTP method (`:get`, `:head`, `:post`, `:put`, `:patch`, `:delete`, `:query`)
158
- # @param url [String] absolute URL or path (when using a request template)
159
- # @param callback [Class, String] callback class to handle the response
160
- # @param headers [Hash, nil] request headers
161
- # @param body [String, nil] raw request body
162
- # @param json [Hash, Array, nil] JSON payload encoded by the request layer
163
- # @param params [Hash, nil] query parameters
164
- # @param timeout [Numeric, nil] timeout in seconds for this request
165
- # @param raise_error_responses [Boolean, nil] when true, non-success responses are
166
- # reported as errors
167
- # @param callback_args [Hash, nil] JSON-compatible callback arguments
168
- # @param follow_method_changing_redirects [Boolean, nil] whether to follow a redirect that changes the
169
- # HTTP method (nil uses the configuration default)
170
- # @param redirect_strip_headers [String, Array<String>, nil] header names (case insensitive)
171
- # to strip from redirected requests, in addition to the configured names
172
- # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] names of preprocessors
173
- # registered on the configuration to apply to the request when it is sent
174
- # @param processor [String, Symbol, nil] name of the processor that should execute
175
- # the request
176
- # @return [Object] return value from the registered request handler
159
+ # @param method [Symbol] The HTTP method: `:get`, `:head`, `:post`, `:put`,
160
+ # `:patch`, `:delete`, or `:query`.
161
+ # @param url [String] The absolute URL, or a path relative to the base URL of
162
+ # the request template.
163
+ # @param callback [Class, String] The callback service class, or its name.
164
+ # @param headers [Hash, nil] The request headers.
165
+ # @param body [String, nil] The request body.
166
+ # @param json [Hash, Array, nil] An object to send as a JSON body. Can't be
167
+ # combined with `body`.
168
+ # @param params [Hash, nil] The query parameters to add to the URL.
169
+ # @param timeout [Numeric, nil] The request timeout in seconds.
170
+ # @param raise_error_responses [Boolean, nil] If `true`, non-2xx responses go
171
+ # to the `on_error` callback as an {HttpError}.
172
+ # @param callback_args [Hash, nil] The JSON-compatible arguments to pass to the
173
+ # callback.
174
+ # @param max_redirects [Integer, nil] The maximum number of redirects to
175
+ # follow. If `0`, redirects aren't followed. If `nil`, the configuration
176
+ # value applies.
177
+ # @param follow_method_changing_redirects [Boolean, nil] Whether to follow a
178
+ # redirect that changes the HTTP method. If `nil`, the configuration value
179
+ # applies.
180
+ # @param redirect_strip_headers [String, Array<String>, nil] The names of headers
181
+ # to remove from redirected requests, in addition to the configured names.
182
+ # Names are case insensitive.
183
+ # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] The names of
184
+ # the registered preprocessors that run on the request before it's sent.
185
+ # @param processor [String, Symbol, nil] The name of the processor that runs
186
+ # the request.
187
+ # @return [Object] The value that the request handler returns.
177
188
  def async_request(
178
189
  method,
179
190
  url,
@@ -185,6 +196,7 @@ module PatientHttp
185
196
  timeout: nil,
186
197
  raise_error_responses: nil,
187
198
  callback_args: nil,
199
+ max_redirects: nil,
188
200
  follow_method_changing_redirects: nil,
189
201
  redirect_strip_headers: nil,
190
202
  preprocessors: nil,
@@ -197,6 +209,7 @@ module PatientHttp
197
209
  headers: headers,
198
210
  params: params,
199
211
  timeout: timeout,
212
+ max_redirects: max_redirects,
200
213
  follow_method_changing_redirects: follow_method_changing_redirects,
201
214
  redirect_strip_headers: redirect_strip_headers,
202
215
  preprocessors: preprocessors,
@@ -216,11 +229,11 @@ module PatientHttp
216
229
  )
217
230
  end
218
231
 
219
- # Returns the RequestTemplate defined for this class or its ancestors, or nil if none
220
- # is defined. This allows subclasses to inherit the request template from their parent
221
- # class if they don't define their own.
232
+ # Returns the request template for this class. A subclass without its own
233
+ # template uses the template of its superclass.
222
234
  #
223
- # @return [RequestTemplate, nil] the request template for this class or its ancestors
235
+ # @return [RequestTemplate, nil] The request template, or `nil` if none is
236
+ # set.
224
237
  # @api private
225
238
  def async_request_template
226
239
  return @patient_http_request_template if @patient_http_request_template
@@ -230,30 +243,38 @@ module PatientHttp
230
243
  end
231
244
  end
232
245
 
233
- # Dispatches an asynchronous HTTP request from an instance context.
234
- #
235
- # This delegates to {.ClassMethods#async_request} on the including class.
246
+ # Makes an async HTTP request. This method calls
247
+ # {ClassMethods#async_request} on the class.
236
248
  #
237
- # @param method [Symbol] HTTP method (`:get`, `:head`, `:post`, `:put`, `:patch`, `:delete`, `:query`)
238
- # @param url [String] absolute URL or path (when using a request template)
239
- # @param callback [Class, String] callback class to handle the response
240
- # @param headers [Hash, nil] request headers
241
- # @param body [String, nil] raw request body
242
- # @param json [Hash, Array, nil] JSON payload encoded by the request layer
243
- # @param params [Hash, nil] query parameters
244
- # @param timeout [Numeric, nil] timeout in seconds for this request
245
- # @param raise_error_responses [Boolean, nil] when true, non-success responses are
246
- # reported as errors
247
- # @param callback_args [Hash, nil] JSON-compatible callback arguments
248
- # @param follow_method_changing_redirects [Boolean, nil] whether to follow a redirect that changes the
249
- # HTTP method (nil uses the configuration default)
250
- # @param redirect_strip_headers [String, Array<String>, nil] header names (case insensitive)
251
- # to strip from redirected requests, in addition to the configured names
252
- # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] names of preprocessors
253
- # registered on the configuration to apply to the request when it is sent
254
- # @param processor [String, Symbol, nil] name of the processor that should execute
255
- # the request
256
- # @return [Object] return value from the registered request handler
249
+ # @param method [Symbol] The HTTP method: `:get`, `:head`, `:post`, `:put`,
250
+ # `:patch`, `:delete`, or `:query`.
251
+ # @param url [String] The absolute URL, or a path relative to the base URL of
252
+ # the request template.
253
+ # @param callback [Class, String] The callback service class, or its name.
254
+ # @param headers [Hash, nil] The request headers.
255
+ # @param body [String, nil] The request body.
256
+ # @param json [Hash, Array, nil] An object to send as a JSON body. Can't be
257
+ # combined with `body`.
258
+ # @param params [Hash, nil] The query parameters to add to the URL.
259
+ # @param timeout [Numeric, nil] The request timeout in seconds.
260
+ # @param raise_error_responses [Boolean, nil] If `true`, non-2xx responses go
261
+ # to the `on_error` callback as an {HttpError}.
262
+ # @param callback_args [Hash, nil] The JSON-compatible arguments to pass to the
263
+ # callback.
264
+ # @param max_redirects [Integer, nil] The maximum number of redirects to
265
+ # follow. If `0`, redirects aren't followed. If `nil`, the configuration
266
+ # value applies.
267
+ # @param follow_method_changing_redirects [Boolean, nil] Whether to follow a
268
+ # redirect that changes the HTTP method. If `nil`, the configuration value
269
+ # applies.
270
+ # @param redirect_strip_headers [String, Array<String>, nil] The names of headers
271
+ # to remove from redirected requests, in addition to the configured names.
272
+ # Names are case insensitive.
273
+ # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] The names of
274
+ # the registered preprocessors that run on the request before it's sent.
275
+ # @param processor [String, Symbol, nil] The name of the processor that runs
276
+ # the request.
277
+ # @return [Object] The value that the request handler returns.
257
278
  def async_request(
258
279
  method,
259
280
  url,
@@ -265,6 +286,7 @@ module PatientHttp
265
286
  timeout: nil,
266
287
  raise_error_responses: nil,
267
288
  callback_args: nil,
289
+ max_redirects: nil,
268
290
  follow_method_changing_redirects: nil,
269
291
  redirect_strip_headers: nil,
270
292
  preprocessors: nil,
@@ -281,6 +303,7 @@ module PatientHttp
281
303
  timeout: timeout,
282
304
  raise_error_responses: raise_error_responses,
283
305
  callback_args: callback_args,
306
+ max_redirects: max_redirects,
284
307
  follow_method_changing_redirects: follow_method_changing_redirects,
285
308
  redirect_strip_headers: redirect_strip_headers,
286
309
  preprocessors: preprocessors,
@@ -1,29 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PatientHttp
4
- # Prepares a {Request} to be sent: resolves any secret references, sets the
5
- # send-time headers (x-request-id and the default user-agent), and invokes any
6
- # preprocessors attached to the request.
4
+ # Prepares a {Request} to be sent. It resolves the secret references, sets
5
+ # the `x-request-id` and default `user-agent` headers, and runs the
6
+ # preprocessors of the request.
7
7
  class RequestPreparer
8
- # Raised when a request references a preprocessor name that is not registered.
8
+ # Raised when a request refers to a preprocessor that isn't registered.
9
9
  class PreprocessorNotFoundError < StandardError; end
10
10
 
11
- # @param config [Configuration] the configuration holding secrets and preprocessors
11
+ # Creates a request preparer.
12
+ #
13
+ # @param config [Configuration] The configuration that has the secrets and
14
+ # preprocessors.
12
15
  def initialize(config)
13
16
  @config = config
14
17
  end
15
18
 
16
- # Prepare a request for sending.
19
+ # Prepares a request for sending.
17
20
  #
18
21
  # Secret references in the headers and query params are resolved first, then the
19
22
  # x-request-id and default user-agent headers are set, and finally each
20
23
  # preprocessor attached to the request is invoked in order with the outgoing
21
24
  # request. Each preprocessor sees any changes made by the ones before it.
22
25
  #
23
- # @param request [Request] the request to prepare
24
- # @param request_id [String] unique request identifier set as the x-request-id header
25
- # @return [OutgoingRequest] the outgoing request with the final URL and headers
26
- # @raise [PreprocessorNotFoundError] if the request references an unregistered preprocessor
26
+ # @param request [Request] The request to prepare.
27
+ # @param request_id [String] Unique request identifier set as the x-request-id header.
28
+ # @return [OutgoingRequest] The outgoing request with the final URL and headers.
29
+ # @raise [PreprocessorNotFoundError] If the request references an unregistered preprocessor.
27
30
  def prepare(request, request_id)
28
31
  headers = @config.secret_manager.resolve_headers(request.headers.to_h)
29
32
  headers["x-request-id"] = request_id