Class: Zephyr
- Inherits:
-
Object
- Object
- Zephyr
- Defined in:
- lib/zephyr.rb,
lib/zephyr/failed_request.rb
Overview
A simple front-end for doing HTTP requests quickly and simply.
Defined Under Namespace
Classes: FailedRequest
Constant Summary collapse
- @@logger =
Logger.new(STDOUT)
Class Method Summary collapse
- .build_query_string(params) ⇒ Object
- .debug_mode ⇒ Object
- .debug_mode=(mode) ⇒ Object
- .logger ⇒ Object
- .logger=(logger) ⇒ Object
- .percent_encode(value) ⇒ Object
Instance Method Summary collapse
- #cleanup! ⇒ Object
- #create_json_response(response, yajl_opts = {}) ⇒ Object
- #create_request_headers(hash) ⇒ Object
-
#custom(method, expected_statuses, timeout, path_components, headers = {}) ⇒ Object
Performs a custom HTTP method request to the specified resource.
- #default_headers ⇒ Object
-
#delete(expected_statuses, timeout, path_components, headers = {}) ⇒ Object
Performs a DELETE request to the specified resource.
-
#get(expected_statuses, timeout, path_components, headers = {}) ⇒ Object
Performs a GET request to the specified resource.
-
#get_json(expected_statuses, timeout, path_components, headers = {}, yajl_opts = {}) ⇒ Object
The same thing as #get, but decodes the response entity as JSON (if it’s application/json) and adds it under the :json key in the returned hash.
-
#head(expected_statuses, timeout, path_components, headers = {}) ⇒ Object
Performs a HEAD request to the specified resource.
-
#initialize(root_uri = '') ⇒ Zephyr
constructor
A new instance of Zephyr.
-
#inspect ⇒ Object
Comes handy in IRB.
- #perform(method, path_components, headers, expect, timeout, data = nil) ⇒ Object
-
#post(expected_statuses, timeout, path_components, entity, headers = {}) ⇒ Object
Performs a POST request to the specified resource.
-
#post_json(expected_statuses, timeout, path_components, entity, headers = {}) ⇒ Object
The same thing as #post, but encodes the entity as JSON and specifies “application/json” as the request entity content type.
-
#put(expected_statuses, timeout, path_components, entity, headers = {}) ⇒ Object
Performs a PUT request to the specified resource.
-
#put_json(expected_statuses, timeout, path_components, entity, headers = {}) ⇒ Object
The same thing as #put, but encodes the entity as JSON and specifies “application/json” as the request entity content type.
- #return_body?(method, code) ⇒ Boolean
-
#uri(given_parts = []) ⇒ Object
Creates a URI object, combining the root_uri passed on initialization with the given parts.
- #valid_response?(expected, actual) ⇒ Boolean
- #verify_path!(path_components) ⇒ Object
- #verify_path_and_entity!(path_components, entity) ⇒ Object
Constructor Details
#initialize(root_uri = '') ⇒ Zephyr
Returns a new instance of Zephyr.
51 52 53 |
# File 'lib/zephyr.rb', line 51 def initialize(root_uri = '') @root_uri = URI.parse(root_uri.to_s).freeze end |
Class Method Details
.build_query_string(params) ⇒ Object
85 86 87 88 89 90 91 92 93 |
# File 'lib/zephyr.rb', line 85 def build_query_string(params) params.map do |k, v| if v.kind_of? Array build_query_string(v.map { |x| [k, x] }) else "#{percent_encode(k)}=#{percent_encode(v)}" end end.sort.join '&' end |
.debug_mode ⇒ Object
73 74 75 |
# File 'lib/zephyr.rb', line 73 def debug_mode @debug_mode end |
.debug_mode=(mode) ⇒ Object
77 78 79 |
# File 'lib/zephyr.rb', line 77 def debug_mode=(mode) @debug_mode = mode end |
.logger ⇒ Object
65 66 67 |
# File 'lib/zephyr.rb', line 65 def logger @@logger end |
.logger=(logger) ⇒ Object
69 70 71 |
# File 'lib/zephyr.rb', line 69 def logger=(logger) @@logger = logger end |
.percent_encode(value) ⇒ Object
81 82 83 |
# File 'lib/zephyr.rb', line 81 def percent_encode(value) Typhoeus::Curl.easy_escape(typhoeus_easy.handle, value.to_s, value.to_s.bytesize) end |
Instance Method Details
#cleanup! ⇒ Object
261 262 263 |
# File 'lib/zephyr.rb', line 261 def cleanup! Typheous::Hydra.hydra.cleanup end |
#create_json_response(response, yajl_opts = {}) ⇒ Object
354 355 356 357 358 359 360 361 362 363 |
# File 'lib/zephyr.rb', line 354 def create_json_response(response, yajl_opts = {}) return response if response.nil? || !response.key?(:headers) || !response[:headers].key?('content-type') content_type = response[:headers]['content-type'] content_type = content_type.first if content_type.respond_to?(:first) if response[:body] && content_type.to_s.strip.match(/^application\/json/) response[:json] = Yajl::Parser.parse(response[:body], yajl_opts) end response end |
#create_request_headers(hash) ⇒ Object
344 345 346 347 348 349 350 351 352 |
# File 'lib/zephyr.rb', line 344 def create_request_headers(hash) h = Headers.new hash.each {|k,v| h.add_field(k,v) } [].tap do |arr| h.each_capitalized do |k,v| arr << "#{k}: #{v}" end end end |
#custom(method, expected_statuses, timeout, path_components, headers = {}) ⇒ Object
Performs a custom HTTP method request to the specified resource.
A PURGE request to /users/#Zephyr.@[email protected] which is expecting a 200 OK within 666ms
http.custom(:purge, 200, 666, ["users", @user.id])
This returns a hash with three keys:
:status The numeric HTTP status code
:body The body of the response entity, if any
:headers A hash of header values
233 234 235 236 237 |
# File 'lib/zephyr.rb', line 233 def custom(method, expected_statuses, timeout, path_components, headers={}) headers = default_headers.merge(headers) verify_path!(path_components) perform(method, path_components, headers, expected_statuses, timeout) end |
#default_headers ⇒ Object
55 56 57 58 59 60 |
# File 'lib/zephyr.rb', line 55 def default_headers { 'Accept' => 'application/json;q=0.7, */*;q=0.5', 'User-Agent' => 'zephyr', } end |
#delete(expected_statuses, timeout, path_components, headers = {}) ⇒ Object
Performs a DELETE request to the specified resource.
A request to /users/#Zephyr.@[email protected]/things?q=woof which is expecting a 204 No Content within 666ms
http.put(200, 666, ["users", @user.id, "things", {"q" => "woof"}])
This returns a hash with three keys:
:status The numeric HTTP status code
:body The body of the response entity, if any
:headers A hash of header values
217 218 219 220 221 |
# File 'lib/zephyr.rb', line 217 def delete(expected_statuses, timeout, path_components, headers={}) headers = default_headers.merge(headers) verify_path!(path_components) perform(:delete, path_components, headers, expected_statuses, timeout) end |
#get(expected_statuses, timeout, path_components, headers = {}) ⇒ Object
Performs a GET request to the specified resource.
A request to /users/#Zephyr.@[email protected]/things?q=woof with an Accept header of “text/plain” which is expecting a 200 OK within 50ms
http.get(200, 50 ["users", @user.id, "things", {"q" => "woof"}], "Accept" => "text/plain")
This returns a hash with three keys:
:status The numeric HTTP status code
:body The body of the response entity, if any
:headers A hash of header values
137 138 139 140 141 |
# File 'lib/zephyr.rb', line 137 def get(expected_statuses, timeout, path_components, headers={}) headers = default_headers.merge(headers) verify_path!(path_components) perform(:get, path_components, headers, expected_statuses, timeout) end |
#get_json(expected_statuses, timeout, path_components, headers = {}, yajl_opts = {}) ⇒ Object
The same thing as #get, but decodes the response entity as JSON (if it’s application/json) and adds it under the :json key in the returned hash.
145 146 147 148 |
# File 'lib/zephyr.rb', line 145 def get_json(expected_statuses, timeout, path_components, headers={}, yajl_opts={}) response = get(expected_statuses, timeout, path_components, headers) create_json_response(response, yajl_opts) end |
#head(expected_statuses, timeout, path_components, headers = {}) ⇒ Object
Performs a HEAD request to the specified resource.
A request to /users/#Zephyr.@[email protected]/things?q=woof with an Accept header of “text/plain” which is expecting a 200 OK within 50ms
http.head(200, 50, ["users", @user.id, "things", {"q" => "woof"}], "Accept" => "text/plain")
This returns a hash with three keys:
:status The numeric HTTP status code
:body The body of the response entity, if any
:headers A hash of header values
120 121 122 123 124 |
# File 'lib/zephyr.rb', line 120 def head(expected_statuses, timeout, path_components, headers={}) headers = default_headers.merge(headers) verify_path!(path_components) perform(:head, path_components, headers, expected_statuses, timeout) end |
#inspect ⇒ Object
Comes handy in IRB
257 258 259 |
# File 'lib/zephyr.rb', line 257 def inspect '#<%s:0x%s root_uri=%s>' % [ self.class.to_s, object_id.to_s(16), uri.to_s ] end |
#perform(method, path_components, headers, expect, timeout, data = nil) ⇒ Object
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/zephyr.rb', line 288 def perform(method, path_components, headers, expect, timeout, data=nil) params = {} params[:headers] = headers params[:timeout] = timeout params[:follow_location] = false if path_components.last.is_a?(Hash) && (!data || data.empty?) params[:params] = path_components.pop end params[:method] = method # seriously, why is this on by default Typhoeus::Hydra.hydra.disable_memoization # if you want debugging params[:verbose] = Zephyr.debug_mode # have a vague feeling this isn't going to work as expected if method == :post || method == :put data = data.read if data.respond_to?(:read) params[:body] = data if data != '' end http_start = Time.now.to_f response = Typhoeus::Request.run(uri(path_components).to_s, params) http_end = Time.now.to_f Zephyr.logger.info "[zephyr:#{$$}:#{Time.now.to_f}] \"%s %s\" %s %0.4f" % [ method.to_s.upcase, response.request.url, response.code, (http_end - http_start) ] # be consistent with what came before response_headers = Headers.new.tap do |h| response.headers.split(/\n/).each do |header_line| h.parse(header_line) end end if !response.timed_out? && valid_response?(expect, response.code) result = { :headers => response_headers.to_hash, :status => response.code } if return_body?(method, response.code) result[:body] = response.body end result else failed_request = FailedRequest.new(:method => method, :uri => response.request.url, :expected_code => expect, :timeout => timeout, :response => response) Zephyr.logger.error "[zephyr:#{$$}:#{Time.now.to_f}]: #{failed_request}" raise failed_request end end |
#post(expected_statuses, timeout, path_components, entity, headers = {}) ⇒ Object
Performs a POST request to the specified resource.
A request to /users/#Zephyr.@[email protected]/things?q=woof with an Content-Type header of “text/plain” and a request entity of “yay” which is expecting a 201 Created within 500ms
http.post(201, 500, ["users", @user.id, "things", {"q" => "woof"}], "yay", "Content-Type" => "text/plain")
This returns a hash with three keys:
:status The numeric HTTP status code
:body The body of the response entity, if any
:headers A hash of header values
187 188 189 190 191 |
# File 'lib/zephyr.rb', line 187 def post(expected_statuses, timeout, path_components, entity, headers={}) headers = default_headers.merge(headers) verify_path_and_entity!(path_components, entity) perform(:post, path_components, headers, expected_statuses, timeout, entity) end |
#post_json(expected_statuses, timeout, path_components, entity, headers = {}) ⇒ Object
The same thing as #post, but encodes the entity as JSON and specifies “application/json” as the request entity content type.
195 196 197 198 199 200 201 202 203 204 |
# File 'lib/zephyr.rb', line 195 def post_json(expected_statuses, timeout, path_components, entity, headers={}) response = post( expected_statuses, timeout, path_components, Yajl::Encoder.encode(entity), headers.merge("Content-Type" => "application/json") ) create_json_response(response) end |
#put(expected_statuses, timeout, path_components, entity, headers = {}) ⇒ Object
Performs a PUT request to the specified resource.
A request to /users/#Zephyr.@[email protected]/things?q=woof with an Content-Type header of “text/plain” and a request entity of “yay” which is expecting a 204 No Content within 1000ms
http.put(204, 1000, ["users", @user.id, "things", {"q" => "woof"}], "yay", "Content-Type" => "text/plain")
This returns a hash with three keys:
:status The numeric HTTP status code
:body The body of the response entity, if any
:headers A hash of header values
162 163 164 165 166 |
# File 'lib/zephyr.rb', line 162 def put(expected_statuses, timeout, path_components, entity, headers={}) headers = default_headers.merge(headers) verify_path_and_entity!(path_components, entity) perform(:put, path_components, headers, expected_statuses, timeout, entity) end |
#put_json(expected_statuses, timeout, path_components, entity, headers = {}) ⇒ Object
The same thing as #put, but encodes the entity as JSON and specifies “application/json” as the request entity content type.
170 171 172 173 |
# File 'lib/zephyr.rb', line 170 def put_json(expected_statuses, timeout, path_components, entity, headers={}) response = put(expected_statuses, timeout, path_components, Yajl::Encoder.encode(entity), headers.merge("Content-Type" => "application/json")) create_json_response(response) end |
#return_body?(method, code) ⇒ Boolean
284 285 286 |
# File 'lib/zephyr.rb', line 284 def return_body?(method, code) method != :head && !valid_response?([204,205,304], code) end |
#uri(given_parts = []) ⇒ Object
Creates a URI object, combining the root_uri passed on initialization with the given parts.
Example:
http = Zephyr.new 'http://host/'
http.uri(['hi', 'bob', {:foo => 'bar'}]) => http://host/hi/bob?foo=bar
247 248 249 250 251 252 253 |
# File 'lib/zephyr.rb', line 247 def uri(given_parts = []) @root_uri.dup.tap do |uri| parts = given_parts.dup.unshift(uri.path) # URI#merge is broken. uri.query = Zephyr.build_query_string(parts.pop) if parts.last.is_a? Hash uri.path = ('/%s' % parts.join('/')).gsub(/\/+/, '/') end end |
#valid_response?(expected, actual) ⇒ Boolean
280 281 282 |
# File 'lib/zephyr.rb', line 280 def valid_response?(expected, actual) Array(expected).map { |code| code.to_i }.include?(actual.to_i) end |
#verify_path!(path_components) ⇒ Object
275 276 277 278 |
# File 'lib/zephyr.rb', line 275 def verify_path!(path_components) path_components = Array(path_components).flatten raise ArgumentError, "Resource path too short" unless path_components.length > 0 end |
#verify_path_and_entity!(path_components, entity) ⇒ Object
265 266 267 268 269 270 271 272 273 |
# File 'lib/zephyr.rb', line 265 def verify_path_and_entity!(path_components, entity) begin verify_path!(path_components) rescue ArgumentError raise ArgumentError, "You must supply both a resource path and a body." end raise ArgumentError, "Request body must be a string or IO." unless String === entity || IO === entity end |