wordnik 4.07 → 4.08
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +14 -10
- data/README.md +24 -18
- data/lib/wordnik.rb +36 -22
- data/lib/wordnik/configuration.rb +20 -8
- data/lib/wordnik/load_balancer.rb +71 -0
- data/lib/wordnik/request.rb +53 -32
- data/lib/wordnik/version.rb +1 -3
- data/spec/100words.txt +100 -0
- data/spec/load_balancer_spec.rb +28 -0
- data/spec/performance.rb +140 -0
- data/spec/request_spec.rb +14 -14
- data/spec/resource_spec.rb +24 -24
- data/spec/response_spec.rb +24 -24
- data/spec/spec_helper.rb +21 -17
- data/spec/wordnik_spec.rb +73 -41
- data/wordnik.gemspec +3 -1
- metadata +125 -25
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
wordnik (4.
|
4
|
+
wordnik (4.08)
|
5
5
|
activemodel (>= 3.0.3)
|
6
6
|
addressable (>= 2.2.4)
|
7
7
|
htmlentities (>= 4.2.4)
|
@@ -13,25 +13,26 @@ GEM
|
|
13
13
|
remote: http://rubygems.org/
|
14
14
|
specs:
|
15
15
|
ZenTest (4.6.2)
|
16
|
-
activemodel (3.2.
|
17
|
-
activesupport (= 3.2.
|
16
|
+
activemodel (3.2.7)
|
17
|
+
activesupport (= 3.2.7)
|
18
18
|
builder (~> 3.0.0)
|
19
|
-
activesupport (3.2.
|
19
|
+
activesupport (3.2.7)
|
20
20
|
i18n (~> 0.6)
|
21
21
|
multi_json (~> 1.0)
|
22
22
|
addressable (2.2.6)
|
23
23
|
autotest (4.4.6)
|
24
24
|
ZenTest (>= 4.4.1)
|
25
25
|
autotest-rails-pure (4.1.2)
|
26
|
-
builder (3.0.
|
26
|
+
builder (3.0.3)
|
27
27
|
crack (0.1.8)
|
28
28
|
diff-lcs (1.1.3)
|
29
29
|
htmlentities (4.3.1)
|
30
|
-
i18n (0.6.
|
31
|
-
json (1.
|
32
|
-
mime-types (1.
|
33
|
-
multi_json (1.
|
34
|
-
nokogiri (1.5.
|
30
|
+
i18n (0.6.1)
|
31
|
+
json (1.7.5)
|
32
|
+
mime-types (1.19)
|
33
|
+
multi_json (1.3.6)
|
34
|
+
nokogiri (1.5.5)
|
35
|
+
rake (0.9.2.2)
|
35
36
|
rspec (2.8.0)
|
36
37
|
rspec-core (~> 2.8.0)
|
37
38
|
rspec-expectations (~> 2.8.0)
|
@@ -40,6 +41,7 @@ GEM
|
|
40
41
|
rspec-expectations (2.8.0)
|
41
42
|
diff-lcs (~> 1.1.2)
|
42
43
|
rspec-mocks (2.8.0)
|
44
|
+
ruby-prof (0.11.2)
|
43
45
|
typhoeus (0.3.3)
|
44
46
|
mime-types
|
45
47
|
vcr (1.11.3)
|
@@ -53,7 +55,9 @@ PLATFORMS
|
|
53
55
|
DEPENDENCIES
|
54
56
|
autotest
|
55
57
|
autotest-rails-pure
|
58
|
+
rake
|
56
59
|
rspec (~> 2.8.0)
|
60
|
+
ruby-prof
|
57
61
|
vcr (~> 1.11.3)
|
58
62
|
webmock (>= 1.6.2)
|
59
63
|
wordnik!
|
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
wordnik rubygem
|
2
2
|
===============
|
3
3
|
|
4
|
-
This is the official Wordnik rubygem. It fully wraps Wordnik's v4 API. Refer to
|
5
|
-
[developer.wordnik.com/docs](http://developer.wordnik.com/docs) to play around
|
6
|
-
in the live API sandbox. All the methods you see there are implemented in this
|
4
|
+
This is the official Wordnik rubygem. It fully wraps Wordnik's v4 API. Refer to
|
5
|
+
[developer.wordnik.com/docs](http://developer.wordnik.com/docs) to play around
|
6
|
+
in the live API sandbox. All the methods you see there are implemented in this
|
7
7
|
ruby gem.
|
8
8
|
|
9
9
|
Installation
|
@@ -69,7 +69,7 @@ Wordnik.words.search_words(:query => '*tin*', :include_part_of_speech => 'verb',
|
|
69
69
|
```
|
70
70
|
|
71
71
|
For a full list of available methods, check out the [Wordnik API documentation](http://developer.wordnik.com/docs).
|
72
|
-
When you make a request using our web-based API sandbox, the response output will show you how to make the
|
72
|
+
When you make a request using our web-based API sandbox, the response output will show you how to make the
|
73
73
|
[equivalent ruby request](http://cl.ly/9FQY). w00t!
|
74
74
|
|
75
75
|
Specs
|
@@ -77,14 +77,7 @@ Specs
|
|
77
77
|
|
78
78
|
The wordnik gem uses rspec 2. To run the test suite, just type `rake` or `bundle exec rake spec` in the gem's base directory.
|
79
79
|
|
80
|
-
Note
|
81
|
-
----
|
82
|
-
For testing locally, you will need to tunnel into the beta box
|
83
80
|
|
84
|
-
ssh -f -N -L 8001:localhost:8001 beta.wordnik.com
|
85
|
-
|
86
|
-
And remember to update the spec_helper
|
87
|
-
|
88
81
|
Contributing
|
89
82
|
------------
|
90
83
|
|
@@ -95,16 +88,29 @@ Contributing
|
|
95
88
|
* Commit and push until you are happy with your contribution
|
96
89
|
* Make sure to add tests for the feature/bugfix. This is important so we don't break it in a future version unintentionally.
|
97
90
|
|
98
|
-
|
99
|
-
|
91
|
+
Releasing
|
92
|
+
---------
|
100
93
|
|
101
|
-
|
102
|
-
|
94
|
+
```bash
|
95
|
+
rake swagger
|
96
|
+
open lib/version.rb # bump the version number
|
97
|
+
rake spec # test
|
98
|
+
git commit -am "newness" # commit
|
99
|
+
git push origin master # push
|
100
|
+
rake release # release
|
101
|
+
```
|
103
102
|
|
104
103
|
Props
|
105
104
|
-----
|
106
105
|
|
107
|
-
* Thanks to [Jason Adams](http://twitter.com/#!/ealdent) for graciously turning
|
106
|
+
* Thanks to [Jason Adams](http://twitter.com/#!/ealdent) for graciously turning
|
108
107
|
over the [wordnik gem name](https://rubygems.org/gems/wordnik).
|
109
|
-
* HTTP requests are made using [Typhoeus](https://github.com/dbalatero/typhoeus),
|
110
|
-
a modern code version of the mythical beast with 100 serpent heads.
|
108
|
+
* HTTP requests are made using [Typhoeus](https://github.com/dbalatero/typhoeus),
|
109
|
+
a modern code version of the mythical beast with 100 serpent heads.
|
110
|
+
|
111
|
+
Notes
|
112
|
+
-----
|
113
|
+
|
114
|
+
* If you are using the Wordnik gem on [Heroku](http://www.heroku.com/), you'll need
|
115
|
+
to use a stack that is compatible with [Typhoeus](https://github.com/dbalatero/typhoeus).
|
116
|
+
As of 2012-08, this means the Cedar stack.
|
data/lib/wordnik.rb
CHANGED
@@ -6,6 +6,7 @@ require 'wordnik/resource'
|
|
6
6
|
require 'wordnik/response'
|
7
7
|
require 'wordnik/configuration'
|
8
8
|
require 'wordnik/version'
|
9
|
+
require 'wordnik/load_balancer'
|
9
10
|
require 'logger'
|
10
11
|
|
11
12
|
# http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html
|
@@ -16,17 +17,17 @@ class Object
|
|
16
17
|
end
|
17
18
|
|
18
19
|
module Wordnik
|
19
|
-
|
20
|
+
|
20
21
|
class << self
|
21
|
-
|
22
|
+
|
22
23
|
# A Wordnik configuration object. Must act like a hash and return sensible
|
23
24
|
# values for all Wordnik configuration options. See Wordnik::Configuration.
|
24
25
|
attr_accessor :configuration
|
25
26
|
|
26
27
|
attr_accessor :resources
|
27
|
-
|
28
|
+
|
28
29
|
attr_accessor :logger
|
29
|
-
|
30
|
+
|
30
31
|
# Call this method to modify defaults in your initializers.
|
31
32
|
#
|
32
33
|
# @example
|
@@ -43,7 +44,7 @@ module Wordnik
|
|
43
44
|
|
44
45
|
# Configure logger. Default to use Rails
|
45
46
|
self.logger ||= configuration.logger || (defined?(Rails) ? Rails.logger : Logger.new(STDOUT))
|
46
|
-
|
47
|
+
|
47
48
|
# remove :// from scheme
|
48
49
|
configuration.scheme.sub!(/:\/\//, '')
|
49
50
|
|
@@ -51,6 +52,15 @@ module Wordnik
|
|
51
52
|
configuration.host.sub!(/https?:\/\//, '')
|
52
53
|
configuration.host = configuration.host.split('/').first
|
53
54
|
|
55
|
+
# do the same if multiple hosts are specified
|
56
|
+
configuration.hosts = configuration.hosts.map{|host| host.sub(/https?:\/\//, '').split('/').first}
|
57
|
+
|
58
|
+
# create a load balancer if no load balancer specified && multiple hosts are specified ...
|
59
|
+
if !configuration.load_balancer && configuration.hosts.size > 0
|
60
|
+
self.logger.debug "Creating a load balancer from #{configuration.hosts.join(', ')}"
|
61
|
+
configuration.load_balancer = LoadBalancer.new(configuration.hosts)
|
62
|
+
end
|
63
|
+
|
54
64
|
# Add leading and trailing slashes to base_path
|
55
65
|
configuration.base_path = "/#{configuration.base_path}".gsub(/\/+/, '/')
|
56
66
|
configuration.base_path = "" if configuration.base_path == "/"
|
@@ -59,17 +69,17 @@ module Wordnik
|
|
59
69
|
# attach resources because they haven't been downloaded.
|
60
70
|
if build
|
61
71
|
self.instantiate_resources
|
62
|
-
self.create_resource_shortcuts
|
72
|
+
self.create_resource_shortcuts
|
63
73
|
end
|
64
74
|
end
|
65
|
-
|
75
|
+
|
66
76
|
# Remove old JSON documentation and generated modules,
|
67
77
|
# then download fresh JSON files.
|
68
78
|
#
|
69
79
|
def download_resource_descriptions
|
70
80
|
system "rm api_docs/*.json"
|
71
81
|
system "rm lib/wordnik/resource_modules/*.rb"
|
72
|
-
|
82
|
+
|
73
83
|
Wordnik::Request.new(:get, "resources.json").response.body['apis'].each do |api|
|
74
84
|
resource_name = api['path'].split(".").first.gsub(/\//, '')
|
75
85
|
description = api['description']
|
@@ -84,19 +94,19 @@ module Wordnik
|
|
84
94
|
# 1. Instantiate a Resource object
|
85
95
|
# 2. Stuff the Resource in Wordnik.resources
|
86
96
|
#
|
87
|
-
def instantiate_resources
|
97
|
+
def instantiate_resources
|
88
98
|
self.resources = {}
|
89
99
|
self.configuration.resource_names.each do |resource_name|
|
90
100
|
name = resource_name.underscore.to_sym # 'fooBar' => :foo_bar
|
91
|
-
filename = File.join(File.dirname(__FILE__), "../api_docs/#{resource_name}.json")
|
101
|
+
filename = File.join(File.dirname(__FILE__), "../api_docs/#{resource_name}.json")
|
92
102
|
resource = Resource.new(
|
93
103
|
:name => name,
|
94
104
|
:raw_data => JSON.parse(File.read(filename))
|
95
105
|
)
|
96
106
|
self.resources[name] = resource
|
97
|
-
end
|
107
|
+
end
|
98
108
|
end
|
99
|
-
|
109
|
+
|
100
110
|
# Use some magic ruby dust to make nice method shortcuts.
|
101
111
|
# Wordnik.word => Wordnik.resources[:word]
|
102
112
|
# Wordnik.users => Wordnik.resources[:user]
|
@@ -109,39 +119,43 @@ module Wordnik
|
|
109
119
|
end
|
110
120
|
end
|
111
121
|
end
|
112
|
-
|
122
|
+
|
113
123
|
def authenticated?
|
114
124
|
Wordnik.configuration.user_id.present? && Wordnik.configuration.auth_token.present?
|
115
125
|
end
|
116
|
-
|
126
|
+
|
117
127
|
def de_authenticate
|
118
128
|
Wordnik.configuration.user_id = nil
|
119
129
|
Wordnik.configuration.auth_token = nil
|
120
130
|
end
|
121
|
-
|
131
|
+
|
132
|
+
def clear_configuration
|
133
|
+
Wordnik.configuration = Configuration.new
|
134
|
+
end
|
135
|
+
|
122
136
|
def authenticate
|
123
137
|
return if Wordnik.authenticated?
|
124
|
-
|
138
|
+
|
125
139
|
if Wordnik.configuration.username.blank? || Wordnik.configuration.password.blank?
|
126
140
|
raise ClientError, "Username and password are required to authenticate."
|
127
141
|
end
|
128
|
-
|
142
|
+
|
129
143
|
request = Wordnik::Request.new(
|
130
|
-
:get,
|
131
|
-
"account/authenticate/{username}",
|
144
|
+
:get,
|
145
|
+
"account/authenticate/{username}",
|
132
146
|
:params => {
|
133
|
-
:username => Wordnik.configuration.username,
|
147
|
+
:username => Wordnik.configuration.username,
|
134
148
|
:password => Wordnik.configuration.password
|
135
149
|
}
|
136
150
|
)
|
137
|
-
|
151
|
+
|
138
152
|
response_body = request.response.body
|
139
153
|
Wordnik.configuration.user_id = response_body['userId']
|
140
154
|
Wordnik.configuration.auth_token = response_body['token']
|
141
155
|
end
|
142
156
|
|
143
157
|
end
|
144
|
-
|
158
|
+
|
145
159
|
end
|
146
160
|
|
147
161
|
class ServerError < StandardError
|
@@ -7,35 +7,39 @@ module Wordnik
|
|
7
7
|
attr_accessor :api_key
|
8
8
|
attr_accessor :username
|
9
9
|
attr_accessor :password
|
10
|
-
|
10
|
+
|
11
11
|
# TODO: Steal all the auth stuff from the old gem!
|
12
12
|
attr_accessor :auth_token
|
13
13
|
attr_accessor :user_id
|
14
|
-
|
14
|
+
|
15
15
|
# Response format can be 'json' (default) or 'xml'
|
16
16
|
attr_accessor :response_format
|
17
|
-
|
17
|
+
|
18
18
|
# A comma-delimited list of the API's resources
|
19
19
|
attr_accessor :resource_names
|
20
|
-
|
20
|
+
|
21
21
|
# The URL of the API server
|
22
22
|
attr_accessor :scheme
|
23
23
|
attr_accessor :host
|
24
|
+
attr_accessor :hosts # to do in process load balancing
|
25
|
+
attr_accessor :load_balancer
|
24
26
|
attr_accessor :base_path
|
25
|
-
|
27
|
+
|
26
28
|
attr_accessor :user_agent
|
27
|
-
|
29
|
+
|
28
30
|
attr_accessor :proxy
|
29
31
|
attr_accessor :proxy_username
|
30
32
|
attr_accessor :proxy_password
|
31
33
|
|
32
34
|
attr_accessor :logger
|
33
|
-
|
35
|
+
|
34
36
|
# Defaults go in here..
|
35
37
|
def initialize
|
36
38
|
@response_format = 'json'
|
37
39
|
@scheme = 'http'
|
38
40
|
@host = 'api.wordnik.com'
|
41
|
+
@hosts = []
|
42
|
+
@load_balancer = nil
|
39
43
|
@base_path = '/v4'
|
40
44
|
@user_agent = "ruby-#{Wordnik::VERSION}"
|
41
45
|
# Build the default set of resource names from the filenames of the API documentation
|
@@ -47,7 +51,7 @@ module Wordnik
|
|
47
51
|
raise "Problem loading the resource files in ./api_docs/"
|
48
52
|
end
|
49
53
|
end
|
50
|
-
|
54
|
+
|
51
55
|
def base_url
|
52
56
|
Addressable::URI.new(
|
53
57
|
:scheme => self.scheme,
|
@@ -56,6 +60,14 @@ module Wordnik
|
|
56
60
|
)
|
57
61
|
end
|
58
62
|
|
63
|
+
def clear
|
64
|
+
initialize
|
65
|
+
end
|
66
|
+
|
67
|
+
def host
|
68
|
+
@load_balancer ? @load_balancer.host : @host
|
69
|
+
end
|
70
|
+
|
59
71
|
end
|
60
72
|
|
61
73
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Wordnik
|
2
|
+
|
3
|
+
# the simple idea here of a load balancer is to keep a set of hosts
|
4
|
+
# around, and use the 'best' one. At Wordnik, we have a set of
|
5
|
+
# API hosts available to us (these servers are invisible to the general web)
|
6
|
+
# The class below implements least recently used.
|
7
|
+
#
|
8
|
+
# The Wordnik Configuration object will convert a :hosts specification
|
9
|
+
# into a LoadBalancer instance
|
10
|
+
#
|
11
|
+
|
12
|
+
#
|
13
|
+
# These should be thread safe.
|
14
|
+
class LoadBalancer
|
15
|
+
|
16
|
+
attr_reader :hosts
|
17
|
+
attr_accessor :all_hosts
|
18
|
+
attr_accessor :failed_hosts_table
|
19
|
+
attr_accessor :current_host
|
20
|
+
|
21
|
+
def initialize(hosts)
|
22
|
+
@all_hosts = hosts
|
23
|
+
@hosts = @all_hosts
|
24
|
+
@failed_hosts_table = {}
|
25
|
+
@current_host = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def host
|
29
|
+
@current_host = hosts.first
|
30
|
+
@hosts.rotate!
|
31
|
+
restore_failed_hosts_maybe
|
32
|
+
@current_host
|
33
|
+
end
|
34
|
+
|
35
|
+
def inform_failure
|
36
|
+
#Wordnik.logger.debug "Informing failure about #{@current_host}. table: #{@failed_hosts_table.inspect}"
|
37
|
+
if @failed_hosts_table.include?(@current_host)
|
38
|
+
failures, failed_time = @failed_hosts_table[@current_host]
|
39
|
+
@failed_hosts_table[@current_host] = [failures+1, failed_time]
|
40
|
+
else
|
41
|
+
@failed_hosts_table[@current_host] = [1, Time.now.to_f] # failure count, first failure time
|
42
|
+
end
|
43
|
+
#Wordnik.logger.debug "Informed failure about #{@current_host}. table now: #{@failed_hosts_table.inspect}"
|
44
|
+
@hosts.delete(@current_host)
|
45
|
+
@hosts = [@current_host] if @hosts.size == 0 # got to have something!
|
46
|
+
end
|
47
|
+
|
48
|
+
# success here means just that a successful connection was made
|
49
|
+
# and the website didn't time out.
|
50
|
+
def inform_success
|
51
|
+
@failed_hosts_table.delete(@current_host)
|
52
|
+
@hosts << @current_host unless @hosts.include? @current_host
|
53
|
+
@hosts
|
54
|
+
end
|
55
|
+
|
56
|
+
def restore_failed_hosts_maybe
|
57
|
+
return if @failed_hosts_table.size == 0
|
58
|
+
@failed_hosts_table.each do |host, pair|
|
59
|
+
failures, failed_time = pair
|
60
|
+
seconds_since_first_failure = (Time.now.to_f - failed_time)
|
61
|
+
#Wordnik.logger.debug "Seconds since #{host}'s first failure: #{seconds_since_first_failure} compared to #{2**(failures-1)}"
|
62
|
+
# exponential backoff, but try every hour...
|
63
|
+
if (seconds_since_first_failure > [3600, 2**(failures-1)].min)
|
64
|
+
@hosts << host # give it a chance to succeed ...
|
65
|
+
#Wordnik.logger.debug "Added #{host} to @hosts; now: #{@hosts}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/lib/wordnik/request.rb
CHANGED
@@ -16,7 +16,7 @@ module Wordnik
|
|
16
16
|
|
17
17
|
# All requests must have an HTTP method and a path
|
18
18
|
# Optionals parameters are :params, :headers, :body, :format, :host
|
19
|
-
#
|
19
|
+
#
|
20
20
|
def initialize(http_method, path, attributes={})
|
21
21
|
attributes[:format] ||= Wordnik.configuration.response_format
|
22
22
|
attributes[:params] ||= {}
|
@@ -32,21 +32,21 @@ module Wordnik
|
|
32
32
|
if attributes[:headers].present? && attributes[:headers].has_key?(:api_key)
|
33
33
|
default_headers.delete(:api_key)
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
# api_key from params hash trumps all others (headers and default_headers)
|
37
37
|
if attributes[:params].present? && attributes[:params].has_key?(:api_key)
|
38
38
|
default_headers.delete(:api_key)
|
39
39
|
attributes[:headers].delete(:api_key) if attributes[:headers].present?
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
# Merge argument headers into defaults
|
43
43
|
attributes[:headers] = default_headers.merge(attributes[:headers] || {})
|
44
|
-
|
44
|
+
|
45
45
|
# Stick in the auth token if there is one
|
46
46
|
if Wordnik.authenticated?
|
47
47
|
attributes[:headers].merge!({:auth_token => Wordnik.configuration.auth_token})
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
self.http_method = http_method.to_sym
|
51
51
|
self.path = path
|
52
52
|
attributes.each do |name, value|
|
@@ -56,20 +56,20 @@ module Wordnik
|
|
56
56
|
|
57
57
|
# Construct a base URL
|
58
58
|
#
|
59
|
-
def url(options = {})
|
59
|
+
def url(options = {})
|
60
60
|
u = Addressable::URI.new(
|
61
61
|
:scheme => Wordnik.configuration.scheme,
|
62
62
|
:host => Wordnik.configuration.host,
|
63
63
|
:path => self.interpreted_path,
|
64
64
|
:query => self.query_string.sub(/\?/, '')
|
65
65
|
).to_s
|
66
|
-
|
66
|
+
|
67
67
|
# Drop trailing question mark, if present
|
68
68
|
u.sub! /\?$/, ''
|
69
|
-
|
69
|
+
|
70
70
|
# Obfuscate API key?
|
71
71
|
u.sub! /api\_key=\w+/, 'api_key=YOUR_API_KEY' if options[:obfuscated]
|
72
|
-
|
72
|
+
|
73
73
|
u
|
74
74
|
end
|
75
75
|
|
@@ -91,14 +91,14 @@ module Wordnik
|
|
91
91
|
end
|
92
92
|
|
93
93
|
p = p.sub("{format}", self.format.to_s)
|
94
|
-
|
94
|
+
|
95
95
|
URI.encode [Wordnik.configuration.base_path, p].join("/").gsub(/\/+/, '/')
|
96
96
|
end
|
97
|
-
|
97
|
+
|
98
98
|
# Massage the request body into a state of readiness
|
99
99
|
# If body is a hash, camelize all keys then convert to a json string
|
100
100
|
#
|
101
|
-
def body=(value)
|
101
|
+
def body=(value)
|
102
102
|
if value.is_a?(Hash)
|
103
103
|
value = value.inject({}) do |memo, (k,v)|
|
104
104
|
memo[k.to_s.camelize(:lower).to_sym] = v
|
@@ -107,13 +107,13 @@ module Wordnik
|
|
107
107
|
end
|
108
108
|
@body = value
|
109
109
|
end
|
110
|
-
|
110
|
+
|
111
111
|
# If body is an object, JSONify it before making the actual request.
|
112
|
-
#
|
112
|
+
#
|
113
113
|
def outgoing_body
|
114
114
|
body.is_a?(String) ? body : body.to_json
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
# Construct a query string from the query-string-type params
|
118
118
|
def query_string
|
119
119
|
|
@@ -127,48 +127,69 @@ module Wordnik
|
|
127
127
|
key = key.to_s.camelize(:lower).to_sym unless key.to_sym == :api_key # api_key is not a camelCased param
|
128
128
|
query_values[key] = value.to_s
|
129
129
|
end
|
130
|
-
|
130
|
+
|
131
131
|
# We don't want to end up with '?' as our query string
|
132
132
|
# if there aren't really any params
|
133
133
|
return "" if query_values.blank?
|
134
|
-
|
134
|
+
|
135
135
|
# Addressable requires query_values to be set after initialization..
|
136
136
|
qs = Addressable::URI.new
|
137
137
|
qs.query_values = query_values
|
138
138
|
qs.to_s
|
139
139
|
end
|
140
|
-
|
141
|
-
def make
|
142
|
-
|
140
|
+
|
141
|
+
def make(attempt = 0)
|
142
|
+
# url is calculated, so we need to compute it once.
|
143
|
+
u = self.url
|
144
|
+
#Wordnik.logger.debug "Making attempt #{attempt}; now fetching #{u}" if attempt > 0
|
145
|
+
request = Typhoeus::Request.new(u,
|
143
146
|
:headers => self.headers.stringify_keys,
|
144
147
|
:method => self.http_method.to_sym)
|
145
|
-
|
146
|
-
# Make request proxy-aware
|
148
|
+
|
149
|
+
# Make request proxy-aware
|
147
150
|
if Wordnik.configuration.proxy.present?
|
148
151
|
request.proxy = Wordnik.configuration.proxy
|
149
152
|
request.proxy_username = Wordnik.configuration.proxy_username if Wordnik.configuration.proxy_username.present?
|
150
153
|
request.proxy_password = Wordnik.configuration.proxy_password if Wordnik.configuration.proxy_password.present?
|
151
154
|
end
|
152
|
-
|
153
|
-
Wordnik.logger.debug "\n #{self.http_method.to_s.upcase} #{
|
154
|
-
|
155
|
+
|
156
|
+
Wordnik.logger.debug "\n #{self.http_method.to_s.upcase} #{u}\n body: #{self.outgoing_body}\n headers: #{request.headers}\n\n"
|
157
|
+
|
155
158
|
request.body = self.outgoing_body unless self.http_method.to_sym == :get
|
156
159
|
|
157
|
-
# Execute the request
|
160
|
+
# Execute the request — blocking call here.
|
158
161
|
Typhoeus::Hydra.hydra.queue request
|
159
|
-
Typhoeus::Hydra.hydra.run
|
160
|
-
|
162
|
+
Typhoeus::Hydra.hydra.run
|
163
|
+
|
164
|
+
# if we are using local load balancing, check for timeouts and connection errors
|
165
|
+
resp = request.response
|
166
|
+
|
167
|
+
if Wordnik.configuration.load_balancer
|
168
|
+
if (resp.timed_out? || resp.code == 0)
|
169
|
+
# Wordnik.logger.debug "informing load balancer about failure"
|
170
|
+
Wordnik.configuration.load_balancer.inform_failure
|
171
|
+
if (attempt <= 3)
|
172
|
+
# Wordnik.logger.debug "Trying again after failing #{attempt} times..."
|
173
|
+
return make(attempt + 1) if attempt <= 3 # try three times to get a result...
|
174
|
+
end
|
175
|
+
else
|
176
|
+
# Wordnik.logger.debug "informing load balancer about success"
|
177
|
+
Wordnik.configuration.load_balancer.inform_success
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
Response.new(resp)
|
161
182
|
end
|
162
|
-
|
183
|
+
|
163
184
|
def response
|
164
185
|
self.make
|
165
186
|
end
|
166
|
-
|
187
|
+
|
167
188
|
def response_code_pretty
|
168
189
|
return unless @response.present?
|
169
|
-
@response.code.to_s
|
190
|
+
@response.code.to_s
|
170
191
|
end
|
171
|
-
|
192
|
+
|
172
193
|
def response_headers_pretty
|
173
194
|
return unless @response.present?
|
174
195
|
# JSON.pretty_generate(@response.headers).gsub(/\n/, '<br/>') # <- This was for RestClient
|