rest-core 3.3.3 → 3.4.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: 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