Class: HTTP::Request

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Base64, 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}".freeze
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

#[], #[]=

Methods included from Base64

encode64

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:



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

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.



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

def body
  @body
end

#proxyObject (readonly)

Returns the value of attribute proxy.



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

def proxy
  @proxy
end

#schemeObject (readonly)

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



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

def scheme
  @scheme
end

#uriObject (readonly)



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

def uri
  @uri
end

#uri_normalizerObject (readonly)

Returns the value of attribute uri_normalizer.



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

def uri_normalizer
  @uri_normalizer
end

#verbObject (readonly)

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



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

def verb
  @verb
end

#versionObject (readonly)

Returns the value of attribute version.



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

def version
  @version
end

Instance Method Details

#connect_using_proxy(socket) ⇒ Object

Setup tunnel through proxy for SSL request



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

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:



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

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



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

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

#include_proxy_headersObject



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

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)


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

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

#proxy_authorization_headerObject



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

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

#proxy_connect_headerObject

Compute HTTP request header SSL proxy connection



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

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

#proxy_connect_headersObject

Headers to send with proxy connect request



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

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



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

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



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

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

#socket_portObject

Port for tcp socket



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

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

#stream(socket) ⇒ Object

Stream the request to a socket



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

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)


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

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

#using_proxy?Boolean

Is this request using a proxy?

Returns:

  • (Boolean)


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

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