Class: HTTP::Request

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Base64, Proxy
Defined in:
lib/http/request.rb,
lib/http/request/body.rb,
lib/http/request/proxy.rb,
lib/http/request/writer.rb,
lib/http/request/builder.rb,
sig/http.rbs

Overview

Represents an HTTP request with verb, URI, headers, and body

Defined Under Namespace

Modules: Proxy Classes: Body, Builder, UnsupportedMethodError, UnsupportedSchemeError, Writer

Constant Summary collapse

USER_AGENT =

Default User-Agent header value

Returns:

  • (String)
"http.rb/#{HTTP::VERSION}".freeze
METHODS =

Supported HTTP methods

Returns:

[
  # RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1
  :options, :get, :head, :post, :put, :delete, :trace, :connect,

  # RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV
  :propfind, :proppatch, :mkcol, :copy, :move, :lock, :unlock,

  # RFC 3648: WebDAV Ordered Collections Protocol
  :orderpatch,

  # RFC 3744: WebDAV Access Control Protocol
  :acl,

  # RFC 6352: vCard Extensions to WebDAV -- CardDAV
  :report,

  # RFC 5789: PATCH Method for HTTP
  :patch,

  # draft-reschke-webdav-search: WebDAV Search
  :search,

  # RFC 4791: Calendaring Extensions to WebDAV -- CalDAV
  :mkcalendar,

  # Implemented by several caching servers, like Squid, Varnish or Fastly
  :purge
].freeze
SCHEMES =

Allowed schemes

Returns:

  • (Array[Symbol])
%i[http https ws wss].freeze
PORTS =

Default ports of supported schemes

Returns:

  • (Hash[Symbol, Integer])
{
  http:  80,
  https: 443,
  ws:    80,
  wss:   443
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Proxy

#connect_using_proxy, #encode64, #include_proxy_authorization_header, #include_proxy_headers, #proxy_authorization_header, #proxy_connect_header, #proxy_connect_headers

Methods included from Base64

encode64, #self?.encode64

Constructor Details

#initialize(verb:, uri:, headers: nil, proxy: {}, body: nil, version: "1.1", uri_normalizer: nil) ⇒ HTTP::Request

Create a new HTTP request

Examples:

Request.new(verb: :get, uri: "https://example.com")

Parameters:

  • verb (#to_s)

    HTTP request method

  • uri (HTTP::URI, #to_s)
  • headers (Hash) (defaults to: nil)
  • proxy (Hash) (defaults to: {})
  • body (String, Enumerable, IO, nil) (defaults to: nil)
  • version (String) (defaults to: "1.1")
  • uri_normalizer (#call) (defaults to: nil)
  • verb: (String, Symbol)
  • uri: (String, URI)
  • headers: (Hash[String | Symbol, untyped], Headers) (defaults to: nil)
  • proxy: (Hash[Symbol, untyped]) (defaults to: {})
  • body: (Object) (defaults to: nil)
  • version: (String) (defaults to: "1.1")
  • uri_normalizer: (^(String | URI) -> URI, nil) (defaults to: nil)


160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/http/request.rb', line 160

def initialize(verb:, uri:, headers: nil, proxy: {}, body: nil, version: "1.1",
               uri_normalizer: nil)
  @uri_normalizer = uri_normalizer || HTTP::URI::NORMALIZER
  @verb = verb.to_s.downcase.to_sym
  parse_uri!(uri)
  validate_method_and_scheme!

  @proxy   = proxy
  @version = version
  @headers = prepare_headers(headers)
  @body    = prepare_body(body)
end

Instance Attribute Details

#bodyHTTP::Request::Body (readonly)

Request body object

Examples:

request.body

Returns:



125
126
127
# File 'lib/http/request.rb', line 125

def body
  @body
end

#headersHTTP::Headers (readonly)

The HTTP headers collection

Examples:

request.headers

Returns:



134
135
136
# File 'lib/http/request.rb', line 134

def headers
  @headers
end

#hostString (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Host from the URI

Returns:

  • (String)


316
# File 'lib/http/request.rb', line 316

def_delegator :@uri, :host

#proxyHash (readonly)

Proxy configuration hash

Examples:

request.proxy

Returns:

  • (Hash)


116
117
118
# File 'lib/http/request.rb', line 116

def proxy
  @proxy
end

#schemeSymbol (readonly)

URI scheme as a lowercase symbol

Examples:

request.scheme # => :https

Returns:

  • (Symbol)


89
90
91
# File 'lib/http/request.rb', line 89

def scheme
  @scheme
end

#uriHTTP::URI (readonly)

Request URI

Examples:

request.uri # => #<HTTP::URI ...>

Returns:



107
108
109
# File 'lib/http/request.rb', line 107

def uri
  @uri
end

#uri_normalizer#call (readonly)

URI normalizer callable

Examples:

request.uri_normalizer

Returns:

  • (#call)


98
99
100
# File 'lib/http/request.rb', line 98

def uri_normalizer
  @uri_normalizer
end

#verbSymbol (readonly)

HTTP method as a lowercase symbol

Examples:

request.verb # => :get

Returns:

  • (Symbol)


80
81
82
# File 'lib/http/request.rb', line 80

def verb
  @verb
end

#versionString (readonly)

HTTP version string

Examples:

request.version # => "1.1"

Returns:

  • (String)


143
144
145
# File 'lib/http/request.rb', line 143

def version
  @version
end

Instance Method Details

#default_host_header_valueString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Build default Host header value

Returns:

  • (String)

Raises:



328
329
330
331
332
333
334
# File 'lib/http/request.rb', line 328

def default_host_header_value
  value = PORTS[@scheme] == port ? host : "#{host}:#{port}"

  raise RequestError, "Invalid host: #{value.inspect}" if value.match?(/\s/)

  value
end

#headlineString

Compute HTTP request header for direct or proxy request

Examples:

request.headline

Returns:

  • (String)

Raises:



237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/http/request.rb', line 237

def headline
  request_uri =
    if using_proxy? && !uri.https?
      uri.omit(:fragment)
    else
      uri.request_uri
    end.to_s

  raise RequestError, "Invalid request URI: #{request_uri.inspect}" if request_uri.match?(/\s/)

  "#{verb.to_s.upcase} #{request_uri} HTTP/#{version}"
end

#inspectString

Human-readable representation of base request info

Examples:


req.inspect
# => #<HTTP::Request/1.1 GET https://example.com>

Returns:

  • (String)


281
282
283
# File 'lib/http/request.rb', line 281

def inspect
  format("#<%s/%s %s %s>", self.class, @version, String(verb).upcase, uri)
end

#parse_uri!(uri) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Parse and normalize the URI, setting scheme

Parameters:

  • uri (String, URI)

Raises:

  • (ArgumentError)


339
340
341
342
343
344
345
# File 'lib/http/request.rb', line 339

def parse_uri!(uri)
  raise ArgumentError, "uri is nil" if uri.nil?
  raise ArgumentError, "uri is empty" if uri.is_a?(String) && uri.empty?

  @uri = @uri_normalizer.call(uri)
  @scheme = String(@uri.scheme).downcase.to_sym if @uri.scheme
end

#portFixnum

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return the port for the request URI

Returns:

  • (Fixnum)


321
322
323
# File 'lib/http/request.rb', line 321

def port
  @uri.port || @uri.default_port
end

#prepare_body(body) ⇒ HTTP::Request::Body

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Coerce input into a Body object

Parameters:

  • body (Object)

Returns:



359
360
361
# File 'lib/http/request.rb', line 359

def prepare_body(body)
  body.is_a?(Body) ? body : Body.new(body)
end

#prepare_headers(headers) ⇒ HTTP::Headers

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Build headers with default values

Parameters:

  • headers (Object)

Returns:



366
367
368
369
370
371
372
373
# File 'lib/http/request.rb', line 366

def prepare_headers(headers)
  headers = Headers.coerce(headers || {})

  headers[Headers::HOST]       ||= default_host_header_value
  headers[Headers::USER_AGENT] ||= USER_AGENT

  headers
end

#redirect(uri, verb = @verb) ⇒ HTTP::Request

Returns new Request with updated uri

Examples:

request.redirect("https://example.com/new")

Parameters:

  • uri (String, URI)
  • verb (verb, Symbol) (defaults to: @verb)

Returns:



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/http/request.rb', line 180

def redirect(uri, verb = @verb)
  redirect_uri = @uri.join(uri)
  headers = redirect_headers(redirect_uri, verb)
  new_body = verb == :get ? nil : body.source

  self.class.new(
    verb:           verb,
    uri:            redirect_uri,
    headers:        headers,
    proxy:          proxy,
    body:           new_body,
    version:        version,
    uri_normalizer: uri_normalizer
  )
end

#redirect_headers(redirect_uri, verb) ⇒ HTTP::Headers

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Build headers for a redirect request

Strips the Host header (it will be regenerated), sensitive credentials on cross-origin redirects, and Content-Type on GET verb changes.

Parameters:

  • redirect_uri (HTTP::URI)

    the target URI

  • verb (Symbol)

    the HTTP verb for the redirected request

Returns:



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/http/request.rb', line 296

def redirect_headers(redirect_uri, verb)
  headers = self.headers.dup
  headers.delete(Headers::HOST)

  # Strip sensitive headers when redirecting to a different origin
  # (scheme + host + port) to prevent credential leakage.
  unless @uri.origin == redirect_uri.origin
    headers.delete(Headers::AUTHORIZATION)
    headers.delete(Headers::COOKIE)
  end

  headers.delete(Headers::CONTENT_TYPE) if verb == :get

  headers
end

#socket_hostString

Host for tcp socket

Examples:

request.socket_host

Returns:

  • (String)


257
258
259
# File 'lib/http/request.rb', line 257

def socket_host
  using_proxy? ? proxy[:proxy_address] : host
end

#socket_portInteger

Port for tcp socket

Examples:

request.socket_port

Returns:

  • (Integer)


268
269
270
# File 'lib/http/request.rb', line 268

def socket_port
  using_proxy? ? proxy[:proxy_port] : port
end

#stream(socket) ⇒ void

This method returns an undefined value.

Stream the request to a socket

Examples:

request.stream(socket)

Parameters:



203
204
205
206
# File 'lib/http/request.rb', line 203

def stream(socket)
  include_proxy_headers if using_proxy? && !@uri.https?
  Request::Writer.new(socket, body, headers, headline).stream
end

#using_authenticated_proxy?Boolean

Is this request using an authenticated proxy?

Examples:

request.using_authenticated_proxy?

Returns:

  • (Boolean)


226
227
228
# File 'lib/http/request.rb', line 226

def using_authenticated_proxy?
  proxy && proxy.keys.size >= 4
end

#using_proxy?Boolean

Is this request using a proxy?

Examples:

request.using_proxy?

Returns:

  • (Boolean)


215
216
217
# File 'lib/http/request.rb', line 215

def using_proxy?
  proxy && proxy.keys.size >= 2
end

#validate_method_and_scheme!void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Validate HTTP method and URI scheme



350
351
352
353
354
# File 'lib/http/request.rb', line 350

def validate_method_and_scheme!
  raise(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(@verb)
  raise(HTTP::URI::InvalidError, "invalid URI: #{@uri}") unless @scheme
  raise(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(@scheme)
end