rest-core 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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