wordnik 4.07 → 4.08
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/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
|