Permalink
Browse files

Switch the querying backend to Curb.

This also adds raising of exception when some http error occurs, instead
of silently "eating" it.
  • Loading branch information...
1 parent bced752 commit f44d6bd6f3dfef97541849daeb56f44f8a2e2e7e @libc committed Mar 20, 2013
View
@@ -5,3 +5,5 @@ platforms :jruby do
end
gemspec
+
+gem 'sinatra'
@@ -14,40 +14,21 @@ def search_user(access_token, args)
def simple_query(access_token, path, args)
puts "simple_query(#{access_token}, #{path}, #{args})"
- url = "#{DEFAULT_OAUTH_OPTIONS[:api_base]}#{path}?access_token=#{access_token}"
- args.each {|key, value| url += "&#{key}=#{CGI.escape(value.to_s)}"}
- uri = URI.parse(url)
- resp = nil
- connection = Net::HTTP.new(uri.host, 443)
- connection.use_ssl = true
- connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
- resp = connection.request_get(uri.path + '?' + uri.query)
+ response = get path, {access_token: access_token}.merge(args)
puts "Viadeo :: resp=#{resp.inspect}"
- return Mash.from_json resp.body
+ resp.body
end
def simple_post_query(access_token, path, args, post_data = "")
puts "simple_post_query(#{access_token}, #{path}, #{args})"
- url = "#{DEFAULT_OAUTH_OPTIONS[:api_base]}#{path}?access_token=#{access_token}"
- args.each {|key, value| url += "&#{key}=#{CGI.escape(value.to_s)}"}
- uri = URI.parse(url)
- resp = nil
- connection = Net::HTTP.new(uri.host, 443)
- connection.use_ssl = true
- connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
-
- if !post_data.respond_to?(:bytesize) && post_data.respond_to?(:map)
- post_data = post_data.map { |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
- end
-
- resp = connection.request_post(uri.path + '?' + uri.query, post_data)
+ response = post path, {access_token: access_token}.merge(args), post_data
puts "Viadeo :: resp=#{resp.inspect}"
- return Mash.from_json resp.body
+ resp.body
end
end
View
@@ -1,19 +1,21 @@
module Viadeo
module Errors
class ViadeoError < StandardError
- attr_reader :data
- def initialize(data)
+ attr_reader :data, :status, :headers
+ def initialize(status = 500, headers = nil, data = nil)
@data = data
- super
+ @headers = headers
+ @status = status
+ super()
end
end
class RateLimitExceededError < ViadeoError; end
class UnauthorizedError < ViadeoError; end
class GeneralError < ViadeoError; end
- class UnavailableError < StandardError; end
- class InformViadeoError < StandardError; end
- class NotFoundError < StandardError; end
+ class UnavailableError < ViadeoError; end
+ class InformViadeoError < ViadeoError; end
+ class NotFoundError < ViadeoError; end
end
end
@@ -1,88 +1,157 @@
+require 'active_support/concern'
+require 'active_support/notifications'
+require 'active_support/core_ext/class/attribute'
+require 'curb'
+
module Viadeo
module Helpers
module Request
+ extend ActiveSupport::Concern
- DEFAULT_HEADERS = {
- 'x-li-format' => 'json'
- }
+ included do
+ class_attribute :base_url
+ self.base_url = 'https://api.viadeo.com/'
+ end
- API_PATH = '/v1'
+ def get(path, query = {}, options={})
+ ActiveSupport::Notifications.instrument('viadeo.request', extra: {path: path, query: query, method: :get}) do
+ request :get, path, query, nil, options
+ end
+ end
- protected
+ %w{post put delete}.each do |method|
+ class_eval <<-RUBY, __FILE__, __LINE__+1
+ def #{method}(path, query = {}, data = nil, options={})
+ ActiveSupport::Notifications.instrument('viadeo.request', extra: {path: path, query: query, method: :#{method}, data: data}) do
+ request :#{method}, path, query, data, options
+ end
+ end
+ RUBY
+ end
- def get(path, options={})
- ActiveRecord::Base.logger.debug "Viadeo::GET #{API_PATH}#{path}"
- start = Time.now
- response = access_token.get("#{API_PATH}#{path}", DEFAULT_HEADERS.merge(options))
- ActiveRecord::Base.logger.debug "Done in #{((Time.now-start) * 100).to_i}ms"
- raise_errors(response)
- response.body
+ private
+
+ def curl
+ @curl ||= Curl::Multi.new.tap do |curl|
+ curl.pipeline = true
end
+ end
+
+ def request(method, path, query, data, options)
+ c = Curl::Easy.new(to_uri(base_url, path, query))
+ c.ssl_version = Curl::CURL_SSLVERSION_TLSv1
+ c.ssl_verify_peer = Rails.env.production? if defined? Rails
+ c.encoding = ''
+ c.verbose = true
- def post(path, body='', options={})
- ActiveRecord::Base.logger.debug "Viadeo::POST #{API_PATH}#{path}"
- start = Time.now
- response = access_token.post("#{API_PATH}#{path}", body, DEFAULT_HEADERS.merge(options))
- ActiveRecord::Base.logger.debug "Done in #{((Time.now-start) * 100).to_i}ms"
- raise_errors(response)
- response
+ error = nil
+ c.on_failure { |easy, code| error = parse_errors(easy) }
+ c.on_missing { |easy, code| error = parse_errors(easy) }
+
+ if !data.respond_to?(:bytesize) && data.respond_to?(:map)
+ data = to_query(data)
end
- def put(path, body, options={})
- ActiveRecord::Base.logger.debug "Viadeo::PUT #{API_PATH}#{path}"
- start = Time.now
- response = access_token.put("#{API_PATH}#{path}", body, DEFAULT_HEADERS.merge(options))
- ActiveRecord::Base.logger.debug "Done in #{((Time.now-start) * 100).to_i}ms"
- raise_errors(response)
- response
+ case method
+ when :get
+ # nothing :-)
+ when :post
+ c.post_body = data
+ when :put
+ c.put_data = data
+ when :delete
+ c.post_body = data if data
+ c.delete = true
end
- def delete(path, options={})
- ActiveRecord::Base.logger.debug "Viadeo::DELETE #{API_PATH}#{path}"
- start = Time.now
- response = access_token.delete("#{API_PATH}#{path}", DEFAULT_HEADERS.merge(options))
- ActiveRecord::Base.logger.debug "Done in #{((Time.now-start) * 100).to_i}ms"
- raise_errors(response)
- response
+ if defined? VCR
+ # VCR doesn't support Curl::Multi
+ c.perform
+ else
+ # reusing the connection if possible
+ curl.add(c)
+ curl.perform
end
- private
-
- def raise_errors(response)
- # Even if the json answer contains the HTTP status code, Viadeo also sets this code
- # in the HTTP answer (thankfully).
- case response.code.to_i
- when 401
- data = Mash.from_json(response.body)
- raise Viadeo::Errors::UnauthorizedError.new(data), "(#{data.status}): #{data.message}"
- when 400, 403
- data = Mash.from_json(response.body)
- raise Viadeo::Errors::GeneralError.new(data), "(#{data.status}): #{data.message}"
- when 404
- raise Viadeo::Errors::NotFoundError, "(#{response.code}): #{response.message}"
- when 500
- raise Viadeo::Errors::InformViadeoError, "Viadeo had an internal error. Please let them know in the forum. (#{response.code}): #{response.message}"
- when 502..503
- raise Viadeo::Errors::UnavailableError, "(#{response.code}): #{response.message}"
- end
+ raise error if error
+
+ parse_response c
+ end
+
+ def parse_response(response)
+ headers = parse_headers(response.header_str)
+ if json_response?(headers)
+ body = Mash.from_json(response.body_str)
+ else
+ body = response.body_str
+ end
+
+ ResponseWrapper.new(response.response_code, headers, body)
+ end
+
+ def parse_errors(response)
+ parsed_response = parse_response(response)
+ klass = case parsed_response.status
+ when 401
+ Viadeo::Errors::UnauthorizedError
+ when 400, 403
+ Viadeo::Errors::GeneralError
+ when 404
+ Viadeo::Errors::NotFoundError
+ when 500
+ Viadeo::Errors::InformViadeoError
+ when 502..503
+ Viadeo::Errors::UnavailableError
+ else
+ Viadeo::Errors::GenericError
+ end
+
+ klass.new(parsed_response.status, parsed_response.headers, parsed_response.body)
+ rescue Exception => e
+ puts "#{e.class}: #{e.message}"
+ puts e.backtrace
+ raise e
+ end
+
+ def to_query(options)
+ options.map { |k, v| "#{CGI.escape k.to_s}=#{CGI.escape v.to_s}" }.join('&')
+ end
+
+ def to_uri(base_url, path, options)
+ uri = URI.parse(File.join(base_url, path))
+
+ if options && options != {}
+ uri.query = to_query(options)
end
+ uri.to_s
+ end
- def to_query(options)
- options.inject([]) do |collection, opt|
- collection << "#{opt[0]}=#{opt[1]}"
- collection
- end * '&'
+ def parse_headers(str)
+ headers = str.split(/\r?\n/)
+ status_line = headers.shift
+ if status_line =~ %r{^HTTP/\d\.\d 1\d{2}} # 100
+ headers.shift # empty line
+ status_line = headers.shift
end
+ hash = Headers.new(headers.map { |h| h.split(/:\s*/, 2) })
+ hash['Status'] = $1.to_i if status_line =~ %r{^HTTP/\d\.\d (\d{3})}
+ hash
+ end
- def to_uri(path, options)
- uri = URI.parse(path)
+ def json_response?(headers)
+ ct = headers['Content-Type']
+ ct =~ %r{^application/json(?:;|$)}
+ end
- if options && options != {}
- uri.query = to_query(options)
- end
- uri.to_s
+ class Headers
+ include Net::HTTPHeader
+
+ def initialize(headers)
+ initialize_http_header(headers)
end
-
+ end
+
+ ResponseWrapper = Struct.new(:status, :headers, :body)
end
end
end
View
@@ -12,9 +12,9 @@ def search(options={})
end
options = format_options_for_query(options)
- result_json = get(to_uri(path, options))
+ result_json = get(path, options)
- Mash.from_json(result_json)
+ result_json.body
end
private
View
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Viadeo::Client do
+ subject(:client) { Viadeo::Client.new('id', 'secret') }
+
+ describe '#get' do
+
+ it 'returns an object with status, headers and body' do
+ obj = client.get('/test')
+ expect(obj).to respond_to(:status)
+ expect(obj).to respond_to(:headers)
+ expect(obj).to respond_to(:body)
+ end
+
+ it 'raises error if error actuall happens' do
+ expect { client.get('/_raise_http_404') }.to raise_error(Viadeo::Errors::NotFoundError)
+ end
+
+ it 'sends queries' do
+ obj = client.get('/test', param1: 'test')
+ expect(obj.body.params.param1).to eq('test')
+ end
+
+ end
+
+ describe '#post' do
+ it 'sends queries and body' do
+ obj = client.post('/test', {param1: 'test'}, {param2: 'body test'})
+ expect(obj.body.params.param1).to eq('test')
+ expect(obj.body.params.param2).to eq('body test')
+ end
+ end
+end
View
@@ -0,0 +1,4 @@
+require 'rspec'
+require 'viadeo'
+
+Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
@@ -0,0 +1,36 @@
+require 'sinatra/base'
+require 'json'
+
+class DummyStubServer < Sinatra::Base
+ [:get, :post, :put, :delete].each do |verb|
+ send(verb, //) do
+ if request.fullpath =~ /_raise_http_(\d{3})/
+ status $1
+ end
+
+ content_type :json
+ {method: verb, url: request.fullpath, params: params}.to_json
+ end
+ end
+
+ class << self
+ attr_accessor :base_url
+ end
+end
+
+RSpec.configure do |config|
+ config.before(:suite) do
+ q = Queue.new
+
+ Thread.fork do
+ DummyStubServer.set(:port, 0)
+ DummyStubServer.set(:server, %w[webrick])
+ DummyStubServer.run! do |server|
+ Viadeo::Client.base_url = "http://localhost:#{server.config[:Port]}/"
+ q.push :go!
+ end
+ end
+
+ q.pop
+ end
+end
View
@@ -5,6 +5,8 @@ Gem::Specification.new do |gem|
gem.add_dependency 'hashie', '>= 1.1.0'
gem.add_dependency 'multi_json', '>= 1.0.3'
gem.add_dependency 'oauth', '>= 0.4.5'
+ gem.add_dependency 'curb'
+ gem.add_dependency 'active_support'
gem.add_development_dependency 'json', '>= 1.6'
gem.add_development_dependency 'rake', '>= 0.9'
gem.add_development_dependency 'rdoc', '>= 3.8'

0 comments on commit f44d6bd

Please sign in to comment.