koala 1.2.0 → 1.3.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/.gitignore +3 -1
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/.yardopts +3 -0
- data/CHANGELOG +43 -0
- data/Gemfile +14 -0
- data/Guardfile +6 -0
- data/koala.gemspec +4 -4
- data/lib/koala/api/batch_operation.rb +83 -0
- data/lib/koala/api/graph_api.rb +476 -0
- data/lib/koala/{graph_batch_api.rb → api/graph_batch_api.rb} +22 -17
- data/lib/koala/api/graph_collection.rb +107 -0
- data/lib/koala/api/legacy.rb +26 -0
- data/lib/koala/{rest_api.rb → api/rest_api.rb} +36 -10
- data/lib/koala/api.rb +93 -0
- data/lib/koala/http_service/multipart_request.rb +41 -0
- data/lib/koala/http_service/response.rb +18 -0
- data/lib/koala/http_service/uploadable_io.rb +187 -0
- data/lib/koala/http_service.rb +73 -23
- data/lib/koala/oauth.rb +173 -36
- data/lib/koala/realtime_updates.rb +89 -51
- data/lib/koala/test_users.rb +122 -32
- data/lib/koala/utils.rb +11 -4
- data/lib/koala/version.rb +1 -1
- data/lib/koala.rb +16 -95
- data/readme.md +30 -13
- data/spec/cases/api_spec.rb +28 -21
- data/spec/cases/error_spec.rb +10 -0
- data/spec/cases/graph_api_batch_spec.rb +100 -58
- data/spec/cases/graph_collection_spec.rb +23 -7
- data/spec/cases/http_service_spec.rb +14 -34
- data/spec/cases/koala_spec.rb +22 -4
- data/spec/cases/legacy_spec.rb +115 -0
- data/spec/cases/multipart_request_spec.rb +66 -0
- data/spec/cases/oauth_spec.rb +232 -108
- data/spec/cases/realtime_updates_spec.rb +154 -47
- data/spec/cases/test_users_spec.rb +276 -217
- data/spec/cases/uploadable_io_spec.rb +1 -1
- data/spec/cases/utils_spec.rb +29 -5
- data/spec/fixtures/mock_facebook_responses.yml +50 -26
- data/spec/spec_helper.rb +3 -0
- data/spec/support/custom_matchers.rb +28 -0
- data/spec/support/graph_api_shared_examples.rb +274 -70
- data/spec/support/koala_test.rb +27 -16
- data/spec/support/mock_http_service.rb +2 -2
- data/spec/support/rest_api_shared_examples.rb +46 -162
- metadata +33 -25
- data/lib/koala/batch_operation.rb +0 -74
- data/lib/koala/graph_api.rb +0 -270
- data/lib/koala/graph_collection.rb +0 -59
- data/lib/koala/uploadable_io.rb +0 -181
- data/spec/cases/graph_and_rest_api_spec.rb +0 -22
- data/spec/cases/graph_api_spec.rb +0 -22
- data/spec/cases/rest_api_spec.rb +0 -41
|
@@ -2,22 +2,47 @@ module Koala
|
|
|
2
2
|
module Facebook
|
|
3
3
|
REST_SERVER = "api.facebook.com"
|
|
4
4
|
|
|
5
|
+
# Methods used to interact with Facebook's legacy REST API.
|
|
6
|
+
# Where possible, you should use the newer, faster Graph API to interact with Facebook;
|
|
7
|
+
# in the future, the REST API will be deprecated.
|
|
8
|
+
# For now, though, there are a few methods that can't be done through the Graph API.
|
|
9
|
+
#
|
|
10
|
+
# When using the REST API, Koala will use Facebook's faster read-only servers
|
|
11
|
+
# whenever the call allows.
|
|
12
|
+
#
|
|
13
|
+
# See https://github.com/arsduo/koala/wiki/REST-API for a general introduction to Koala
|
|
14
|
+
# and the Rest API.
|
|
5
15
|
module RestAPIMethods
|
|
6
|
-
|
|
7
|
-
|
|
16
|
+
# Set a Facebook application's properties.
|
|
17
|
+
#
|
|
18
|
+
# @param properties a hash of properties you want to update with their new values.
|
|
19
|
+
# @param (see #rest_call)
|
|
20
|
+
# @param options (see #rest_call)
|
|
21
|
+
#
|
|
22
|
+
# @return true if successful, false if not. (This call currently doesn't give useful feedback on failure.)
|
|
23
|
+
def set_app_properties(properties, args = {}, options = {})
|
|
24
|
+
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "setAppProperties requires an access token"}) unless @access_token
|
|
25
|
+
rest_call("admin.setAppProperties", args.merge(:properties => MultiJson.encode(properties)), options, "post")
|
|
8
26
|
end
|
|
9
27
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
# Make a call to the REST API.
|
|
29
|
+
#
|
|
30
|
+
# @note The order of the last two arguments is non-standard (for historical reasons). Sorry.
|
|
31
|
+
#
|
|
32
|
+
# @param fb_method the API call you want to make
|
|
33
|
+
# @param args (see Koala::Facebook::GraphAPIMethods#graph_call)
|
|
34
|
+
# @param options (see Koala::Facebook::GraphAPIMethods#graph_call)
|
|
35
|
+
# @param verb (see Koala::Facebook::GraphAPIMethods#graph_call)
|
|
36
|
+
#
|
|
37
|
+
# @raise [Koala::Facebook::APIError] if Facebook returns an error
|
|
38
|
+
#
|
|
39
|
+
# @return the result from Facebook
|
|
40
|
+
def rest_call(fb_method, args = {}, options = {}, verb = "get")
|
|
41
|
+
Koala::Utils.deprecate("The REST API is now deprecated; please use the equivalent Graph API methods instead. See http://developers.facebook.com/blog/post/616/.")
|
|
16
42
|
|
|
17
|
-
def rest_call(fb_method, args = {}, options = {}, method = "get")
|
|
18
43
|
options = options.merge!(:rest_api => true, :read_only => READ_ONLY_METHODS.include?(fb_method.to_s))
|
|
19
44
|
|
|
20
|
-
api("method/#{fb_method}", args.merge('format' => 'json'),
|
|
45
|
+
api("method/#{fb_method}", args.merge('format' => 'json'), verb, options) do |response|
|
|
21
46
|
# check for REST API-specific errors
|
|
22
47
|
if response.is_a?(Hash) && response["error_code"]
|
|
23
48
|
raise APIError.new("type" => response["error_code"], "message" => response["error_msg"])
|
|
@@ -25,6 +50,7 @@ module Koala
|
|
|
25
50
|
end
|
|
26
51
|
end
|
|
27
52
|
|
|
53
|
+
# @private
|
|
28
54
|
# read-only methods for which we can use API-read
|
|
29
55
|
# taken directly from the FB PHP library (https://github.com/facebook/php-sdk/blob/master/src/facebook.php)
|
|
30
56
|
READ_ONLY_METHODS = [
|
data/lib/koala/api.rb
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# graph_batch_api and legacy are required at the bottom, since they depend on API being defined
|
|
2
|
+
require 'koala/api/graph_api'
|
|
3
|
+
require 'koala/api/rest_api'
|
|
4
|
+
|
|
5
|
+
module Koala
|
|
6
|
+
module Facebook
|
|
7
|
+
class API
|
|
8
|
+
# Creates a new API client.
|
|
9
|
+
# @param [String] access_token access token
|
|
10
|
+
# @note If no access token is provided, you can only access some public information.
|
|
11
|
+
# @return [Koala::Facebook::API] the API client
|
|
12
|
+
def initialize(access_token = nil)
|
|
13
|
+
@access_token = access_token
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :access_token
|
|
17
|
+
|
|
18
|
+
include GraphAPIMethods
|
|
19
|
+
include RestAPIMethods
|
|
20
|
+
|
|
21
|
+
# Makes a request to the appropriate Facebook API.
|
|
22
|
+
# @note You'll rarely need to call this method directly.
|
|
23
|
+
#
|
|
24
|
+
# @see GraphAPIMethods#graph_call
|
|
25
|
+
# @see RestAPIMethods#rest_call
|
|
26
|
+
#
|
|
27
|
+
# @param path the server path for this request (leading / is prepended if not present)
|
|
28
|
+
# @param args arguments to be sent to Facebook
|
|
29
|
+
# @param verb the HTTP method to use
|
|
30
|
+
# @param options request-related options for Koala and Faraday.
|
|
31
|
+
# See https://github.com/arsduo/koala/wiki/HTTP-Services for additional options.
|
|
32
|
+
# @option options [Symbol] :http_component which part of the response (headers, body, or status) to return
|
|
33
|
+
# @option options [Boolean] :beta use Facebook's beta tier
|
|
34
|
+
# @option options [Boolean] :use_ssl force SSL for this request, even if it's tokenless.
|
|
35
|
+
# (All API requests with access tokens use SSL.)
|
|
36
|
+
# @param error_checking_block a block to evaluate the response status for additional JSON-encoded errors
|
|
37
|
+
#
|
|
38
|
+
# @yield The response body for evaluation
|
|
39
|
+
#
|
|
40
|
+
# @raise [Koala::Facebook::APIError] if Facebook returns an error (response status >= 500)
|
|
41
|
+
#
|
|
42
|
+
# @return the body of the response from Facebook (unless another http_component is requested)
|
|
43
|
+
def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
|
|
44
|
+
# Fetches the given path in the Graph API.
|
|
45
|
+
args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
|
|
46
|
+
|
|
47
|
+
# add a leading /
|
|
48
|
+
path = "/#{path}" unless path =~ /^\//
|
|
49
|
+
|
|
50
|
+
# make the request via the provided service
|
|
51
|
+
result = Koala.make_request(path, args, verb, options)
|
|
52
|
+
|
|
53
|
+
# Check for any 500 errors before parsing the body
|
|
54
|
+
# since we're not guaranteed that the body is valid JSON
|
|
55
|
+
# in the case of a server error
|
|
56
|
+
raise APIError.new({"type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}"}) if result.status >= 500
|
|
57
|
+
|
|
58
|
+
# parse the body as JSON and run it through the error checker (if provided)
|
|
59
|
+
# Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
|
|
60
|
+
# and cause MultiJson.decode to fail -- so we account for that by wrapping the result in []
|
|
61
|
+
body = MultiJson.decode("[#{result.body.to_s}]")[0]
|
|
62
|
+
yield body if error_checking_block
|
|
63
|
+
|
|
64
|
+
# if we want a component other than the body (e.g. redirect header for images), return that
|
|
65
|
+
if component = options[:http_component]
|
|
66
|
+
component == :response ? result : result.send(options[:http_component])
|
|
67
|
+
else
|
|
68
|
+
body
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class APIError < StandardError
|
|
74
|
+
attr_accessor :fb_error_type, :raw_response
|
|
75
|
+
|
|
76
|
+
# Creates a new APIError.
|
|
77
|
+
#
|
|
78
|
+
# Assigns the error type (as reported by Facebook) to #fb_error_type
|
|
79
|
+
# and the raw error response available to #raw_response.
|
|
80
|
+
#
|
|
81
|
+
# @param details error details containing "type" and "message" keys.
|
|
82
|
+
def initialize(details = {})
|
|
83
|
+
self.raw_response = details
|
|
84
|
+
self.fb_error_type = details["type"]
|
|
85
|
+
super("#{fb_error_type}: #{details["message"]}")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
require 'koala/api/graph_batch_api'
|
|
92
|
+
# legacy support for old pre-1.2 API interfaces
|
|
93
|
+
require 'koala/api/legacy'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
|
|
3
|
+
module Koala
|
|
4
|
+
module HTTPService
|
|
5
|
+
class MultipartRequest < Faraday::Request::Multipart
|
|
6
|
+
# Facebook expects nested parameters to be passed in a certain way
|
|
7
|
+
# Based on our testing (https://github.com/arsduo/koala/issues/125),
|
|
8
|
+
# Faraday needs two changes to make that work:
|
|
9
|
+
# 1) [] need to be escaped (e.g. params[foo]=bar ==> params%5Bfoo%5D=bar)
|
|
10
|
+
# 2) such messages need to be multipart-encoded
|
|
11
|
+
|
|
12
|
+
self.mime_type = 'multipart/form-data'.freeze
|
|
13
|
+
|
|
14
|
+
def process_request?(env)
|
|
15
|
+
# if the request values contain any hashes or arrays, multipart it
|
|
16
|
+
super || !!(env[:body].respond_to?(:values) && env[:body].values.find {|v| v.is_a?(Hash) || v.is_a?(Array)})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def process_params(params, prefix = nil, pieces = nil, &block)
|
|
21
|
+
params.inject(pieces || []) do |all, (key, value)|
|
|
22
|
+
key = "#{prefix}%5B#{key}%5D" if prefix
|
|
23
|
+
|
|
24
|
+
case value
|
|
25
|
+
when Array
|
|
26
|
+
values = value.inject([]) { |a,v| a << [nil, v] }
|
|
27
|
+
process_params(values, key, all, &block)
|
|
28
|
+
when Hash
|
|
29
|
+
process_params(value, key, all, &block)
|
|
30
|
+
else
|
|
31
|
+
all << block.call(key, value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @private
|
|
39
|
+
# legacy support for when MultipartRequest lived directly under Koala
|
|
40
|
+
MultipartRequest = HTTPService::MultipartRequest
|
|
41
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Koala
|
|
2
|
+
module HTTPService
|
|
3
|
+
class Response
|
|
4
|
+
attr_reader :status, :body, :headers
|
|
5
|
+
|
|
6
|
+
# Creates a new Response object, which standardizes the response received by Facebook for use within Koala.
|
|
7
|
+
def initialize(status, body, headers)
|
|
8
|
+
@status = status
|
|
9
|
+
@body = body
|
|
10
|
+
@headers = headers
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @private
|
|
16
|
+
# legacy support for when Response lived directly under Koala
|
|
17
|
+
Response = HTTPService::Response
|
|
18
|
+
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
require "net/http/post/multipart"
|
|
2
|
+
|
|
3
|
+
module Koala
|
|
4
|
+
module HTTPService
|
|
5
|
+
class UploadableIO
|
|
6
|
+
attr_reader :io_or_path, :content_type, :filename
|
|
7
|
+
|
|
8
|
+
def initialize(io_or_path_or_mixed, content_type = nil, filename = nil)
|
|
9
|
+
# see if we got the right inputs
|
|
10
|
+
parse_init_mixed_param io_or_path_or_mixed, content_type
|
|
11
|
+
|
|
12
|
+
# filename is used in the Ads API
|
|
13
|
+
# if it's provided, take precedence over the detected filename
|
|
14
|
+
# otherwise, fall back to a dummy name
|
|
15
|
+
@filename = filename || @filename || "koala-io-file.dum"
|
|
16
|
+
|
|
17
|
+
raise KoalaError.new("Invalid arguments to initialize an UploadableIO") unless @io_or_path
|
|
18
|
+
raise KoalaError.new("Unable to determine MIME type for UploadableIO") if !@content_type
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_upload_io
|
|
22
|
+
UploadIO.new(@io_or_path, @content_type, @filename)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_file
|
|
26
|
+
@io_or_path.is_a?(String) ? File.open(@io_or_path) : @io_or_path
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.binary_content?(content)
|
|
30
|
+
content.is_a?(UploadableIO) || DETECTION_STRATEGIES.detect {|method| send(method, content)}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
DETECTION_STRATEGIES = [
|
|
35
|
+
:sinatra_param?,
|
|
36
|
+
:rails_3_param?,
|
|
37
|
+
:file_param?
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
PARSE_STRATEGIES = [
|
|
41
|
+
:parse_rails_3_param,
|
|
42
|
+
:parse_sinatra_param,
|
|
43
|
+
:parse_file_object,
|
|
44
|
+
:parse_string_path,
|
|
45
|
+
:parse_io
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
def parse_init_mixed_param(mixed, content_type = nil)
|
|
49
|
+
PARSE_STRATEGIES.each do |method|
|
|
50
|
+
send(method, mixed, content_type)
|
|
51
|
+
return if @io_or_path && @content_type
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Expects a parameter of type ActionDispatch::Http::UploadedFile
|
|
56
|
+
def self.rails_3_param?(uploaded_file)
|
|
57
|
+
uploaded_file.respond_to?(:content_type) and uploaded_file.respond_to?(:tempfile) and uploaded_file.tempfile.respond_to?(:path)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def parse_rails_3_param(uploaded_file, content_type = nil)
|
|
61
|
+
if UploadableIO.rails_3_param?(uploaded_file)
|
|
62
|
+
@io_or_path = uploaded_file.tempfile.path
|
|
63
|
+
@content_type = content_type || uploaded_file.content_type
|
|
64
|
+
@filename = uploaded_file.original_filename
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Expects a Sinatra hash of file info
|
|
69
|
+
def self.sinatra_param?(file_hash)
|
|
70
|
+
file_hash.kind_of?(Hash) and file_hash.has_key?(:type) and file_hash.has_key?(:tempfile)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def parse_sinatra_param(file_hash, content_type = nil)
|
|
74
|
+
if UploadableIO.sinatra_param?(file_hash)
|
|
75
|
+
@io_or_path = file_hash[:tempfile]
|
|
76
|
+
@content_type = content_type || file_hash[:type] || detect_mime_type(tempfile)
|
|
77
|
+
@filename = file_hash[:filename]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# takes a file object
|
|
82
|
+
def self.file_param?(file)
|
|
83
|
+
file.kind_of?(File)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def parse_file_object(file, content_type = nil)
|
|
87
|
+
if UploadableIO.file_param?(file)
|
|
88
|
+
@io_or_path = file
|
|
89
|
+
@content_type = content_type || detect_mime_type(file.path)
|
|
90
|
+
@filename = File.basename(file.path)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def parse_string_path(path, content_type = nil)
|
|
95
|
+
if path.kind_of?(String)
|
|
96
|
+
@io_or_path = path
|
|
97
|
+
@content_type = content_type || detect_mime_type(path)
|
|
98
|
+
@filename = File.basename(path)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def parse_io(io, content_type = nil)
|
|
103
|
+
if io.respond_to?(:read)
|
|
104
|
+
@io_or_path = io
|
|
105
|
+
@content_type = content_type
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
MIME_TYPE_STRATEGIES = [
|
|
110
|
+
:use_mime_module,
|
|
111
|
+
:use_simple_detection
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
def detect_mime_type(filename)
|
|
115
|
+
if filename
|
|
116
|
+
MIME_TYPE_STRATEGIES.each do |method|
|
|
117
|
+
result = send(method, filename)
|
|
118
|
+
return result if result
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
nil # if we can't find anything
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def use_mime_module(filename)
|
|
125
|
+
# if the user has installed mime/types, we can use that
|
|
126
|
+
# if not, rescue and return nil
|
|
127
|
+
begin
|
|
128
|
+
type = MIME::Types.type_for(filename).first
|
|
129
|
+
type ? type.to_s : nil
|
|
130
|
+
rescue
|
|
131
|
+
nil
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def use_simple_detection(filename)
|
|
136
|
+
# very rudimentary extension analysis for images
|
|
137
|
+
# first, get the downcased extension, or an empty string if it doesn't exist
|
|
138
|
+
extension = ((filename.match(/\.([a-zA-Z0-9]+)$/) || [])[1] || "").downcase
|
|
139
|
+
case extension
|
|
140
|
+
when ""
|
|
141
|
+
nil
|
|
142
|
+
# images
|
|
143
|
+
when "jpg", "jpeg"
|
|
144
|
+
"image/jpeg"
|
|
145
|
+
when "png"
|
|
146
|
+
"image/png"
|
|
147
|
+
when "gif"
|
|
148
|
+
"image/gif"
|
|
149
|
+
|
|
150
|
+
# video
|
|
151
|
+
when "3g2"
|
|
152
|
+
"video/3gpp2"
|
|
153
|
+
when "3gp", "3gpp"
|
|
154
|
+
"video/3gpp"
|
|
155
|
+
when "asf"
|
|
156
|
+
"video/x-ms-asf"
|
|
157
|
+
when "avi"
|
|
158
|
+
"video/x-msvideo"
|
|
159
|
+
when "flv"
|
|
160
|
+
"video/x-flv"
|
|
161
|
+
when "m4v"
|
|
162
|
+
"video/x-m4v"
|
|
163
|
+
when "mkv"
|
|
164
|
+
"video/x-matroska"
|
|
165
|
+
when "mod"
|
|
166
|
+
"video/mod"
|
|
167
|
+
when "mov", "qt"
|
|
168
|
+
"video/quicktime"
|
|
169
|
+
when "mp4", "mpeg4"
|
|
170
|
+
"video/mp4"
|
|
171
|
+
when "mpe", "mpeg", "mpg", "tod", "vob"
|
|
172
|
+
"video/mpeg"
|
|
173
|
+
when "nsv"
|
|
174
|
+
"application/x-winamp"
|
|
175
|
+
when "ogm", "ogv"
|
|
176
|
+
"video/ogg"
|
|
177
|
+
when "wmv"
|
|
178
|
+
"video/x-ms-wmv"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# @private
|
|
185
|
+
# legacy support for when UploadableIO lived directly under Koala
|
|
186
|
+
UploadableIO = HTTPService::UploadableIO
|
|
187
|
+
end
|
data/lib/koala/http_service.rb
CHANGED
|
@@ -1,36 +1,61 @@
|
|
|
1
1
|
require 'faraday'
|
|
2
|
+
require 'koala/http_service/multipart_request'
|
|
3
|
+
require 'koala/http_service/uploadable_io'
|
|
4
|
+
require 'koala/http_service/response'
|
|
2
5
|
|
|
3
6
|
module Koala
|
|
4
|
-
|
|
5
|
-
attr_reader :status, :body, :headers
|
|
6
|
-
def initialize(status, body, headers)
|
|
7
|
-
@status = status
|
|
8
|
-
@body = body
|
|
9
|
-
@headers = headers
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
module HTTPService
|
|
14
|
-
# common functionality for all HTTP services
|
|
15
|
-
|
|
7
|
+
module HTTPService
|
|
16
8
|
class << self
|
|
17
|
-
|
|
9
|
+
# A customized stack of Faraday middleware that will be used to make each request.
|
|
10
|
+
attr_accessor :faraday_middleware
|
|
11
|
+
# A default set of HTTP options (see https://github.com/arsduo/koala/wiki/HTTP-Services)
|
|
12
|
+
attr_accessor :http_options
|
|
18
13
|
end
|
|
19
14
|
|
|
20
15
|
@http_options ||= {}
|
|
21
16
|
|
|
17
|
+
# Koala's default middleware stack.
|
|
18
|
+
# We encode requests in a Facebook-compatible multipart request,
|
|
19
|
+
# and use whichever adapter has been configured for this application.
|
|
22
20
|
DEFAULT_MIDDLEWARE = Proc.new do |builder|
|
|
23
|
-
builder.
|
|
21
|
+
builder.use Koala::HTTPService::MultipartRequest
|
|
24
22
|
builder.request :url_encoded
|
|
25
23
|
builder.adapter Faraday.default_adapter
|
|
26
24
|
end
|
|
27
25
|
|
|
26
|
+
# The address of the appropriate Facebook server.
|
|
27
|
+
#
|
|
28
|
+
# @param options various flags to indicate which server to use.
|
|
29
|
+
# @option options :rest_api use the old REST API instead of the Graph API
|
|
30
|
+
# @option options :video use the server designated for video uploads
|
|
31
|
+
# @option options :beta use the beta tier
|
|
32
|
+
# @option options :use_ssl force https, even if not needed
|
|
33
|
+
#
|
|
34
|
+
# @return a complete server address with protocol
|
|
28
35
|
def self.server(options = {})
|
|
29
36
|
server = "#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
|
|
30
37
|
server.gsub!(/\.facebook/, "-video.facebook") if options[:video]
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
server.gsub!(/\.facebook/, ".beta.facebook") if options[:beta]
|
|
39
|
+
"#{options[:use_ssl] ? "https" : "http"}://#{server}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Makes a request directly to Facebook.
|
|
43
|
+
# @note You'll rarely need to call this method directly.
|
|
44
|
+
#
|
|
45
|
+
# @see Koala::Facebook::API#api
|
|
46
|
+
# @see Koala::Facebook::GraphAPIMethods#graph_call
|
|
47
|
+
# @see Koala::Facebook::RestAPIMethods#rest_call
|
|
48
|
+
#
|
|
49
|
+
# @param path the server path for this request
|
|
50
|
+
# @param args (see Koala::Facebook::API#api)
|
|
51
|
+
# @param verb the HTTP method to use.
|
|
52
|
+
# If not get or post, this will be turned into a POST request with the appropriate :method
|
|
53
|
+
# specified in the arguments.
|
|
54
|
+
# @param options (see Koala::Facebook::API#api)
|
|
55
|
+
#
|
|
56
|
+
# @raise an appropriate connection error if unable to make the request to Facebook
|
|
57
|
+
#
|
|
58
|
+
# @return [Koala::HTTPService::Response] a response object representing the results from Facebook
|
|
34
59
|
def self.make_request(path, args, verb, options = {})
|
|
35
60
|
# if the verb isn't get or post, send it as a post argument
|
|
36
61
|
args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
|
|
@@ -47,13 +72,20 @@ module Koala
|
|
|
47
72
|
conn = Faraday.new(server(request_options), request_options, &(faraday_middleware || DEFAULT_MIDDLEWARE))
|
|
48
73
|
|
|
49
74
|
response = conn.send(verb, path, (verb == "post" ? params : {}))
|
|
50
|
-
Koala::Response.new(response.status.to_i, response.body, response.headers)
|
|
51
|
-
end
|
|
52
|
-
|
|
75
|
+
Koala::HTTPService::Response.new(response.status.to_i, response.body, response.headers)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Encodes a given hash into a query string.
|
|
79
|
+
# This is used mainly by the Batch API nowadays, since Faraday handles this for regular cases.
|
|
80
|
+
#
|
|
81
|
+
# @param params_hash a hash of values to CGI-encode and appropriately join
|
|
82
|
+
#
|
|
83
|
+
# @example
|
|
84
|
+
# Koala.http_service.encode_params({:a => 2, :b => "My String"})
|
|
85
|
+
# => "a=2&b=My+String"
|
|
86
|
+
#
|
|
87
|
+
# @return the appropriately-encoded string
|
|
53
88
|
def self.encode_params(param_hash)
|
|
54
|
-
# unfortunately, we can't use to_query because that's Rails, not Ruby
|
|
55
|
-
# if no hash (e.g. no auth token) return empty string
|
|
56
|
-
# this is used mainly by the Batch API nowadays
|
|
57
89
|
((param_hash || {}).collect do |key_and_value|
|
|
58
90
|
key_and_value[1] = MultiJson.encode(key_and_value[1]) unless key_and_value[1].is_a? String
|
|
59
91
|
"#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
|
|
@@ -63,76 +95,92 @@ module Koala
|
|
|
63
95
|
# deprecations
|
|
64
96
|
# not elegant or compact code, but temporary
|
|
65
97
|
|
|
98
|
+
# @private
|
|
66
99
|
def self.always_use_ssl
|
|
67
100
|
Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
|
|
68
101
|
http_options[:use_ssl]
|
|
69
102
|
end
|
|
70
103
|
|
|
104
|
+
# @private
|
|
71
105
|
def self.always_use_ssl=(value)
|
|
72
106
|
Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
|
|
73
107
|
http_options[:use_ssl] = value
|
|
74
108
|
end
|
|
75
109
|
|
|
110
|
+
# @private
|
|
76
111
|
def self.timeout
|
|
77
112
|
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
|
|
78
113
|
http_options[:timeout]
|
|
79
114
|
end
|
|
80
115
|
|
|
116
|
+
# @private
|
|
81
117
|
def self.timeout=(value)
|
|
82
118
|
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
|
|
83
119
|
http_options[:timeout] = value
|
|
84
120
|
end
|
|
85
121
|
|
|
122
|
+
# @private
|
|
86
123
|
def self.timeout
|
|
87
124
|
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
|
|
88
125
|
http_options[:timeout]
|
|
89
126
|
end
|
|
90
127
|
|
|
128
|
+
# @private
|
|
91
129
|
def self.timeout=(value)
|
|
92
130
|
Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
|
|
93
131
|
http_options[:timeout] = value
|
|
94
132
|
end
|
|
95
133
|
|
|
134
|
+
# @private
|
|
96
135
|
def self.proxy
|
|
97
136
|
Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
|
|
98
137
|
http_options[:proxy]
|
|
99
138
|
end
|
|
100
139
|
|
|
140
|
+
# @private
|
|
101
141
|
def self.proxy=(value)
|
|
102
142
|
Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
|
|
103
143
|
http_options[:proxy] = value
|
|
104
144
|
end
|
|
105
145
|
|
|
146
|
+
# @private
|
|
106
147
|
def self.ca_path
|
|
107
148
|
Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
|
|
108
149
|
(http_options[:ssl] || {})[:ca_path]
|
|
109
150
|
end
|
|
110
151
|
|
|
152
|
+
# @private
|
|
111
153
|
def self.ca_path=(value)
|
|
112
154
|
Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
|
|
113
155
|
(http_options[:ssl] ||= {})[:ca_path] = value
|
|
114
156
|
end
|
|
115
157
|
|
|
158
|
+
# @private
|
|
116
159
|
def self.ca_file
|
|
117
160
|
Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
|
|
118
161
|
(http_options[:ssl] || {})[:ca_file]
|
|
119
162
|
end
|
|
120
163
|
|
|
164
|
+
# @private
|
|
121
165
|
def self.ca_file=(value)
|
|
122
166
|
Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
|
|
123
167
|
(http_options[:ssl] ||= {})[:ca_file] = value
|
|
124
168
|
end
|
|
125
169
|
|
|
170
|
+
# @private
|
|
126
171
|
def self.verify_mode
|
|
127
172
|
Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
|
|
128
173
|
(http_options[:ssl] || {})[:verify_mode]
|
|
129
174
|
end
|
|
130
175
|
|
|
176
|
+
# @private
|
|
131
177
|
def self.verify_mode=(value)
|
|
132
178
|
Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
|
|
133
179
|
(http_options[:ssl] ||= {})[:verify_mode] = value
|
|
134
180
|
end
|
|
135
181
|
|
|
182
|
+
private
|
|
183
|
+
|
|
136
184
|
def self.process_options(options)
|
|
137
185
|
if typhoeus_options = options.delete(:typhoeus_options)
|
|
138
186
|
Koala::Utils.deprecate("typhoeus_options should now be included directly in the http_options hash. Support for this key will be removed in a future version.")
|
|
@@ -158,6 +206,7 @@ module Koala
|
|
|
158
206
|
end
|
|
159
207
|
end
|
|
160
208
|
|
|
209
|
+
# @private
|
|
161
210
|
module TyphoeusService
|
|
162
211
|
def self.deprecated_interface
|
|
163
212
|
# support old-style interface with a warning
|
|
@@ -166,6 +215,7 @@ module Koala
|
|
|
166
215
|
end
|
|
167
216
|
end
|
|
168
217
|
|
|
218
|
+
# @private
|
|
169
219
|
module NetHTTPService
|
|
170
220
|
def self.deprecated_interface
|
|
171
221
|
# support old-style interface with a warning
|