resourceful 0.5.4 → 0.6.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/History.txt +18 -0
- data/Manifest +11 -1
- data/README.markdown +11 -2
- data/Rakefile +38 -18
- data/lib/resourceful.rb +6 -2
- data/lib/resourceful/abstract_form_data.rb +18 -0
- data/lib/resourceful/header.rb +228 -97
- data/lib/resourceful/http_accessor.rb +32 -26
- data/lib/resourceful/multipart_form_data.rb +46 -0
- data/lib/resourceful/net_http_adapter.rb +19 -5
- data/lib/resourceful/options_interpretation.rb +72 -0
- data/lib/resourceful/request.rb +6 -2
- data/lib/resourceful/resource.rb +25 -9
- data/lib/resourceful/response.rb +5 -5
- data/lib/resourceful/urlencoded_form_data.rb +17 -0
- data/resourceful.gemspec +7 -7
- data/spec/acceptance/caching_spec.rb +6 -8
- data/spec/acceptance/header_spec.rb +1 -1
- data/spec/acceptance/resource_spec.rb +3 -3
- data/spec/caching_spec.rb +89 -0
- data/spec/resourceful/header_spec.rb +8 -0
- data/spec/resourceful/multipart_form_data_spec.rb +77 -0
- data/spec/resourceful/resource_spec.rb +20 -0
- data/spec/resourceful/urlencoded_form_data_spec.rb +44 -0
- data/spec/simple_sinatra_server.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- metadata +18 -6
- data/lib/resourceful/options_interpreter.rb +0 -78
data/History.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Version 0.6.0
|
2
|
+
=============
|
3
|
+
|
4
|
+
* Improved support for multi-valued header fields. (Peter Williams)
|
5
|
+
* Added convenience mechanisms for making URL encoded and multipart form data requests. (Peter Williams)
|
6
|
+
* Ruby 1.9 support (Peter Williams)
|
7
|
+
|
8
|
+
Compatibility issues
|
9
|
+
--------------------
|
10
|
+
|
11
|
+
* The semantics of the Resourceful::Header API have changed slightly.
|
12
|
+
Previously, any header might return a string or an array. Now
|
13
|
+
fields are either "single-value" and always return a string, or
|
14
|
+
"multi-value" and always return an array. See API doc for more
|
15
|
+
details.
|
16
|
+
|
17
|
+
|
18
|
+
|
data/Manifest
CHANGED
@@ -1,17 +1,22 @@
|
|
1
1
|
lib/resourceful.rb
|
2
2
|
lib/resourceful/net_http_adapter.rb
|
3
|
+
lib/resourceful/options_interpretation.rb
|
3
4
|
lib/resourceful/stubbed_resource_proxy.rb
|
5
|
+
lib/resourceful/urlencoded_form_data.rb
|
4
6
|
lib/resourceful/header.rb
|
5
7
|
lib/resourceful/memcache_cache_manager.rb
|
6
8
|
lib/resourceful/response.rb
|
7
9
|
lib/resourceful/util.rb
|
8
|
-
lib/resourceful/
|
10
|
+
lib/resourceful/abstract_form_data.rb
|
9
11
|
lib/resourceful/cache_manager.rb
|
10
12
|
lib/resourceful/request.rb
|
11
13
|
lib/resourceful/resource.rb
|
12
14
|
lib/resourceful/exceptions.rb
|
15
|
+
lib/resourceful/multipart_form_data.rb
|
13
16
|
lib/resourceful/http_accessor.rb
|
14
17
|
lib/resourceful/authentication_manager.rb
|
18
|
+
History.txt
|
19
|
+
resourceful.gemspec
|
15
20
|
README.markdown
|
16
21
|
MIT-LICENSE
|
17
22
|
Rakefile
|
@@ -26,4 +31,9 @@ spec/acceptance/header_spec.rb
|
|
26
31
|
spec/acceptance/resource_spec.rb
|
27
32
|
spec/acceptance/caching_spec.rb
|
28
33
|
spec/acceptance/redirecting_spec.rb
|
34
|
+
spec/resourceful/multipart_form_data_spec.rb
|
35
|
+
spec/resourceful/header_spec.rb
|
36
|
+
spec/resourceful/resource_spec.rb
|
37
|
+
spec/resourceful/urlencoded_form_data_spec.rb
|
38
|
+
spec/caching_spec.rb
|
29
39
|
spec/spec.opts
|
data/README.markdown
CHANGED
@@ -58,8 +58,17 @@ Post a URL encoded form
|
|
58
58
|
|
59
59
|
require 'resourceful'
|
60
60
|
http = Resourceful::HttpAccessor.new
|
61
|
-
resp = http.resource('http://mysite.example/service').
|
62
|
-
post('
|
61
|
+
resp = http.resource('http://mysite.example/service').
|
62
|
+
post(Resourceful::UrlencodedFormData.new(:hostname => 'test', :level => 'super'))
|
63
|
+
|
64
|
+
Post a Mulitpart form with a file
|
65
|
+
-----------------------
|
66
|
+
|
67
|
+
require 'resourceful'
|
68
|
+
http = Resourceful::HttpAccessor.new
|
69
|
+
form_data = Resourceful::MultipartFormData.new(:username => 'me')
|
70
|
+
form_data.add_file('avatar', '/tmp/my_avatar.png', 'image/png')
|
71
|
+
resp = http.resource('http://mysite.example/service').post(form_data)
|
63
72
|
|
64
73
|
Put an XML document
|
65
74
|
-------------------
|
data/Rakefile
CHANGED
@@ -12,8 +12,9 @@ begin
|
|
12
12
|
p.email = "psadauskas@gmail.com"
|
13
13
|
|
14
14
|
p.ignore_pattern = ["pkg/*", "tmp/*"]
|
15
|
-
p.dependencies = ['addressable', 'httpauth']
|
15
|
+
p.dependencies = [['addressable', '>= 2.1.0'], 'httpauth']
|
16
16
|
p.development_dependencies = ['thin', 'yard', 'sinatra', 'rspec']
|
17
|
+
p.retain_gemspec = true
|
17
18
|
end
|
18
19
|
rescue LoadError => e
|
19
20
|
puts "install 'echoe' gem to be able to build the gem"
|
@@ -21,31 +22,50 @@ end
|
|
21
22
|
|
22
23
|
require 'spec/rake/spectask'
|
23
24
|
|
24
|
-
desc 'Run all specs'
|
25
|
+
desc 'Run all acceptance specs'
|
26
|
+
|
25
27
|
Spec::Rake::SpecTask.new(:spec) do |t|
|
26
28
|
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
27
29
|
t.libs << 'lib'
|
28
|
-
t.spec_files = FileList['spec
|
30
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
29
31
|
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
namespace :spec do
|
34
|
+
desc 'Run all acceptance specs'
|
35
|
+
Spec::Rake::SpecTask.new(:acceptance) do |t|
|
36
|
+
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
37
|
+
t.libs << 'lib'
|
38
|
+
t.spec_files = FileList['spec/acceptance/*_spec.rb']
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'Run all unit specs'
|
42
|
+
Spec::Rake::SpecTask.new(:unit) do |t|
|
43
|
+
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
44
|
+
t.libs << 'lib'
|
45
|
+
t.spec_files = FileList['spec/**/*_spec.rb'] - (FileList['spec/acceptance/*_spec.rb'] + FileList['spec/simple_sinatra_server_spec.rb'] )
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'Run the specs for the server'
|
49
|
+
Spec::Rake::SpecTask.new('server') do |t|
|
50
|
+
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
51
|
+
t.libs << 'lib'
|
52
|
+
t.spec_files = FileList['spec/simple_sinatra_server_spec.rb']
|
53
|
+
end
|
36
54
|
end
|
37
55
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
56
|
+
task :server do
|
57
|
+
begin
|
58
|
+
require 'spec/simple_sinatra_server'
|
59
|
+
desc "Run the sinatra echo server, with loggin"
|
60
|
+
task :server do
|
61
|
+
Sinatra::Default.set(
|
62
|
+
:run => true,
|
63
|
+
:logging => true
|
64
|
+
)
|
65
|
+
end
|
66
|
+
rescue LoadError => e
|
67
|
+
puts "Install 'sinatra' gem to run the server"
|
46
68
|
end
|
47
|
-
rescue LoadError => e
|
48
|
-
puts "Install 'sinatra' gem to run the server"
|
49
69
|
end
|
50
70
|
|
51
71
|
desc 'Default: Run Specs'
|
data/lib/resourceful.rb
CHANGED
@@ -10,9 +10,13 @@ require 'resourceful/util'
|
|
10
10
|
require 'resourceful/header'
|
11
11
|
require 'resourceful/http_accessor'
|
12
12
|
|
13
|
+
module Resourceful
|
14
|
+
autoload :MultipartFormData, 'resourceful/multipart_form_data'
|
15
|
+
autoload :UrlencodedFormData, 'resourceful/urlencoded_form_data'
|
16
|
+
end
|
17
|
+
|
13
18
|
# Resourceful is a library that provides a high level HTTP interface.
|
14
19
|
module Resourceful
|
15
|
-
VERSION = "0.
|
20
|
+
VERSION = "0.6.0"
|
16
21
|
RESOURCEFUL_USER_AGENT_TOKEN = "Resourceful/#{VERSION}(Ruby/#{RUBY_VERSION})"
|
17
|
-
|
18
22
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Resourceful
|
2
|
+
class AbstractFormData
|
3
|
+
def initialize(contents = {})
|
4
|
+
@form_data = []
|
5
|
+
|
6
|
+
contents.each do |k,v|
|
7
|
+
add(k, v)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(name, value)
|
12
|
+
form_data << [name.to_s, value]
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
attr_reader :form_data
|
17
|
+
end
|
18
|
+
end
|
data/lib/resourceful/header.rb
CHANGED
@@ -1,125 +1,256 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'resourceful/options_interpretation'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
# Represents the header fields of an HTTP message. To access a field
|
5
|
+
# you can use `#[]` and `#[]=`. For example, to get the content type
|
6
|
+
# of a response you can do
|
7
|
+
#
|
8
|
+
# response.header['Content-Type'] # => "application/xml"
|
9
|
+
#
|
10
|
+
# Lookups and modifications done in this way are case insensitive, so
|
11
|
+
# 'Content-Type', 'content-type' and :content_type are all equivalent.
|
12
|
+
#
|
13
|
+
# Multi-valued fields
|
14
|
+
# -------------------
|
15
|
+
#
|
16
|
+
# Multi-value fields (e.g. Accept) are always returned as an Array
|
17
|
+
# regardless of the number of values, if the field is present.
|
18
|
+
# Single-value fields (e.g. Content-Type) are always returned as
|
19
|
+
# strings. The multi/single valueness of a header field is determined
|
20
|
+
# by the way it is defined in the HTTP spec. Unknown fields are
|
21
|
+
# treated as multi-valued.
|
22
|
+
#
|
23
|
+
# (This behavior is new in 0.6 and may be slightly incompatible with
|
24
|
+
# the way previous versions worked in some situations.)
|
25
|
+
#
|
26
|
+
# For example
|
27
|
+
#
|
28
|
+
# h = Resourceful::Header.new
|
29
|
+
# h['Accept'] = "application/xml"
|
30
|
+
# h['Accept'] # => ["application/xml"]
|
31
|
+
#
|
3
32
|
module Resourceful
|
4
|
-
class Header
|
33
|
+
class Header
|
34
|
+
include Enumerable
|
35
|
+
|
5
36
|
def initialize(hash={})
|
37
|
+
@raw_fields = {}
|
6
38
|
hash.each { |k, v| self[k] = v }
|
7
39
|
end
|
8
40
|
|
9
41
|
def to_hash
|
10
|
-
|
42
|
+
@raw_fields.dup
|
11
43
|
end
|
12
44
|
|
13
45
|
def [](k)
|
14
|
-
|
46
|
+
field_def(k).get_from(@raw_fields)
|
15
47
|
end
|
16
48
|
|
17
49
|
def []=(k, v)
|
18
|
-
|
50
|
+
field_def(k).set_to(v, @raw_fields)
|
19
51
|
end
|
20
52
|
|
21
53
|
def has_key?(k)
|
22
|
-
|
54
|
+
field_def(k).exists_in?(@raw_fields)
|
55
|
+
end
|
56
|
+
|
57
|
+
def each(&blk)
|
58
|
+
@raw_fields.each(&blk)
|
59
|
+
end
|
60
|
+
alias each_field each
|
61
|
+
|
62
|
+
def merge!(another)
|
63
|
+
another.each do |k,v|
|
64
|
+
self[k] = v
|
65
|
+
end
|
66
|
+
self
|
23
67
|
end
|
24
68
|
|
25
|
-
def
|
26
|
-
|
69
|
+
def merge(another)
|
70
|
+
self.class.new(self).merge!(another)
|
27
71
|
end
|
28
72
|
|
29
|
-
def
|
30
|
-
|
31
|
-
blk.call capitalize(k), v
|
32
|
-
}
|
73
|
+
def reverse_merge(another)
|
74
|
+
self.class.new(another).merge!(self)
|
33
75
|
end
|
34
76
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
#{const} = "#{header}".freeze # ACCEPT = "accept".freeze
|
92
|
-
|
93
|
-
def #{meth} # def accept
|
94
|
-
self[#{const}] # self[ACCEPT]
|
95
|
-
end # end
|
96
|
-
|
97
|
-
def #{meth}=(str) # def accept=(str)
|
98
|
-
self[#{const}] = str # self[ACCEPT] = str
|
99
|
-
end # end
|
100
|
-
RUBY
|
77
|
+
def dup
|
78
|
+
self.class.new(@raw_fields.dup)
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# Class to handle the details of each type of field.
|
83
|
+
class HeaderFieldDef
|
84
|
+
include Comparable
|
85
|
+
include OptionsInterpretation
|
86
|
+
|
87
|
+
##
|
88
|
+
attr_reader :name
|
89
|
+
|
90
|
+
def initialize(name, options = {})
|
91
|
+
@name = name
|
92
|
+
extract_opts(options) do |opts|
|
93
|
+
@repeatable = opts.extract(:repeatable, :default => false)
|
94
|
+
@hop_by_hop = opts.extract(:hop_by_hop, :default => false)
|
95
|
+
@modifiable = opts.extract(:modifiable, :default => true)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def repeatable?
|
100
|
+
@repeatable
|
101
|
+
end
|
102
|
+
|
103
|
+
def hop_by_hop?
|
104
|
+
@hop_by_hop
|
105
|
+
end
|
106
|
+
|
107
|
+
def modifiable?
|
108
|
+
@modifiable
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_from(raw_fields_hash)
|
112
|
+
raw_fields_hash[name]
|
113
|
+
end
|
114
|
+
|
115
|
+
def set_to(value, raw_fields_hash)
|
116
|
+
raw_fields_hash[name] = if repeatable?
|
117
|
+
Array(value)
|
118
|
+
elsif value.kind_of?(Array)
|
119
|
+
raise ArgumentError, "#{name} field may only have one value" if value.size > 1
|
120
|
+
value.first
|
121
|
+
else
|
122
|
+
value
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def exists_in?(raw_fields_hash)
|
127
|
+
raw_fields_hash.has_key?(name)
|
128
|
+
end
|
129
|
+
|
130
|
+
def <=>(another)
|
131
|
+
name <=> another.name
|
132
|
+
end
|
101
133
|
|
134
|
+
def ==(another)
|
135
|
+
name_pattern === another.name
|
136
|
+
end
|
137
|
+
alias eql? ==
|
138
|
+
|
139
|
+
def ===(another)
|
140
|
+
if another.kind_of?(HeaderFieldDef)
|
141
|
+
self == another
|
142
|
+
else
|
143
|
+
name_pattern === another
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def name_pattern
|
148
|
+
Regexp.new('^' + name.gsub('-', '[_-]') + '$', Regexp::IGNORECASE)
|
149
|
+
end
|
150
|
+
|
151
|
+
def methodized_name
|
152
|
+
name.downcase.gsub('-', '_')
|
153
|
+
end
|
154
|
+
|
155
|
+
alias to_s name
|
156
|
+
|
157
|
+
def gen_setter(klass)
|
158
|
+
klass.class_eval <<-RUBY
|
159
|
+
def #{methodized_name}=(val) # def accept=(val)
|
160
|
+
self['#{name}'] = val # self['Accept'] = val
|
161
|
+
end # end
|
162
|
+
RUBY
|
163
|
+
end
|
164
|
+
|
165
|
+
def gen_getter(klass)
|
166
|
+
klass.class_eval <<-RUBY
|
167
|
+
def #{methodized_name} # def accept
|
168
|
+
self['#{name}'] # self['Accept']
|
169
|
+
end # end
|
170
|
+
RUBY
|
171
|
+
end
|
172
|
+
|
173
|
+
def gen_canonical_name_const(klass)
|
174
|
+
const_name = name.upcase.gsub('-', '_')
|
175
|
+
|
176
|
+
klass.const_set(const_name, name)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
@@header_field_defs = Set.new
|
181
|
+
|
182
|
+
def self.header_field(name, options = {})
|
183
|
+
hfd = HeaderFieldDef.new(name, options)
|
184
|
+
|
185
|
+
@@header_field_defs << hfd
|
186
|
+
|
187
|
+
hfd.gen_getter(self)
|
188
|
+
hfd.gen_setter(self)
|
189
|
+
hfd.gen_canonical_name_const(self)
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.hop_by_hop_headers
|
193
|
+
@@header_field_defs.select{|hfd| hfd.hop_by_hop?}
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.non_modifiable_headers
|
197
|
+
@@header_field_defs.reject{|hfd| hfd.repeatable?}
|
198
|
+
end
|
199
|
+
|
200
|
+
def field_def(name)
|
201
|
+
@@header_field_defs.find{|hfd| hfd === name} ||
|
202
|
+
HeaderFieldDef.new(name.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }.gsub('_', '-'), :repeatable => true)
|
102
203
|
end
|
103
204
|
|
104
|
-
HOP_BY_HOP_HEADERS = [
|
105
|
-
CONNECTION,
|
106
|
-
KEEP_ALIVE,
|
107
|
-
PROXY_AUTHENTICATE,
|
108
|
-
PROXY_AUTHORIZATION,
|
109
|
-
TE,
|
110
|
-
TRAILER,
|
111
|
-
TRANSFER_ENCODING,
|
112
|
-
UPGRADE
|
113
|
-
].freeze
|
114
|
-
|
115
|
-
NON_MODIFIABLE_HEADERS = [
|
116
|
-
CONTENT_LOCATION,
|
117
|
-
CONTENT_MD5,
|
118
|
-
ETAG,
|
119
|
-
LAST_MODIFIED,
|
120
|
-
EXPIRES
|
121
|
-
].freeze
|
122
205
|
|
206
|
+
header_field('Accept', :repeatable => true)
|
207
|
+
header_field('Accept-Charset', :repeatable => true)
|
208
|
+
header_field('Accept-Encoding', :repeatable => true)
|
209
|
+
header_field('Accept-Language', :repeatable => true)
|
210
|
+
header_field('Accept-Ranges', :repeatable => true)
|
211
|
+
header_field('Age')
|
212
|
+
header_field('Allow', :repeatable => true)
|
213
|
+
header_field('Authorization', :repeatable => true)
|
214
|
+
header_field('Cache-Control', :repeatable => true)
|
215
|
+
header_field('Connection', :hop_by_hop => true)
|
216
|
+
header_field('Content-Encoding', :repeatable => true)
|
217
|
+
header_field('Content-Language', :repeatable => true)
|
218
|
+
header_field('Content-Length')
|
219
|
+
header_field('Content-Location', :modifiable => false)
|
220
|
+
header_field('Content-MD5', :modifiable => false)
|
221
|
+
header_field('Content-Range')
|
222
|
+
header_field('Content-Type')
|
223
|
+
header_field('Date')
|
224
|
+
header_field('ETag', :modifiable => false)
|
225
|
+
header_field('Expect', :repeatable => true)
|
226
|
+
header_field('Expires', :modifiable => false)
|
227
|
+
header_field('From')
|
228
|
+
header_field('Host')
|
229
|
+
header_field('If-Match', :repeatable => true)
|
230
|
+
header_field('If-Modified-Since')
|
231
|
+
header_field('If-None-Match', :repeatable => true)
|
232
|
+
header_field('If-Range')
|
233
|
+
header_field('If-Unmodified-Since')
|
234
|
+
header_field('Keep-Alive', :hop_by_hop => true)
|
235
|
+
header_field('Last-Modified', :modifiable => false)
|
236
|
+
header_field('Location')
|
237
|
+
header_field('Max-Forwards')
|
238
|
+
header_field('Pragma', :repeatable => true)
|
239
|
+
header_field('Proxy-Authenticate', :hop_by_hop => true)
|
240
|
+
header_field('Proxy-Authorization', :hop_by_hop => true)
|
241
|
+
header_field('Range')
|
242
|
+
header_field('Referer')
|
243
|
+
header_field('Retry-After')
|
244
|
+
header_field('Server')
|
245
|
+
header_field('TE', :repeatable => true, :hop_by_hop => true)
|
246
|
+
header_field('Trailer', :repeatable => true, :hop_by_hop => true)
|
247
|
+
header_field('Transfer-Encoding', :repeatable => true, :hop_by_hop => true)
|
248
|
+
header_field('Upgrade', :repeatable => true, :hop_by_hop => true)
|
249
|
+
header_field('User-Agent')
|
250
|
+
header_field('Vary', :repeatable => true)
|
251
|
+
header_field('Via', :repeatable => true)
|
252
|
+
header_field('Warning', :repeatable => true)
|
253
|
+
header_field('WWW-Authenticate', :repeatable => true)
|
123
254
|
end
|
124
255
|
end
|
125
256
|
|