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 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
@@ -0,0 +1,2 @@
1
+ /pkg/
2
+ /coverage/
data/.gitmodules ADDED
@@ -0,0 +1,6 @@
1
+ [submodule "task"]
2
+ path = task
3
+ url = git://github.com/godfat/gemgem
4
+ [submodule "promise_pool"]
5
+ path = promise_pool
6
+ url = git://github.com/godfat/promise_pool.git
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
@@ -0,0 +1,5 @@
1
+ # CHANGES
2
+
3
+ ## rest-builder 0.9.0 -- 2016-01-31
4
+
5
+ * Birthday!
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 [![Build Status](https://secure.travis-ci.org/godfat/rest-builder.png?branch=master)](http://travis-ci.org/godfat/rest-builder) [![Coverage Status](https://coveralls.io/repos/godfat/rest-builder/badge.png)](https://coveralls.io/r/godfat/rest-builder) [![Join the chat at https://gitter.im/godfat/rest-builder](https://badges.gitter.im/Join%20Chat.svg)](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.