htty 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE.rdoc +9 -0
- data/README.rdoc +199 -0
- data/VERSION +1 -0
- data/app/htty.rb +14 -0
- data/app/htty/cli.rb +77 -0
- data/app/htty/cli/command.rb +185 -0
- data/app/htty/cli/commands.rb +43 -0
- data/app/htty/cli/commands/address.rb +84 -0
- data/app/htty/cli/commands/body_clear.rb +20 -0
- data/app/htty/cli/commands/body_request.rb +51 -0
- data/app/htty/cli/commands/body_response.rb +59 -0
- data/app/htty/cli/commands/body_set.rb +58 -0
- data/app/htty/cli/commands/body_unset.rb +45 -0
- data/app/htty/cli/commands/cd.rb +20 -0
- data/app/htty/cli/commands/cookie_add.rb +20 -0
- data/app/htty/cli/commands/cookie_remove.rb +20 -0
- data/app/htty/cli/commands/cookies.rb +68 -0
- data/app/htty/cli/commands/cookies_add.rb +62 -0
- data/app/htty/cli/commands/cookies_clear.rb +20 -0
- data/app/htty/cli/commands/cookies_remove.rb +60 -0
- data/app/htty/cli/commands/cookies_remove_all.rb +50 -0
- data/app/htty/cli/commands/cookies_use.rb +57 -0
- data/app/htty/cli/commands/delete.rb +20 -0
- data/app/htty/cli/commands/exit.rb +20 -0
- data/app/htty/cli/commands/follow.rb +56 -0
- data/app/htty/cli/commands/form.rb +20 -0
- data/app/htty/cli/commands/form_add.rb +20 -0
- data/app/htty/cli/commands/form_clear.rb +26 -0
- data/app/htty/cli/commands/form_remove.rb +20 -0
- data/app/htty/cli/commands/form_remove_all.rb +20 -0
- data/app/htty/cli/commands/fragment_clear.rb +20 -0
- data/app/htty/cli/commands/fragment_set.rb +59 -0
- data/app/htty/cli/commands/fragment_unset.rb +48 -0
- data/app/htty/cli/commands/get.rb +20 -0
- data/app/htty/cli/commands/header_set.rb +20 -0
- data/app/htty/cli/commands/header_unset.rb +20 -0
- data/app/htty/cli/commands/headers_clear.rb +20 -0
- data/app/htty/cli/commands/headers_request.rb +70 -0
- data/app/htty/cli/commands/headers_response.rb +65 -0
- data/app/htty/cli/commands/headers_set.rb +57 -0
- data/app/htty/cli/commands/headers_unset.rb +54 -0
- data/app/htty/cli/commands/headers_unset_all.rb +48 -0
- data/app/htty/cli/commands/help.rb +100 -0
- data/app/htty/cli/commands/history.rb +60 -0
- data/app/htty/cli/commands/history_verbose.rb +81 -0
- data/app/htty/cli/commands/host_set.rb +59 -0
- data/app/htty/cli/commands/http_delete.rb +40 -0
- data/app/htty/cli/commands/http_get.rb +42 -0
- data/app/htty/cli/commands/http_head.rb +36 -0
- data/app/htty/cli/commands/http_options.rb +36 -0
- data/app/htty/cli/commands/http_post.rb +34 -0
- data/app/htty/cli/commands/http_put.rb +29 -0
- data/app/htty/cli/commands/http_trace.rb +36 -0
- data/app/htty/cli/commands/path_set.rb +54 -0
- data/app/htty/cli/commands/port_set.rb +55 -0
- data/app/htty/cli/commands/post.rb +20 -0
- data/app/htty/cli/commands/put.rb +20 -0
- data/app/htty/cli/commands/query_clear.rb +20 -0
- data/app/htty/cli/commands/query_set.rb +59 -0
- data/app/htty/cli/commands/query_unset.rb +56 -0
- data/app/htty/cli/commands/query_unset_all.rb +50 -0
- data/app/htty/cli/commands/quit.rb +24 -0
- data/app/htty/cli/commands/reuse.rb +74 -0
- data/app/htty/cli/commands/scheme_set.rb +69 -0
- data/app/htty/cli/commands/status.rb +52 -0
- data/app/htty/cli/commands/undo.rb +13 -0
- data/app/htty/cli/commands/userinfo_clear.rb +20 -0
- data/app/htty/cli/commands/userinfo_set.rb +56 -0
- data/app/htty/cli/commands/userinfo_unset.rb +47 -0
- data/app/htty/cli/cookie_clearing_command.rb +26 -0
- data/app/htty/cli/display.rb +182 -0
- data/app/htty/cli/http_method_command.rb +75 -0
- data/app/htty/cli/url_escaping.rb +27 -0
- data/app/htty/cookies_util.rb +36 -0
- data/app/htty/no_location_header_error.rb +12 -0
- data/app/htty/no_response_error.rb +12 -0
- data/app/htty/no_set_cookie_header_error.rb +13 -0
- data/app/htty/ordered_hash.rb +69 -0
- data/app/htty/payload.rb +48 -0
- data/app/htty/request.rb +471 -0
- data/app/htty/requests_util.rb +92 -0
- data/app/htty/response.rb +34 -0
- data/app/htty/session.rb +29 -0
- data/bin/htty +5 -0
- data/spec/unit/htty/cli_spec.rb +27 -0
- data/spec/unit/htty/ordered_hash_spec.rb +54 -0
- data/spec/unit/htty/request_spec.rb +1236 -0
- data/spec/unit/htty/response_spec.rb +0 -0
- data/spec/unit/htty/session_spec.rb +13 -0
- metadata +158 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
# Defines HTTY::CLI::HTTPMethodCommand.
|
2
|
+
|
3
|
+
require File.expand_path("#{File.dirname __FILE__}/../request")
|
4
|
+
require File.expand_path("#{File.dirname __FILE__}/display")
|
5
|
+
require File.expand_path("#{File.dirname __FILE__}/commands/cookies_use")
|
6
|
+
# This 'require' statement leads to an unresolvable circular dependency.
|
7
|
+
# require File.expand_path("#{File.dirname __FILE__}/commands/follow")
|
8
|
+
|
9
|
+
module HTTY; end
|
10
|
+
|
11
|
+
class HTTY::CLI; end
|
12
|
+
|
13
|
+
# Encapsulates behavior common to all HTTP-method-oriented HTTY::CLI::Command
|
14
|
+
# subclasses.
|
15
|
+
module HTTY::CLI::HTTPMethodCommand
|
16
|
+
|
17
|
+
include HTTY::CLI::Display
|
18
|
+
|
19
|
+
# Returns the name of a category under which help for the _http-get_ command
|
20
|
+
# should appear.
|
21
|
+
def self.category
|
22
|
+
'Issuing Requests'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Performs the command.
|
26
|
+
def perform
|
27
|
+
add_request_if_has_response do |request|
|
28
|
+
request = request.send("#{method}!", *arguments)
|
29
|
+
unless body? || request.body.to_s.empty?
|
30
|
+
puts notice("The body of your #{method.upcase} request is not being " +
|
31
|
+
'sent')
|
32
|
+
end
|
33
|
+
notify_if_cookies
|
34
|
+
notify_if_follow
|
35
|
+
request
|
36
|
+
end
|
37
|
+
show_response session.last_response
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Returns true if the command sends the request body.
|
44
|
+
def body?
|
45
|
+
HTTY::Request::METHODS_SENDING_BODY.include? method
|
46
|
+
end
|
47
|
+
|
48
|
+
def method
|
49
|
+
self.class.name.split('::').last.gsub(/^http/i, '').downcase.to_sym
|
50
|
+
end
|
51
|
+
|
52
|
+
def notify_if_cookies
|
53
|
+
request = session.requests.last
|
54
|
+
response = session.last_response
|
55
|
+
unless response.cookies.empty? || (request.cookies == response.cookies)
|
56
|
+
puts notice('Type ' +
|
57
|
+
"#{strong HTTY::CLI::Commands::CookiesUse.command_line} to " +
|
58
|
+
'use cookies offered in the response')
|
59
|
+
end
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def notify_if_follow
|
64
|
+
location_header = session.last_response.headers.detect do |header|
|
65
|
+
header.first == 'Location'
|
66
|
+
end
|
67
|
+
if location_header
|
68
|
+
puts notice('Type ' +
|
69
|
+
"#{strong HTTY::CLI::Commands::Follow.command_line} to " +
|
70
|
+
"follow the 'Location' header received in the response")
|
71
|
+
end
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Defines HTTY::CLI::UrlEscaping.
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require File.expand_path("#{File.dirname __FILE__}/display")
|
5
|
+
|
6
|
+
module HTTY; end
|
7
|
+
|
8
|
+
class HTTY::CLI; end
|
9
|
+
|
10
|
+
# Encapsulates the URL escaping logic of _htty_'s command-line interface.
|
11
|
+
module HTTY::CLI::UrlEscaping
|
12
|
+
|
13
|
+
include HTTY::CLI::Display
|
14
|
+
|
15
|
+
def escape_or_warn_of_escape_sequences(arguments)
|
16
|
+
arguments.collect do |a|
|
17
|
+
if a =~ /%[0-9a-f]{2}/i
|
18
|
+
say "Argument '#{a}' was not URL-escaped because it contains escape " +
|
19
|
+
'sequences'
|
20
|
+
a
|
21
|
+
else
|
22
|
+
URI.escape a
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Defines HTTY::CookiesUtil.
|
2
|
+
|
3
|
+
module HTTY; end
|
4
|
+
|
5
|
+
# Provides support for marshaling HTTP cookies to and from strings.
|
6
|
+
module HTTY::CookiesUtil
|
7
|
+
|
8
|
+
# Returns the specified _cookies_string_ HTTP header value deserialized to an
|
9
|
+
# array of cookies.
|
10
|
+
def self.cookies_from_string(cookies_string)
|
11
|
+
return [] unless cookies_string
|
12
|
+
cookies_string.split(COOKIES_DELIMITER).collect do |name_value_string|
|
13
|
+
name_and_value = name_value_string.split(COOKIE_NAME_VALUE_DELIMITER, 2)
|
14
|
+
name_and_value << nil if (name_and_value.length < 2)
|
15
|
+
name_and_value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the specified array of _cookies_ serialized to an HTTP header value.
|
20
|
+
# Returns +nil+ if _cookies_ is +nil+ or empty or if it contains only +nil+
|
21
|
+
# cookie values.
|
22
|
+
def self.cookies_to_string(cookies)
|
23
|
+
cookies = Array(cookies)
|
24
|
+
return nil if cookies.empty?
|
25
|
+
|
26
|
+
cookies.collect do |name, value|
|
27
|
+
[name, value].compact.join COOKIE_NAME_VALUE_DELIMITER
|
28
|
+
end.join COOKIES_DELIMITER
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
COOKIE_NAME_VALUE_DELIMITER = '='
|
34
|
+
COOKIES_DELIMITER = '; '
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Defines HTTY::NoLocationHeaderError.
|
2
|
+
|
3
|
+
module HTTY; end
|
4
|
+
|
5
|
+
# Indicates that the _Location_ header was missing from HTTY::Request#response.
|
6
|
+
class HTTY::NoLocationHeaderError < StandardError
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super "response does not have a 'Location' header"
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Defines HTTY::NoSetCookieHeaderError.
|
2
|
+
|
3
|
+
module HTTY; end
|
4
|
+
|
5
|
+
# Indicates that the _Set-Cookie_ header was missing from
|
6
|
+
# HTTY::Request#response.
|
7
|
+
class HTTY::NoSetCookieHeaderError < StandardError
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
super "response does not have a 'Set-Cookie' header"
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Defines HTTY::OrderedHash.
|
2
|
+
|
3
|
+
module HTTY; end
|
4
|
+
|
5
|
+
# Represents a Hash that preserves the insertion order of values. This class
|
6
|
+
# exists because Hash did not have this behavior in Ruby v1.8.7 and earlier.
|
7
|
+
class HTTY::OrderedHash
|
8
|
+
|
9
|
+
def initialize(hash={})
|
10
|
+
@inner_hash = {}
|
11
|
+
hash.each_pair do |key, value|
|
12
|
+
@inner_hash[key] = value
|
13
|
+
end
|
14
|
+
@inner_keys = []
|
15
|
+
@inner_hash.each_key do |k|
|
16
|
+
@inner_keys << k
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize_copy(source) #:nodoc:
|
21
|
+
super
|
22
|
+
@inner_hash = @inner_hash.dup
|
23
|
+
@inner_keys = @inner_keys.dup
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](key)
|
27
|
+
@inner_hash[key]
|
28
|
+
end
|
29
|
+
|
30
|
+
def []=(key, value)
|
31
|
+
@inner_keys << key unless @inner_hash.key?(key)
|
32
|
+
@inner_hash[key] = value
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(other_hash)
|
37
|
+
if other_hash.kind_of?(self.class)
|
38
|
+
return (other_hash.instance_variable_get('@inner_hash') == @inner_hash) &&
|
39
|
+
(other_hash.instance_variable_get('@inner_keys') == @inner_keys)
|
40
|
+
end
|
41
|
+
if other_hash.kind_of?(@inner_hash.class)
|
42
|
+
return other_hash.keys.all? do |k|
|
43
|
+
other_hash[k] == @inner_hash[k]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
def clear
|
50
|
+
@inner_hash.clear
|
51
|
+
@inner_keys.clear
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete(key)
|
55
|
+
@inner_keys.delete(key) if @inner_hash.key?(key)
|
56
|
+
@inner_hash.delete key
|
57
|
+
end
|
58
|
+
|
59
|
+
def empty?
|
60
|
+
@inner_hash.empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_a
|
64
|
+
@inner_keys.inject([]) do |result, key|
|
65
|
+
result + [[key, @inner_hash[key]]]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/app/htty/payload.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Defines HTTY::Payload.
|
2
|
+
|
3
|
+
require File.expand_path("#{File.dirname __FILE__}/ordered_hash")
|
4
|
+
|
5
|
+
module HTTY; end
|
6
|
+
|
7
|
+
# Encapsulates the headers and body of an HTTP(S) request or response.
|
8
|
+
class HTTY::Payload
|
9
|
+
|
10
|
+
# Returns the body of the payload.
|
11
|
+
attr_reader :body
|
12
|
+
|
13
|
+
# Returns +true+ if _other_payload_ is equivalent to the payload.
|
14
|
+
def ==(other_payload)
|
15
|
+
return false unless other_payload.kind_of?(HTTY::Payload)
|
16
|
+
(other_payload.body == body) && (other_payload.headers == headers)
|
17
|
+
end
|
18
|
+
alias :eql? :==
|
19
|
+
|
20
|
+
# Returns an array of the headers belonging to the payload.
|
21
|
+
def headers
|
22
|
+
@headers.to_a
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
# Initializes a new HTTY::Payload with attribute values specified in the
|
28
|
+
# _attributes_ hash.
|
29
|
+
#
|
30
|
+
# Valid _attributes_ keys include:
|
31
|
+
#
|
32
|
+
# * <tt>:body</tt>
|
33
|
+
# * <tt>:headers</tt>
|
34
|
+
def initialize(attributes={})
|
35
|
+
@body = attributes[:body]
|
36
|
+
@headers = HTTY::OrderedHash.new
|
37
|
+
Array(attributes[:headers]).each do |name, value|
|
38
|
+
@headers[name] = value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize_copy(source) #:nodoc:
|
43
|
+
super
|
44
|
+
@body = @body.dup if @body
|
45
|
+
@headers = @headers.dup
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/app/htty/request.rb
ADDED
@@ -0,0 +1,471 @@
|
|
1
|
+
# Defines HTTY::Request.
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'uri'
|
5
|
+
require File.expand_path("#{File.dirname __FILE__}/../htty")
|
6
|
+
require File.expand_path("#{File.dirname __FILE__}/cookies_util")
|
7
|
+
require File.expand_path("#{File.dirname __FILE__}/no_location_header_error")
|
8
|
+
require File.expand_path("#{File.dirname __FILE__}/no_response_error")
|
9
|
+
require File.expand_path("#{File.dirname __FILE__}/no_set_cookie_header_error")
|
10
|
+
require File.expand_path("#{File.dirname __FILE__}/payload")
|
11
|
+
require File.expand_path("#{File.dirname __FILE__}/requests_util")
|
12
|
+
require File.expand_path("#{File.dirname __FILE__}/response")
|
13
|
+
|
14
|
+
module HTTY; end
|
15
|
+
|
16
|
+
# Encapsulates an HTTP(S) request.
|
17
|
+
class HTTY::Request < HTTY::Payload
|
18
|
+
|
19
|
+
COOKIES_HEADER_NAME = 'Cookie'
|
20
|
+
|
21
|
+
METHODS_SENDING_BODY = [:post, :put]
|
22
|
+
|
23
|
+
# Returns a URI authority (a combination of userinfo, host, and port)
|
24
|
+
# corresponding to the specified _components_ hash. Valid _components_ keys
|
25
|
+
# include:
|
26
|
+
#
|
27
|
+
# * <tt>:userinfo</tt>
|
28
|
+
# * <tt>:host</tt>
|
29
|
+
# * <tt>:port</tt>
|
30
|
+
def self.build_authority(components)
|
31
|
+
userinfo_and_host = [components[:userinfo],
|
32
|
+
components[:host]].compact.join('@')
|
33
|
+
all = [userinfo_and_host, components[:port]].compact.join(':')
|
34
|
+
return nil if (all == '')
|
35
|
+
all
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns a combination of a URI path, query, and fragment, corresponding to
|
39
|
+
# the specified _components_ hash. Valid _components_ keys include:
|
40
|
+
#
|
41
|
+
# * <tt>:path</tt>
|
42
|
+
# * <tt>:query</tt>
|
43
|
+
# * <tt>:fragment</tt>
|
44
|
+
def self.build_path_query_and_fragment(components)
|
45
|
+
path = components[:path]
|
46
|
+
query = components[:query] ? "?#{components[:query]}" : nil
|
47
|
+
fragment = components[:fragment] ? "##{components[:fragment]}" : nil
|
48
|
+
all = [path, query, fragment].compact.join
|
49
|
+
return nil if (all == '')
|
50
|
+
all
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns a URI corresponding to the specified _components_ hash, or raises
|
54
|
+
# URI::InvalidURIError. Valid _components_ keys include:
|
55
|
+
#
|
56
|
+
# * <tt>:scheme</tt>
|
57
|
+
# * <tt>:userinfo</tt>
|
58
|
+
# * <tt>:host</tt>
|
59
|
+
# * <tt>:port</tt>
|
60
|
+
# * <tt>:path</tt>
|
61
|
+
# * <tt>:query</tt>
|
62
|
+
# * <tt>:fragment</tt>
|
63
|
+
def self.build_uri(components)
|
64
|
+
scheme = (components[:scheme] || 'http') + '://'
|
65
|
+
authority = build_authority(components)
|
66
|
+
path_query_and_fragment = build_path_query_and_fragment(components)
|
67
|
+
path_query_and_fragment ||= '/' if authority
|
68
|
+
unless scheme == 'http://'
|
69
|
+
raise ArgumentError, "#{scheme.inspect} is not yet supported"
|
70
|
+
end
|
71
|
+
URI.parse([scheme, authority, path_query_and_fragment].join)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns a URI corresponding to the specified _address_, or raises
|
75
|
+
# URI::InvalidURIError.
|
76
|
+
def self.parse_uri(address)
|
77
|
+
address = '0.0.0.0' if address.nil? || (address == '')
|
78
|
+
|
79
|
+
scheme_missing = false
|
80
|
+
if (address !~ /^[a-z]+:\/\//) && (address !~ /^mailto:/)
|
81
|
+
scheme_missing = true
|
82
|
+
address = 'http://' + address
|
83
|
+
end
|
84
|
+
|
85
|
+
scheme,
|
86
|
+
userinfo,
|
87
|
+
host,
|
88
|
+
port,
|
89
|
+
registry, # Not used by HTTP
|
90
|
+
path,
|
91
|
+
opaque, # Not used by HTTP
|
92
|
+
query,
|
93
|
+
fragment = URI.split(address)
|
94
|
+
|
95
|
+
scheme = nil if scheme_missing
|
96
|
+
path = nil if (path == '')
|
97
|
+
|
98
|
+
unless scheme
|
99
|
+
scheme = (port.to_i == URI::HTTPS::DEFAULT_PORT) ? 'https' : 'http'
|
100
|
+
end
|
101
|
+
|
102
|
+
build_uri :scheme => scheme,
|
103
|
+
:userinfo => userinfo,
|
104
|
+
:host => host,
|
105
|
+
:port => port,
|
106
|
+
:path => path,
|
107
|
+
:query => query,
|
108
|
+
:fragment => fragment
|
109
|
+
end
|
110
|
+
|
111
|
+
protected
|
112
|
+
|
113
|
+
def self.clear_cookies_if_host_changes(request)
|
114
|
+
previous_host = request.uri.host
|
115
|
+
yield
|
116
|
+
request.cookies_remove_all unless request.uri.host == previous_host
|
117
|
+
request
|
118
|
+
end
|
119
|
+
|
120
|
+
public
|
121
|
+
|
122
|
+
# Returns the HTTP method of the request, if any.
|
123
|
+
attr_reader :request_method
|
124
|
+
|
125
|
+
# Returns the response received for the request, if any.
|
126
|
+
attr_reader :response
|
127
|
+
|
128
|
+
# Returns the URI of the request.
|
129
|
+
attr_reader :uri
|
130
|
+
|
131
|
+
# Initializes a new HTTY::Request with a #uri corresponding to the specified
|
132
|
+
# _address_.
|
133
|
+
def initialize(address)
|
134
|
+
super({:headers => [['User-Agent', "htty/#{HTTY::VERSION}"]]})
|
135
|
+
@uri = self.class.parse_uri(address)
|
136
|
+
establish_content_length
|
137
|
+
end
|
138
|
+
|
139
|
+
def initialize_copy(source) #:nodoc:
|
140
|
+
super
|
141
|
+
@response = @response.dup if @response
|
142
|
+
@uri = @uri.dup
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns +true+ if _other_request_ is equivalent to the request.
|
146
|
+
def ==(other_request)
|
147
|
+
return false unless super(other_request)
|
148
|
+
return false unless other_request.kind_of?(self.class)
|
149
|
+
(other_request.response == response) && (other_request.uri == uri)
|
150
|
+
end
|
151
|
+
alias :eql? :==
|
152
|
+
|
153
|
+
# Establishes a new #uri corresponding to the specified _address_. If the host
|
154
|
+
# of the _address_ is different from the host of #uri, then #cookies are
|
155
|
+
# cleared.
|
156
|
+
def address(address)
|
157
|
+
uri = self.class.parse_uri(address)
|
158
|
+
if response
|
159
|
+
dup = dup_without_response
|
160
|
+
return self.class.clear_cookies_if_host_changes(dup) do
|
161
|
+
dup.uri = uri
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
self.class.clear_cookies_if_host_changes self do
|
166
|
+
@uri = uri
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Sets the body of the request.
|
171
|
+
def body_set(body)
|
172
|
+
return dup_without_response.body_set(body) if response
|
173
|
+
|
174
|
+
@body = body ? body.to_s : nil
|
175
|
+
establish_content_length
|
176
|
+
end
|
177
|
+
|
178
|
+
# Clears the body of the request.
|
179
|
+
def body_unset
|
180
|
+
body_set nil
|
181
|
+
end
|
182
|
+
|
183
|
+
# Makes an HTTP +CONNECT+ request using the path of #uri.
|
184
|
+
def connect!
|
185
|
+
request! :connect
|
186
|
+
end
|
187
|
+
|
188
|
+
# Appends to #cookies using the specified _name_ (required) and _value_
|
189
|
+
# (optional).
|
190
|
+
def cookie_add(name, value=nil)
|
191
|
+
return dup_without_response.cookie_add(name, value) if response
|
192
|
+
|
193
|
+
cookies_string = HTTY::CookiesUtil.cookies_to_string(cookies +
|
194
|
+
[[name.to_s, value]])
|
195
|
+
if cookies_string
|
196
|
+
@headers[COOKIES_HEADER_NAME] = cookies_string
|
197
|
+
else
|
198
|
+
@headers.delete COOKIES_HEADER_NAME
|
199
|
+
end
|
200
|
+
self
|
201
|
+
end
|
202
|
+
|
203
|
+
# Removes the last element of #cookies having the specified _name_.
|
204
|
+
def cookie_remove(name)
|
205
|
+
return dup_without_response.cookie_remove(name) if response
|
206
|
+
|
207
|
+
# Remove just one matching cookie from the end.
|
208
|
+
rejected = false
|
209
|
+
new_cookies = cookies.reverse.reject do |cookie_name, cookie_value|
|
210
|
+
if !rejected && (cookie_name == name)
|
211
|
+
rejected = true
|
212
|
+
else
|
213
|
+
false
|
214
|
+
end
|
215
|
+
end.reverse
|
216
|
+
|
217
|
+
cookies_string = HTTY::CookiesUtil.cookies_to_string(new_cookies)
|
218
|
+
if cookies_string
|
219
|
+
@headers[COOKIES_HEADER_NAME] = cookies_string
|
220
|
+
else
|
221
|
+
@headers.delete COOKIES_HEADER_NAME
|
222
|
+
end
|
223
|
+
self
|
224
|
+
end
|
225
|
+
|
226
|
+
# Returns an array of the cookies belonging to the request.
|
227
|
+
def cookies
|
228
|
+
HTTY::CookiesUtil.cookies_from_string @headers[COOKIES_HEADER_NAME]
|
229
|
+
end
|
230
|
+
|
231
|
+
# Removes all #cookies.
|
232
|
+
def cookies_remove_all
|
233
|
+
return dup_without_response.cookies_remove_all if response
|
234
|
+
|
235
|
+
@headers.delete COOKIES_HEADER_NAME
|
236
|
+
self
|
237
|
+
end
|
238
|
+
|
239
|
+
# Sets #cookies according to the _Set-Cookie_ header of the specified
|
240
|
+
# _response_, or raises either HTTY::NoResponseError or
|
241
|
+
# HTTY::NoSetCookieHeaderError.
|
242
|
+
def cookies_use(response)
|
243
|
+
raise HTTY::NoResponseError unless response
|
244
|
+
|
245
|
+
cookies_header = response.headers.detect do |name, value|
|
246
|
+
name == HTTY::Response::COOKIES_HEADER_NAME
|
247
|
+
end
|
248
|
+
unless cookies_header && cookies_header.last
|
249
|
+
raise HTTY::NoSetCookieHeaderError
|
250
|
+
end
|
251
|
+
header_set COOKIES_HEADER_NAME, cookies_header.last
|
252
|
+
end
|
253
|
+
|
254
|
+
# Makes an HTTP +DELETE+ request using the path of #uri.
|
255
|
+
def delete!
|
256
|
+
request! :delete
|
257
|
+
end
|
258
|
+
|
259
|
+
# Establishes a new #uri according to the _Location_ header of the specified
|
260
|
+
# _response_, or raises either HTTY::NoResponseError or
|
261
|
+
# HTTY::NoLocationHeaderError.
|
262
|
+
def follow(response)
|
263
|
+
raise HTTY::NoResponseError unless response
|
264
|
+
|
265
|
+
location_header = response.headers.detect do |name, value|
|
266
|
+
name == 'Location'
|
267
|
+
end
|
268
|
+
unless location_header && location_header.last
|
269
|
+
raise HTTY::NoLocationHeaderError
|
270
|
+
end
|
271
|
+
address location_header.last
|
272
|
+
end
|
273
|
+
|
274
|
+
# Establishes a new #uri with the specified _fragment_.
|
275
|
+
def fragment_set(fragment)
|
276
|
+
rebuild_uri :fragment => fragment
|
277
|
+
end
|
278
|
+
|
279
|
+
# Establishes a new #uri without a fragment.
|
280
|
+
def fragment_unset
|
281
|
+
fragment_set nil
|
282
|
+
end
|
283
|
+
|
284
|
+
# Makes an HTTP +GET+ request using the path of #uri.
|
285
|
+
def get!
|
286
|
+
request! :get
|
287
|
+
end
|
288
|
+
|
289
|
+
# Makes an HTTP +HEAD+ request using the path of #uri.
|
290
|
+
def head!
|
291
|
+
request! :head
|
292
|
+
end
|
293
|
+
|
294
|
+
# Appends to #headers or changes the element of #headers using the specified
|
295
|
+
# _name_ and _value_.
|
296
|
+
def header_set(name, value)
|
297
|
+
return dup_without_response.header_set(name, value) if response
|
298
|
+
|
299
|
+
name = name.to_s
|
300
|
+
if value.nil?
|
301
|
+
@headers.delete name
|
302
|
+
return self
|
303
|
+
end
|
304
|
+
|
305
|
+
@headers[name] = value.to_s
|
306
|
+
self
|
307
|
+
end
|
308
|
+
|
309
|
+
# Removes the element of #headers having the specified _name_.
|
310
|
+
def header_unset(name)
|
311
|
+
header_set name, nil
|
312
|
+
end
|
313
|
+
|
314
|
+
# Returns an array of the headers belonging to the payload. If
|
315
|
+
# _include_content_length_ is +false+, then a 'Content Length' header will be
|
316
|
+
# omitted. If _include_content_length_ is not specified, then it will be
|
317
|
+
# +true+ if #request_method is an HTTP method for which body content is
|
318
|
+
# expected.
|
319
|
+
def headers(include_content_length=
|
320
|
+
METHODS_SENDING_BODY.include?(request_method))
|
321
|
+
unless include_content_length
|
322
|
+
return super().reject do |name, value|
|
323
|
+
name == 'Content-Length'
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
super()
|
328
|
+
end
|
329
|
+
|
330
|
+
# Removes all #headers.
|
331
|
+
def headers_unset_all
|
332
|
+
return dup_without_response.headers_unset_all if response
|
333
|
+
|
334
|
+
@headers.clear
|
335
|
+
self
|
336
|
+
end
|
337
|
+
|
338
|
+
# Establishes a new #uri with the specified _host_.
|
339
|
+
def host_set(host)
|
340
|
+
rebuild_uri :host => host
|
341
|
+
end
|
342
|
+
|
343
|
+
# Makes an HTTP +OPTIONS+ request using the path of #uri.
|
344
|
+
def options!
|
345
|
+
request! :options
|
346
|
+
end
|
347
|
+
|
348
|
+
# Makes an HTTP +PATCH+ request using the path of #uri.
|
349
|
+
def patch!
|
350
|
+
request! :patch
|
351
|
+
end
|
352
|
+
|
353
|
+
# Establishes a new #uri with the specified _path_ which may be absolute or
|
354
|
+
# relative.
|
355
|
+
def path_set(path)
|
356
|
+
absolute_path = (Pathname.new(uri.path) + path).to_s
|
357
|
+
rebuild_uri :path => absolute_path
|
358
|
+
end
|
359
|
+
|
360
|
+
# Establishes a new #uri with the specified _port_.
|
361
|
+
def port_set(port)
|
362
|
+
rebuild_uri :port => port
|
363
|
+
end
|
364
|
+
|
365
|
+
# Makes an HTTP +POST+ request using the path of #uri.
|
366
|
+
def post!
|
367
|
+
request! :post
|
368
|
+
end
|
369
|
+
|
370
|
+
# Makes an HTTP +PUT+ request using the path of #uri.
|
371
|
+
def put!
|
372
|
+
request! :put
|
373
|
+
end
|
374
|
+
|
375
|
+
# Establishes a new #uri, with the specified _value_ for the query-string
|
376
|
+
# parameter specified by _name_.
|
377
|
+
def query_set(name, value)
|
378
|
+
query = uri.query ? "&#{uri.query}&" : ''
|
379
|
+
parameter = Regexp.new("&#{Regexp.escape name}=.+?&")
|
380
|
+
if query =~ parameter
|
381
|
+
new_query = value.nil? ?
|
382
|
+
query.gsub(parameter, '&') :
|
383
|
+
query.gsub(parameter, "&#{name}=#{value}&")
|
384
|
+
else
|
385
|
+
new_query = value.nil? ? query : "#{query}#{name}=#{value}"
|
386
|
+
end
|
387
|
+
new_query = new_query.gsub(/^&/, '').gsub(/&$/, '')
|
388
|
+
new_query = nil if (new_query == '')
|
389
|
+
rebuild_uri :query => new_query
|
390
|
+
end
|
391
|
+
|
392
|
+
# Establishes a new #uri, without the query-string parameter specified by
|
393
|
+
# _name_.
|
394
|
+
def query_unset(name)
|
395
|
+
query_set name, nil
|
396
|
+
end
|
397
|
+
|
398
|
+
# Establishes a new #uri without a query string.
|
399
|
+
def query_unset_all
|
400
|
+
rebuild_uri :query => nil
|
401
|
+
end
|
402
|
+
|
403
|
+
# Establishes a new #uri with the specified _scheme_.
|
404
|
+
def scheme_set(scheme)
|
405
|
+
rebuild_uri :scheme => scheme
|
406
|
+
end
|
407
|
+
|
408
|
+
# Makes an HTTP +TRACE+ request using the path of #uri.
|
409
|
+
def trace!
|
410
|
+
request! :trace
|
411
|
+
end
|
412
|
+
|
413
|
+
# Establishes a new #uri with the specified _userinfo_.
|
414
|
+
def userinfo_set(userinfo)
|
415
|
+
rebuild_uri :userinfo => userinfo
|
416
|
+
end
|
417
|
+
|
418
|
+
# Establishes a new #uri without userinfo.
|
419
|
+
def userinfo_unset
|
420
|
+
userinfo_set nil
|
421
|
+
end
|
422
|
+
|
423
|
+
protected
|
424
|
+
|
425
|
+
def dup_without_response
|
426
|
+
request = self.dup
|
427
|
+
request.response = nil
|
428
|
+
request.instance_variable_set '@request_method', nil
|
429
|
+
request
|
430
|
+
end
|
431
|
+
|
432
|
+
def establish_content_length
|
433
|
+
header_set 'Content-Length', body.to_s.length
|
434
|
+
end
|
435
|
+
|
436
|
+
def path_query_and_fragment
|
437
|
+
self.class.build_path_query_and_fragment :path => uri.path,
|
438
|
+
:query => uri.query,
|
439
|
+
:fragment => uri.fragment
|
440
|
+
end
|
441
|
+
|
442
|
+
def rebuild_uri(changed_components)
|
443
|
+
return dup_without_response.rebuild_uri(changed_components) if response
|
444
|
+
|
445
|
+
components = URI::HTTP::COMPONENT.inject({}) do |result, c|
|
446
|
+
result.merge c => uri.send(c)
|
447
|
+
end
|
448
|
+
self.class.clear_cookies_if_host_changes self do
|
449
|
+
@uri = self.class.build_uri(components.merge(changed_components))
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
attr_writer :response
|
454
|
+
|
455
|
+
attr_writer :uri
|
456
|
+
|
457
|
+
private
|
458
|
+
|
459
|
+
def authority
|
460
|
+
self.class.build_authority :userinfo => uri.userinfo,
|
461
|
+
:host => uri.host,
|
462
|
+
:port => uri.port
|
463
|
+
end
|
464
|
+
|
465
|
+
def request!(method)
|
466
|
+
request = response ? dup_without_response : self
|
467
|
+
request.instance_variable_set '@request_method', method
|
468
|
+
HTTY::RequestsUtil.send method, request
|
469
|
+
end
|
470
|
+
|
471
|
+
end
|