Class: HTTP::Request

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Headers::Mixin
Defined in:
lib/http/request.rb,
lib/http/request/body.rb,
lib/http/request/writer.rb

Defined Under Namespace

Classes: Body, UnsupportedMethodError, UnsupportedSchemeError, Writer

Constant Summary collapse

USER_AGENT =

Default User-Agent header value

"http.rb/#{HTTP::VERSION}"
METHODS =
[
  # 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

%i[http https ws wss].freeze
PORTS =

Default ports of supported schemes

{
  :http  => 80,
  :https => 443,
  :ws    => 80,
  :wss   => 443
}.freeze

Instance Attribute Summary collapse

Attributes included from Headers::Mixin

#headers

Instance Method Summary collapse

Methods included from Headers::Mixin

#[], #[]=

Constructor Details

#initialize(opts) ⇒ Request

Returns a new instance of Request.

Parameters:

  • opts (Hash)

    a customizable set of options

Options Hash (opts):

  • :version (String)
  • :verb (#to_s)

    HTTP request method

  • :uri_normalizer (#call) — default: HTTP::URI::NORMALIZER
  • :uri (HTTP::URI, #to_s)
  • :headers (Hash)
  • :proxy (Hash)
  • :body (String, Enumerable, IO, nil)

Raises:



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/http/request.rb', line 89

def initialize(opts)
  @verb           = opts.fetch(:verb).to_s.downcase.to_sym
  @uri_normalizer = opts[:uri_normalizer] || HTTP::URI::NORMALIZER

  @uri    = @uri_normalizer.call(opts.fetch(:uri))
  @scheme = @uri.scheme.to_s.downcase.to_sym if @uri.scheme

  raise(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(@verb)
  raise(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(@scheme)

  @proxy   = opts[:proxy] || {}
  @version = opts[:version] || "1.1"
  @headers = prepare_headers(opts[:headers])
  @body    = prepare_body(opts[:body])
end

Instance Attribute Details

#bodyObject (readonly)

Returns the value of attribute body.



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

def body
  @body
end

#proxyObject (readonly)

Returns the value of attribute proxy.



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

def proxy
  @proxy
end

#schemeObject (readonly)

Scheme is normalized to be a lowercase symbol e.g. :http, :https



73
74
75
# File 'lib/http/request.rb', line 73

def scheme
  @scheme
end

#uriObject (readonly)



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

def uri
  @uri
end

#uri_normalizerObject (readonly)

Returns the value of attribute uri_normalizer.



75
76
77
# File 'lib/http/request.rb', line 75

def uri_normalizer
  @uri_normalizer
end

#verbObject (readonly)

Method is given as a lowercase symbol e.g. :get, :post



70
71
72
# File 'lib/http/request.rb', line 70

def verb
  @verb
end

#versionObject (readonly)

Returns the value of attribute version.



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

def version
  @version
end

Instance Method Details

#connect_using_proxy(socket) ⇒ Object

Setup tunnel through proxy for SSL request



167
168
169
# File 'lib/http/request.rb', line 167

def connect_using_proxy(socket)
  Request::Writer.new(socket, nil, proxy_connect_headers, proxy_connect_header).connect_through_proxy
end

#headlineObject

Compute HTTP request header for direct or proxy request

Raises:



172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/http/request.rb', line 172

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

#include_proxy_authorization_headerObject

Compute and add the Proxy-Authorization header



157
158
159
# File 'lib/http/request.rb', line 157

def include_proxy_authorization_header
  headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header
end

#include_proxy_headersObject



151
152
153
154
# File 'lib/http/request.rb', line 151

def include_proxy_headers
  headers.merge!(proxy[:proxy_headers]) if proxy.key?(:proxy_headers)
  include_proxy_authorization_header if using_authenticated_proxy?
end

#inspectString

Human-readable representation of base request info.

Examples:


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

Returns:

  • (String)


220
221
222
# File 'lib/http/request.rb', line 220

def inspect
  "#<#{self.class}/#{@version} #{verb.to_s.upcase} #{uri}>"
end

#proxy_authorization_headerObject



161
162
163
164
# File 'lib/http/request.rb', line 161

def proxy_authorization_header
  digest = Base64.strict_encode64("#{proxy[:proxy_username]}:#{proxy[:proxy_password]}")
  "Basic #{digest}"
end

#proxy_connect_headerObject

Compute HTTP request header SSL proxy connection



186
187
188
# File 'lib/http/request.rb', line 186

def proxy_connect_header
  "CONNECT #{host}:#{port} HTTP/#{version}"
end

#proxy_connect_headersObject

Headers to send with proxy connect request



191
192
193
194
195
196
197
198
199
200
# File 'lib/http/request.rb', line 191

def proxy_connect_headers
  connect_headers = HTTP::Headers.coerce(
    Headers::HOST       => headers[Headers::HOST],
    Headers::USER_AGENT => headers[Headers::USER_AGENT]
  )

  connect_headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header if using_authenticated_proxy?
  connect_headers.merge!(proxy[:proxy_headers]) if proxy.key?(:proxy_headers)
  connect_headers
end

#redirect(uri, verb = @verb) ⇒ Object

Returns new Request with updated uri



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
# File 'lib/http/request.rb', line 106

def redirect(uri, verb = @verb)
  headers = self.headers.dup
  headers.delete(Headers::HOST)

  new_body = body.source
  if verb == :get
    # request bodies should not always be resubmitted when following a redirect
    # some servers will close the connection after receiving the request headers
    # which may cause Errno::ECONNRESET: Connection reset by peer
    # see https://github.com/httprb/http/issues/649
    # new_body = Request::Body.new(nil)
    new_body = nil
    # the CONTENT_TYPE header causes problems if set on a get request w/ an empty body
    # the server might assume that there should be content if it is set to multipart
    # rack raises EmptyContentError if this happens
    headers.delete(Headers::CONTENT_TYPE)
  end

  self.class.new(
    :verb           => verb,
    :uri            => @uri.join(uri),
    :headers        => headers,
    :proxy          => proxy,
    :body           => new_body,
    :version        => version,
    :uri_normalizer => uri_normalizer
  )
end

#socket_hostObject

Host for tcp socket



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

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

#socket_portObject

Port for tcp socket



208
209
210
# File 'lib/http/request.rb', line 208

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

#stream(socket) ⇒ Object

Stream the request to a socket



136
137
138
139
# File 'lib/http/request.rb', line 136

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?

Returns:

  • (Boolean)


147
148
149
# File 'lib/http/request.rb', line 147

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

#using_proxy?Boolean

Is this request using a proxy?

Returns:

  • (Boolean)


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

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