hugs 2.6.0 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -41,9 +41,3 @@ ruby 1.9.2
41
41
  Tests can run offline thanks to [webmock](https://github.com/bblimke/webmock).
42
42
 
43
43
  $ bundle exec rake
44
-
45
- ## TODOs
46
-
47
- * avoid the parse (eg client.raw)
48
- * override type per request
49
- * support hased query params
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
6
6
  s.name = "hugs"
7
7
  s.version = Hugs::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
- s.authors = ["John Dewey"]
10
- s.email = ["john@dewey.ws"]
9
+ s.authors = ["John Dewey", "Josh Kleinpeter"]
10
+ s.email = ["john@dewey.ws", "josh@kleinpeter.org"]
11
11
  s.homepage = %q{http://github.com/retr0h/hugs}
12
12
  s.summary = %q{Hugs net-http-persistent with convenient get, delete, post, and put methods.}
13
13
 
@@ -21,7 +21,6 @@ Gem::Specification.new do |s|
21
21
  s.add_dependency "yajl-ruby", "~> 0.7.9"
22
22
  s.add_dependency "nokogiri", "~> 1.4.4"
23
23
  s.add_dependency "net-http-persistent", "~> 1.4.1"
24
- s.add_dependency "multipart-post", "~> 1.0.1"
25
24
 
26
25
  s.add_development_dependency "rake"
27
26
  s.add_development_dependency "webmock"
@@ -11,6 +11,7 @@ module Hugs
11
11
  :binary => "application/octet-stream",
12
12
  :json => "application/json",
13
13
  :xml => "application/xml",
14
+ :none => "text/plain"
14
15
  }.freeze
15
16
 
16
17
  CLASSES = [
@@ -25,24 +26,22 @@ module Hugs
25
26
  # Required options:
26
27
  # +host+: A String with the host to connect.
27
28
  # Optional:
28
- # +user+: A String containing the username for use in HTTP Basic Authentication.
29
- # +pass+: A String containing the password for use in HTTP Basic Authentication.
30
- # +port+: An Integer containing the port to connect.
31
- # +scheme+: A String containing the HTTP scheme.
32
- # +type+: A Symbol containing (:json or :xml) for automatic content-type parsing
33
- # and encoding.
34
- # +headers+: A Hash containing HTTP headers.
35
- # +raises+: A boolean if HTTP 4xx and 5xx status codes should be raised.
29
+ # +user+: A String containing the username for use in HTTP Basic Authentication.
30
+ # +pass+: A String containing the password for use in HTTP Basic Authentication.
31
+ # +port+: An Integer containing the port to connect.
32
+ # +scheme+: A String containing the HTTP scheme.
33
+ # +type+: A Symbol containing (:json or :xml) for automatic content-type parsing
34
+ # and encoding.
35
+ # +raises+: A boolean if HTTP 4xx and 5xx status codes should be raised.
36
36
 
37
37
  def initialize options
38
38
  @user = options[:user]
39
39
  @pass = options[:password]
40
40
  host = options[:host]
41
41
  raises = options[:raise_errors]
42
- port = options[:port] || 80
43
- scheme = options[:scheme] || "http"
44
- @type = options[:type] || :json
45
- @headers = options[:headers] || {}
42
+ port = options[:port] || 80
43
+ scheme = options[:scheme] || "http"
44
+ @type = options[:type] || :json
46
45
 
47
46
  @http = Net::HTTP::Persistent.new
48
47
  @errors = Errors::HTTP.new :raise_errors => raises
@@ -53,14 +52,6 @@ module Hugs
53
52
 
54
53
  ##
55
54
  # Perform an HTTP Delete, Head, Get, Post, or Put.
56
- #
57
- # +path+: A String with the path to the HTTP resource.
58
- # +params+: A Hash with the following keys:
59
- # +:query+: A String with the format "foo=bar".
60
- # +:body+: A String containing the message body for Put and Post requests.
61
- # +:upload+: A Hash with the following keys:
62
- # +file+: The file to be HTTP chunked uploaded.
63
- # +headers+: A Hash containing additional HTTP headers.
64
55
 
65
56
  CLASSES.each do |clazz|
66
57
  verb = clazz.to_s.split("::").last.downcase
@@ -92,74 +83,102 @@ module Hugs
92
83
  ##
93
84
  # Worker method to be called by #delete, #get, #head, #post, or #put.
94
85
  #
95
- # Method arguments have been documented in the callers.
86
+ # +clazz+: A Net::HTTP request Object.
87
+ # +path+: A String with the path to the HTTP resource.
88
+ # +params+: A Hash containing the following keys:
89
+ # +:query+: A String with the format "foo=bar".
90
+ # +:type+: A Symbol with the mime_type.
91
+ # +:body+: A String containing the message body for Put and Post requests.
92
+ # +:upload+: A sub-Hash with the following keys:
93
+ # +:file+: The file to be HTTP chunked uploaded.
94
+ # +:headers+: A Hash containing additional HTTP headers.
96
95
 
97
96
  def response_for clazz, path, params
98
97
  request = clazz.new path_with_query path, params[:query]
99
- request.body = encode params[:body]
98
+ request.body = encode params[:body], params[:type]
100
99
 
101
- handle_request request, params[:upload]
100
+ handle_request request, params[:upload], params[:type], params[:headers] || {}
102
101
  end
103
102
 
104
103
  ##
105
104
  # Handles setting headers, performing the HTTP connection, parsing the
106
105
  # response, and checking for any response errors (if configured).
107
106
  #
108
- # +request+: A net/http request Object.
107
+ # +request+: A Net::HTTP request Object.
108
+ # +:upload+: A sub-Hash with the following keys:
109
+ # +:file+: The file to be HTTP chunked uploaded.
110
+ # +:headers+: A Hash containing additional HTTP headers.
111
+ # +:type+: A Symbol with the mime_type.
112
+ # +:headers+: A Hash containing additional HTTP headers.
109
113
 
110
- def handle_request request, upload
111
- uploads request, upload
112
- headers request, upload
114
+ def handle_request request, upload, type, headers
115
+ handle_uploads request, upload, type
116
+ handle_headers request, upload, type, headers
113
117
 
114
118
  response = @http.request @uri, request
115
- response.body = parse response.body
119
+ response.body = parse response.body, type
116
120
 
117
- request.body_stream.close if request.body_stream
121
+ finish_uploads request
118
122
 
119
- @errors.errors response
123
+ @errors.on response
120
124
  end
121
125
 
122
126
  ##
123
127
  # Handle chunked uploads.
124
128
  #
125
- # +request+: A net/http request Object.
126
- # +upload+: A Hash containing :file and :headers for uploading.
129
+ # +request+: A Net::HTTP request Object.
130
+ # +upload+: A Hash with the following keys:
131
+ # +:file+: The file to be HTTP chunked uploaded.
132
+ # +:headers+: A Hash containing additional HTTP headers.
133
+ # +:type+: A Symbol with the mime_type.
127
134
 
128
- def uploads request, upload
135
+ def handle_uploads request, upload, type
129
136
  return unless upload
130
137
 
131
- @headers.merge! chunked_headers upload if upload
132
-
133
138
  request.body_stream = File.open upload[:file]
134
139
  end
135
140
 
141
+ ##
142
+ # Close filehandles used for chunked uploads.
143
+ #
144
+ # +request+: A Net::HTTP request Object.
145
+
146
+ def finish_uploads request
147
+ request.body_stream.close if request.body_stream
148
+ end
149
+
136
150
  ##
137
151
  # Handles the setting of various default and custom headers.
138
152
  # Headers set in initialize can override all others.
139
153
  #
140
- # +request+: A net/http request Object.
154
+ # +request+: A Net::HTTP request Object.
155
+ # +upload+: A Hash with the following keys:
156
+ # +:type+: A Symbol with the mime_type.
157
+ # +:headers+: A Hash containing additional HTTP headers.
141
158
 
142
- def headers request, upload
159
+ def handle_headers request, upload, type, headers
143
160
  request.basic_auth(@user, @pass) if requires_authentication?
144
161
 
145
- request.add_field "Accept", MIME_TYPES[@type]
146
- request.add_field "Content-Type", MIME_TYPES[@type] if requires_content_type? request
162
+ request.add_field "Accept", mime_type(type)
163
+ request.add_field "Content-Type", mime_type(type) if requires_content_type? request
147
164
 
148
- @headers.each do |header, value|
165
+ headers.merge! chunked_headers upload
166
+ headers.each do |header, value|
149
167
  request[header] = value
150
- end if @headers
168
+ end
151
169
  end
152
170
 
153
171
  ##
154
172
  # Setting of chunked upload headers.
155
173
  #
156
- # +:upload+: A Hash with the following keys:
157
- # +file+: The file to be HTTP chunked uploaded.
158
- # +headers+: A Hash containing additional HTTP headers.
174
+ # +upload+: A Hash with the following keys:
175
+ # +:file+: The file to be HTTP chunked uploaded.
159
176
 
160
177
  def chunked_headers upload
178
+ return {} unless upload
179
+
161
180
  chunked_headers = {
162
- "Content-Type" => MIME_TYPES[:binary],
181
+ "Content-Type" => mime_type(:binary),
163
182
  "Content-Length" => File.size(upload[:file]),
164
183
  "Transfer-Encoding" => "chunked"
165
184
  }.merge upload[:headers] || {}
@@ -169,22 +188,22 @@ module Hugs
169
188
  [path, query].compact.join "?"
170
189
  end
171
190
 
172
- def parse data
191
+ def parse data, type
173
192
  return unless data
174
193
 
175
- if is_json?
194
+ if is_json? type
176
195
  Yajl::Parser.parse data
177
- elsif is_xml?
196
+ elsif is_xml? type
178
197
  Nokogiri::XML.parse data
179
198
  else
180
199
  data
181
200
  end
182
201
  end
183
202
 
184
- def encode body
203
+ def encode body, type
185
204
  return unless body
186
205
 
187
- is_json? ? (Yajl::Encoder.encode body) : body
206
+ (is_json? type) ? (Yajl::Encoder.encode body) : body
188
207
  end
189
208
 
190
209
  def requires_authentication?
@@ -195,12 +214,16 @@ module Hugs
195
214
  [Net::HTTP::Post, Net::HTTP::Put].include? request.class
196
215
  end
197
216
 
198
- def is_xml?
199
- @type == :xml
217
+ def is_xml? type
218
+ (mime_type type) =~ %r{/xml$}
219
+ end
220
+
221
+ def is_json? type
222
+ (mime_type type) =~ %r{/json$}
200
223
  end
201
224
 
202
- def is_json?
203
- @type == :json
225
+ def mime_type type
226
+ MIME_TYPES[type] || MIME_TYPES[@type]
204
227
  end
205
228
  end
206
229
  end
@@ -42,11 +42,11 @@ module Hugs
42
42
  class HTTP
43
43
  def initialize options
44
44
  @raise_errors = options[:raise_errors]
45
-
45
+
46
46
  initialize_errors
47
47
  end
48
48
 
49
- def errors response
49
+ def on response
50
50
  case response.code
51
51
  when @raise_errors && %r{^[45]{1}[0-9]{2}$} ; raise_for(response)
52
52
  end
@@ -1,3 +1,3 @@
1
1
  module Hugs
2
- VERSION = "2.6.0"
2
+ VERSION = "2.7.0"
3
3
  end
@@ -69,29 +69,44 @@ describe Hugs::Client do
69
69
  end
70
70
  end
71
71
 
72
- describe "custom" do
72
+ describe "override :headers per request" do
73
73
  before do
74
74
  @client = Hugs::Client.new(
75
- :host => @uri.host,
76
- :headers => { "foo" => "bar", "baz" => "xyzzy" }
75
+ :host => @uri.host
77
76
  )
78
77
  end
79
78
 
80
79
  it "adds headers" do
81
- must_have_headers_for :get, {}, "foo" => "bar", "baz" => "xyzzy"
80
+ must_have_headers_for :get, { :headers => { "foo" => "bar" } }, "foo" => "bar"
81
+ end
82
+
83
+ it "overrides default headers" do
84
+ must_have_headers_for :get, { :headers => { "Accept" => "foo bar" } }, "Accept" => "foo bar"
82
85
  end
83
86
  end
84
87
 
85
- describe "custom override defaults" do
88
+ describe "override :type per request" do
86
89
  before do
87
90
  @client = Hugs::Client.new(
88
- :host => @uri.host,
89
- :headers => { "Accept" => "foo bar" }
91
+ :host => @uri.host
90
92
  )
91
93
  end
92
94
 
93
- it "replaces headers" do
94
- must_have_headers_for :get, {}, "Accept" => "foo bar"
95
+ it "adds Accept header" do
96
+ must_have_headers_for_get :xml, :type => :xml
97
+ end
98
+
99
+ it "adds Content-Type header" do
100
+ must_have_headers_for_put :xml, :type => :xml
101
+ must_have_headers_for_post :xml, :type => :xml
102
+ end
103
+
104
+ it "doesn't parse body" do
105
+ stub_request(:get, @uri.to_s).to_return :body => "raw body"
106
+
107
+ response = @client.get @uri.path, :type => :none
108
+
109
+ response.body.must_equal "raw body"
95
110
  end
96
111
  end
97
112
  end
@@ -139,6 +154,20 @@ describe Hugs::Client do
139
154
  response.body.xpath('foo').text.must_equal "bar"
140
155
  end
141
156
  end
157
+
158
+ describe "none" do
159
+ before do
160
+ @client = Hugs::Client.new :host => @uri.host, :type => :none
161
+ end
162
+
163
+ it "doesn't parse body" do
164
+ stub_request(:get, @uri.to_s).to_return :body => "raw body"
165
+
166
+ response = @client.get @uri.path, :type => :none
167
+
168
+ response.body.must_equal "raw body"
169
+ end
170
+ end
142
171
  end
143
172
 
144
173
  describe "encode body" do
@@ -182,7 +211,7 @@ describe Hugs::Client do
182
211
  }
183
212
  end
184
213
 
185
- it "has chunked headers" do
214
+ it "has headers" do
186
215
  must_have_headers_for :post, @upload, {
187
216
  "Content-Type" => "application/octet-stream",
188
217
  ###"Content-Length" => "0",
@@ -190,7 +219,7 @@ describe Hugs::Client do
190
219
  }
191
220
  end
192
221
 
193
- it "upload headers override default headers" do
222
+ it "overrides default headers" do
194
223
  must_have_headers_for :post, @upload_with_headers, "Accept" => "foo bar"
195
224
  end
196
225
 
@@ -257,16 +286,16 @@ describe Hugs::Client do
257
286
  assert_requested verb, @uri.to_s
258
287
  end
259
288
 
260
- def must_have_headers_for_get type
261
- must_have_headers_for :get, {}, "Accept" => ["*/*", "application/#{type}"]
289
+ def must_have_headers_for_get type, options = {}
290
+ must_have_headers_for :get, options, "Accept" => ["*/*", "application/#{type}"]
262
291
  end
263
292
 
264
- def must_have_headers_for_put type
265
- must_have_headers_for :put, {}, "Content-Type" => "application/#{type}"
293
+ def must_have_headers_for_put type, options = {}
294
+ must_have_headers_for :put, options, "Content-Type" => "application/#{type}"
266
295
  end
267
296
 
268
- def must_have_headers_for_post type
269
- must_have_headers_for :post, {}, "Content-Type" => "application/#{type}"
297
+ def must_have_headers_for_post type, options = {}
298
+ must_have_headers_for :post, options, "Content-Type" => "application/#{type}"
270
299
  end
271
300
 
272
301
  def must_have_headers_for verb, options, headers
metadata CHANGED
@@ -2,15 +2,16 @@
2
2
  name: hugs
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 2.6.0
5
+ version: 2.7.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - John Dewey
9
+ - Josh Kleinpeter
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
13
 
13
- date: 2011-05-17 00:00:00 -07:00
14
+ date: 2011-06-07 00:00:00 -07:00
14
15
  default_executable:
15
16
  dependencies:
16
17
  - !ruby/object:Gem::Dependency
@@ -46,53 +47,43 @@ dependencies:
46
47
  version: 1.4.1
47
48
  type: :runtime
48
49
  version_requirements: *id003
49
- - !ruby/object:Gem::Dependency
50
- name: multipart-post
51
- prerelease: false
52
- requirement: &id004 !ruby/object:Gem::Requirement
53
- none: false
54
- requirements:
55
- - - ~>
56
- - !ruby/object:Gem::Version
57
- version: 1.0.1
58
- type: :runtime
59
- version_requirements: *id004
60
50
  - !ruby/object:Gem::Dependency
61
51
  name: rake
62
52
  prerelease: false
63
- requirement: &id005 !ruby/object:Gem::Requirement
53
+ requirement: &id004 !ruby/object:Gem::Requirement
64
54
  none: false
65
55
  requirements:
66
56
  - - ">="
67
57
  - !ruby/object:Gem::Version
68
58
  version: "0"
69
59
  type: :development
70
- version_requirements: *id005
60
+ version_requirements: *id004
71
61
  - !ruby/object:Gem::Dependency
72
62
  name: webmock
73
63
  prerelease: false
74
- requirement: &id006 !ruby/object:Gem::Requirement
64
+ requirement: &id005 !ruby/object:Gem::Requirement
75
65
  none: false
76
66
  requirements:
77
67
  - - ">="
78
68
  - !ruby/object:Gem::Version
79
69
  version: "0"
80
70
  type: :development
81
- version_requirements: *id006
71
+ version_requirements: *id005
82
72
  - !ruby/object:Gem::Dependency
83
73
  name: minitest
84
74
  prerelease: false
85
- requirement: &id007 !ruby/object:Gem::Requirement
75
+ requirement: &id006 !ruby/object:Gem::Requirement
86
76
  none: false
87
77
  requirements:
88
78
  - - ">="
89
79
  - !ruby/object:Gem::Version
90
80
  version: "0"
91
81
  type: :development
92
- version_requirements: *id007
82
+ version_requirements: *id006
93
83
  description:
94
84
  email:
95
85
  - john@dewey.ws
86
+ - josh@kleinpeter.org
96
87
  executables: []
97
88
 
98
89
  extensions: []