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
].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:


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/http/request.rb', line 86

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.


77
78
79
# File 'lib/http/request.rb', line 77

def body
  @body
end

#proxyObject (readonly)

Returns the value of attribute proxy.


77
78
79
# File 'lib/http/request.rb', line 77

def proxy
  @proxy
end

#schemeObject (readonly)

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


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

def scheme
  @scheme
end

#uriObject (readonly)


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

def uri
  @uri
end

#uri_normalizerObject (readonly)

Returns the value of attribute uri_normalizer.


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

def uri_normalizer
  @uri_normalizer
end

#verbObject (readonly)

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


67
68
69
# File 'lib/http/request.rb', line 67

def verb
  @verb
end

#versionObject (readonly)

Returns the value of attribute version.


77
78
79
# File 'lib/http/request.rb', line 77

def version
  @version
end

Instance Method Details

#connect_using_proxy(socket) ⇒ Object

Setup tunnel through proxy for SSL request


164
165
166
# File 'lib/http/request.rb', line 164

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


169
170
171
172
173
174
175
176
177
178
# File 'lib/http/request.rb', line 169

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

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

#include_proxy_authorization_headerObject

Compute and add the Proxy-Authorization header


154
155
156
# File 'lib/http/request.rb', line 154

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

#include_proxy_headersObject


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

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)

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

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

#proxy_authorization_headerObject


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

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


181
182
183
# File 'lib/http/request.rb', line 181

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

#proxy_connect_headersObject

Headers to send with proxy connect request


186
187
188
189
190
191
192
193
194
195
# File 'lib/http/request.rb', line 186

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


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

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


198
199
200
# File 'lib/http/request.rb', line 198

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

#socket_portObject

Port for tcp socket


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

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

#stream(socket) ⇒ Object

Stream the request to a socket


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

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)

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

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

#using_proxy?Boolean

Is this request using a proxy?

Returns:

  • (Boolean)

139
140
141
# File 'lib/http/request.rb', line 139

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