Class: HTTY::Request

Inherits:
Payload show all
Defined in:
lib/htty/request.rb

Overview

Encapsulates an HTTP(S) request.

Constant Summary collapse

AUTHORIZATION_HEADER_NAME =
'Authorization'
COOKIES_HEADER_NAME =
'Cookie'
METHODS_SENDING_BODY =
[:patch, :post, :put]

Instance Attribute Summary collapse

Attributes inherited from Payload

#body

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Payload

#cookies_from, #header, #headers_with_key

Constructor Details

#initialize(address) ⇒ Request

Initializes a new HTTY::Request with a #uri corresponding to the specified address.



128
129
130
131
132
133
# File 'lib/htty/request.rb', line 128

def initialize(address)
  super({:headers => [['User-Agent', "htty/#{HTTY::VERSION}"]]})
  @uri = self.class.parse_uri(address)
  establish_basic_authentication
  establish_content_length
end

Instance Attribute Details

#request_methodObject (readonly)

Returns the HTTP method of the request, if any.



118
119
120
# File 'lib/htty/request.rb', line 118

def request_method
  @request_method
end

#responseObject

Returns the response received for the request, if any.



121
122
123
# File 'lib/htty/request.rb', line 121

def response
  @response
end

#uriObject

Returns the URI of the request.



124
125
126
# File 'lib/htty/request.rb', line 124

def uri
  @uri
end

Class Method Details

.build_authority(components) ⇒ Object

Returns a URI authority (a combination of userinfo, host, and port) corresponding to the specified components hash. Valid components keys include:

  • :userinfo

  • :host

  • :port



21
22
23
24
25
26
27
# File 'lib/htty/request.rb', line 21

def self.build_authority(components)
  userinfo_and_host = [components[:userinfo],
                       components[:host]].compact.join('@')
  all               = [userinfo_and_host, components[:port]].compact.join(':')
  return nil if (all == '')
  all
end

.build_path_query_and_fragment(components) ⇒ Object

Returns a combination of a URI path, query, and fragment, corresponding to the specified components hash. Valid components keys include:

  • :path

  • :query

  • :fragment



35
36
37
38
39
40
41
42
# File 'lib/htty/request.rb', line 35

def self.build_path_query_and_fragment(components)
  path     = components[:path]
  query    = components[:query]    ? "?#{components[:query]}"    : nil
  fragment = components[:fragment] ? "##{components[:fragment]}" : nil
  all      = [path, query, fragment].compact.join
  return nil if (all == '')
  all
end

.build_uri(components) ⇒ Object

Returns a URI corresponding to the specified components hash, or raises URI::InvalidURIError. Valid components keys include:

  • :scheme

  • :userinfo

  • :host

  • :port

  • :path

  • :query

  • :fragment



54
55
56
57
58
59
60
61
62
63
64
# File 'lib/htty/request.rb', line 54

def self.build_uri(components)
  scheme                  = (components[:scheme] || 'http') + '://'
  unless %w(http:// https://).include?(scheme)
    raise ArgumentError, 'only http:// and https:// schemes are supported'
  end

  authority               = build_authority(components)
  path_query_and_fragment = build_path_query_and_fragment(components)
  path_query_and_fragment ||= '/' if authority
  URI.parse([scheme, authority, path_query_and_fragment].join)
end

.parse_uri(address) ⇒ Object

Returns a URI corresponding to the specified address, or raises URI::InvalidURIError.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/htty/request.rb', line 68

def self.parse_uri(address)
  address = '0.0.0.0' if address.nil? || (address == '')

  scheme_missing = false
  if (address !~ /^[a-z]+:\/\//) && (address !~ /^mailto:/)
    scheme_missing = true
    address = 'http://' + address
  end

  scheme,
  userinfo,
  host,
  port,
  registry, # Not used by HTTP
  path,
  opaque,   # Not used by HTTP
  query,
  fragment = URI.split(address)

  scheme = nil if scheme_missing
  path   = nil if (path == '')

  unless scheme
    scheme = (port.to_i == URI::HTTPS::DEFAULT_PORT) ? 'https' : 'http'
  end

  build_uri :scheme   => scheme,
            :userinfo => userinfo,
            :host     => host,
            :port     => port,
            :path     => path,
            :query    => query,
            :fragment => fragment
end

.set_up_cookies_and_authentication(request) ⇒ Object (protected)



105
106
107
108
109
110
111
112
113
# File 'lib/htty/request.rb', line 105

def self.set_up_cookies_and_authentication(request)
  previous_host = request.uri.host
  yield
  request.cookies_remove_all unless request.uri.host == previous_host

  request.send :establish_basic_authentication

  request
end

Instance Method Details

#==(other_request) ⇒ Object Also known as: eql?

Returns true if other_request is equivalent to the request.



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

def ==(other_request)
  return false unless super(other_request)
  return false unless other_request.kind_of?(self.class)
  (other_request.response == response) && (other_request.uri == uri)
end

#address(address) ⇒ Object

Establishes a new #uri corresponding to the specified address. If the host of the address is different from the host of #uri, then #cookies are cleared.



146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/htty/request.rb', line 146

def address(address)
  uri = self.class.parse_uri(address)
  if response
    dup = dup_without_response
    return self.class.send(:set_up_cookies_and_authentication, dup) do
      dup.uri = uri
    end
  end

  self.class.send(:set_up_cookies_and_authentication, self) do
    @uri = uri
  end
end

#body_set(body) ⇒ Object

Sets the body of the request.



161
162
163
164
165
166
# File 'lib/htty/request.rb', line 161

def body_set(body)
  return dup_without_response.body_set(body) if response

  @body = body ? body.to_s : nil
  establish_content_length
end

#body_unsetObject

Clears the body of the request.



169
170
171
# File 'lib/htty/request.rb', line 169

def body_unset
  body_set nil
end

#connect!Object

Makes an HTTP CONNECT request using the path of #uri.



174
175
176
# File 'lib/htty/request.rb', line 174

def connect!
  request! :connect
end

Appends to #cookies using the specified name (required) and value (optional).



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

def cookie_add(name, value=nil)
  return dup_without_response.cookie_add(name, value) if response

  cookies_string = HTTY::CookiesUtil.cookies_to_string(cookies +
                                                       [[name.to_s, value]])
  if cookies_string
    @headers[COOKIES_HEADER_NAME] = cookies_string
  else
    @headers.delete COOKIES_HEADER_NAME
  end
  self
end

Removes the last element of #cookies having the specified name.



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/htty/request.rb', line 194

def cookie_remove(name)
  return dup_without_response.cookie_remove(name) if response

  # Remove just one matching cookie from the end.
  rejected = false
  new_cookies = cookies.reverse.reject { |cookie_name, cookie_value|
    if !rejected && (cookie_name == name)
      rejected = true
    else
      false
    end
  }.reverse

  cookies_string = HTTY::CookiesUtil.cookies_to_string(new_cookies)
  if cookies_string
    @headers[COOKIES_HEADER_NAME] = cookies_string
  else
    @headers.delete COOKIES_HEADER_NAME
  end
  self
end

#cookiesObject

Returns an array of the cookies belonging to the request.



222
223
224
# File 'lib/htty/request.rb', line 222

def cookies
  cookies_from(COOKIES_HEADER_NAME)
end

#cookies?Boolean

Returns true if has some cookies.

Returns:

  • (Boolean)


217
218
219
# File 'lib/htty/request.rb', line 217

def cookies?
  not cookies.empty?
end

#cookies_remove_allObject

Removes all #cookies.



227
228
229
230
231
232
# File 'lib/htty/request.rb', line 227

def cookies_remove_all
  return dup_without_response.cookies_remove_all if response

  @headers.delete COOKIES_HEADER_NAME
  self
end

#cookies_use(response) ⇒ Object

Sets #cookies according to the Set-Cookie header of the specified response, or raises either HTTY::NoResponseError or HTTY::NoSetCookieHeaderError.



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

def cookies_use(response)
  raise HTTY::NoResponseError unless response

  cookies_header = response.headers.detect do |name, value|
    name == HTTY::Response::COOKIES_HEADER_NAME
  end
  unless cookies_header && cookies_header.last
    raise HTTY::NoSetCookieHeaderError
  end
  header_set COOKIES_HEADER_NAME, cookies_header.last
end

#delete!Object

Makes an HTTP DELETE request using the path of #uri.



250
251
252
# File 'lib/htty/request.rb', line 250

def delete!
  request! :delete
end

#dup_without_responseObject (protected)



473
474
475
476
477
478
# File 'lib/htty/request.rb', line 473

def dup_without_response
  request = self.dup
  request.response = nil
  request.instance_variable_set '@request_method', nil
  request
end

#establish_basic_authenticationObject (protected)



480
481
482
483
484
485
# File 'lib/htty/request.rb', line 480

def establish_basic_authentication
  value = uri.userinfo                                                 ?
          "Basic #{Base64.encode64(URI.unescape(uri.userinfo)).chomp}" :
          nil
  header_set AUTHORIZATION_HEADER_NAME, value
end

#follow(response) ⇒ Object

Establishes a new #uri according to the Location header of the specified response, or raises either HTTY::NoResponseError or HTTY::NoLocationHeaderError.



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

def follow(response)
  raise HTTY::NoResponseError unless response
  response.follow_relative_to(self)
end

#fragment_set(fragment) ⇒ Object

Establishes a new #uri with the specified fragment.



263
264
265
# File 'lib/htty/request.rb', line 263

def fragment_set(fragment)
  rebuild_uri :fragment => fragment
end

#fragment_unsetObject

Establishes a new #uri without a fragment.



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

def fragment_unset
  fragment_set nil
end

#get!Object

Makes an HTTP GET request using the path of #uri.



273
274
275
# File 'lib/htty/request.rb', line 273

def get!
  request! :get
end

#head!Object

Makes an HTTP HEAD request using the path of #uri.



278
279
280
# File 'lib/htty/request.rb', line 278

def head!
  request! :head
end

#header_set(name, value) ⇒ Object

Appends to #headers or changes the element of #headers using the specified name and value.



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/htty/request.rb', line 284

def header_set(name, value)
  return dup_without_response.header_set(name, value) if response

  name = name && name.to_s
  value = value && value.to_s

  # to avoid recursion when rebuild_uri
  return self if @headers[name] == value

  if value.nil?
    @headers.delete name
    if name.downcase == AUTHORIZATION_HEADER_NAME.downcase
      return rebuild_uri :userinfo => nil
    end
    return self
  end

  @headers[name] = value
  if name.downcase == AUTHORIZATION_HEADER_NAME.downcase
    HTTY::Headers.credentials_from(value) do |username, password|
      return rebuild_uri :userinfo => [
        HTTY::URI.escape_component(username),
        HTTY::URI.escape_component(password)
      ].compact.join(':')
    end
  end
  self
end

#header_unset(name) ⇒ Object

Removes the element of #headers having the specified name.



314
315
316
# File 'lib/htty/request.rb', line 314

def header_unset(name)
  header_set name, nil
end

#headers(include_content_length = METHODS_SENDING_BODY.include?(request_method)) ⇒ Object

Returns an array of the headers belonging to the payload. If include_content_length is false, then a ‘Content Length’ header will be omitted. If include_content_length is not specified, then it will be true if #request_method is an HTTP method for which body content is expected.



331
332
333
334
335
336
337
338
339
340
# File 'lib/htty/request.rb', line 331

def headers(include_content_length=
            METHODS_SENDING_BODY.include?(request_method))
  unless include_content_length
    return super().reject do |name, value|
      name == 'Content-Length'
    end
  end

  super()
end

#headers_unset_allObject

Removes all #headers.



319
320
321
322
323
324
# File 'lib/htty/request.rb', line 319

def headers_unset_all
  return dup_without_response.headers_unset_all if response

  @headers.clear
  rebuild_uri :userinfo => nil
end

#host_set(host) ⇒ Object

Establishes a new #uri with the specified host.



343
344
345
# File 'lib/htty/request.rb', line 343

def host_set(host)
  rebuild_uri :host => host
end

#options!Object

Makes an HTTP OPTIONS request using the path of #uri.



348
349
350
# File 'lib/htty/request.rb', line 348

def options!
  request! :options
end

#patch!Object

Makes an HTTP PATCH request using the path of #uri.



353
354
355
# File 'lib/htty/request.rb', line 353

def patch!
  request! :patch
end

#path_query_and_fragmentObject (protected)



487
488
489
490
491
# File 'lib/htty/request.rb', line 487

def path_query_and_fragment
  self.class.build_path_query_and_fragment :path     => uri.path,
                                           :query    => uri.query,
                                           :fragment => uri.fragment
end

#path_set(path) ⇒ Object

Establishes a new #uri with the specified path which may be absolute or relative.



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

def path_set(path)
  absolute_path = (Pathname.new(uri.path) + path).to_s
  rebuild_uri :path => absolute_path
end

#port_set(port) ⇒ Object

Establishes a new #uri with the specified port.



365
366
367
# File 'lib/htty/request.rb', line 365

def port_set(port)
  rebuild_uri :port => port
end

#post!Object

Makes an HTTP POST request using the path of #uri.



370
371
372
# File 'lib/htty/request.rb', line 370

def post!
  request! :post
end

#put!Object

Makes an HTTP PUT request using the path of #uri.



375
376
377
# File 'lib/htty/request.rb', line 375

def put!
  request! :put
end

#query_add(name, value = nil) ⇒ Object

Establishes a new #uri with an additional query-string parameter specified by name and value. The value is optional.



381
382
383
384
385
# File 'lib/htty/request.rb', line 381

def query_add(name, value=nil)
  entries = current_query_entries
  entries << name + (value.nil? ? '' : "=#{value}")
  query_set_all(entries)
end

#query_remove(name, value = nil) ⇒ Object

Establishes a new #uri, removing the last query-string parameter specified by name and value. The value is optional.

If there is more than one query-string parameter named name, those parameters matching both name and value (if specified) are removed.



419
420
421
422
423
424
425
426
427
428
429
# File 'lib/htty/request.rb', line 419

def query_remove(name, value=nil)
  return unless uri.query
  entries = current_query_entries
  entries.reverse.each do |entry|
    if entry =~ field_matcher(name, value)
      entries.delete(entry)
      break
    end
  end
  query_set_all(entries)
end

#query_set(name, value = nil) ⇒ Object

Establishes a new #uri with the specified value for the query-string parameter specified by name. The value is optional.

If there is more than one query-string parameter named name, they are replaced by a single one with the specified value.



392
393
394
395
396
# File 'lib/htty/request.rb', line 392

def query_set(name, value=nil)
  entries = current_query_entries
  add_or_replace_field(entries, name, value)
  query_set_all(entries)
end

#query_set_all(query_string) ⇒ Object

Establishes a new #uri with the query-string parameter specified by by query_string parameter



400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/htty/request.rb', line 400

def query_set_all(query_string)
  # _query_string_ as an array of parameters is only for internal usage
  query_string =
    case query_string
      when Array
        query_string.empty? ? nil : query_string.join('&')
      when String
        query_string
      else
        nil
    end
  rebuild_uri :query => query_string
end

#query_unset(name) ⇒ Object

Establishes a new #uri without the query-string parameter specified by name.

If there is more than one query-string parameter named name, they are removed.



436
437
438
439
440
441
442
443
# File 'lib/htty/request.rb', line 436

def query_unset(name)
  return unless uri.query
  entries = current_query_entries
  entries.delete_if do |entry|
    entry =~ field_matcher(name)
  end
  query_set_all(entries)
end

#query_unset_allObject

Establishes a new #uri without a query string.



446
447
448
# File 'lib/htty/request.rb', line 446

def query_unset_all
  rebuild_uri :query => nil
end

#rebuild_uri(changed_components) ⇒ Object (protected)



493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/htty/request.rb', line 493

def rebuild_uri(changed_components)
  return dup_without_response.rebuild_uri(changed_components) if response

  components = URI::HTTP::COMPONENT.inject({}) do |result, c|
    result.merge c => uri.send(c)
  end
  components = components.merge(changed_components)
  components[:query] = nil if components[:query] && components[:query].empty?
  self.class.send(:set_up_cookies_and_authentication, self) do
    @uri = self.class.build_uri(components)
  end
end

#scheme_set(scheme) ⇒ Object

Establishes a new #uri with the specified scheme.



451
452
453
# File 'lib/htty/request.rb', line 451

def scheme_set(scheme)
  rebuild_uri :scheme => scheme
end

#trace!Object

Makes an HTTP TRACE request using the path of #uri.



456
457
458
# File 'lib/htty/request.rb', line 456

def trace!
  request! :trace
end

#userinfo_set(username, password = nil) ⇒ Object

Establishes a new #uri with the specified userinfo.



461
462
463
464
# File 'lib/htty/request.rb', line 461

def userinfo_set(username, password=nil)
  userinfo = [username, password].compact.join(':')
  rebuild_uri :userinfo => userinfo
end

#userinfo_unsetObject

Establishes a new #uri without userinfo.



467
468
469
# File 'lib/htty/request.rb', line 467

def userinfo_unset
  userinfo_set nil
end