koala 1.0.0 → 1.1.0rc2
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/.autotest +12 -0
- data/.gitignore +2 -1
- data/CHANGELOG +18 -0
- data/autotest/discover.rb +1 -0
- data/koala.gemspec +5 -5
- data/lib/koala/graph_api.rb +71 -31
- data/lib/koala/graph_api_batch.rb +151 -0
- data/lib/koala/http_services/net_http_service.rb +87 -0
- data/lib/koala/http_services/typhoeus_service.rb +37 -0
- data/lib/koala/http_services.rb +10 -112
- data/lib/koala/oauth.rb +181 -0
- data/lib/koala/realtime_updates.rb +5 -14
- data/lib/koala/rest_api.rb +13 -8
- data/lib/koala/uploadable_io.rb +35 -7
- data/lib/koala.rb +29 -194
- data/readme.md +19 -7
- data/spec/cases/api_base_spec.rb +2 -2
- data/spec/cases/graph_api_batch_spec.rb +600 -0
- data/spec/cases/http_services/http_service_spec.rb +76 -1
- data/spec/cases/http_services/net_http_service_spec.rb +166 -50
- data/spec/cases/http_services/typhoeus_service_spec.rb +29 -21
- data/spec/cases/koala_spec.rb +55 -0
- data/spec/cases/test_users_spec.rb +1 -1
- data/spec/cases/uploadable_io_spec.rb +56 -14
- data/spec/fixtures/mock_facebook_responses.yml +89 -5
- data/spec/support/graph_api_shared_examples.rb +34 -7
- data/spec/support/mock_http_service.rb +54 -56
- data/spec/support/rest_api_shared_examples.rb +131 -7
- data/spec/support/setup_mocks_or_live.rb +3 -3
- metadata +28 -48
data/.autotest
ADDED
data/.gitignore
CHANGED
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
v1.1
|
|
2
|
+
New methods:
|
|
3
|
+
-- Batch API support through Koala::Facebook::GraphAPI.batch (thanks, seejohnrun!)
|
|
4
|
+
-- includes file uploads, error handling, and FQL
|
|
5
|
+
-- Added GraphAPI#get_comments_for_urls (thanks, amrnt!)
|
|
6
|
+
-- Added RestAPI#fql_multiquery, which simplifies the results (thanks, amrnt!)
|
|
7
|
+
Updated methods:
|
|
8
|
+
-- RealtimeUpdates now uses a GraphAPI object instead of its own API
|
|
9
|
+
-- RestAPI#rest_call now has an optional last argument for method, for calls requiring POST, DELETE, etc. (thanks, sshilo!)
|
|
10
|
+
-- Filename can now be specified when uploading (e.g. for Ads API) (thanks, sshilo!)
|
|
11
|
+
-- get_objects([]) returns [] instead of a Facebook error in non-batch mode (thanks, aselder!)
|
|
12
|
+
Internal improvements:
|
|
13
|
+
-- HTTP services are more modular and can be changed on the fly (thanks, chadk!)
|
|
14
|
+
-- Includes support for uploading StringIOs and other non-files via Net::HTTP even when using TyphoeusService
|
|
15
|
+
-- Support for global proxy and timeout settings (thanks, itchy!)
|
|
16
|
+
-- Support for setting certificate path and file to address Net::HTTP errors under Ruby 1.9.2
|
|
17
|
+
-- Koala now uses the modern Typhoeus API (thanks, aselder!)
|
|
18
|
+
|
|
1
19
|
v1.0
|
|
2
20
|
New methods:
|
|
3
21
|
-- Photo and file upload now supported through #put_picture
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Autotest.add_discovery { "rspec2" }
|
data/koala.gemspec
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |s|
|
|
4
4
|
s.name = %q{koala}
|
|
5
|
-
s.version = "1.
|
|
6
|
-
s.date = %q{2011-
|
|
5
|
+
s.version = "1.1.0rc2"
|
|
6
|
+
s.date = %q{2011-06-06}
|
|
7
7
|
|
|
8
8
|
s.summary = %q{A lightweight, flexible library for Facebook with support for the Graph API, the REST API, realtime updates, and OAuth authentication.}
|
|
9
9
|
s.description = %q{Koala is a lightweight, flexible Ruby SDK for Facebook. It allows read/write access to the social graph via the Graph and REST APIs, as well as support for realtime updates and OAuth and Facebook Connect authentication. Koala is fully tested and supports Net::HTTP and Typhoeus connections out of the box and can accept custom modules for other services.}
|
|
@@ -30,20 +30,20 @@ Gem::Specification.new do |s|
|
|
|
30
30
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
|
31
31
|
s.add_runtime_dependency(%q<json>, ["~> 1.0"])
|
|
32
32
|
s.add_runtime_dependency(%q<multipart-post>, ["~> 1.0"])
|
|
33
|
-
s.add_development_dependency(%q<rspec>, ["~> 2.5
|
|
33
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.5"])
|
|
34
34
|
s.add_development_dependency(%q<rake>, ["~> 0.8.7"])
|
|
35
35
|
s.add_development_dependency(%q<typhoeus>, ["~> 0.2.4"])
|
|
36
36
|
else
|
|
37
37
|
s.add_dependency(%q<json>, ["~> 1.0"])
|
|
38
38
|
s.add_dependency(%q<multipart-post>, ["~> 1.0"])
|
|
39
|
-
s.add_dependency(%q<rspec>, ["~> 2.5
|
|
39
|
+
s.add_dependency(%q<rspec>, ["~> 2.5"])
|
|
40
40
|
s.add_dependency(%q<rake>, ["~> 0.8.7"])
|
|
41
41
|
s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
|
|
42
42
|
end
|
|
43
43
|
else
|
|
44
44
|
s.add_dependency(%q<json>, ["~> 1.0"])
|
|
45
45
|
s.add_dependency(%q<multipart-post>, ["~> 1.0"])
|
|
46
|
-
s.add_dependency(%q<rspec>, ["~> 2.5
|
|
46
|
+
s.add_dependency(%q<rspec>, ["~> 2.5"])
|
|
47
47
|
s.add_dependency(%q<rake>, ["~> 0.8.7"])
|
|
48
48
|
s.add_dependency(%q<typhoeus>, ["~> 0.2.4"])
|
|
49
49
|
end
|
data/lib/koala/graph_api.rb
CHANGED
|
@@ -28,7 +28,20 @@ module Koala
|
|
|
28
28
|
# If you are using the JavaScript SDK, you can use the
|
|
29
29
|
# Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token
|
|
30
30
|
# for the active user from the cookie saved by the SDK.
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
def self.included(base)
|
|
33
|
+
base.class_eval do
|
|
34
|
+
def self.check_response(response)
|
|
35
|
+
# check for Graph API-specific errors
|
|
36
|
+
# this returns an error, which is immediately raised (non-batch)
|
|
37
|
+
# or added to the list of batch results (batch)
|
|
38
|
+
if response.is_a?(Hash) && error_details = response["error"]
|
|
39
|
+
APIError.new(error_details)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
32
45
|
# Objects
|
|
33
46
|
|
|
34
47
|
def get_object(id, args = {}, options = {})
|
|
@@ -37,10 +50,10 @@ module Koala
|
|
|
37
50
|
end
|
|
38
51
|
|
|
39
52
|
def get_objects(ids, args = {}, options = {})
|
|
40
|
-
# Fetchs all of the given
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
graph_call("", args.merge("ids" => ids.join(",")), "get", options)
|
|
53
|
+
# Fetchs all of the given objects from the graph.
|
|
54
|
+
# If any of the IDs are invalid, they'll raise an exception.
|
|
55
|
+
return [] if ids.empty?
|
|
56
|
+
graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options)
|
|
44
57
|
end
|
|
45
58
|
|
|
46
59
|
def put_object(parent_object, connection_name, args = {}, options = {})
|
|
@@ -71,10 +84,19 @@ module Koala
|
|
|
71
84
|
|
|
72
85
|
def get_connections(id, connection_name, args = {}, options = {})
|
|
73
86
|
# Fetchs the connections for given object.
|
|
74
|
-
|
|
75
|
-
|
|
87
|
+
graph_call("#{id}/#{connection_name}", args, "get", options) do |result|
|
|
88
|
+
result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
|
|
89
|
+
end
|
|
76
90
|
end
|
|
77
91
|
|
|
92
|
+
def get_comments_for_urls(urls = [], args = {}, options = {})
|
|
93
|
+
# Fetchs the comments for given URLs (array or comma-separated string)
|
|
94
|
+
# see https://developers.facebook.com/blog/post/490
|
|
95
|
+
return [] if urls.empty?
|
|
96
|
+
args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
|
|
97
|
+
get_object("comments", args, options)
|
|
98
|
+
end
|
|
99
|
+
|
|
78
100
|
def put_connections(id, connection_name, args = {}, options = {})
|
|
79
101
|
# Posts a certain connection
|
|
80
102
|
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
|
|
@@ -93,8 +115,9 @@ module Koala
|
|
|
93
115
|
|
|
94
116
|
def get_picture(object, args = {}, options = {})
|
|
95
117
|
# Gets a picture object, returning the URL (which Facebook sends as a header)
|
|
96
|
-
|
|
97
|
-
|
|
118
|
+
graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
|
|
119
|
+
result["Location"]
|
|
120
|
+
end
|
|
98
121
|
end
|
|
99
122
|
|
|
100
123
|
def put_picture(*picture_args)
|
|
@@ -123,7 +146,9 @@ module Koala
|
|
|
123
146
|
options = picture_args[3 + args_offset] || {}
|
|
124
147
|
|
|
125
148
|
args["source"] = Koala::UploadableIO.new(*picture_args.slice(0, 1 + args_offset))
|
|
126
|
-
|
|
149
|
+
|
|
150
|
+
options[:http_service] = Koala.base_http_service if args["source"].requires_base_http_service
|
|
151
|
+
|
|
127
152
|
self.put_object(target_id, "photos", args, options)
|
|
128
153
|
end
|
|
129
154
|
|
|
@@ -172,47 +197,62 @@ module Koala
|
|
|
172
197
|
|
|
173
198
|
def search(search_terms, args = {}, options = {})
|
|
174
199
|
args.merge!({:q => search_terms}) unless search_terms.nil?
|
|
175
|
-
|
|
176
|
-
|
|
200
|
+
graph_call("search", args, "get", options) do |result|
|
|
201
|
+
result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
|
|
202
|
+
end
|
|
177
203
|
end
|
|
178
204
|
|
|
179
205
|
# API access
|
|
180
|
-
|
|
181
|
-
|
|
206
|
+
|
|
207
|
+
# Make a call which may or may not be batched
|
|
208
|
+
def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
|
|
182
209
|
# Direct access to the Facebook API
|
|
183
210
|
# see any of the above methods for example invocations
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
211
|
+
unless GraphAPI.batch_mode?
|
|
212
|
+
result = api(path, args, verb, options) do |response|
|
|
213
|
+
if error = GraphAPI.check_response(response)
|
|
214
|
+
raise error
|
|
215
|
+
end
|
|
188
216
|
end
|
|
217
|
+
|
|
218
|
+
# now process as appropriate (get picture header, make GraphCollection, etc.)
|
|
219
|
+
post_processing ? post_processing.call(result) : result
|
|
220
|
+
else
|
|
221
|
+
# for batch APIs, we queue up the call details (incl. post-processing)
|
|
222
|
+
GraphAPI.batch_calls << BatchOperation.new(
|
|
223
|
+
:url => path,
|
|
224
|
+
:args => args,
|
|
225
|
+
:method => verb,
|
|
226
|
+
:access_token => @access_token,
|
|
227
|
+
:http_options => options,
|
|
228
|
+
:post_processing => post_processing
|
|
229
|
+
)
|
|
230
|
+
nil # batch operations return nothing immediately
|
|
189
231
|
end
|
|
190
|
-
|
|
191
|
-
response
|
|
192
|
-
end
|
|
232
|
+
end
|
|
193
233
|
|
|
194
234
|
# GraphCollection support
|
|
195
|
-
|
|
196
235
|
def get_page(params)
|
|
197
236
|
# Pages through a set of results stored in a GraphCollection
|
|
198
237
|
# Used for connections and search results
|
|
199
|
-
|
|
200
|
-
|
|
238
|
+
graph_call(*params) do |result|
|
|
239
|
+
result ? GraphCollection.new(result, self) : nil # when facebook is down nil can be returned
|
|
240
|
+
end
|
|
201
241
|
end
|
|
202
242
|
|
|
203
243
|
end
|
|
204
244
|
|
|
205
245
|
|
|
206
246
|
class GraphCollection < Array
|
|
207
|
-
#This class is a light wrapper for collections returned
|
|
208
|
-
#from the Graph API.
|
|
247
|
+
# This class is a light wrapper for collections returned
|
|
248
|
+
# from the Graph API.
|
|
209
249
|
#
|
|
210
|
-
#It extends Array to allow direct access to the data colleciton
|
|
211
|
-
#which should allow it to drop in seamlessly.
|
|
250
|
+
# It extends Array to allow direct access to the data colleciton
|
|
251
|
+
# which should allow it to drop in seamlessly.
|
|
212
252
|
#
|
|
213
|
-
#It also allows access to paging information and the
|
|
214
|
-
#ability to get the next/previous page in the collection
|
|
215
|
-
#by calling next_page or previous_page.
|
|
253
|
+
# It also allows access to paging information and the
|
|
254
|
+
# ability to get the next/previous page in the collection
|
|
255
|
+
# by calling next_page or previous_page.
|
|
216
256
|
attr_reader :paging
|
|
217
257
|
attr_reader :api
|
|
218
258
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
module Koala
|
|
2
|
+
module Facebook
|
|
3
|
+
class BatchOperation
|
|
4
|
+
attr_reader :access_token, :http_options, :post_processing, :files
|
|
5
|
+
|
|
6
|
+
def initialize(options = {})
|
|
7
|
+
@args = (options[:args] || {}).dup # because we modify it below
|
|
8
|
+
@access_token = options[:access_token]
|
|
9
|
+
@http_options = (options[:http_options] || {}).dup # dup because we modify it below
|
|
10
|
+
@batch_args = @http_options.delete(:batch_args) || {}
|
|
11
|
+
@url = options[:url]
|
|
12
|
+
@method = options[:method].to_sym
|
|
13
|
+
@post_processing = options[:post_processing]
|
|
14
|
+
|
|
15
|
+
process_binary_args
|
|
16
|
+
|
|
17
|
+
raise Koala::KoalaError, "Batch operations require an access token, none provided." unless @access_token
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_batch_params(main_access_token)
|
|
21
|
+
# set up the arguments
|
|
22
|
+
args_string = Koala.http_service.encode_params(@access_token == main_access_token ? @args : @args.merge(:access_token => @access_token))
|
|
23
|
+
|
|
24
|
+
response = {
|
|
25
|
+
:method => @method,
|
|
26
|
+
:relative_url => @url,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# handle batch-level arguments, such as name, depends_on, and attached_files
|
|
30
|
+
@batch_args[:attached_files] = @files.keys.join(",") if @files
|
|
31
|
+
response.merge!(@batch_args) if @batch_args
|
|
32
|
+
|
|
33
|
+
# for get and delete, we append args to the URL string
|
|
34
|
+
# otherwise, they go in the body
|
|
35
|
+
if args_string.length > 0
|
|
36
|
+
if args_in_url?
|
|
37
|
+
response[:relative_url] += (@url =~ /\?/ ? "&" : "?") + args_string if args_string.length > 0
|
|
38
|
+
else
|
|
39
|
+
response[:body] = args_string if args_string.length > 0
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
response
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
protected
|
|
47
|
+
|
|
48
|
+
def process_binary_args
|
|
49
|
+
# collect binary files
|
|
50
|
+
@args.each_pair do |key, value|
|
|
51
|
+
if UploadableIO.binary_content?(value)
|
|
52
|
+
@files ||= {}
|
|
53
|
+
# we use object_id to ensure unique file identifiers across multiple batch operations
|
|
54
|
+
# remove it from the original hash and add it to the file store
|
|
55
|
+
id = "file#{GraphAPI.batch_calls.length}_#{@files.keys.length}"
|
|
56
|
+
@files[id] = @args.delete(key).is_a?(UploadableIO) ? value : UploadableIO.new(value)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def args_in_url?
|
|
62
|
+
@method == :get || @method == :delete
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
module GraphAPIBatchMethods
|
|
67
|
+
def self.included(base)
|
|
68
|
+
base.class_eval do
|
|
69
|
+
# batch mode flags
|
|
70
|
+
def self.batch_mode?
|
|
71
|
+
!!@batch_mode
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.batch_calls
|
|
75
|
+
raise KoalaError, "GraphAPI.batch_calls accessed when not in batch block!" unless batch_mode?
|
|
76
|
+
@batch_calls
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.batch(http_options = {}, &block)
|
|
80
|
+
@batch_mode = true
|
|
81
|
+
@batch_http_options = http_options
|
|
82
|
+
@batch_calls = []
|
|
83
|
+
yield
|
|
84
|
+
begin
|
|
85
|
+
results = batch_api(@batch_calls)
|
|
86
|
+
ensure
|
|
87
|
+
@batch_mode = false
|
|
88
|
+
end
|
|
89
|
+
results
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.batch_api(batch_calls)
|
|
93
|
+
return [] unless batch_calls.length > 0
|
|
94
|
+
# Facebook requires a top-level access token
|
|
95
|
+
|
|
96
|
+
# Get the access token for the user and start building a hash to store params
|
|
97
|
+
# Turn the call args collected into what facebook expects
|
|
98
|
+
args = {}
|
|
99
|
+
access_token = args["access_token"] = batch_calls.first.access_token
|
|
100
|
+
args['batch'] = batch_calls.map { |batch_op|
|
|
101
|
+
args.merge!(batch_op.files) if batch_op.files
|
|
102
|
+
batch_op.to_batch_params(access_token)
|
|
103
|
+
}.to_json
|
|
104
|
+
|
|
105
|
+
# Make the POST request for the batch call
|
|
106
|
+
# batch operations have to go over SSL, but since there's an access token, that secures that
|
|
107
|
+
result = Koala.make_request('/', args, 'post', @batch_http_options)
|
|
108
|
+
# Raise an error if we get a 500
|
|
109
|
+
raise APIError.new("type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}") if result.status >= 500
|
|
110
|
+
|
|
111
|
+
response = JSON.parse(result.body.to_s)
|
|
112
|
+
# raise an error if we get a Batch API error message
|
|
113
|
+
raise APIError.new("type" => "Error #{response["error"]}", "message" => response["error_description"]) if response.is_a?(Hash) && response["error"]
|
|
114
|
+
|
|
115
|
+
# otherwise, map the results with post-processing included
|
|
116
|
+
index = 0 # keep compat with ruby 1.8 - no with_index for map
|
|
117
|
+
response.map do |call_result|
|
|
118
|
+
# Get the options hash
|
|
119
|
+
batch_op = batch_calls[index]
|
|
120
|
+
index += 1
|
|
121
|
+
|
|
122
|
+
if call_result
|
|
123
|
+
# (see note in regular api method about JSON parsing)
|
|
124
|
+
body = JSON.parse("[#{call_result['body'].to_s}]")[0]
|
|
125
|
+
unless call_result["code"].to_i >= 500 || error = GraphAPI.check_response(body)
|
|
126
|
+
# Get the HTTP component they want
|
|
127
|
+
data = case batch_op.http_options[:http_component]
|
|
128
|
+
when :status
|
|
129
|
+
call_result["code"].to_i
|
|
130
|
+
when :headers
|
|
131
|
+
# facebook returns the headers as an array of k/v pairs, but we want a regular hash
|
|
132
|
+
call_result['headers'].inject({}) { |headers, h| headers[h['name']] = h['value']; headers}
|
|
133
|
+
else
|
|
134
|
+
body
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# process it if we are given a block to process with
|
|
138
|
+
batch_op.post_processing ? batch_op.post_processing.call(data) : data
|
|
139
|
+
else
|
|
140
|
+
error || APIError.new({"type" => "HTTP #{call_result["code"].to_s}", "message" => "Response body: #{body}"})
|
|
141
|
+
end
|
|
142
|
+
else
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require "net/http" unless defined?(Net::HTTP)
|
|
2
|
+
require "net/https"
|
|
3
|
+
require "net/http/post/multipart"
|
|
4
|
+
|
|
5
|
+
module Koala
|
|
6
|
+
module NetHTTPService
|
|
7
|
+
# this service uses Net::HTTP to send requests to the graph
|
|
8
|
+
include Koala::HTTPService
|
|
9
|
+
|
|
10
|
+
def self.make_request(path, args, verb, options = {})
|
|
11
|
+
# We translate args to a valid query string. If post is specified,
|
|
12
|
+
# we send a POST request to the given path with the given arguments.
|
|
13
|
+
|
|
14
|
+
# by default, we use SSL only for private requests
|
|
15
|
+
# this makes public requests faster
|
|
16
|
+
private_request = args["access_token"] || @always_use_ssl || options[:use_ssl]
|
|
17
|
+
|
|
18
|
+
# if proxy/timeout options aren't passed, check if defaults are set
|
|
19
|
+
options[:proxy] ||= proxy
|
|
20
|
+
options[:timeout] ||= timeout
|
|
21
|
+
|
|
22
|
+
# if the verb isn't get or post, send it as a post argument
|
|
23
|
+
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
|
24
|
+
|
|
25
|
+
http = create_http(server(options), private_request, options)
|
|
26
|
+
|
|
27
|
+
result = http.start do |http|
|
|
28
|
+
response, body = if verb == "post"
|
|
29
|
+
if params_require_multipart? args
|
|
30
|
+
http.request Net::HTTP::Post::Multipart.new path, encode_multipart_params(args)
|
|
31
|
+
else
|
|
32
|
+
http.post(path, encode_params(args))
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
http.get("#{path}?#{encode_params(args)}")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Koala::Response.new(response.code.to_i, body, response)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
protected
|
|
43
|
+
def self.encode_params(param_hash)
|
|
44
|
+
# unfortunately, we can't use to_query because that's Rails, not Ruby
|
|
45
|
+
# if no hash (e.g. no auth token) return empty string
|
|
46
|
+
((param_hash || {}).collect do |key_and_value|
|
|
47
|
+
key_and_value[1] = key_and_value[1].to_json if key_and_value[1].class != String
|
|
48
|
+
"#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
|
|
49
|
+
end).join("&")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.encode_multipart_params(param_hash)
|
|
53
|
+
Hash[*param_hash.collect do |key, value|
|
|
54
|
+
[key, value.kind_of?(Koala::UploadableIO) ? value.to_upload_io : value]
|
|
55
|
+
end.flatten]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.create_http(server, private_request, options)
|
|
59
|
+
if options[:proxy]
|
|
60
|
+
proxy = URI.parse(options[:proxy])
|
|
61
|
+
http = Net::HTTP.new(server, private_request ? 443 : nil,
|
|
62
|
+
proxy.host, proxy.port, proxy.user, proxy.password)
|
|
63
|
+
else
|
|
64
|
+
http = Net::HTTP.new(server, private_request ? 443 : nil)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if options[:timeout]
|
|
68
|
+
http.open_timeout = options[:timeout]
|
|
69
|
+
http.read_timeout = options[:timeout]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# For HTTPS requests, set the proper CA certificates
|
|
73
|
+
if private_request
|
|
74
|
+
http.use_ssl = true
|
|
75
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
76
|
+
|
|
77
|
+
options[:ca_file] ||= ca_file
|
|
78
|
+
http.ca_file = options[:ca_file] if options[:ca_file] && File.exists?(options[:ca_file])
|
|
79
|
+
|
|
80
|
+
options[:ca_path] ||= ca_path
|
|
81
|
+
http.ca_path = options[:ca_path] if options[:ca_path] && Dir.exists?(options[:ca_path])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
http
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require "typhoeus" unless defined?(Typhoeus)
|
|
2
|
+
|
|
3
|
+
module Koala
|
|
4
|
+
module TyphoeusService
|
|
5
|
+
# this service uses Typhoeus to send requests to the graph
|
|
6
|
+
include Typhoeus
|
|
7
|
+
include Koala::HTTPService
|
|
8
|
+
|
|
9
|
+
def self.make_request(path, args, verb, options = {})
|
|
10
|
+
# if the verb isn't get or post, send it as a post argument
|
|
11
|
+
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
|
12
|
+
|
|
13
|
+
# switch any UploadableIOs to the files Typhoeus expects
|
|
14
|
+
args.each_pair {|key, value| args[key] = value.to_file if value.is_a?(UploadableIO)}
|
|
15
|
+
|
|
16
|
+
# you can pass arguments directly to Typhoeus using the :typhoeus_options key
|
|
17
|
+
typhoeus_options = {:params => args}.merge(options[:typhoeus_options] || {})
|
|
18
|
+
|
|
19
|
+
# if proxy/timeout options aren't passed, check if defaults are set
|
|
20
|
+
typhoeus_options[:proxy] ||= proxy
|
|
21
|
+
typhoeus_options[:timeout] ||= timeout
|
|
22
|
+
|
|
23
|
+
# by default, we use SSL only for private requests (e.g. with access token)
|
|
24
|
+
# this makes public requests faster
|
|
25
|
+
prefix = (args["access_token"] || @always_use_ssl || options[:use_ssl]) ? "https" : "http"
|
|
26
|
+
|
|
27
|
+
response = Typhoeus::Request.send(verb, "#{prefix}://#{server(options)}#{path}", typhoeus_options)
|
|
28
|
+
Koala::Response.new(response.code, response.body, response.headers_hash)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
protected
|
|
32
|
+
|
|
33
|
+
def self.multipart_requires_content_type?
|
|
34
|
+
false # Typhoeus handles multipart file types, we don't have to require it
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/koala/http_services.rb
CHANGED
|
@@ -13,134 +13,32 @@ module Koala
|
|
|
13
13
|
def self.included(base)
|
|
14
14
|
base.class_eval do
|
|
15
15
|
class << self
|
|
16
|
-
attr_accessor :always_use_ssl
|
|
16
|
+
attr_accessor :always_use_ssl, :proxy, :timeout, :ca_path, :ca_file
|
|
17
17
|
end
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
def self.server(options = {})
|
|
20
20
|
"#{options[:beta] ? "beta." : ""}#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
|
|
21
21
|
end
|
|
22
|
-
|
|
23
|
-
protected
|
|
24
22
|
|
|
25
|
-
def self.params_require_multipart?(param_hash)
|
|
26
|
-
param_hash.any? { |key, value| value.kind_of?(Koala::UploadableIO) }
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def self.multipart_requires_content_type?
|
|
30
|
-
true
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
module NetHTTPService
|
|
37
|
-
# this service uses Net::HTTP to send requests to the graph
|
|
38
|
-
def self.included(base)
|
|
39
|
-
base.class_eval do
|
|
40
|
-
require "net/http" unless defined?(Net::HTTP)
|
|
41
|
-
require "net/https"
|
|
42
|
-
require "net/http/post/multipart"
|
|
43
|
-
|
|
44
|
-
include Koala::HTTPService
|
|
45
|
-
|
|
46
|
-
def self.make_request(path, args, verb, options = {})
|
|
47
|
-
# We translate args to a valid query string. If post is specified,
|
|
48
|
-
# we send a POST request to the given path with the given arguments.
|
|
49
|
-
|
|
50
|
-
# by default, we use SSL only for private requests
|
|
51
|
-
# this makes public requests faster
|
|
52
|
-
private_request = args["access_token"] || @always_use_ssl || options[:use_ssl]
|
|
53
|
-
|
|
54
|
-
# if the verb isn't get or post, send it as a post argument
|
|
55
|
-
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
|
56
|
-
|
|
57
|
-
http = create_http(server(options), private_request, options)
|
|
58
|
-
http.use_ssl = true if private_request
|
|
59
|
-
|
|
60
|
-
result = http.start do |http|
|
|
61
|
-
response, body = if verb == "post"
|
|
62
|
-
if params_require_multipart? args
|
|
63
|
-
http.request Net::HTTP::Post::Multipart.new path, encode_multipart_params(args)
|
|
64
|
-
else
|
|
65
|
-
http.post(path, encode_params(args))
|
|
66
|
-
end
|
|
67
|
-
else
|
|
68
|
-
http.get("#{path}?#{encode_params(args)}")
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
Koala::Response.new(response.code.to_i, body, response)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
protected
|
|
76
23
|
def self.encode_params(param_hash)
|
|
77
24
|
# unfortunately, we can't use to_query because that's Rails, not Ruby
|
|
78
25
|
# if no hash (e.g. no auth token) return empty string
|
|
79
26
|
((param_hash || {}).collect do |key_and_value|
|
|
80
|
-
key_and_value[1] = key_and_value[1].to_json
|
|
27
|
+
key_and_value[1] = key_and_value[1].to_json unless key_and_value[1].is_a? String
|
|
81
28
|
"#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
|
|
82
29
|
end).join("&")
|
|
83
30
|
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
84
33
|
|
|
85
|
-
def self.
|
|
86
|
-
|
|
87
|
-
[key, value.kind_of?(Koala::UploadableIO) ? value.to_upload_io : value]
|
|
88
|
-
end.flatten]
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def self.create_http(server, private_request, options)
|
|
92
|
-
if options[:proxy]
|
|
93
|
-
proxy = URI.parse(options[:proxy])
|
|
94
|
-
http = Net::HTTP.new(server, private_request ? 443 : nil,
|
|
95
|
-
proxy.host, proxy.port, proxy.user, proxy.password)
|
|
96
|
-
else
|
|
97
|
-
http = Net::HTTP.new(server, private_request ? 443 : nil)
|
|
98
|
-
end
|
|
99
|
-
if options[:timeout]
|
|
100
|
-
http.open_timeout = options[:timeout]
|
|
101
|
-
http.read_timeout = options[:timeout]
|
|
102
|
-
end
|
|
103
|
-
http
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
module TyphoeusService
|
|
111
|
-
# this service uses Typhoeus to send requests to the graph
|
|
112
|
-
|
|
113
|
-
def self.included(base)
|
|
114
|
-
base.class_eval do
|
|
115
|
-
require "typhoeus" unless defined?(Typhoeus)
|
|
116
|
-
include Typhoeus
|
|
117
|
-
|
|
118
|
-
include Koala::HTTPService
|
|
119
|
-
|
|
120
|
-
def self.make_request(path, args, verb, options = {})
|
|
121
|
-
# if the verb isn't get or post, send it as a post argument
|
|
122
|
-
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
|
123
|
-
|
|
124
|
-
# switch any UploadableIOs to the files Typhoeus expects
|
|
125
|
-
args.each_pair {|key, value| args[key] = value.to_file if value.is_a?(UploadableIO)}
|
|
126
|
-
|
|
127
|
-
# you can pass arguments directly to Typhoeus using the :typhoeus_options key
|
|
128
|
-
typhoeus_options = {:params => args}.merge(options[:typhoeus_options] || {})
|
|
129
|
-
|
|
130
|
-
# by default, we use SSL only for private requests (e.g. with access token)
|
|
131
|
-
# this makes public requests faster
|
|
132
|
-
prefix = (args["access_token"] || @always_use_ssl || options[:use_ssl]) ? "https" : "http"
|
|
133
|
-
|
|
134
|
-
response = self.send(verb, "#{prefix}://#{server(options)}#{path}", typhoeus_options)
|
|
135
|
-
Koala::Response.new(response.code, response.body, response.headers_hash)
|
|
34
|
+
def self.params_require_multipart?(param_hash)
|
|
35
|
+
param_hash.any? { |key, value| value.kind_of?(Koala::UploadableIO) }
|
|
136
36
|
end
|
|
137
|
-
|
|
138
|
-
private
|
|
139
|
-
|
|
37
|
+
|
|
140
38
|
def self.multipart_requires_content_type?
|
|
141
|
-
|
|
39
|
+
true
|
|
142
40
|
end
|
|
143
|
-
end
|
|
41
|
+
end
|
|
144
42
|
end
|
|
145
43
|
end
|
|
146
44
|
end
|