rest-core 3.3.3 → 3.4.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: 07acac398a096c63464e9dca43b964dbe0d6359a
4
- data.tar.gz: 887c818f4a83591b316511c709fbbf10e2d45c35
3
+ metadata.gz: ab6a9f5e489329946784228471a7647aac66bbdd
4
+ data.tar.gz: 4b8d30e22074977305146a9a0cc875334c473599
5
5
  SHA512:
6
- metadata.gz: 24e9ebd41021150461013695d6e7cf4d2e6c628ea5156787c7dd957d4cd72e9df3452529518cfc9a48d1c837379aebee3cc6485bbe6422ba196fb3951f02da19
7
- data.tar.gz: 6a0da34cf3b1221b9b791fb63eee8419c2ce93b6766e395019453f5dc65a8859f24a73a48ef46728ef7e9262186ff0d032334d82ddb777cfcad4b08ec920e4ad
6
+ metadata.gz: 09378189003073e9e0fcde4b210c6ef3e5e7441388e006ba5f5e9bb916acd6b191ac7fd7e9649837dcd82e8e5d360a1efe41f3e6fa247fc9911e584b5dd074ce
7
+ data.tar.gz: cdf158fd0a2ca140d64792e7daa29575c9914ca5ede20b556ef04f64bc93792f053c3d2e49f59a6888823baa623fc467dbd41edac26544b9d97057e7b511b7e2
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  /pkg/
2
+ /coverage/
data/.travis.yml CHANGED
@@ -1,9 +1,11 @@
1
- before_install: 'git submodule update --init'
2
- script: 'ruby -r bundler/setup -S rake test'
3
1
 
2
+ language: ruby
4
3
  rvm:
5
4
  - 1.9
6
5
  - 2.0
7
6
  - 2.1
8
7
  - rbx-2
9
8
  - jruby
9
+
10
+ install: 'bundle install --retry=3'
11
+ script: 'ruby -r bundler/setup -S rake test'
data/CHANGES.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # CHANGES
2
2
 
3
+ ## rest-core 3.4.0 -- 2014-11-26
4
+
5
+ ### Incompatible changes
6
+
7
+ * Removed rest-client support.
8
+ * Removed net-http-persistent support.
9
+ * Removed patch for old multi-json.
10
+
11
+ ### Bugs fixed
12
+
13
+ * `RC::JsonRequest` can now POST, PUT, or PATCH with a single `false`.
14
+
15
+ * Previously, we're not handling timeout correctly. We left all that to
16
+ httpclient, and which would raise `HTTPClient::ConnectTimeoutError` and
17
+ `HTTPClient::ReceiveTimeoutError`. The problem is that connecting and
18
+ receiving are counted separately. That means, if we have 30 seconds timeout,
19
+ we might be ending up with 58 seconds requesting time, for 29 seconds
20
+ connecting time and 29 seconds receiving time. Now it should probably
21
+ interrupt the request in 30 seconds by handling this by ourselves with a
22
+ timer thread in the background. The timer thread would be shut down
23
+ automatically if there's no jobs left, and it would recreate the thread
24
+ when there's a job comes in, keeping working until there's no jobs left.
25
+
26
+ ### Enhancements
27
+
28
+ * Introduced `RC::Timer.interval` which is the interval to check if there's
29
+ a request timed out. The default interval is 1 second, which means it would
30
+ check if there's a request timed out for every 1 second. This also means
31
+ timeout with less than 1 second won't be accurate at all. You could
32
+ decrease the value if you need timeout less than 1 second, or increase it
33
+ if your timeout is far from 30 second by calling `RC::Timer.interval = 5`.
34
+
3
35
  ## rest-core 3.3.3 -- 2014-11-07
4
36
 
5
37
  ### Bugs fixed
data/Gemfile CHANGED
@@ -16,6 +16,9 @@ gem 'multi_json'
16
16
 
17
17
  gem 'rack'
18
18
 
19
+ gem 'simplecov', :require => false if ENV['COV']
20
+ gem 'coveralls', :require => false if ENV['CI']
21
+
19
22
  platforms :ruby do
20
23
  gem 'yajl-ruby'
21
24
  end
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # rest-core [![Build Status](https://secure.travis-ci.org/godfat/rest-core.png?branch=master)](http://travis-ci.org/godfat/rest-core)
1
+ # rest-core [![Build Status](https://secure.travis-ci.org/godfat/rest-core.png?branch=master)](http://travis-ci.org/godfat/rest-core) [![Coverage Status](https://coveralls.io/repos/godfat/rest-core/badge.png)](https://coveralls.io/r/godfat/rest-core)
2
2
 
3
3
  by Lin Jen-Shin ([godfat](http://godfat.org))
4
4
 
data/example/use-cases.rb CHANGED
@@ -23,13 +23,8 @@ def def_use_case name, &block
23
23
  end
24
24
  end
25
25
 
26
- def q str, m=nil
27
- p = lambda{ puts "\e[33m=> #{str.inspect}\e[0m" }
28
- if m
29
- m.synchronize(&p)
30
- else
31
- p.call
32
- end
26
+ def q str
27
+ Thread.exclusive{ puts "\e[33m=> #{str.inspect}\e[0m" }
33
28
  end
34
29
 
35
30
  # ----------------------------------------------------------------------
@@ -51,36 +46,36 @@ def_use_case 'pure_ruby_cache_requests' do
51
46
  end
52
47
 
53
48
  def_use_case 'pure_ruby_callback_requests' do
54
- m = Mutex.new
55
49
  RC::Universal.new(:json_response => true ,
56
50
  :site => 'https://graph.facebook.com/' ,
57
- :log_method => lambda{|str| m.synchronize{puts(str)}}).
51
+ :log_method => lambda{|str|
52
+ Thread.exclusive{puts(str)}}).
58
53
  get('4'){ |res|
59
54
  if res.kind_of?(Exception)
60
- p "Encountering: #{res}"
55
+ q "Encountering: #{res}"
61
56
  next
62
57
  end
63
- q res['name'], m
58
+ q res['name']
64
59
  }.
65
60
  get('5'){ |res|
66
61
  if res.kind_of?(Exception)
67
- p "Encountering: #{res}"
62
+ q "Encountering: #{res}"
68
63
  next
69
64
  end
70
- q res['name'], m
65
+ q res['name']
71
66
  }.wait
72
67
  end
73
68
 
74
69
  def_use_case 'pure_ruby_nested_concurrent_requests' do
75
- m = Mutex.new
76
70
  c = RC::Universal.new(:json_response => true ,
77
71
  :site => 'https://graph.facebook.com/' ,
78
- :log_method => lambda{|str| m.synchronize{puts(str)}})
72
+ :log_method => lambda{ |str|
73
+ Thread.exclusive{puts(str)}})
79
74
 
80
75
  %w[4 5].each{ |user|
81
76
  c.get(user, :fields => 'cover'){ |data|
82
77
  if data.kind_of?(Exception)
83
- q "Encountering: #{data}", m
78
+ q "Encountering: #{data}"
84
79
  next
85
80
  end
86
81
 
@@ -89,11 +84,11 @@ def_use_case 'pure_ruby_nested_concurrent_requests' do
89
84
  likes = c.get("#{cover['id']}/likes")
90
85
  most_liked_comment = comments['data'].max_by{|d|d['like_count']}
91
86
 
92
- q "Author of most liked comment from #{user}'s cover photo:", m
93
- q most_liked_comment['from']['name'], m
87
+ q "Author of most liked comment from #{user}'s cover photo:"
88
+ q most_liked_comment['from']['name']
94
89
 
95
90
  y = !!likes['data'].find{|d|d['id'] == most_liked_comment['from']['id']}
96
- q "Did the user also like the cover?: #{y}", m
91
+ q "Did the user also like the cover?: #{y}"
97
92
  }
98
93
  }
99
94
 
data/lib/rest-core.rb CHANGED
@@ -73,8 +73,6 @@ module RestCore
73
73
  # engines
74
74
  autoload :Dry , 'rest-core/engine/dry'
75
75
  autoload :HttpClient , 'rest-core/engine/http-client'
76
- autoload :RestClient , 'rest-core/engine/rest-client'
77
- autoload :NetHttpPersistent, 'rest-core/engine/net-http-persistent'
78
76
 
79
77
  # clients
80
78
  autoload :Simple , 'rest-core/client/simple'
@@ -17,11 +17,6 @@ class RestCore::Engine
17
17
  Payload.generate_with_headers(env[REQUEST_PAYLOAD], env[REQUEST_HEADERS])
18
18
  end
19
19
 
20
- def calculate_timeout timer
21
- return [] unless timer
22
- [timer.timeout, timer.timeout]
23
- end
24
-
25
20
  def normalize_headers headers
26
21
  headers.inject({}){ |r, (k, v)|
27
22
  r[k.to_s.upcase.tr('-', '_')] = if v.kind_of?(Array) && v.size == 1
@@ -19,9 +19,6 @@ class RestCore::HttpClient < RestCore::Engine
19
19
  end
20
20
 
21
21
  def request_sync client, payload, headers, promise, env
22
- client.connect_timeout, client.receive_timeout =
23
- calculate_timeout(env[TIMER])
24
-
25
22
  res = client.request(env[REQUEST_METHOD], env[REQUEST_URI], nil,
26
23
  payload, {'User-Agent' => 'Ruby'}.merge(headers))
27
24
 
@@ -7,11 +7,12 @@ class RestCore::JsonRequest
7
7
  include RestCore::Middleware
8
8
 
9
9
  JSON_REQUEST_HEADER = {'Content-Type' => 'application/json'}.freeze
10
+ JSON_REQUEST_METHOD = [:post, :put, :patch]
10
11
 
11
12
  def call env, &k
12
13
  return app.call(env, &k) unless json_request(env)
13
- return app.call(env, &k) unless env[REQUEST_PAYLOAD] &&
14
- !env[REQUEST_PAYLOAD].empty?
14
+ return app.call(env, &k) unless
15
+ JSON_REQUEST_METHOD.include?(env[REQUEST_METHOD])
15
16
 
16
17
  app.call(env.merge(
17
18
  REQUEST_HEADERS => JSON_REQUEST_HEADER.merge(env[REQUEST_HEADERS]||{}),
@@ -62,19 +62,28 @@ class RestCore::Promise
62
62
  end
63
63
 
64
64
  # called in client thread
65
- def defer &job
65
+ def defer
66
66
  if pool_size < 0 # negative number for blocking call
67
- job.call
68
- elsif pool_size > 0
69
- backtrace = caller + self.class.backtrace
70
- self.task = client_class.thread_pool.defer do
71
- synchronized_yield(backtrace){ job.call }
72
- end
67
+ self.thread = Thread.current
68
+ # set timeout after thread set, before yield (because yield is blocking)
69
+ env[TIMER].on_timeout{ cancel_task } if env[TIMER]
70
+ protected_yield{ yield }
73
71
  else
74
72
  backtrace = caller + self.class.backtrace
75
- Thread.new{ synchronized_yield(backtrace){ job.call } }
73
+ if pool_size > 0
74
+ self.task = client_class.thread_pool.defer(mutex) do
75
+ Thread.current[:backtrace] = backtrace
76
+ protected_yield{ yield }
77
+ end
78
+ else
79
+ self.thread = Thread.new do
80
+ Thread.current[:backtrace] = backtrace
81
+ protected_yield{ yield }
82
+ end
83
+ end
84
+ # set timeout after thread/task set
85
+ env[TIMER].on_timeout{ cancel_task } if env[TIMER]
76
86
  end
77
- env[TIMER].on_timeout{ reject(env[TIMER].error) } if env[TIMER]
78
87
  end
79
88
 
80
89
  # called in client thread (client.wait)
@@ -92,24 +101,13 @@ class RestCore::Promise
92
101
  # called in requesting thread after the request is done
93
102
  def fulfill body, status, headers, socket=nil
94
103
  env[TIMER].cancel if env[TIMER]
95
- self.body, self.status, self.headers, self.socket =
96
- body, status, headers, socket
97
- # under ASYNC callback, should call immediately
98
- callback if immediate
99
- ensure
100
- condv.broadcast # client or response might be waiting
104
+ mutex.synchronize{ fulfilling(body, status, headers, socket) }
101
105
  end
102
106
 
103
107
  # called in requesting thread if something goes wrong or timed out
104
108
  def reject error
105
- task.cancel if task
106
-
107
- self.error = if error.kind_of?(Exception)
108
- error
109
- else
110
- Error.new(error || 'unknown')
111
- end
112
- fulfill('', 0, {})
109
+ env[TIMER].cancel if env[TIMER]
110
+ mutex.synchronize{ rejecting(error) }
113
111
  end
114
112
 
115
113
  # append your actions, which would be called when we're calling back
@@ -130,21 +128,42 @@ class RestCore::Promise
130
128
  attr_accessor :env, :k, :immediate,
131
129
  :body, :status, :headers, :socket,
132
130
  :response, :error, :called,
133
- :condv, :mutex, :task
131
+ :condv, :mutex, :task, :thread
134
132
 
135
133
  private
134
+ def fulfilling body, status, headers, socket=nil
135
+ self.body, self.status, self.headers, self.socket =
136
+ body, status, headers, socket
137
+ # under ASYNC callback, should call immediately
138
+ callback if immediate
139
+ ensure
140
+ condv.broadcast # client or response might be waiting
141
+ end
142
+
143
+ def rejecting error
144
+ self.error = if error.kind_of?(Exception)
145
+ error
146
+ else
147
+ Error.new(error || 'unknown')
148
+ end
149
+ fulfilling('', 0, {})
150
+ end
151
+
136
152
  # called in a new thread if pool_size == 0, otherwise from the pool
137
153
  # i.e. requesting thread
138
- def synchronized_yield backtrace
139
- Thread.current[:backtrace] = backtrace
140
- mutex.synchronize{ yield }
154
+ def protected_yield
155
+ yield
141
156
  rescue Exception => e
142
- self.class.set_backtrace(e)
143
- # nothing we can do here for an asynchronous exception,
144
- # so we just log the error
145
- # TODO: add error_log_method
146
- warn "RestCore: ERROR: #{e}\n from #{e.backtrace.inspect}"
147
- reject(e) unless done? # not done: i/o error; done: callback error
157
+ # pray if timeout won't trigger here!
158
+ env[TIMER].cancel if env[TIMER]
159
+ mutex.synchronize do
160
+ self.class.set_backtrace(e)
161
+ # nothing we can do here for an asynchronous exception,
162
+ # so we just log the error
163
+ # TODO: add error_log_method
164
+ warn "RestCore: ERROR: #{e}\n from #{e.backtrace.inspect}"
165
+ rejecting(e) unless done? # not done: i/o error; done: callback error
166
+ end
148
167
  end
149
168
 
150
169
  # called in client thread, when yield is called
@@ -161,6 +180,18 @@ class RestCore::Promise
161
180
  self.called = true
162
181
  end
163
182
 
183
+ def cancel_task
184
+ mutex.synchronize do
185
+ next if done?
186
+ if t = thread || task.thread
187
+ t.raise(env[TIMER].error)
188
+ else
189
+ task.cancel
190
+ rejecting(env[TIMER].error)
191
+ end
192
+ end
193
+ end
194
+
164
195
  def client_class; env[CLIENT].class; end
165
196
  def pool_size
166
197
  @pool_size ||= if client_class.respond_to?(:pool_size)
@@ -26,7 +26,7 @@ class RestCore::ThreadPool
26
26
  mutex.synchronize do
27
27
  if queue.empty?
28
28
  condv.wait(mutex, timeout)
29
- queue.shift || lambda{ false } # shutdown idle workers
29
+ queue.shift || lambda{ |_| false } # shutdown idle workers
30
30
  else
31
31
  queue.shift
32
32
  end
@@ -41,19 +41,20 @@ class RestCore::ThreadPool
41
41
  attr_reader :queue, :mutex, :condv
42
42
  end
43
43
 
44
- class Task < Struct.new(:job)
44
+ class Task < Struct.new(:job, :mutex, :thread, :cancelled)
45
45
  # this should never fail
46
- def call
47
- job.call unless cancelled
46
+ def call working_thread
47
+ mutex.synchronize do
48
+ return if cancelled
49
+ self.thread = working_thread
50
+ end
51
+ job.call
48
52
  true
49
53
  end
50
54
 
51
- # called from the other thread telling us it's timed out
52
55
  def cancel
53
- @cancelled = true
56
+ self.cancelled = true
54
57
  end
55
- protected
56
- attr_reader :thread, :mutex, :cancelled
57
58
  end
58
59
 
59
60
  def self.[] client_class
@@ -86,9 +87,9 @@ class RestCore::ThreadPool
86
87
  client_class.pool_idle_time
87
88
  end
88
89
 
89
- def defer &job
90
+ def defer mutex=nil, &job
90
91
  mutex.synchronize do
91
- task = Task.new(job)
92
+ task = Task.new(job, mutex)
92
93
  queue << task
93
94
  spawn_worker if waiting == 0 && workers.size < max_size
94
95
  task
@@ -96,7 +97,7 @@ class RestCore::ThreadPool
96
97
  end
97
98
 
98
99
  def trim force=false
99
- queue << lambda{ false } if force || waiting > 0
100
+ queue << lambda{ |_| false } if force || waiting > 0
100
101
  end
101
102
 
102
103
  # Block on shutting down, and should not add more jobs while shutting down
@@ -119,7 +120,7 @@ class RestCore::ThreadPool
119
120
  mutex.synchronize{ @waiting += 1 }
120
121
  task = queue.pop(idle_time)
121
122
  mutex.synchronize{ @waiting -= 1 }
122
- end while task.call
123
+ end while task.call(Thread.current)
123
124
 
124
125
  mutex.synchronize{ workers.delete(Thread.current) }
125
126
  }
@@ -1,12 +1,33 @@
1
1
 
2
+ require 'thread'
2
3
  require 'timers'
3
4
 
4
5
  class RestCore::Timer
5
- TimerGen = if Timers.respond_to?(:new)
6
- Timers.new
7
- else
8
- Timers::Group.new
9
- end
6
+ @mutex = Mutex.new
7
+ @interval = 1
8
+
9
+ singleton_class.module_eval do
10
+ attr_accessor :interval
11
+
12
+ def group
13
+ @group ||= @mutex.synchronize{ @group || group_new }
14
+ end
15
+
16
+ private
17
+ def group_new
18
+ g = Timers::Group.new
19
+ g.every(interval){}
20
+ @thread = Thread.new do
21
+ begin
22
+ g.wait
23
+ rescue => e
24
+ warn "RestCore::Timer: ERROR: #{e}\n from #{e.backtrace.inspect}"
25
+ end while g.count > 1
26
+ @group = nil
27
+ end
28
+ g
29
+ end
30
+ end
10
31
 
11
32
  attr_accessor :timeout, :error
12
33
  def initialize timeout, error, &block
@@ -22,11 +43,12 @@ class RestCore::Timer
22
43
 
23
44
  def cancel
24
45
  timer.cancel
46
+ self.block = nil
25
47
  end
26
48
 
27
49
  def start
28
50
  return if timeout.nil? || timeout.zero?
29
- self.timer = TimerGen.after(timeout){ block.call }
51
+ self.timer = self.class.group.after(timeout){ block.call if block }
30
52
  end
31
53
 
32
54
  protected
@@ -3,7 +3,6 @@ module RestCore; end
3
3
  module RestCore::Json
4
4
  module MultiJson
5
5
  def self.extended mod
6
- require 'rest-core/patch/multi_json'
7
6
  mod.const_set(:ParseError, ::MultiJson::DecodeError)
8
7
  end
9
8
  def encode hash
@@ -1,4 +1,4 @@
1
1
 
2
2
  module RestCore
3
- VERSION = '3.3.3'
3
+ VERSION = '3.4.0'
4
4
  end
data/rest-core.gemspec CHANGED
@@ -1,14 +1,14 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: rest-core 3.3.3 ruby lib
2
+ # stub: rest-core 3.4.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "rest-core"
6
- s.version = "3.3.3"
6
+ s.version = "3.4.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-11-07"
11
+ s.date = "2014-11-26"
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 = [
@@ -33,8 +33,6 @@ Gem::Specification.new do |s|
33
33
  "lib/rest-core/engine.rb",
34
34
  "lib/rest-core/engine/dry.rb",
35
35
  "lib/rest-core/engine/http-client.rb",
36
- "lib/rest-core/engine/net-http-persistent.rb",
37
- "lib/rest-core/engine/rest-client.rb",
38
36
  "lib/rest-core/error.rb",
39
37
  "lib/rest-core/event.rb",
40
38
  "lib/rest-core/event_source.rb",
@@ -61,8 +59,6 @@ Gem::Specification.new do |s|
61
59
  "lib/rest-core/middleware/query_response.rb",
62
60
  "lib/rest-core/middleware/smash_response.rb",
63
61
  "lib/rest-core/middleware/timeout.rb",
64
- "lib/rest-core/patch/multi_json.rb",
65
- "lib/rest-core/patch/rest-client.rb",
66
62
  "lib/rest-core/promise.rb",
67
63
  "lib/rest-core/test.rb",
68
64
  "lib/rest-core/thread_pool.rb",
@@ -98,6 +94,7 @@ Gem::Specification.new do |s|
98
94
  "test/test_event_source.rb",
99
95
  "test/test_follow_redirect.rb",
100
96
  "test/test_future.rb",
97
+ "test/test_httpclient.rb",
101
98
  "test/test_json_request.rb",
102
99
  "test/test_json_response.rb",
103
100
  "test/test_oauth1_header.rb",
@@ -106,7 +103,6 @@ Gem::Specification.new do |s|
106
103
  "test/test_payload.rb",
107
104
  "test/test_promise.rb",
108
105
  "test/test_query_response.rb",
109
- "test/test_rest-client.rb",
110
106
  "test/test_simple.rb",
111
107
  "test/test_smash.rb",
112
108
  "test/test_smash_response.rb",
@@ -115,7 +111,7 @@ Gem::Specification.new do |s|
115
111
  "test/test_universal.rb"]
116
112
  s.homepage = "https://github.com/godfat/rest-core"
117
113
  s.licenses = ["Apache License 2.0"]
118
- s.rubygems_version = "2.4.2"
114
+ s.rubygems_version = "2.4.4"
119
115
  s.summary = "Modular Ruby clients interface for REST APIs."
120
116
  s.test_files = [
121
117
  "test/test_auth_basic.rb",
@@ -136,6 +132,7 @@ Gem::Specification.new do |s|
136
132
  "test/test_event_source.rb",
137
133
  "test/test_follow_redirect.rb",
138
134
  "test/test_future.rb",
135
+ "test/test_httpclient.rb",
139
136
  "test/test_json_request.rb",
140
137
  "test/test_json_response.rb",
141
138
  "test/test_oauth1_header.rb",
@@ -144,7 +141,6 @@ Gem::Specification.new do |s|
144
141
  "test/test_payload.rb",
145
142
  "test/test_promise.rb",
146
143
  "test/test_query_response.rb",
147
- "test/test_rest-client.rb",
148
144
  "test/test_simple.rb",
149
145
  "test/test_smash.rb",
150
146
  "test/test_smash_response.rb",
data/task/gemgem.rb CHANGED
@@ -38,6 +38,99 @@ module Gemgem
38
38
  self.spec = spec
39
39
  end
40
40
 
41
+ def gem_install
42
+ require 'rubygems/commands/install_command'
43
+ # read ~/.gemrc
44
+ Gem.use_paths(Gem.configuration[:gemhome], Gem.configuration[:gempath])
45
+ Gem::Command.extra_args = Gem.configuration[:gem]
46
+
47
+ # setup install options
48
+ cmd = Gem::Commands::InstallCommand.new
49
+ cmd.handle_options([])
50
+
51
+ # install
52
+ install = Gem::Installer.new(gem_path, cmd.options)
53
+ install.install
54
+ puts "\e[35mGem installed: \e[33m#{strip_path(install.gem_dir)}\e[0m"
55
+ end
56
+
57
+ def gem_spec
58
+ create
59
+ write
60
+ end
61
+
62
+ def gem_build
63
+ require 'fileutils'
64
+ require 'rubygems/package'
65
+ gem = nil
66
+ Dir.chdir(dir) do
67
+ gem = Gem::Package.build(Gem::Specification.load(spec_path))
68
+ FileUtils.mkdir_p(pkg_dir)
69
+ FileUtils.mv(gem, pkg_dir) # gem is relative path, but might be ok
70
+ end
71
+ puts "\e[35mGem built: \e[33m#{strip_path("#{pkg_dir}/#{gem}")}\e[0m"
72
+ end
73
+
74
+ def gem_release
75
+ sh_git('tag', gem_tag)
76
+ sh_git('push')
77
+ sh_git('push', '--tags')
78
+ sh_gem('push', gem_path)
79
+ end
80
+
81
+ def gem_check
82
+ ver = spec.version.to_s
83
+
84
+ if ENV['VERSION'].nil?
85
+ puts("\e[35mExpected " \
86
+ "\e[33mVERSION\e[35m=\e[33m#{ver}\e[0m")
87
+ exit(1)
88
+
89
+ elsif ENV['VERSION'] != ver
90
+ puts("\e[35mExpected \e[33mVERSION\e[35m=\e[33m#{ver} " \
91
+ "\e[35mbut got\n " \
92
+ "\e[33mVERSION\e[35m=\e[33m#{ENV['VERSION']}\e[0m")
93
+ exit(2)
94
+ end
95
+ end
96
+
97
+ def test
98
+ return if test_files.empty?
99
+
100
+ if ENV['COV'] || ENV['CI']
101
+ require 'simplecov'
102
+ if ENV['CI']
103
+ require 'coveralls'
104
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
105
+ end
106
+ SimpleCov.start do
107
+ add_filter('test/')
108
+ add_filter('test.rb')
109
+ end
110
+ end
111
+
112
+ test_files.each{ |file| require "#{dir}/#{file[0..-4]}" }
113
+ end
114
+
115
+ def clean
116
+ return if ignored_files.empty?
117
+
118
+ require 'fileutils'
119
+ trash = File.expand_path("~/.Trash/#{spec.name}")
120
+ puts "Move the following files into: \e[35m#{strip_path(trash)}\e[33m"
121
+
122
+ ignored_files.each do |file|
123
+ from = "#{dir}/#{file}"
124
+ to = "#{trash}/#{File.dirname(file)}"
125
+ puts strip_path(from)
126
+
127
+ FileUtils.mkdir_p(to)
128
+ FileUtils.mv(from, to)
129
+ end
130
+
131
+ print "\e[0m"
132
+ end
133
+
41
134
  def write
42
135
  File.open(spec_path, 'w'){ |f| f << split_lines(spec.to_ruby) }
43
136
  end
@@ -173,82 +266,38 @@ namespace :gem do
173
266
 
174
267
  desc 'Install gem'
175
268
  task :install => [:build] do
176
- Gemgem.sh_gem('install', Gemgem.gem_path)
269
+ Gemgem.gem_install
177
270
  end
178
271
 
179
272
  desc 'Build gem'
180
273
  task :build => [:spec] do
181
- require 'fileutils'
182
- require 'rubygems/package'
183
- gem = nil
184
- Dir.chdir(Gemgem.dir) do
185
- gem = Gem::Package.build(Gem::Specification.load(Gemgem.spec_path))
186
- FileUtils.mkdir_p(Gemgem.pkg_dir)
187
- FileUtils.mv(gem, Gemgem.pkg_dir) # gem is relative path, but might be ok
188
- end
189
- puts "\e[35mGem built: \e[33m" \
190
- "#{Gemgem.strip_path("#{Gemgem.pkg_dir}/#{gem}")}\e[0m"
274
+ Gemgem.gem_build
191
275
  end
192
276
 
193
277
  desc 'Generate gemspec'
194
278
  task :spec do
195
- Gemgem.create
196
- Gemgem.write
279
+ Gemgem.gem_spec
197
280
  end
198
281
 
199
282
  desc 'Release gem'
200
283
  task :release => [:spec, :check, :build] do
201
- Gemgem.module_eval do
202
- sh_git('tag', Gemgem.gem_tag)
203
- sh_git('push')
204
- sh_git('push', '--tags')
205
- sh_gem('push', Gemgem.gem_path)
206
- end
284
+ Gemgem.gem_release
207
285
  end
208
286
 
209
287
  task :check do
210
- ver = Gemgem.spec.version.to_s
211
-
212
- if ENV['VERSION'].nil?
213
- puts("\e[35mExpected " \
214
- "\e[33mVERSION\e[35m=\e[33m#{ver}\e[0m")
215
- exit(1)
216
-
217
- elsif ENV['VERSION'] != ver
218
- puts("\e[35mExpected \e[33mVERSION\e[35m=\e[33m#{ver} " \
219
- "\e[35mbut got\n " \
220
- "\e[33mVERSION\e[35m=\e[33m#{ENV['VERSION']}\e[0m")
221
- exit(2)
222
- end
288
+ Gemgem.gem_check
223
289
  end
224
290
 
225
291
  end # of gem namespace
226
292
 
227
293
  desc 'Run tests'
228
294
  task :test do
229
- next if Gemgem.test_files.empty?
230
- Gemgem.test_files.each{ |file| require "#{Gemgem.dir}/#{file[0..-4]}" }
295
+ Gemgem.test
231
296
  end
232
297
 
233
298
  desc 'Trash ignored files'
234
299
  task :clean => ['gem:spec'] do
235
- next if Gemgem.ignored_files.empty?
236
-
237
- require 'fileutils'
238
- trash = File.expand_path("~/.Trash/#{Gemgem.spec.name}")
239
- puts "Move the following files into:" \
240
- " \e[35m#{Gemgem.strip_path(trash)}\e[33m"
241
-
242
- Gemgem.ignored_files.each do |file|
243
- from = "#{Gemgem.dir}/#{file}"
244
- to = "#{trash}/#{File.dirname(file)}"
245
- puts Gemgem.strip_path(from)
246
-
247
- FileUtils.mkdir_p(to)
248
- FileUtils.mv(from, to)
249
- end
250
-
251
- print "\e[0m"
300
+ Gemgem.clean
252
301
  end
253
302
 
254
303
  task :default do
data/test/test_builder.rb CHANGED
@@ -10,13 +10,13 @@ describe RC::Builder do
10
10
  RC::Builder.new.to_app.should.kind_of? RC::Engine
11
11
  end
12
12
 
13
- would 'switch default_engine to RestCore::RestClient' do
13
+ would 'switch default_engine to RestCore::Dry a' do
14
14
  builder = Class.new(RC::Builder)
15
- builder.default_engine = RC::RestClient
16
- builder.new.to_app.class.should.eq RC::RestClient
15
+ builder.default_engine = RC::Dry
16
+ builder.new.to_app.class.should.eq RC::Dry
17
17
  end
18
18
 
19
- would 'switch default_engine to RestCore::Dry' do
19
+ would 'switch default_engine to RestCore::Dry b' do
20
20
  builder = RC::Builder.dup
21
21
  builder.default_engine = RC::Dry
22
22
  builder.client.new.app.class.should.eq RC::Dry
data/test/test_client.rb CHANGED
@@ -137,7 +137,7 @@ describe RC::Simple do
137
137
  client = Class.new(RC::Simple).new
138
138
  stub_request(:get, url).to_return(:body => 'nnf')
139
139
 
140
- (0..1).each do |size|
140
+ (-1..1).each do |size|
141
141
  mock(any_instance_of(RC::Promise)).warn(is_a(String)) do |msg|
142
142
  msg.should.include?('nnf')
143
143
  end
@@ -147,14 +147,6 @@ describe RC::Simple do
147
147
  end
148
148
  client.class.shutdown
149
149
  end
150
-
151
- client.class.pool_size = -1
152
- should.raise do
153
- client.get(url) do |body|
154
- raise body
155
- end
156
- end.message.should.eq 'nnf'
157
- client.class.shutdown
158
150
  end
159
151
 
160
152
  would 'be able to access caller outside the callback' do
@@ -1,14 +1,14 @@
1
1
 
2
2
  require 'rest-core/test'
3
3
 
4
- describe RC::RestClient do
4
+ describe RC::HttpClient do
5
5
  describe 'POST Payload' do
6
6
  after do
7
7
  WebMock.reset!
8
8
  end
9
9
 
10
10
  client = RC::Builder.client
11
- client.builder.run(RC::RestClient)
11
+ client.builder.run(RC::HttpClient)
12
12
  path = 'http://example.com'
13
13
  ok = 'OK'
14
14
  c = client.new
@@ -39,24 +39,11 @@ describe RC::RestClient do
39
39
  end
40
40
 
41
41
  would 'not kill the thread if error was coming from the task' do
42
- mock(RestClient::Request).execute{ raise 'boom' }.with_any_args
43
- c.request(RC::RESPONSE_KEY => RC::FAIL).first.message.should.eq 'boom'
44
- Muack.verify
45
- end
46
-
47
- would 'cancel the task if timing out' do
48
- timer = Object.new.instance_eval do
49
- def on_timeout; yield ; end
50
- def error ; 'boom'; end
51
- def cancel ; ; end
52
- self
42
+ mock(any_instance_of(RC::Promise)).warn(is_a(String)) do |msg|
43
+ msg.should.include?('boom')
53
44
  end
54
- stub(c.class).pool_size{ 1 }
55
- stub(c.class.thread_pool).queue{ [] } # don't queue the task
56
- mock(RC::ThreadPool::Task).new.with_any_args.
57
- peek_return{ |t| mock(t).cancel; t } # the task should be cancelled
58
- c.request(RC::RESPONSE_KEY => RC::FAIL, RC::TIMER => timer).
59
- first.message.should.eq 'boom'
45
+ mock(HTTPClient).new{ raise 'boom' }.with_any_args
46
+ c.request(RC::RESPONSE_KEY => RC::FAIL).first.message.should.eq 'boom'
60
47
  Muack.verify
61
48
  end
62
49
  end
@@ -3,7 +3,7 @@ require 'rest-core/test'
3
3
 
4
4
  describe RC::JsonRequest do
5
5
  app = RC::JsonRequest.new(RC::Dry.new, true)
6
- env = {RC::REQUEST_HEADERS => {}}
6
+ env = {RC::REQUEST_HEADERS => {}, RC::REQUEST_METHOD => :post}
7
7
  request_params = {
8
8
  'key' => 'value',
9
9
  'array' => [1, 2, 3],
@@ -21,9 +21,21 @@ describe RC::JsonRequest do
21
21
  RC::REQUEST_PAYLOAD => RC::Json.encode(request_params))}
22
22
  end
23
23
 
24
- would 'do nothing if payload is empty' do
25
- e = env.merge(RC::REQUEST_PAYLOAD => {})
26
- app.call(e){ |res| res.should.eq e }
24
+ would 'encode false and nil' do
25
+ [[nil, 'null'], [false, 'false'], [true, 'true']].each do |(value, exp)|
26
+ [:post, :put, :patch].each do |meth|
27
+ e = env.merge(RC::REQUEST_METHOD => meth,
28
+ RC::REQUEST_PAYLOAD => value)
29
+ app.call(e){ |res| res[RC::REQUEST_PAYLOAD].should.eq(exp) }
30
+ end
31
+ end
32
+ end
33
+
34
+ would 'do nothing for get, delete, head, options' do
35
+ [:get, :delete, :head, :options].each do |meth|
36
+ e = env.merge(RC::REQUEST_PAYLOAD => {}, RC::REQUEST_METHOD => meth)
37
+ app.call(e){ |res| res.should.eq e }
38
+ end
27
39
  end
28
40
 
29
41
  would 'do nothing if json_request is false' do
data/test/test_timeout.rb CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
2
  require 'rest-core/test'
3
+ require 'rest-core/engine'
3
4
 
4
5
  describe RC::Timeout do
5
6
  app = RC::Timeout.new(RC::Dry.new, 0)
@@ -26,4 +27,57 @@ describe RC::Timeout do
26
27
  lambda{ app.call(env){} }.should .raise(RuntimeError)
27
28
  lambda{ sleep 0.01 }.should.not.raise(Timeout::Error)
28
29
  end
30
+
31
+ would 'cancel the task if timing out' do
32
+ timer = Object.new.instance_eval do
33
+ def on_timeout; yield ; end
34
+ def error ; 'boom'; end
35
+ def cancel ; ; end
36
+ self
37
+ end
38
+ app = RC::Builder.client do
39
+ run Class.new(RC::Engine){
40
+ def request _, _
41
+ sleep
42
+ end
43
+ }
44
+ end
45
+ app.pool_size = 1
46
+ app.new.request(RC::RESPONSE_KEY => RC::FAIL, RC::TIMER => timer).
47
+ first.message.should.eq 'boom'
48
+ Muack.verify
49
+ end
50
+
51
+ would 'interrupt the task if timing out' do
52
+ rd, wr = IO.pipe
53
+ timer = Object.new.instance_eval do
54
+ define_singleton_method :on_timeout do |&block|
55
+ Thread.new do
56
+ rd.gets
57
+ block.call
58
+ end
59
+ end
60
+ def error ; 'boom'; end
61
+ def cancel ; ; end
62
+ self
63
+ end
64
+ app = RC::Builder.client do
65
+ run Class.new(RC::Engine){
66
+ def request _, env
67
+ env['pipe'].puts
68
+ sleep
69
+ end
70
+ }
71
+ end
72
+ (-1..1).each do |size|
73
+ mock(any_instance_of(RC::Promise)).warn(is_a(String)) do |msg|
74
+ msg.should.include?('boom')
75
+ end
76
+ app.pool_size = size
77
+ app.new.request(RC::RESPONSE_KEY => RC::FAIL, RC::TIMER => timer,
78
+ 'pipe' => wr).
79
+ first.message.should.eq 'boom'
80
+ Muack.verify
81
+ end
82
+ end
29
83
  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.3.3
4
+ version: 3.4.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-11-07 00:00:00.000000000 Z
11
+ date: 2014-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
@@ -90,8 +90,6 @@ files:
90
90
  - lib/rest-core/engine.rb
91
91
  - lib/rest-core/engine/dry.rb
92
92
  - lib/rest-core/engine/http-client.rb
93
- - lib/rest-core/engine/net-http-persistent.rb
94
- - lib/rest-core/engine/rest-client.rb
95
93
  - lib/rest-core/error.rb
96
94
  - lib/rest-core/event.rb
97
95
  - lib/rest-core/event_source.rb
@@ -118,8 +116,6 @@ files:
118
116
  - lib/rest-core/middleware/query_response.rb
119
117
  - lib/rest-core/middleware/smash_response.rb
120
118
  - lib/rest-core/middleware/timeout.rb
121
- - lib/rest-core/patch/multi_json.rb
122
- - lib/rest-core/patch/rest-client.rb
123
119
  - lib/rest-core/promise.rb
124
120
  - lib/rest-core/test.rb
125
121
  - lib/rest-core/thread_pool.rb
@@ -155,6 +151,7 @@ files:
155
151
  - test/test_event_source.rb
156
152
  - test/test_follow_redirect.rb
157
153
  - test/test_future.rb
154
+ - test/test_httpclient.rb
158
155
  - test/test_json_request.rb
159
156
  - test/test_json_response.rb
160
157
  - test/test_oauth1_header.rb
@@ -163,7 +160,6 @@ files:
163
160
  - test/test_payload.rb
164
161
  - test/test_promise.rb
165
162
  - test/test_query_response.rb
166
- - test/test_rest-client.rb
167
163
  - test/test_simple.rb
168
164
  - test/test_smash.rb
169
165
  - test/test_smash_response.rb
@@ -190,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
190
186
  version: '0'
191
187
  requirements: []
192
188
  rubyforge_project:
193
- rubygems_version: 2.4.2
189
+ rubygems_version: 2.4.4
194
190
  signing_key:
195
191
  specification_version: 4
196
192
  summary: Modular Ruby clients interface for REST APIs.
@@ -213,6 +209,7 @@ test_files:
213
209
  - test/test_event_source.rb
214
210
  - test/test_follow_redirect.rb
215
211
  - test/test_future.rb
212
+ - test/test_httpclient.rb
216
213
  - test/test_json_request.rb
217
214
  - test/test_json_response.rb
218
215
  - test/test_oauth1_header.rb
@@ -221,7 +218,6 @@ test_files:
221
218
  - test/test_payload.rb
222
219
  - test/test_promise.rb
223
220
  - test/test_query_response.rb
224
- - test/test_rest-client.rb
225
221
  - test/test_simple.rb
226
222
  - test/test_smash.rb
227
223
  - test/test_smash_response.rb
@@ -1,21 +0,0 @@
1
-
2
- require 'net/http/persistent'
3
- require 'rest-core/engine'
4
-
5
- class RestCore::NetHttpPersistent < RestCore::Engine
6
- def request promise, env
7
- http = ::Net::HTTP::Persistent.new
8
- http.open_timeout, http.read_timeout = calculate_timeout(env[TIMER])
9
- payload, headers = payload_and_headers(env)
10
-
11
- uri = ::URI.parse(env[REQUEST_URI])
12
- req = ::Net::HTTP.const_get(env[REQUEST_METHOD].to_s.capitalize).
13
- new(uri, headers)
14
- req.body_stream = payload
15
- res = http.request(uri, req)
16
-
17
- promise.fulfill(res.body, res.code.to_i, normalize_headers(res.to_hash))
18
- rescue Exception => e
19
- promise.reject(e)
20
- end
21
- end
@@ -1,29 +0,0 @@
1
-
2
- require 'restclient'
3
- require 'rest-core/patch/rest-client'
4
- require 'rest-core/engine'
5
-
6
- class RestCore::RestClient < RestCore::Engine
7
- def request promise, env
8
- open_timeout, read_timeout = calculate_timeout(env[TIMER])
9
- payload, headers = payload_and_headers(env)
10
- res = ::RestClient::Request.execute(:method => env[REQUEST_METHOD],
11
- :url => env[REQUEST_URI] ,
12
- :payload => payload ,
13
- :headers => headers ,
14
- :max_redirects => 0 ,
15
- :open_timeout => open_timeout ,
16
- :timeout => read_timeout )
17
- promise.fulfill(res.body, res.code, normalize_headers(res.raw_headers))
18
- rescue ::RestClient::Exception => e
19
- if res = e.response
20
- # we don't want to raise an exception for 404 requests
21
- promise.fulfill(res.body, res.code, normalize_headers(res.raw_headers))
22
- else
23
- promise.reject(e)
24
- end
25
-
26
- rescue Exception => e
27
- promise.reject(e)
28
- end
29
- end
@@ -1,8 +0,0 @@
1
-
2
- # encode and decode will be remove in multi_json 2.0
3
- module MultiJson
4
- class << self
5
- alias_method :dump, :encode if not respond_to?(:dump)
6
- alias_method :load, :decode if not respond_to?(:load)
7
- end
8
- end
@@ -1,35 +0,0 @@
1
-
2
- module RestClient
3
- module AbstractResponse
4
- # begin patch
5
- # https://github.com/archiloque/rest-client/pull/103
6
- remove_method :to_i if method_defined?(:to_i)
7
- # end patch
8
-
9
- # Follow a redirection
10
- def follow_redirection request = nil, result = nil, & block
11
- url = headers[:location]
12
- if url !~ /^http/u
13
- url = URI.parse(args[:url]).merge(url).to_s
14
- end
15
- args[:url] = url
16
- if request
17
- if request.max_redirects == 0
18
- # begin patch
19
- # https://github.com/archiloque/rest-client/pull/118
20
- raise MaxRedirectsReached.new(self, code)
21
- # end patch
22
- end
23
- args[:password] = request.password
24
- args[:user] = request.user
25
- args[:headers] = request.headers
26
- args[:max_redirects] = request.max_redirects - 1
27
- # pass any cookie set in the result
28
- if result && result['set-cookie']
29
- args[:headers][:cookies] = (args[:headers][:cookies] || {}).merge(parse_cookie(result['set-cookie']))
30
- end
31
- end
32
- Request.execute args, &block
33
- end
34
- end
35
- end