rest-builder 0.9.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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.gitmodules +6 -0
- data/.travis.yml +14 -0
- data/CHANGES.md +5 -0
- data/Gemfile +24 -0
- data/README.md +577 -0
- data/Rakefile +21 -0
- data/lib/rest-builder.rb +27 -0
- data/lib/rest-builder/builder.rb +164 -0
- data/lib/rest-builder/client.rb +282 -0
- data/lib/rest-builder/engine.rb +57 -0
- data/lib/rest-builder/engine/dry.rb +11 -0
- data/lib/rest-builder/engine/http-client.rb +46 -0
- data/lib/rest-builder/error.rb +4 -0
- data/lib/rest-builder/event_source.rb +137 -0
- data/lib/rest-builder/middleware.rb +147 -0
- data/lib/rest-builder/payload.rb +173 -0
- data/lib/rest-builder/promise.rb +35 -0
- data/lib/rest-builder/test.rb +26 -0
- data/lib/rest-builder/version.rb +4 -0
- data/rest-builder.gemspec +73 -0
- data/task/README.md +54 -0
- data/task/gemgem.rb +316 -0
- data/test/test_builder.rb +45 -0
- data/test/test_client.rb +212 -0
- data/test/test_event_source.rb +152 -0
- data/test/test_future.rb +21 -0
- data/test/test_httpclient.rb +118 -0
- data/test/test_payload.rb +205 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2ebc038a71c8fa9399ddd41fde21d014c2a47e3a
|
4
|
+
data.tar.gz: 49ee5262a808a711f8bbcff6ec7f5a011c7832ed
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 14a85d6c66f9e61df4fd7d68c8de783641994ef893bc124f384ee1e0696a756d7e2146e2144ddecc0b9de5cc9be36b57609435cdca4020436bc43a32c57b7243
|
7
|
+
data.tar.gz: 984ad801875e5d21f1dba3d13e91875451987f22b00d9be1e598e030019a4492aaf3c61de541260bf2f16b46be69bc92dca379a534b9df0b4b4062039a22c086
|
data/.gitignore
ADDED
data/.gitmodules
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
sudo: false
|
2
|
+
language: ruby
|
3
|
+
rvm:
|
4
|
+
- 2.2
|
5
|
+
- 2.3.0
|
6
|
+
- rbx
|
7
|
+
- jruby-9
|
8
|
+
|
9
|
+
before_install:
|
10
|
+
- rvm get head
|
11
|
+
- rvm reload
|
12
|
+
- rvm use --install $TRAVIS_RUBY_VERSION --binary --latest
|
13
|
+
install: 'bundle install --retry=3'
|
14
|
+
script: 'ruby -vr bundler/setup -S rake test'
|
data/CHANGES.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
source 'https://rubygems.org/'
|
3
|
+
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'rake'
|
7
|
+
gem 'pork'
|
8
|
+
gem 'muack'
|
9
|
+
gem 'webmock'
|
10
|
+
|
11
|
+
gem 'simplecov', :require => false if ENV['COV']
|
12
|
+
gem 'coveralls', :require => false if ENV['CI']
|
13
|
+
|
14
|
+
platforms :rbx do
|
15
|
+
gem 'rubysl-weakref' # used in rest-core
|
16
|
+
gem 'rubysl-socket' # used in test
|
17
|
+
gem 'rubysl-singleton' # used in rake
|
18
|
+
gem 'rubysl-rexml' # used in crack used in webmock
|
19
|
+
gem 'rubysl-bigdecimal' # used in crack used in webmock
|
20
|
+
end
|
21
|
+
|
22
|
+
platforms :jruby do
|
23
|
+
gem 'jruby-openssl'
|
24
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,577 @@
|
|
1
|
+
# rest-builder [](http://travis-ci.org/godfat/rest-builder) [](https://coveralls.io/r/godfat/rest-builder) [](https://gitter.im/godfat/rest-builder)
|
2
|
+
|
3
|
+
by Lin Jen-Shin ([godfat](http://godfat.org))
|
4
|
+
|
5
|
+
## LINKS:
|
6
|
+
|
7
|
+
* [github](https://github.com/godfat/rest-builder)
|
8
|
+
* [rubygems](https://rubygems.org/gems/rest-builder)
|
9
|
+
* [rdoc](http://rdoc.info/projects/godfat/rest-builder)
|
10
|
+
|
11
|
+
## DESCRIPTION:
|
12
|
+
|
13
|
+
Modular Ruby clients interface for REST APIs.
|
14
|
+
|
15
|
+
Build your own API clients for less dependencies, less codes, less memory,
|
16
|
+
less conflicts, and run faster. Checkout [rest-core][] for pre-built
|
17
|
+
middleware and [rest-more][] for pre-built clients.
|
18
|
+
|
19
|
+
[rest-core]: https://github.com/godfat/rest-core
|
20
|
+
[rest-more]: https://github.com/godfat/rest-more
|
21
|
+
|
22
|
+
## FEATURES:
|
23
|
+
|
24
|
+
* Modular interface for REST clients similar to WSGI/Rack for servers.
|
25
|
+
* Concurrent requests with synchronous or asynchronous interfaces with
|
26
|
+
threads.
|
27
|
+
|
28
|
+
## WHY?
|
29
|
+
|
30
|
+
This was extracted from [rest-core][] because rest-core itself is getting
|
31
|
+
too complex, and this would be the new core of rest-core. Naming it
|
32
|
+
rest-core-core is a bit silly, and due to compatibility reason,
|
33
|
+
rest-core should work as is.
|
34
|
+
|
35
|
+
## REQUIREMENTS:
|
36
|
+
|
37
|
+
### Mandatory:
|
38
|
+
|
39
|
+
* Tested with MRI (official CRuby), Rubinius and JRuby.
|
40
|
+
* gem [promise_pool][]
|
41
|
+
* gem [timers][]
|
42
|
+
* gem [httpclient][]
|
43
|
+
* gem [mime-types][]
|
44
|
+
|
45
|
+
[promise_pool]: https://github.com/godfat/promise_pool
|
46
|
+
[timers]: https://github.com/celluloid/timers
|
47
|
+
[httpclient]: https://github.com/nahi/httpclient
|
48
|
+
[mime-types]: https://github.com/halostatue/mime-types
|
49
|
+
|
50
|
+
## INSTALLATION:
|
51
|
+
|
52
|
+
``` shell
|
53
|
+
gem install rest-builder
|
54
|
+
```
|
55
|
+
|
56
|
+
Or if you want development version, put this in Gemfile:
|
57
|
+
|
58
|
+
``` ruby
|
59
|
+
gem 'rest-builder', :git => 'git://github.com/godfat/rest-builder.git',
|
60
|
+
:submodules => true
|
61
|
+
```
|
62
|
+
|
63
|
+
If you want to use pre-built middleware instead of rolling your own,
|
64
|
+
please checkout [rest-core][].
|
65
|
+
|
66
|
+
If you just want to use Facebook or Twitter clients, please take a look at
|
67
|
+
[rest-more][] which has a lot of clients built with rest-core.
|
68
|
+
|
69
|
+
## Basic Usage:
|
70
|
+
|
71
|
+
Use `RestBuilder::Builder` to build your own clients like `Rack::Builder` to
|
72
|
+
build your application. The client you built this way would be a class which
|
73
|
+
you could then make client instances from. This way, each instance could
|
74
|
+
carry different configuration, e.g. different cache time or timeout time.
|
75
|
+
|
76
|
+
``` ruby
|
77
|
+
require 'rest-builder'
|
78
|
+
|
79
|
+
YourClient = RestBuilder::Builder.client do
|
80
|
+
# use ...
|
81
|
+
# use ...
|
82
|
+
# run ...
|
83
|
+
end
|
84
|
+
client = YourClient.new
|
85
|
+
client.get('http://example.com/') # make a request to http://example.com/
|
86
|
+
```
|
87
|
+
|
88
|
+
## Build Your Own Middleware:
|
89
|
+
|
90
|
+
### How We Pick the Default Value:
|
91
|
+
|
92
|
+
There are a number of ways to specify a default value, each with different
|
93
|
+
priorities. Suppose we have a middleware which remembers an integer:
|
94
|
+
|
95
|
+
``` ruby
|
96
|
+
class HP
|
97
|
+
def self.members; [:hp]; end
|
98
|
+
include RestBuilder::Middleware
|
99
|
+
def call env, &k
|
100
|
+
puts "HP: #{hp(env)}"
|
101
|
+
app.call(env, &k)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
Mage = RestBuilder::Builder.client do
|
105
|
+
use HP, 5 # the very last default
|
106
|
+
end
|
107
|
+
mage = Mage.new
|
108
|
+
```
|
109
|
+
|
110
|
+
1. The one passed to the request directly gets the first priority, e.g.
|
111
|
+
|
112
|
+
``` ruby
|
113
|
+
mage.get('http://example.com/', {}, :hp => 1) # prints HP: 1
|
114
|
+
```
|
115
|
+
|
116
|
+
2. The one saved as an instance variable in the client gets the 2nd place.
|
117
|
+
|
118
|
+
``` ruby
|
119
|
+
mage.hp = 2
|
120
|
+
mage.get('http://example.com/') # prints HP: 2
|
121
|
+
mage.get('http://example.com/', {}, :hp => 1) # prints HP: 1
|
122
|
+
mage.hp # still 2
|
123
|
+
mage.hp = false # disable hp
|
124
|
+
mage.hp = nil # reset to default
|
125
|
+
```
|
126
|
+
|
127
|
+
3. The method defined in the client instance named `default_hp` gets the 3rd.
|
128
|
+
|
129
|
+
``` ruby
|
130
|
+
class Mage
|
131
|
+
def default_hp
|
132
|
+
3
|
133
|
+
end
|
134
|
+
end
|
135
|
+
mage.get('http://example.com/') # prints HP: 3
|
136
|
+
mage.hp # 3
|
137
|
+
mage.hp = nil # reset default
|
138
|
+
Mage.send(:remove_method, :default_hp)
|
139
|
+
```
|
140
|
+
|
141
|
+
4. The method defined in the client class named `default_hp` gets the 4rd.
|
142
|
+
P.S. In [rest-more][], with `RestCore::Config` it would generate a
|
143
|
+
`DefaultAttributes` module which defines this kind of default method and
|
144
|
+
then is extended into the client class. You could still define this method
|
145
|
+
to override the default though.
|
146
|
+
|
147
|
+
``` ruby
|
148
|
+
class Mage
|
149
|
+
def self.default_hp
|
150
|
+
4
|
151
|
+
end
|
152
|
+
end
|
153
|
+
mage.get('http://example.com/') # prints HP: 4
|
154
|
+
mage.hp # 4
|
155
|
+
mage.hp = nil # reset to default
|
156
|
+
Mage.singleton_class.send(:remove_method, :default_hp)
|
157
|
+
```
|
158
|
+
|
159
|
+
5. The one defined in the middleware gets the last place.
|
160
|
+
|
161
|
+
``` ruby
|
162
|
+
mage.get('http://example.com/') # prints HP: 5
|
163
|
+
mage.hp # 5
|
164
|
+
mage.hp = nil # reset to default
|
165
|
+
```
|
166
|
+
|
167
|
+
You can find all the details in client.rb and middleware.rb. See the
|
168
|
+
included method hooks.
|
169
|
+
|
170
|
+
## Concurrent Requests with Futures:
|
171
|
+
|
172
|
+
You can also make concurrent requests easily:
|
173
|
+
(see "Advanced Concurrent HTTP Requests -- Embrace the Future" for detail)
|
174
|
+
|
175
|
+
``` ruby
|
176
|
+
a = [client.get('http://example.com/a'), client.get('http://example.com/b')]
|
177
|
+
puts "It's not blocking... but doing concurrent requests underneath"
|
178
|
+
p a # Here we want the values, so it blocks here
|
179
|
+
puts "DONE"
|
180
|
+
```
|
181
|
+
|
182
|
+
## Exception Handling for Futures:
|
183
|
+
|
184
|
+
Note that since the API call would only block whenever you're looking at
|
185
|
+
the response, it won't raise any exception at the time the API was called.
|
186
|
+
So if you want to block and handle the exception at the time API was called,
|
187
|
+
you would do something like this:
|
188
|
+
|
189
|
+
``` ruby
|
190
|
+
begin
|
191
|
+
response = client.get('http://nonexist/').itself # itself is the point
|
192
|
+
do_the_work(response)
|
193
|
+
rescue => e
|
194
|
+
puts "Got an exception: #{e}"
|
195
|
+
end
|
196
|
+
```
|
197
|
+
|
198
|
+
The trick here is forcing the future immediately give you the exact response,
|
199
|
+
so that rest-builder could see the response and raise the exception. You can
|
200
|
+
call whatever methods on the future to force this behaviour, but since
|
201
|
+
`itself` is a method from `Kernel` (which is included in `Object`), it's
|
202
|
+
always available and would return the original value, so it is the easiest
|
203
|
+
method to be remembered and used.
|
204
|
+
|
205
|
+
If you know the response must be a string, then you can also use `to_s`.
|
206
|
+
Like this:
|
207
|
+
|
208
|
+
``` ruby
|
209
|
+
begin
|
210
|
+
response = client.get('http://nonexist/').to_s
|
211
|
+
do_the_work(response)
|
212
|
+
rescue => e
|
213
|
+
puts "Got an exception: #{e}"
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
Or you can also do this:
|
218
|
+
|
219
|
+
``` ruby
|
220
|
+
begin
|
221
|
+
response = client.get('http://nonexist/')
|
222
|
+
response.class # simply force it to load
|
223
|
+
do_the_work(response)
|
224
|
+
rescue => e
|
225
|
+
puts "Got an exception: #{e}"
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
The point is simply making a method call to force it load, whatever method
|
230
|
+
should work.
|
231
|
+
|
232
|
+
## Concurrent Requests with Callbacks:
|
233
|
+
|
234
|
+
On the other hand, callback mode also available:
|
235
|
+
|
236
|
+
``` ruby
|
237
|
+
client.get('http://example.com/'){ |v| p v }
|
238
|
+
puts "It's not blocking... but doing concurrent requests underneath"
|
239
|
+
client.wait # we block here to wait for the request done
|
240
|
+
puts "DONE"
|
241
|
+
```
|
242
|
+
|
243
|
+
## Exception Handling for Callbacks:
|
244
|
+
|
245
|
+
What about exception handling in callback mode? You know that we cannot
|
246
|
+
raise any exception in the case of using a callback. So rest-builder would
|
247
|
+
pass the exception object into your callback. You can handle the exception
|
248
|
+
like this:
|
249
|
+
|
250
|
+
``` ruby
|
251
|
+
client.get('http://nonexist/') do |response|
|
252
|
+
if response.kind_of?(Exception)
|
253
|
+
puts "Got an exception: #{response}"
|
254
|
+
else
|
255
|
+
do_the_work(response)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
puts "It's not blocking... but doing concurrent requests underneath"
|
259
|
+
client.wait # we block here to wait for the request done
|
260
|
+
puts "DONE"
|
261
|
+
```
|
262
|
+
|
263
|
+
## Thread Pool / Connection Pool
|
264
|
+
|
265
|
+
Underneath, rest-builder would spawn a thread for each request, freeing you
|
266
|
+
from blocking. However, occasionally we would not want this behaviour,
|
267
|
+
giving that we might have limited resource and cannot maximize performance.
|
268
|
+
|
269
|
+
For example, maybe we could not afford so many threads running concurrently,
|
270
|
+
or the target server cannot accept so many concurrent connections. In those
|
271
|
+
cases, we would want to have limited concurrent threads or connections.
|
272
|
+
|
273
|
+
``` ruby
|
274
|
+
YourClient.pool_size = 10
|
275
|
+
YourClient.pool_idle_time = 60
|
276
|
+
```
|
277
|
+
|
278
|
+
This could set the thread pool size to 10, having a maximum of 10 threads
|
279
|
+
running together, growing from requests. Each threads idled more than 60
|
280
|
+
seconds would be shut down automatically.
|
281
|
+
|
282
|
+
Note that `pool_size` should at least be larger than 4, or it might be
|
283
|
+
very likely to have _deadlock_ if you're using nested callbacks and having
|
284
|
+
a large number of concurrent calls.
|
285
|
+
|
286
|
+
Also, setting `pool_size` to `-1` would mean we want to make blocking
|
287
|
+
requests, without spawning any threads. This might be useful for debugging.
|
288
|
+
|
289
|
+
## Gracefully shutdown
|
290
|
+
|
291
|
+
To shutdown gracefully, consider shutdown the thread pool (if we're using it),
|
292
|
+
and wait for all requests for a given client. For example, we'll do this when
|
293
|
+
we're shutting down:
|
294
|
+
|
295
|
+
``` ruby
|
296
|
+
YourClient.shutdown
|
297
|
+
```
|
298
|
+
|
299
|
+
We could put them in `at_exit` callback like this:
|
300
|
+
|
301
|
+
``` ruby
|
302
|
+
at_exit do
|
303
|
+
YourClient.shutdown
|
304
|
+
end
|
305
|
+
```
|
306
|
+
|
307
|
+
If you're using unicorn, you probably want to put that in the config.
|
308
|
+
|
309
|
+
## Random Asynchronous Tasks
|
310
|
+
|
311
|
+
Occasionally we might want to do some asynchronous tasks which could take
|
312
|
+
the advantage of the concurrency facilities inside rest-builder, for example,
|
313
|
+
using `wait` and `shutdown`. You could do this with `defer` for a particular
|
314
|
+
client. For example:
|
315
|
+
|
316
|
+
``` ruby
|
317
|
+
YourClient.defer do
|
318
|
+
sleep(1)
|
319
|
+
puts "Slow task done"
|
320
|
+
end
|
321
|
+
|
322
|
+
YourClient.wait
|
323
|
+
```
|
324
|
+
|
325
|
+
## Persistent connections (keep-alive connections)
|
326
|
+
|
327
|
+
Since we're using [httpclient][] by default now, we would reuse connections,
|
328
|
+
making it much faster for hitting the same host repeatedly.
|
329
|
+
|
330
|
+
## Streaming Requests
|
331
|
+
|
332
|
+
Suppose we want to POST a file, instead of trying to read all the contents
|
333
|
+
in memory and send them, we could stream it from the file system directly.
|
334
|
+
|
335
|
+
``` ruby
|
336
|
+
client.post('path', File.open('README.md'))
|
337
|
+
```
|
338
|
+
|
339
|
+
Basically, payloads could be any IO object. Check out
|
340
|
+
[RestBuilder::Payload](lib/rest-builder/payload.rb) for more information.
|
341
|
+
|
342
|
+
## Streaming Responses
|
343
|
+
|
344
|
+
This one is much harder then streaming requests, since all built-in
|
345
|
+
middleware actually assume the responses should be blocking and buffered.
|
346
|
+
Say, some JSON parser could not really parse from streams.
|
347
|
+
|
348
|
+
We solve this issue similarly to the way Rack solves it. That is, we hijack
|
349
|
+
the socket. This would be how we're doing:
|
350
|
+
|
351
|
+
``` ruby
|
352
|
+
sock = client.get('path', {}, RestBuilder::HIJACK => true)
|
353
|
+
p sock.read(10)
|
354
|
+
p sock.read(10)
|
355
|
+
p sock.read(10)
|
356
|
+
```
|
357
|
+
|
358
|
+
Of course, if we don't want to block in order to get the socket, we could
|
359
|
+
always use the callback form:
|
360
|
+
|
361
|
+
``` ruby
|
362
|
+
client.get('path', {}, RestBuilder::HIJACK => true) do |sock|
|
363
|
+
p sock.read(10)
|
364
|
+
p sock.read(10)
|
365
|
+
p sock.read(10)
|
366
|
+
end
|
367
|
+
```
|
368
|
+
|
369
|
+
Note that since the socket would be put inside `RestBuilder::RESPONSE_SOCKET`
|
370
|
+
instead of `RestBuilder::RESPONSE_BODY`, not all middleware would handle the
|
371
|
+
socket. In the case of hijacking, `RestBuilder::RESPONSE_BODY` would always
|
372
|
+
be mapped to an empty string, as it does not make sense to store the response
|
373
|
+
in this case.
|
374
|
+
|
375
|
+
## SSE (Server-Sent Events)
|
376
|
+
|
377
|
+
Not only JavaScript could receive server-sent events, any languages could.
|
378
|
+
Doing so would establish a keep-alive connection to the server, and receive
|
379
|
+
data periodically. We'll take Firebase as an example:
|
380
|
+
|
381
|
+
If you are using Firebase, please consider [rest-firebase][] instead.
|
382
|
+
|
383
|
+
[rest-firebase]: https://github.com/CodementorIO/rest-firebase
|
384
|
+
|
385
|
+
``` ruby
|
386
|
+
require 'rest-builder'
|
387
|
+
|
388
|
+
# Streaming over 'users/tom.json'
|
389
|
+
cl = RestBuilder::Builder.client.new
|
390
|
+
ph = 'https://SampleChat.firebaseIO-demo.com/users/tom.json'
|
391
|
+
es = cl.event_source(ph, {}, # this is query, none here
|
392
|
+
RestBuilder::REQUEST_HEADERS =>
|
393
|
+
{'Accept' => 'text/event-stream'})
|
394
|
+
|
395
|
+
@reconnect = true
|
396
|
+
|
397
|
+
es.onopen { |sock| p sock } # Called when connected
|
398
|
+
es.onmessage{ |event, data, sock| p event, data } # Called for each message
|
399
|
+
es.onerror { |error, sock| p error } # Called whenever there's an error
|
400
|
+
# Extra: If we return true in onreconnect callback, it would automatically
|
401
|
+
# reconnect the node for us if disconnected.
|
402
|
+
es.onreconnect{ |error, sock| p error; @reconnect }
|
403
|
+
|
404
|
+
# Start making the request
|
405
|
+
es.start
|
406
|
+
|
407
|
+
# Try to close the connection and see it reconnects automatically
|
408
|
+
es.close
|
409
|
+
|
410
|
+
# Update users/tom.json
|
411
|
+
p cl.put(ph, '{"some":"data"}')
|
412
|
+
p cl.post(ph, '{"some":"other"}')
|
413
|
+
p cl.get(ph)
|
414
|
+
p cl.delete(ph)
|
415
|
+
|
416
|
+
# Need to tell onreconnect stops reconnecting, or even if we close
|
417
|
+
# the connection manually, it would still try to reconnect again.
|
418
|
+
@reconnect = false
|
419
|
+
|
420
|
+
# Close the connection to gracefully shut it down.
|
421
|
+
es.close
|
422
|
+
```
|
423
|
+
|
424
|
+
Those callbacks would be called in a separate background thread,
|
425
|
+
so we don't have to worry about blocking it. If we want to wait for
|
426
|
+
the connection to be closed, we could call `wait`:
|
427
|
+
|
428
|
+
``` ruby
|
429
|
+
es.wait # This would block until the connection is closed
|
430
|
+
```
|
431
|
+
|
432
|
+
## More Control with `request_full`:
|
433
|
+
|
434
|
+
You can also use `request_full` to retrieve everything including response
|
435
|
+
status, response headers, and also other rest-builder options. But since
|
436
|
+
using this interface is like using Rack directly, you have to build the env
|
437
|
+
manually. To help you build the env manually, everything has a default,
|
438
|
+
including the path.
|
439
|
+
|
440
|
+
``` ruby
|
441
|
+
client.request_full(RestBuilder::REQUEST_PATH =>
|
442
|
+
'http://example.com/')[RestBuilder::RESPONSE_BODY]
|
443
|
+
client.request_full(RestBuilder::REQUEST_PATH =>
|
444
|
+
'http://example.com/')[RestBuilder::RESPONSE_STATUS]
|
445
|
+
client.request_full(RestBuilder::REQUEST_PATH =>
|
446
|
+
'http://example.com/')[RestBuilder::RESPONSE_HEADERS]
|
447
|
+
# Headers are normalized with all upper cases and
|
448
|
+
# dashes are replaced by underscores.
|
449
|
+
|
450
|
+
# To make POST (or any other request methods) request:
|
451
|
+
client.request_full(RestBuilder::REQUEST_PATH => 'http://example.com/',
|
452
|
+
RestBuilder::REQUEST_METHOD =>
|
453
|
+
:post)[RestBuilder::RESPONSE_STATUS] # 404
|
454
|
+
```
|
455
|
+
|
456
|
+
## Advanced Concurrent HTTP Requests -- Embrace the Future
|
457
|
+
|
458
|
+
### The Interface
|
459
|
+
|
460
|
+
There are a number of different ways to make concurrent requests in
|
461
|
+
rest-builder. They could be roughly categorized to two different forms.
|
462
|
+
One is using the well known callbacks, while the other one is using
|
463
|
+
through a technique called [future][]. Basically, it means it would
|
464
|
+
return you a promise, which would eventually become the real value
|
465
|
+
(response here) you were asking for whenever you really want it.
|
466
|
+
Otherwise, the program keeps running until the value is evaluated,
|
467
|
+
and blocks there if the computation (response) hasn't been done yet.
|
468
|
+
If the computation is already done, then it would simply return you
|
469
|
+
the result.
|
470
|
+
|
471
|
+
Here's a very simple example for using futures:
|
472
|
+
|
473
|
+
``` ruby
|
474
|
+
client = YourClient.new
|
475
|
+
puts "httpclient with threads doing concurrent requests"
|
476
|
+
a = [client.get('http://example.com/a'), client.get('http://example.com/b')]
|
477
|
+
puts "It's not blocking... but doing concurrent requests underneath"
|
478
|
+
p a # Here we want the values, so it blocks here
|
479
|
+
puts "DONE"
|
480
|
+
```
|
481
|
+
|
482
|
+
And here's a corresponded version for using callbacks:
|
483
|
+
|
484
|
+
``` ruby
|
485
|
+
client = YourClient.new
|
486
|
+
puts "httpclient with threads doing concurrent requests"
|
487
|
+
client.get('http://example.com/a'){ |v|
|
488
|
+
p v
|
489
|
+
}.
|
490
|
+
get('http://example.com/b'){ |v|
|
491
|
+
p v
|
492
|
+
}
|
493
|
+
puts "It's not blocking... but doing concurrent requests underneath"
|
494
|
+
client.wait # until all requests are done
|
495
|
+
puts "DONE"
|
496
|
+
```
|
497
|
+
|
498
|
+
You can pick whatever works for you.
|
499
|
+
|
500
|
+
[future]: http://en.wikipedia.org/wiki/Futures_and_promises
|
501
|
+
|
502
|
+
## Configure the underlying HTTP engine
|
503
|
+
|
504
|
+
Occasionally we might want to configure the underlying HTTP engine, which
|
505
|
+
for now is [httpclient][]. For example, we might not want to decompress
|
506
|
+
gzip automatically, (rest-core configures httpclient to request and
|
507
|
+
decompress gzip automatically). or we might want to skip verifying SSL
|
508
|
+
in some situation. (e.g. making requests against a self-signed testing server)
|
509
|
+
|
510
|
+
In such cases, we could use `config_engine` option to configure the underlying
|
511
|
+
engine. This could be set with request based, client instance based, or
|
512
|
+
client class based. Please refer to:
|
513
|
+
[How We Pick the Default Value](#how-we-pick-the-default-value),
|
514
|
+
except that there's no middleware for `config_engine`.
|
515
|
+
|
516
|
+
Here are some examples:
|
517
|
+
|
518
|
+
``` ruby
|
519
|
+
# class based:
|
520
|
+
def YourClient.default_config_engine
|
521
|
+
lambda do |engine|
|
522
|
+
# disable auto-gzip:
|
523
|
+
engine.transparent_gzip_decompression = false
|
524
|
+
|
525
|
+
# disable verifying SSL
|
526
|
+
engine.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
# instance based:
|
531
|
+
client = YourClient.new(:config_engine => lambda do |engine|
|
532
|
+
# disable auto-gzip:
|
533
|
+
engine.transparent_gzip_decompression = false
|
534
|
+
|
535
|
+
# disable verifying SSL
|
536
|
+
engine.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
537
|
+
end)
|
538
|
+
|
539
|
+
# request based:
|
540
|
+
client.get('http://example.com/', {}, :config_engine => lambda do |engine|
|
541
|
+
# disable auto-gzip:
|
542
|
+
engine.transparent_gzip_decompression = false
|
543
|
+
|
544
|
+
# disable verifying SSL
|
545
|
+
engine.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
546
|
+
end)
|
547
|
+
```
|
548
|
+
|
549
|
+
As we stated in
|
550
|
+
[How We Pick the Default Value](#how-we-pick-the-default-value),
|
551
|
+
the priority here is:
|
552
|
+
|
553
|
+
0. request based
|
554
|
+
0. instance based
|
555
|
+
0. class based
|
556
|
+
|
557
|
+
## CONTRIBUTORS:
|
558
|
+
|
559
|
+
* Lin Jen-Shin (@godfat)
|
560
|
+
|
561
|
+
## LICENSE:
|
562
|
+
|
563
|
+
Apache License 2.0
|
564
|
+
|
565
|
+
Copyright (c) 2016, Lin Jen-Shin (godfat)
|
566
|
+
|
567
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
568
|
+
you may not use this file except in compliance with the License.
|
569
|
+
You may obtain a copy of the License at
|
570
|
+
|
571
|
+
<http://www.apache.org/licenses/LICENSE-2.0>
|
572
|
+
|
573
|
+
Unless required by applicable law or agreed to in writing, software
|
574
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
575
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
576
|
+
See the License for the specific language governing permissions and
|
577
|
+
limitations under the License.
|