rest-core 3.0.0 → 3.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 58ed90f247d23da6267042bf108e50637e8dba98
4
- data.tar.gz: a0d0b5f7dd766fee5426fc0faf5c5d0dd6adb229
3
+ metadata.gz: 070bdd0392158ac265903b7e713245a18933aaa8
4
+ data.tar.gz: 015edd1a6221a2a0b55dc04958dbed0df1729ecf
5
5
  SHA512:
6
- metadata.gz: a45a9c11e364a08414a7e583feedbe3a289a59fba42aa8d0180e0144e91279b7a77662737c489ec466402a72db4a2833f9e674ad27954d93e325e33713f4766d
7
- data.tar.gz: 3f3b2e09cd1131543a677e9a61cf4872c70fb272aad936eccd0f005d6490646b6589e0128ffd33f6107b644c77ef772a34f6433bd6c3d37fffe0aa1a53b72010
6
+ metadata.gz: 30bcbb75a17c797b3ebf823d5e46837209bd628df0abc0190f9d67823f4367b1556a1f11461befb176400aba3863ec920561f503da32dd1edc00682e9e2b44a9
7
+ data.tar.gz: fd18d5ecd9cb318c9b8ce3616c3f7432bd55ea43b6c517ea0bbc21a8d33670b0fca238678b08faf25b6c80b95f3c9d6be449a69a6b74ecb929ea90995b49b4ce
data/.travis.yml CHANGED
@@ -5,5 +5,5 @@ rvm:
5
5
  - 1.9.3
6
6
  - 2.0.0
7
7
  - ruby
8
- - rbx
8
+ - rbx-2
9
9
  - jruby
data/CHANGES.md CHANGED
@@ -1,6 +1,37 @@
1
1
  # CHANGES
2
2
 
3
- ## rest-core 3.0.0 -- ?
3
+ ## rest-core 3.1.0 -- 2014-05-09
4
+
5
+ ### Incompatible changes
6
+
7
+ * Now that the second argument `key` in `RC::Client#request` is replaced by
8
+ `RC::RESPONSE_KEY` passed in env. This would make it easier to use and
9
+ more consistent internally.
10
+ * Now RC::EventSource#onmessage would receive the event in the first argument,
11
+ and the data in the second argument instead of a hash in the first argument.
12
+
13
+ ### Enhancement
14
+
15
+ * Added RC::REQUEST_URI in the env so that we could access the requesting
16
+ URI easily.
17
+ * Added middleware RC::QueryResponse which could parse query in response.
18
+ * Added RC::Client.event_source_class which we could easily swap the class
19
+ used for event_source. Used in Firebase client to parse data in JSON.
20
+ * Now methods in RC::EventSource would return itself so that we could chain
21
+ callbacks.
22
+ * Added RC::EventSource#onreconnect callback to handle if we're going to
23
+ reconnect automatically or not.
24
+ * RC::Config was extracted from rest-more, which could help us manage config
25
+ files.
26
+ * RC::Json using JSON would now parse in quirks_mode, so that it could parse
27
+ not only JSON but also a single value.
28
+
29
+ ### Bugs Fixes
30
+
31
+ * We should never cache hijacked requests.
32
+ * Now we preserve payload and properly ignore query for RC::FollowRedirect.
33
+
34
+ ## rest-core 3.0.0 -- 2014-05-04
4
35
 
5
36
  Highlights:
6
37
 
data/README.md CHANGED
@@ -280,17 +280,44 @@ Not only JavaScript could receive server-sent events, any languages could.
280
280
  Doing so would establish a keep-alive connection to the server, and receive
281
281
  data periodically. We'll take Firebase as an example:
282
282
 
283
+ If you are using Firebase, please consider the pre-built client in
284
+ [rest-more][] instead.
285
+
283
286
  ``` ruby
284
- es = RC::Universal.new.event_source(
285
- 'https://SampleChat.firebaseIO-demo.com/users/tom/.json')
287
+ require 'rest-core'
288
+
289
+ # Streaming over 'users/tom.json'
290
+ cl = RC::Universal.new(:site => 'https://SampleChat.firebaseIO-demo.com/')
291
+ es = cl.event_source('users/tom.json', {}, # this is query, none here
292
+ :headers => {'Accept' => 'text/event-stream'})
293
+
294
+ @reconnect = true
295
+
296
+ es.onopen { |sock| p sock } # Called when connected
297
+ es.onmessage{ |event, data, sock| p event, data } # Called for each message
298
+ es.onerror { |error, sock| p error } # Called whenever there's an error
299
+ # Extra: If we return true in onreconnect callback, it would automatically
300
+ # reconnect the node for us if disconnected.
301
+ es.onreconnect{ |error, sock| p error; @reconnect }
302
+
303
+ # Start making the request
304
+ es.start
305
+
306
+ # Try to close the connection and see it reconnects automatically
307
+ es.close
308
+
309
+ # Update users/tom.json
310
+ p cl.put('users/tom.json', RC::Json.encode(:some => 'data'))
311
+ p cl.post('users/tom.json', RC::Json.encode(:some => 'other'))
312
+ p cl.get('users/tom.json')
313
+ p cl.delete('users/tom.json')
286
314
 
287
- es.onopen{ |sock| p "Socket: #{sock}" }
288
- es.onmessage{ |event| p "Event: #{event}" }
289
- es.onerror{ |error| p "Error: #{error}" }
315
+ # Need to tell onreconnect stops reconnecting, or even if we close
316
+ # the connection manually, it would still try to reconnect again.
317
+ @reconnect = false
290
318
 
291
- es.start # Start making the request
292
- sleep(5) # Sleep awhile to see anything is happening
293
- es.close # Close the connection when we're done
319
+ # Close the connection to gracefully shut it down.
320
+ es.close
294
321
  ```
295
322
 
296
323
  Those callbacks would be called in a separate background thread,
@@ -26,7 +26,7 @@ class RestCore::Builder
26
26
  client.send(:include, Client)
27
27
  class << client
28
28
  attr_reader :builder
29
- attr_accessor :pool_size, :pool_idle_time
29
+ attr_accessor :pool_size, :pool_idle_time, :event_source_class
30
30
 
31
31
  def thread_pool
32
32
  RestCore::ThreadPool[self]
@@ -35,6 +35,7 @@ class RestCore::Builder
35
35
  client.instance_variable_set(:@builder, self)
36
36
  client.instance_variable_set(:@pool_size, 0) # default to no pool
37
37
  client.instance_variable_set(:@pool_idle_time, 60) # default to 60 seconds
38
+ client.instance_variable_set(:@event_source_class, EventSource)
38
39
  client
39
40
  end
40
41
 
@@ -16,6 +16,7 @@ module RestCore
16
16
  use ErrorHandler, nil
17
17
  use ErrorDetectorHttp
18
18
  use JsonResponse, false
19
+ use QueryResponse, false
19
20
  end
20
21
  end
21
22
  end
@@ -111,28 +111,30 @@ module RestCore::Client
111
111
  request(
112
112
  {REQUEST_METHOD => :get ,
113
113
  REQUEST_PATH => path ,
114
- REQUEST_QUERY => query }.merge(opts), response_key(opts), &cb)
114
+ REQUEST_QUERY => query }.merge(opts), &cb)
115
115
  end
116
116
 
117
117
  def delete path, query={}, opts={}, &cb
118
118
  request(
119
119
  {REQUEST_METHOD => :delete,
120
120
  REQUEST_PATH => path ,
121
- REQUEST_QUERY => query }.merge(opts), response_key(opts), &cb)
121
+ REQUEST_QUERY => query }.merge(opts), &cb)
122
122
  end
123
123
 
124
124
  def head path, query={}, opts={}, &cb
125
125
  request(
126
126
  {REQUEST_METHOD => :head ,
127
127
  REQUEST_PATH => path ,
128
- REQUEST_QUERY => query }.merge(opts), RESPONSE_HEADERS, &cb)
128
+ REQUEST_QUERY => query ,
129
+ RESPONSE_KEY => RESPONSE_HEADERS}.merge(opts), &cb)
129
130
  end
130
131
 
131
132
  def options path, query={}, opts={}, &cb
132
133
  request(
133
134
  {REQUEST_METHOD => :options,
134
135
  REQUEST_PATH => path ,
135
- REQUEST_QUERY => query }.merge(opts), RESPONSE_HEADERS, &cb)
136
+ REQUEST_QUERY => query ,
137
+ RESPONSE_KEY => RESPONSE_HEADERS}.merge(opts), &cb)
136
138
  end
137
139
 
138
140
  def post path, payload={}, query={}, opts={}, &cb
@@ -140,7 +142,7 @@ module RestCore::Client
140
142
  {REQUEST_METHOD => :post ,
141
143
  REQUEST_PATH => path ,
142
144
  REQUEST_QUERY => query ,
143
- REQUEST_PAYLOAD => payload}.merge(opts), response_key(opts), &cb)
145
+ REQUEST_PAYLOAD => payload}.merge(opts), &cb)
144
146
  end
145
147
 
146
148
  def put path, payload={}, query={}, opts={}, &cb
@@ -148,7 +150,7 @@ module RestCore::Client
148
150
  {REQUEST_METHOD => :put ,
149
151
  REQUEST_PATH => path ,
150
152
  REQUEST_QUERY => query ,
151
- REQUEST_PAYLOAD => payload}.merge(opts), response_key(opts), &cb)
153
+ REQUEST_PAYLOAD => payload}.merge(opts), &cb)
152
154
  end
153
155
 
154
156
  def patch path, payload={}, query={}, opts={}, &cb
@@ -156,18 +158,18 @@ module RestCore::Client
156
158
  {REQUEST_METHOD => :patch ,
157
159
  REQUEST_PATH => path ,
158
160
  REQUEST_QUERY => query ,
159
- REQUEST_PAYLOAD => payload}.merge(opts), response_key(opts), &cb)
161
+ REQUEST_PAYLOAD => payload}.merge(opts), &cb)
160
162
  end
161
163
 
162
164
  def event_source path, query={}, opts={}
163
- EventSource.new(self, path, query, opts)
165
+ self.class.event_source_class.new(self, path, query, opts)
164
166
  end
165
167
 
166
- def request env, key=RESPONSE_BODY, app=app
168
+ def request env, app=app
167
169
  if block_given?
168
- request_full(env, app){ |response| yield(response[key]) }
170
+ request_full(env, app){ |response| yield(response[response_key(env)]) }
169
171
  else
170
- request_full(env, app)[key]
172
+ request_full(env, app)[response_key(env)]
171
173
  end
172
174
  end
173
175
 
@@ -214,7 +216,8 @@ module RestCore::Client
214
216
  end
215
217
 
216
218
  def response_key opts
217
- if opts[HIJACK] then RESPONSE_SOCKET else RESPONSE_BODY end
219
+ opts[RESPONSE_KEY] ||
220
+ if opts[HIJACK] then RESPONSE_SOCKET else RESPONSE_BODY end
218
221
  end
219
222
 
220
223
  def lighten_hash hash
@@ -24,7 +24,7 @@ class RestCore::HttpClient < RestCore::Engine
24
24
  client.connect_timeout, client.receive_timeout =
25
25
  calculate_timeout(env[TIMER])
26
26
 
27
- res = client.request(env[REQUEST_METHOD], request_uri(env), nil,
27
+ res = client.request(env[REQUEST_METHOD], env[REQUEST_URI], nil,
28
28
  payload, {'User-Agent' => 'Ruby'}.merge(headers))
29
29
 
30
30
  promise.fulfill(res.content, res.status,
@@ -32,7 +32,7 @@ class RestCore::HttpClient < RestCore::Engine
32
32
  end
33
33
 
34
34
  def request_async client, payload, headers, promise, env
35
- res = client.request_async(env[REQUEST_METHOD], request_uri(env), nil,
35
+ res = client.request_async(env[REQUEST_METHOD], env[REQUEST_URI], nil,
36
36
  payload, {'User-Agent' => 'Ruby'}.merge(headers)).pop
37
37
 
38
38
  promise.fulfill('', res.status,
@@ -8,7 +8,7 @@ class RestCore::NetHttpPersistent < RestCore::Engine
8
8
  http.open_timeout, http.read_timeout = calculate_timeout(env[TIMER])
9
9
  payload, headers = payload_and_headers(env)
10
10
 
11
- uri = ::URI.parse(request_uri(env))
11
+ uri = ::URI.parse(env[REQUEST_URI])
12
12
  req = ::Net::HTTP.const_get(env[REQUEST_METHOD].to_s.capitalize).
13
13
  new(uri, headers)
14
14
  req.body_stream = payload
@@ -8,7 +8,7 @@ class RestCore::RestClient < RestCore::Engine
8
8
  open_timeout, read_timeout = calculate_timeout(env[TIMER])
9
9
  payload, headers = payload_and_headers(env)
10
10
  res = ::RestClient::Request.execute(:method => env[REQUEST_METHOD],
11
- :url => request_uri(env) ,
11
+ :url => env[REQUEST_URI] ,
12
12
  :payload => payload ,
13
13
  :headers => headers ,
14
14
  :max_redirects => 0 ,
@@ -6,9 +6,10 @@ class RestCore::Engine
6
6
  include RestCore::Middleware
7
7
 
8
8
  def call env, &k
9
- promise = Promise.new(env, k, env[ASYNC])
10
- promise.defer{ request(promise, env) }
11
- env.merge(RESPONSE_BODY => promise.future_body,
9
+ req = env.merge(REQUEST_URI => request_uri(env))
10
+ promise = Promise.new(req, k, req[ASYNC])
11
+ promise.defer{ request(promise, req) }
12
+ req.merge(RESPONSE_BODY => promise.future_body,
12
13
  RESPONSE_STATUS => promise.future_status,
13
14
  RESPONSE_HEADERS => promise.future_headers,
14
15
  RESPONSE_SOCKET => promise.future_socket,
@@ -8,13 +8,12 @@ class RestCore::EventSource < Struct.new(:client, :path, :query, :opts,
8
8
  def start
9
9
  self.mutex = Mutex.new
10
10
  self.condv = ConditionVariable.new
11
- @onopen ||= nil
12
- @onmessage_for ||= nil
13
- @onerror ||= nil
14
-
15
- o = {REQUEST_HEADERS => {'Accept' => 'text/event-stream'},
16
- HIJACK => true}.merge(opts)
17
- client.get(path, query, o){ |sock| onopen(sock) }
11
+ @onopen ||= nil
12
+ @onmessage ||= nil
13
+ @onerror ||= nil
14
+ @onreconnect ||= nil
15
+ reconnect
16
+ self
18
17
  end
19
18
 
20
19
  def closed?
@@ -29,15 +28,18 @@ class RestCore::EventSource < Struct.new(:client, :path, :query, :opts,
29
28
  def wait
30
29
  raise RC::Error.new("Not yet started for: #{self}") unless mutex
31
30
  mutex.synchronize{ condv.wait(mutex) until closed? } unless closed?
31
+ self
32
32
  end
33
33
 
34
34
  def onopen sock=nil, &cb
35
35
  if block_given?
36
36
  @onopen = cb
37
37
  else
38
+ self.socket = sock # for you to track the socket
38
39
  @onopen.call(sock) if @onopen
39
40
  onmessage_for(sock)
40
41
  end
42
+ self
41
43
  rescue Exception => e
42
44
  begin # close the socket since we're going to stop anyway
43
45
  sock.close # if we don't close it, client might wait forever
@@ -47,12 +49,13 @@ class RestCore::EventSource < Struct.new(:client, :path, :query, :opts,
47
49
  onerror(e, sock)
48
50
  end
49
51
 
50
- def onmessage event=nil, sock=nil, &cb
52
+ def onmessage event=nil, data=nil, sock=nil, &cb
51
53
  if block_given?
52
54
  @onmessage = cb
53
55
  elsif @onmessage
54
- @onmessage.call(event, sock)
56
+ @onmessage.call(event, data, sock)
55
57
  end
58
+ self
56
59
  end
57
60
 
58
61
  # would also be called upon closing, would always be called at least once
@@ -62,10 +65,26 @@ class RestCore::EventSource < Struct.new(:client, :path, :query, :opts,
62
65
  else
63
66
  begin
64
67
  @onerror.call(error, sock) if @onerror
65
- ensure
66
- condv.signal # should never deadlock someone
68
+ onreconnect(error, sock)
69
+ rescue
70
+ condv.signal # so we can't be reconnecting, need to try to unblock
71
+ raise
67
72
  end
68
73
  end
74
+ self
75
+ end
76
+
77
+ # would be called upon closing,
78
+ # and would try to reconnect if a callback is set and return true
79
+ def onreconnect error=nil, sock=nil, &cb
80
+ if block_given?
81
+ @onreconnect = cb
82
+ elsif closed? && @onreconnect && @onreconnect.call(error, sock)
83
+ reconnect
84
+ else
85
+ condv.signal # we could be closing, let's try to unblock it
86
+ end
87
+ self
69
88
  end
70
89
 
71
90
  protected
@@ -74,18 +93,23 @@ class RestCore::EventSource < Struct.new(:client, :path, :query, :opts,
74
93
  private
75
94
  # called in requesting thread after the request is done
76
95
  def onmessage_for sock
77
- self.socket = sock # for you to track the socket
78
96
  until sock.eof?
79
97
  event = sock.readline("\n\n").split("\n").inject({}) do |r, i|
80
98
  k, v = i.split(': ', 2)
81
99
  r[k] = v
82
100
  r
83
101
  end
84
- onmessage(event, sock)
102
+ onmessage(event['event'], event['data'], sock)
85
103
  end
86
104
  sock.close
87
105
  onerror(EOFError.new, sock)
88
106
  rescue IOError => e
89
107
  onerror(e, sock)
90
108
  end
109
+
110
+ def reconnect
111
+ o = {REQUEST_HEADERS => {'Accept' => 'text/event-stream'},
112
+ HIJACK => true}.merge(opts)
113
+ client.get(path, query, o){ |sock| onopen(sock) }
114
+ end
91
115
  end
@@ -106,7 +106,8 @@ class RestCore::Cache
106
106
  end
107
107
 
108
108
  def cache_for? env
109
- [:get, :head, :otpions].include?(env[REQUEST_METHOD]) && !env[DRY]
109
+ [:get, :head, :otpions].include?(env[REQUEST_METHOD]) &&
110
+ !env[DRY] && !env[HIJACK]
110
111
  end
111
112
 
112
113
  def header_cache_key env
@@ -30,9 +30,9 @@ class RestCore::FollowRedirect
30
30
  res[REQUEST_METHOD]
31
31
  end
32
32
 
33
- call(res.merge(REQUEST_PATH => location,
34
- REQUEST_METHOD => meth ,
35
- REQUEST_PAYLOAD => {} ,
33
+ call(res.merge(REQUEST_METHOD => meth ,
34
+ REQUEST_PATH => location,
35
+ REQUEST_QUERY => {} ,
36
36
  'follow_redirect.max_redirects' =>
37
37
  res['follow_redirect.max_redirects'] - 1), &k)
38
38
  end
@@ -0,0 +1,23 @@
1
+
2
+ require 'rest-core/middleware'
3
+ require 'rest-core/util/parse_query'
4
+
5
+ class RestCore::QueryResponse
6
+ def self.members; [:query_response]; end
7
+ include RestCore::Middleware
8
+
9
+ QUERY_RESPONSE_HEADER =
10
+ {'Accept' => 'application/x-www-form-urlencoded'}.freeze
11
+
12
+ def call env, &k
13
+ return app.call(env, &k) if env[DRY]
14
+ return app.call(env, &k) unless query_response(env)
15
+
16
+ headers = QUERY_RESPONSE_HEADER.merge(env[REQUEST_HEADERS]||{})
17
+ app.call(env.merge(REQUEST_HEADERS => headers)) do |response|
18
+ body = ParseQuery.parse_query(response[RESPONSE_BODY])
19
+ yield(response.merge(RESPONSE_BODY => body))
20
+ end
21
+ end
22
+ end
23
+
@@ -45,6 +45,7 @@ class RestCore::ThreadPool
45
45
  # this should never fail
46
46
  def call
47
47
  job.call unless cancelled
48
+ true
48
49
  end
49
50
 
50
51
  # called from the other thread telling us it's timed out
@@ -0,0 +1,50 @@
1
+
2
+ require 'erb'
3
+ require 'yaml'
4
+
5
+ module RestCore; end
6
+ module RestCore::Config
7
+ extend self
8
+ DefaultModuleName = 'DefaultAttributes'
9
+
10
+ def load klass, path, env, namespace=nil
11
+ config = YAML.load(ERB.new(File.read(path)).result(binding))
12
+ defaults = config[env]
13
+ return false unless defaults
14
+ return false unless defaults[namespace] if namespace
15
+ data = if namespace
16
+ defaults[namespace]
17
+ else
18
+ defaults
19
+ end
20
+ raise ArgumentError.new("#{data} is not a hash") unless
21
+ data.kind_of?(Hash)
22
+
23
+ default_attributes_module(klass).module_eval(
24
+ data.inject(["extend self\n"]){ |r, (k, v)|
25
+ # quote strings, leave others free (e.g. false, numbers, etc)
26
+ r << <<-RUBY
27
+ def default_#{k}
28
+ #{v.inspect}
29
+ end
30
+ RUBY
31
+ }.join, __FILE__, __LINE__)
32
+ end
33
+
34
+ def default_attributes_module klass
35
+ mod = if klass.const_defined?(DefaultModuleName)
36
+ klass.const_get(DefaultModuleName)
37
+ else
38
+ klass.send(:const_set, DefaultModuleName, Module.new)
39
+ end
40
+
41
+ singleton_class = if klass.respond_to?(:singleton_class)
42
+ klass.singleton_class
43
+ else
44
+ class << klass; self; end
45
+ end
46
+
47
+ klass.send(:extend, mod) unless singleton_class < mod
48
+ mod
49
+ end
50
+ end
@@ -34,7 +34,7 @@ module RestCore::Json
34
34
  JSON.dump(hash)
35
35
  end
36
36
  def decode json
37
- JSON.parse(json)
37
+ JSON.parse(json, :quirks_mode => true)
38
38
  end
39
39
  end
40
40
 
@@ -1,4 +1,4 @@
1
1
 
2
2
  module RestCore
3
- VERSION = '3.0.0'
3
+ VERSION = '3.1.0'
4
4
  end
data/lib/rest-core.rb CHANGED
@@ -5,11 +5,13 @@ module RestCore
5
5
  REQUEST_QUERY = 'REQUEST_QUERY'
6
6
  REQUEST_PAYLOAD = 'REQUEST_PAYLOAD'
7
7
  REQUEST_HEADERS = 'REQUEST_HEADERS'
8
+ REQUEST_URI = 'REQUEST_URI'
8
9
 
9
10
  RESPONSE_BODY = 'RESPONSE_BODY'
10
11
  RESPONSE_STATUS = 'RESPONSE_STATUS'
11
12
  RESPONSE_HEADERS = 'RESPONSE_HEADERS'
12
13
  RESPONSE_SOCKET = 'RESPONSE_SOCKET'
14
+ RESPONSE_KEY = 'RESPONSE_KEY'
13
15
 
14
16
  DRY = 'core.dry'
15
17
  FAIL = 'core.fail'
@@ -40,6 +42,7 @@ module RestCore
40
42
  autoload :Json , 'rest-core/util/json'
41
43
  autoload :ParseQuery , 'rest-core/util/parse_query'
42
44
  autoload :Payload , 'rest-core/util/payload'
45
+ autoload :Config , 'rest-core/util/config'
43
46
 
44
47
  # middlewares
45
48
  autoload :AuthBasic , 'rest-core/middleware/auth_basic'
@@ -57,6 +60,7 @@ module RestCore
57
60
  autoload :FollowRedirect, 'rest-core/middleware/follow_redirect'
58
61
  autoload :JsonRequest , 'rest-core/middleware/json_request'
59
62
  autoload :JsonResponse , 'rest-core/middleware/json_response'
63
+ autoload :QueryResponse , 'rest-core/middleware/query_response'
60
64
  autoload :Oauth1Header , 'rest-core/middleware/oauth1_header'
61
65
  autoload :Oauth2Header , 'rest-core/middleware/oauth2_header'
62
66
  autoload :Oauth2Query , 'rest-core/middleware/oauth2_query'
data/rest-core.gemspec CHANGED
@@ -1,14 +1,14 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: rest-core 3.0.0 ruby lib
2
+ # stub: rest-core 3.1.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "rest-core"
6
- s.version = "3.0.0"
6
+ s.version = "3.1.0"
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib"]
10
10
  s.authors = ["Lin Jen-Shin (godfat)"]
11
- s.date = "2014-05-04"
11
+ s.date = "2014-05-09"
12
12
  s.description = "Modular Ruby clients interface for REST APIs.\n\nThere has been an explosion in the number of REST APIs available today.\nTo address the need for a way to access these APIs easily and elegantly,\nwe have developed rest-core, which consists of composable middleware\nthat allows you to build a REST client for any REST API. Or in the case of\ncommon APIs such as Facebook, Github, and Twitter, you can simply use the\ndedicated clients provided by [rest-more][].\n\n[rest-more]: https://github.com/godfat/rest-more"
13
13
  s.email = ["godfat (XD) godfat.org"]
14
14
  s.files = [
@@ -57,6 +57,7 @@ Gem::Specification.new do |s|
57
57
  "lib/rest-core/middleware/oauth1_header.rb",
58
58
  "lib/rest-core/middleware/oauth2_header.rb",
59
59
  "lib/rest-core/middleware/oauth2_query.rb",
60
+ "lib/rest-core/middleware/query_response.rb",
60
61
  "lib/rest-core/middleware/timeout.rb",
61
62
  "lib/rest-core/patch/multi_json.rb",
62
63
  "lib/rest-core/patch/rest-client.rb",
@@ -64,6 +65,7 @@ Gem::Specification.new do |s|
64
65
  "lib/rest-core/test.rb",
65
66
  "lib/rest-core/thread_pool.rb",
66
67
  "lib/rest-core/timer.rb",
68
+ "lib/rest-core/util/config.rb",
67
69
  "lib/rest-core/util/hmac.rb",
68
70
  "lib/rest-core/util/json.rb",
69
71
  "lib/rest-core/util/parse_query.rb",
@@ -73,11 +75,13 @@ Gem::Specification.new do |s|
73
75
  "rest-core.gemspec",
74
76
  "task/README.md",
75
77
  "task/gemgem.rb",
78
+ "test/config/rest-core.yaml",
76
79
  "test/test_auth_basic.rb",
77
80
  "test/test_builder.rb",
78
81
  "test/test_cache.rb",
79
82
  "test/test_client.rb",
80
83
  "test/test_client_oauth1.rb",
84
+ "test/test_config.rb",
81
85
  "test/test_default_payload.rb",
82
86
  "test/test_default_query.rb",
83
87
  "test/test_error_detector.rb",
@@ -92,6 +96,7 @@ Gem::Specification.new do |s|
92
96
  "test/test_oauth2_header.rb",
93
97
  "test/test_payload.rb",
94
98
  "test/test_promise.rb",
99
+ "test/test_query_response.rb",
95
100
  "test/test_rest-client.rb",
96
101
  "test/test_simple.rb",
97
102
  "test/test_thread_pool.rb",
@@ -108,6 +113,7 @@ Gem::Specification.new do |s|
108
113
  "test/test_cache.rb",
109
114
  "test/test_client.rb",
110
115
  "test/test_client_oauth1.rb",
116
+ "test/test_config.rb",
111
117
  "test/test_default_payload.rb",
112
118
  "test/test_default_query.rb",
113
119
  "test/test_error_detector.rb",
@@ -122,6 +128,7 @@ Gem::Specification.new do |s|
122
128
  "test/test_oauth2_header.rb",
123
129
  "test/test_payload.rb",
124
130
  "test/test_promise.rb",
131
+ "test/test_query_response.rb",
125
132
  "test/test_rest-client.rb",
126
133
  "test/test_simple.rb",
127
134
  "test/test_thread_pool.rb",
@@ -0,0 +1,8 @@
1
+
2
+ test:
3
+ facebook:
4
+ app_id: 41829
5
+ secret: <%= 'r41829'.reverse %>
6
+ json_response: false
7
+ lang: zh-tw
8
+ auto_authorize_scope: 'publish_stream'
data/test/test_cache.rb CHANGED
@@ -34,7 +34,8 @@ describe RC::Cache do
34
34
  c.app.app.tick.should.eq 2
35
35
  c.head('/').should.eq('A' => 'B')
36
36
  c.get('/').should.eq 'response'
37
- c.request({RC::REQUEST_PATH => '/'}, RC::RESPONSE_STATUS).should.eq 200
37
+ c.request(RC::REQUEST_PATH => '/',
38
+ RC::RESPONSE_KEY => RC::RESPONSE_STATUS).should.eq 200
38
39
  end
39
40
 
40
41
  should 'basic 1' do
@@ -155,6 +156,17 @@ describe RC::Cache do
155
156
  c.cache.should.eq({})
156
157
  end
157
158
 
159
+ should 'not cache hijacking' do
160
+ stub_request(:get, 'http://a').to_return(:body => 'ok')
161
+ c = RC::Builder.client{use RC::Cache, {}, nil}.new
162
+ 2.times do
163
+ c.get('http://a', {}, RC::HIJACK => true,
164
+ RC::RESPONSE_KEY => RC::RESPONSE_SOCKET).
165
+ read.should.eq 'ok'
166
+ end
167
+ c.cache.should.eq({})
168
+ end
169
+
158
170
  should 'update cache if there is cache option set to false' do
159
171
  url, body = "https://cache", 'ok'
160
172
  stub_request(:get, url).to_return(:body => body)
@@ -0,0 +1,28 @@
1
+
2
+ require 'rest-core/test'
3
+
4
+ describe RC::Config do
5
+ before do
6
+ @klass = RC::Builder.client
7
+ end
8
+
9
+ after do
10
+ Muack.verify
11
+ end
12
+
13
+ def check
14
+ @klass.default_app_id .should.eq 41829
15
+ @klass.default_secret .should.eq 'r41829'.reverse
16
+ @klass.default_json_response.should.eq false
17
+ @klass.default_lang .should.eq 'zh-tw'
18
+ end
19
+
20
+ should 'honor config' do
21
+ RC::Config.load(
22
+ @klass,
23
+ "#{File.dirname(__FILE__)}/config/rest-core.yaml",
24
+ 'test',
25
+ 'facebook')
26
+ check
27
+ end
28
+ end
@@ -39,6 +39,6 @@ describe RC::ErrorHandler do
39
39
 
40
40
  should 'no exception but errors' do
41
41
  client.new(:error_handler => lambda{ |res| 1 }).
42
- request({RC::FAIL => [0]}, RC::FAIL).should.eq [0, 1]
42
+ request(RC::FAIL => [0], RC::RESPONSE_KEY => RC::FAIL).should.eq [0, 1]
43
43
  end
44
44
  end
@@ -3,7 +3,11 @@ require 'socket'
3
3
  require 'rest-core/test'
4
4
 
5
5
  describe RC::EventSource do
6
- client = RC::Builder.client.new
6
+ after do
7
+ WebMock.reset!
8
+ end
9
+
10
+ client = RC::Builder.client{use RC::Cache, {}, nil}.new
7
11
  server = lambda do |close=true|
8
12
  serv = TCPServer.new(0)
9
13
  port = serv.addr[1]
@@ -39,25 +43,21 @@ SSE
39
43
  sock.should.kind_of IO
40
44
  flag.should.eq 0
41
45
  flag += 1
42
- end
43
-
44
- es.onmessage do |event, sock|
45
- event.should.eq(m.shift)
46
+ end.
47
+ onmessage do |event, data, sock|
48
+ {'event' => event, 'data' => data}.should.eq m.shift
46
49
  sock.should.kind_of IO
47
50
  sock.should.not.closed?
48
51
  flag += 1
49
- end
50
-
51
- es.onerror do |error, sock|
52
+ end.
53
+ onerror do |error, sock|
52
54
  error.should.kind_of EOFError
53
- m.should.eq []
55
+ m.should.empty
54
56
  sock.should.closed?
55
57
  flag.should.eq 3
56
58
  flag += 1
57
- end
59
+ end.start.wait
58
60
 
59
- es.start
60
- es.wait
61
61
  flag.should.eq 4
62
62
  t.join
63
63
  end
@@ -68,10 +68,67 @@ SSE
68
68
  es.onmessage do
69
69
  es.close
70
70
  flag += 1
71
- end
72
- es.start
73
- es.wait
71
+ end.start.wait
72
+
74
73
  flag.should.eq 1
75
74
  t.join
76
75
  end
76
+
77
+ should 'reconnect' do
78
+ stub_request(:get, 'https://a?b=c').to_return(:body => <<-SSE)
79
+ event: put
80
+ data: 0
81
+
82
+ event: put
83
+ data: 1
84
+ SSE
85
+ stub_request(:get, 'https://a?c=d').to_return(:body => <<-SSE)
86
+ event: put
87
+ data: 2
88
+
89
+ event: put
90
+ data: 3
91
+ SSE
92
+ es = client.event_source('https://a', :b => 'c')
93
+ m = ('0'..'3').to_a
94
+ es.onmessage do |event, data|
95
+ data.should.eq m.shift
96
+
97
+ end.onerror do |error|
98
+ error.should.kind_of EOFError
99
+ es.query = {:c => 'd'}
100
+
101
+ end.onreconnect do |error, sock|
102
+ error.should.kind_of EOFError
103
+ sock.should.respond_to :read
104
+ !m.empty? # not empty to reconnect
105
+
106
+ end.start.wait
107
+ m.should.empty
108
+ end
109
+
110
+ should 'not cache' do
111
+ stub_request(:get, 'https://a?b=c').to_return(:body => <<-SSE)
112
+ event: put
113
+ data: 0
114
+
115
+ event: put
116
+ data: 1
117
+ SSE
118
+ es = client.event_source('https://a', :b => 'c')
119
+ m = %w[0 1 0 1]
120
+ es.onmessage do |event, data|
121
+ data.should.eq m.shift
122
+
123
+ end.onerror do |error|
124
+ error.should.kind_of EOFError
125
+
126
+ end.onreconnect do |error, sock|
127
+ error.should.kind_of EOFError
128
+ sock.should.respond_to :read
129
+ !m.empty? # not empty to reconnect
130
+
131
+ end.start.wait
132
+ m.should.empty
133
+ end
77
134
  end
@@ -18,25 +18,42 @@ describe RC::FollowRedirect do
18
18
  [301, 302, 303, 307].each do |status|
19
19
  should "not follow redirect if reached max_redirects: #{status}" do
20
20
  dry.status = status
21
- app.call(RC::REQUEST_METHOD => :get, 'max_redirects' => 0){ |res|
21
+ app.call(RC::REQUEST_METHOD => :get, 'max_redirects' => 0) do |res|
22
22
  res[RC::RESPONSE_HEADERS]['LOCATION'].should.eq 'location'
23
- }
23
+ end
24
24
  end
25
25
 
26
26
  should "follow once: #{status}" do
27
27
  dry.status = status
28
- app.call(RC::REQUEST_METHOD => :get){ |res|
28
+ app.call(RC::REQUEST_METHOD => :get) do |res|
29
29
  res[RC::RESPONSE_HEADERS]['LOCATION'].should.eq 'location'
30
- }
30
+ end
31
31
  end
32
+
33
+ should "not carry query string: #{status}" do
34
+ dry.status = status
35
+ app.call(RC::REQUEST_METHOD => :get,
36
+ RC::REQUEST_QUERY => {:a => 'a'}) do |res|
37
+ res[RC::REQUEST_PATH] .should.eq 'location'
38
+ res[RC::REQUEST_QUERY].should.empty
39
+ end
40
+ end
41
+
42
+ should "carry payload for #{status}" do
43
+ dry.status = status
44
+ app.call(RC::REQUEST_METHOD => :put,
45
+ RC::REQUEST_PAYLOAD => {:a => 'a'}) do |res|
46
+ res[RC::REQUEST_PAYLOAD].should.eq(:a => 'a')
47
+ end
48
+ end if status != 303
32
49
  end
33
50
 
34
51
  [200, 201, 404, 500].each do |status|
35
52
  should "not follow redirect if it's not a redirect status: #{status}" do
36
53
  dry.status = status
37
- app.call(RC::REQUEST_METHOD => :get, 'max_redirects' => 9){ |res|
54
+ app.call(RC::REQUEST_METHOD => :get, 'max_redirects' => 9) do |res|
38
55
  res[RC::RESPONSE_HEADERS]['LOCATION'].should.eq 'location'
39
- }
56
+ end
40
57
  end
41
58
  end
42
59
  end
@@ -0,0 +1,49 @@
1
+
2
+ require 'rest-core/test'
3
+
4
+ describe RC::QueryResponse do
5
+ describe 'app' do
6
+ app = RC::QueryResponse.new(RC::Dry.new, true)
7
+ expected = {RC::RESPONSE_BODY => {},
8
+ RC::REQUEST_HEADERS =>
9
+ {'Accept' => 'application/x-www-form-urlencoded'}}
10
+
11
+ should 'give {} for nil' do
12
+ app.call({}){ |response| response.should.eq(expected) }
13
+ end
14
+
15
+ should 'give {} for ""' do
16
+ app.call(RC::RESPONSE_BODY => ''){ |r| r.should.eq(expected) }
17
+ end
18
+
19
+ should 'give {"a" => "b"} for "a=b"' do
20
+ e = expected.merge(RC::RESPONSE_BODY => {'a' => 'b'})
21
+ app.call(RC::RESPONSE_BODY => 'a=b'){ |r| r.should.eq(e) }
22
+ end
23
+ end
24
+
25
+ describe 'client' do
26
+ client = RC::Builder.client do
27
+ use RC::QueryResponse, true
28
+ run Class.new{
29
+ def call env
30
+ yield(env.merge(RC::RESPONSE_BODY => 'a=b&c=d'))
31
+ end
32
+ }
33
+ end
34
+
35
+ should 'do nothing' do
36
+ expected = 'a=b&c=d'
37
+ client.new(:query_response => false).get(''){ |response|
38
+ response.should.eq(expected)
39
+ }.get('').should.eq(expected)
40
+ end
41
+
42
+ should 'parse' do
43
+ expected = {'a' => 'b', 'c' => 'd'}
44
+ client.new.get(''){ |response|
45
+ response.should.eq(expected)
46
+ }.get('').should.eq(expected)
47
+ end
48
+ end
49
+ end
@@ -39,7 +39,7 @@ describe RC::RestClient do
39
39
 
40
40
  should 'not kill the thread if error was coming from the task' do
41
41
  mock(RestClient::Request).execute{ raise 'boom' }.with_any_args
42
- c.request({}, RC::FAIL).first.message.should.eq 'boom'
42
+ c.request(RC::RESPONSE_KEY => RC::FAIL).first.message.should.eq 'boom'
43
43
  Muack.verify
44
44
  end
45
45
 
@@ -54,7 +54,8 @@ describe RC::RestClient do
54
54
  stub(c.class.thread_pool).queue{ [] } # don't queue the task
55
55
  mock(RC::ThreadPool::Task).new.with_any_args.
56
56
  peek_return{ |t| mock(t).cancel; t } # the task should be cancelled
57
- c.request({RC::TIMER => timer}, RC::FAIL).first.message.should.eq 'boom'
57
+ c.request(RC::RESPONSE_KEY => RC::FAIL, RC::TIMER => timer).
58
+ first.message.should.eq 'boom'
58
59
  Muack.verify
59
60
  end
60
61
  end
data/test/test_simple.rb CHANGED
@@ -2,13 +2,36 @@
2
2
  require 'rest-core/test'
3
3
 
4
4
  describe RC::Simple do
5
- before do
6
- @path = 'http://example.com'
7
- stub_request(:get, @path).to_return(:body => 'OK')
5
+ path = 'http://example.com/'
6
+
7
+ after do
8
+ WebMock.reset!
8
9
  end
9
10
 
10
- should 'work with RC' do
11
- RC::Simple.new.get(@path).should.eq 'OK'
11
+ should 'give RESPONSE_BODY' do
12
+ stub_request(:get, path).to_return(:body => 'OK')
13
+ RC::Simple.new.get(path).should.eq 'OK'
14
+ end
15
+
16
+ should 'give RESPONSE_HEADERS' do
17
+ stub_request(:head, path).to_return(:headers => {'A' => 'B'})
18
+ RC::Simple.new.head(path).should.eq 'A' => 'B'
19
+ end
20
+
21
+ should 'give RESPONSE_HEADERS' do
22
+ stub_request(:get, path).to_return(:status => 199)
23
+ RC::Simple.new.get(path, {},
24
+ RC::RESPONSE_KEY => RC::RESPONSE_STATUS).should.eq 199
25
+ end
26
+
27
+ should 'give RESPONSE_SOCKET' do
28
+ stub_request(:get, path).to_return(:body => 'OK')
29
+ RC::Simple.new.get(path, {}, RC::HIJACK => true).read.should.eq 'OK'
12
30
  end
13
- end
14
31
 
32
+ should 'give REQUEST_URI' do
33
+ stub_request(:get, "#{path}?a=b").to_return(:body => 'OK')
34
+ RC::Simple.new.get(path, {:a => 'b'},
35
+ RC::RESPONSE_KEY => RC::REQUEST_URI).should.eq "#{path}?a=b"
36
+ end
37
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lin Jen-Shin (godfat)
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-04 00:00:00.000000000 Z
11
+ date: 2014-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
@@ -114,6 +114,7 @@ files:
114
114
  - lib/rest-core/middleware/oauth1_header.rb
115
115
  - lib/rest-core/middleware/oauth2_header.rb
116
116
  - lib/rest-core/middleware/oauth2_query.rb
117
+ - lib/rest-core/middleware/query_response.rb
117
118
  - lib/rest-core/middleware/timeout.rb
118
119
  - lib/rest-core/patch/multi_json.rb
119
120
  - lib/rest-core/patch/rest-client.rb
@@ -121,6 +122,7 @@ files:
121
122
  - lib/rest-core/test.rb
122
123
  - lib/rest-core/thread_pool.rb
123
124
  - lib/rest-core/timer.rb
125
+ - lib/rest-core/util/config.rb
124
126
  - lib/rest-core/util/hmac.rb
125
127
  - lib/rest-core/util/json.rb
126
128
  - lib/rest-core/util/parse_query.rb
@@ -130,11 +132,13 @@ files:
130
132
  - rest-core.gemspec
131
133
  - task/README.md
132
134
  - task/gemgem.rb
135
+ - test/config/rest-core.yaml
133
136
  - test/test_auth_basic.rb
134
137
  - test/test_builder.rb
135
138
  - test/test_cache.rb
136
139
  - test/test_client.rb
137
140
  - test/test_client_oauth1.rb
141
+ - test/test_config.rb
138
142
  - test/test_default_payload.rb
139
143
  - test/test_default_query.rb
140
144
  - test/test_error_detector.rb
@@ -149,6 +153,7 @@ files:
149
153
  - test/test_oauth2_header.rb
150
154
  - test/test_payload.rb
151
155
  - test/test_promise.rb
156
+ - test/test_query_response.rb
152
157
  - test/test_rest-client.rb
153
158
  - test/test_simple.rb
154
159
  - test/test_thread_pool.rb
@@ -185,6 +190,7 @@ test_files:
185
190
  - test/test_cache.rb
186
191
  - test/test_client.rb
187
192
  - test/test_client_oauth1.rb
193
+ - test/test_config.rb
188
194
  - test/test_default_payload.rb
189
195
  - test/test_default_query.rb
190
196
  - test/test_error_detector.rb
@@ -199,6 +205,7 @@ test_files:
199
205
  - test/test_oauth2_header.rb
200
206
  - test/test_payload.rb
201
207
  - test/test_promise.rb
208
+ - test/test_query_response.rb
202
209
  - test/test_rest-client.rb
203
210
  - test/test_simple.rb
204
211
  - test/test_thread_pool.rb