rest-core 1.0.1 → 1.0.2

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.
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ rdoc
4
4
  .bundle
5
5
  .yardoc
6
6
  Gemfile.lock
7
+ task
data/CHANGES.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # CHANGES
2
2
 
3
+ ## rest-core 1.0.2 -- 2012-06-05
4
+
5
+ ### Enhancement
6
+
7
+ * Some internal refactoring.
8
+
9
+ ### Bugs fixes
10
+
11
+ * Properly handle asynchronous timers for eventmachine and cool.io.
12
+
3
13
  ## rest-core 1.0.1 -- 2012-05-14
4
14
 
5
15
  * [`Auto`] Check for eventmachine first instead of cool.io
data/Rakefile CHANGED
@@ -1,8 +1,13 @@
1
1
  # encoding: utf-8
2
2
 
3
- require "#{dir = File.dirname(__FILE__)}/task/gemgem"
4
- Gemgem.dir = dir
3
+ begin
4
+ require "#{dir = File.dirname(__FILE__)}/task/gemgem"
5
+ rescue LoadError
6
+ sh "git submodule update --init"
7
+ exec Gem.ruby, "-S", "rake", *ARGV
8
+ end
5
9
 
10
+ Gemgem.dir = dir
6
11
  ($LOAD_PATH << File.expand_path("#{Gemgem.dir}/lib" )).uniq!
7
12
 
8
13
  desc 'Generate gemspec'
@@ -11,10 +11,10 @@ class RestCore::CoolioFiber
11
11
  :url => request_uri(env) ,
12
12
  :payload => env[REQUEST_PAYLOAD],
13
13
  :headers => env[REQUEST_HEADERS]))
14
+ rescue FiberError
14
15
  end
15
16
 
16
17
  def process env, response
17
- env[TIMER].detach if env[TIMER]
18
18
  Thread.current[:coolio_http_client].detach if
19
19
  Thread.current[:coolio_http_client].kind_of?(::Coolio::HttpFiber)
20
20
 
@@ -28,7 +28,7 @@ class RestCore::EmHttpRequestAsync
28
28
  end
29
29
 
30
30
  def respond env, client
31
- env[TIMER].cancel if env[TIMER]
31
+ env[TIMER].cancel if env[TIMER] && !env[TIMER].canceled?
32
32
  env[ASYNC].call(env.merge(
33
33
  RESPONSE_BODY => client.response,
34
34
  RESPONSE_STATUS => client.response_header.status,
@@ -28,8 +28,11 @@ class RestCore::EmHttpRequestFiber
28
28
  end
29
29
 
30
30
  def respond f, env, client
31
- env[TIMER].cancel if env[TIMER]
32
31
  f.resume(process(env, client)) if f.alive?
32
+ rescue FiberError
33
+ # whenever timeout, client.close would be called,
34
+ # and then errback would be called. in this case,
35
+ # the fiber is already resumed by the timer
33
36
  end
34
37
 
35
38
  def process env, client
@@ -6,9 +6,6 @@ class RestCore::Builder
6
6
  include RestCore
7
7
  include Wrapper
8
8
 
9
- class << self
10
- attr_writer :default_app
11
- end
12
9
  def self.default_app
13
10
  @default_app ||= RestClient
14
11
  end
@@ -176,7 +176,7 @@ module RestCore::Client
176
176
  FAIL => [] ,
177
177
  LOG => [] ,
178
178
  ASYNC => if block_given?
179
- lambda{ |response| yield(response) }
179
+ lambda{ |res| yield(res) }
180
180
  else
181
181
  nil
182
182
  end}.merge(env)))
@@ -23,6 +23,7 @@ class RestCore::Cache
23
23
  end
24
24
 
25
25
  if cached = cache_get(e)
26
+ env[TIMER].cancel if env[TIMER] && !env[TIMER].canceled?
26
27
  wrapped.call(cached)
27
28
  else
28
29
  if e[ASYNC]
@@ -25,7 +25,10 @@ class RestCore::Timeout
25
25
  if root_fiber? && env[ASYNC]
26
26
  yield(env.merge(TIMER => timeout_with_callback(env, class_name)))
27
27
  else
28
- yield(env.merge(TIMER => timeout_with_resume( env, class_name)))
28
+ timer = timeout_with_resume(env, class_name)
29
+ response = yield(env.merge(TIMER => timer))
30
+ timer.cancel unless timer.canceled?
31
+ response
29
32
  end
30
33
  else
31
34
  ::Timeout.timeout(timeout(env)){ yield(env) }
@@ -60,6 +63,9 @@ class RestCore::Timeout
60
63
  f = Fiber.current
61
64
  EventMachineTimer.new(timeout(env), error = timeout_error){
62
65
  f.resume(error) if f.alive?
66
+ # no need to check if the fiber is already resumed or not,
67
+ # because monitor should have already handled this in the
68
+ # case of fibers
63
69
  }
64
70
 
65
71
  when /Coolio/
@@ -1,4 +1,10 @@
1
1
 
2
2
  class RestCore::Timeout::CoolioTimer < ::Coolio::TimerWatcher
3
3
  attr_accessor :error
4
+
5
+ alias_method :cancel, :detach
6
+
7
+ def canceled?
8
+ !attached?
9
+ end
4
10
  end
@@ -6,9 +6,19 @@ class RestCore::Timeout::EventMachineTimer < ::EventMachine::Timer
6
6
  super(timeout, &block) if block_given?
7
7
  self.timeout = timeout
8
8
  self.error = error
9
+ @canceled = false
9
10
  end
10
11
 
11
12
  def on_timeout &block
12
13
  send(:initialize, timeout, error, &block)
13
14
  end
15
+
16
+ def cancel
17
+ super
18
+ @canceled = true
19
+ end
20
+
21
+ def canceled?
22
+ @canceled
23
+ end
14
24
  end
@@ -1,4 +1,4 @@
1
1
 
2
2
  module RestCore
3
- VERSION = '1.0.1'
3
+ VERSION = '1.0.2'
4
4
  end
@@ -3,16 +3,21 @@ require 'rest-core'
3
3
 
4
4
  module RestCore::Wrapper
5
5
  include RestCore
6
+
7
+ module DefaultApp
8
+ def default_app
9
+ @default_app ||= RestCore::Dry
10
+ end
11
+ end
12
+
6
13
  def self.included mod
7
- mod.send(:attr_reader, :init, :middles, :wrapped)
14
+ mod.send(:extend, DefaultApp)
8
15
  class << mod
9
16
  attr_writer :default_app
10
- def default_app
11
- @default_app ||= RestCore::Dry
12
- end
13
17
  end
14
18
  end
15
19
 
20
+ attr_reader :init, :middles, :wrapped
16
21
  attr_writer :default_app
17
22
  def default_app
18
23
  @default_app ||= self.class.default_app
@@ -22,6 +27,7 @@ module RestCore::Wrapper
22
27
  @middles ||= []
23
28
  instance_eval(&block) if block_given?
24
29
  @wrapped ||= to_app
30
+ @init = nil
25
31
  end
26
32
 
27
33
  def use middle, *args, &block
@@ -43,9 +49,9 @@ module RestCore::Wrapper
43
49
  }.flatten
44
50
  end
45
51
 
46
- def to_app init=@init || default_app
52
+ def to_app app=init || default_app
47
53
  # === foldr m.new app middles
48
- middles.reverse.inject(init.new){ |app_, (middle, args, block)|
54
+ middles.reverse.inject(app.new){ |app_, (middle, args, block)|
49
55
  begin
50
56
  middle.new(app_, *partial_deep_copy(args), &block)
51
57
  rescue ArgumentError => e
@@ -2,13 +2,13 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "rest-core"
5
- s.version = "1.0.1"
5
+ s.version = "1.0.2"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = [
9
9
  "Cardinal Blue",
10
10
  "Lin Jen-Shin (godfat)"]
11
- s.date = "2012-05-14"
11
+ s.date = "2012-06-05"
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-core]: https://github.com/cardinalblue/rest-core\n[rest-more]: https://github.com/cardinalblue/rest-more"
13
13
  s.email = ["dev (XD) cardinalblue.com"]
14
14
  s.files = [
@@ -78,10 +78,9 @@ Gem::Specification.new do |s|
78
78
  "pending/test_multi.rb",
79
79
  "pending/test_test_util.rb",
80
80
  "rest-core.gemspec",
81
- "task/.gitignore",
82
- "task/gemgem.rb",
83
81
  "test/test_auth_basic.rb",
84
82
  "test/test_builder.rb",
83
+ "test/test_cache.rb",
85
84
  "test/test_client.rb",
86
85
  "test/test_client_oauth1.rb",
87
86
  "test/test_default_query.rb",
@@ -101,6 +100,7 @@ Gem::Specification.new do |s|
101
100
  s.test_files = [
102
101
  "test/test_auth_basic.rb",
103
102
  "test/test_builder.rb",
103
+ "test/test_cache.rb",
104
104
  "test/test_client.rb",
105
105
  "test/test_client_oauth1.rb",
106
106
  "test/test_default_query.rb",
@@ -0,0 +1,69 @@
1
+
2
+ require 'rest-core/test'
3
+
4
+ describe RC::Cache do
5
+ after do
6
+ WebMock.reset!
7
+ RR.verify
8
+ end
9
+
10
+ should 'basic' do
11
+ c = RC::Builder.client do
12
+ use RC::Cache, {}, 3600
13
+ run Class.new{
14
+ attr_accessor :tick
15
+ def initialize
16
+ self.tick = 0
17
+ end
18
+ def call env
19
+ self.tick +=1
20
+ env.merge(RC::RESPONSE_BODY => 'response')
21
+ end
22
+ }
23
+ end.new
24
+ c.get('/')
25
+ c.cache.should.eq({Digest::MD5.hexdigest('/') => 'response'})
26
+ c.app.app.tick.should.eq 1
27
+ c.get('/')
28
+ c.app.app.tick.should.eq 1
29
+ c.cache.clear
30
+ c.get('/')
31
+ c.app.app.tick.should.eq 2
32
+ end
33
+
34
+ should 'cancel timeout for fiber' do
35
+ any_instance_of(RC::Timeout::EventMachineTimer) do |timer|
36
+ proxy.mock(timer).cancel.times(2)
37
+ end
38
+ path = 'http://example.com/'
39
+ stub_request(:get, path).to_return(:body => 'response')
40
+ c = RC::Builder.client do
41
+ use RC::Timeout, 10
42
+ use RC::Cache, {}, 3600
43
+ run RC::EmHttpRequestFiber
44
+ end.new
45
+ EM.run{ Fiber.new{
46
+ c.request_full(RC::REQUEST_PATH => path)
47
+ c.request_full(RC::REQUEST_PATH => path)
48
+ EM.stop }.resume }
49
+ c.cache.size.should.eq 1
50
+ end if defined?(Fiber)
51
+
52
+ should 'cancel timeout for async' do
53
+ path = 'http://example.com/'
54
+ any_instance_of(RC::Timeout::EventMachineTimer) do |timer|
55
+ mock(timer).cancel.times(2)
56
+ end
57
+ stub_request(:get, path).to_return(:body => 'response')
58
+ c = RC::Builder.client do
59
+ use RC::Timeout, 10
60
+ use RC::Cache, {}, 3600
61
+ run RC::EmHttpRequestAsync
62
+ end.new
63
+ EM.run{
64
+ c.request_full(RC::REQUEST_PATH => path){
65
+ c.request_full(RC::REQUEST_PATH => path){
66
+ EM.stop }}}
67
+ c.cache.size.should.eq 1
68
+ end
69
+ end
@@ -6,6 +6,10 @@ describe RC::Timeout do
6
6
  @app = RC::Timeout.new(RC::Dry.new, 0)
7
7
  end
8
8
 
9
+ after do
10
+ WebMock.reset!
11
+ end
12
+
9
13
  should 'bypass timeout if timeout is 0' do
10
14
  mock(@app).monitor.times(0)
11
15
  @app.call({}).should.eq({})
@@ -16,4 +20,15 @@ describe RC::Timeout do
16
20
  mock.proxy(@app).monitor(env).times(1)
17
21
  @app.call(env).should.eq(env)
18
22
  end
23
+
24
+ should 'return correct result under fibers' do
25
+ path = 'http://example.com/'
26
+ stub_request(:get, path).to_return(:body => 'response')
27
+
28
+ c = RC::Builder.client do
29
+ use RC::Timeout, 10
30
+ run RC::EmHttpRequestFiber
31
+ end.new
32
+ EM.run{Fiber.new{c.get(path).should.eq('response');EM.stop}.resume}
33
+ end if defined?(Fiber)
19
34
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-05-14 00:00:00.000000000 Z
13
+ date: 2012-06-05 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rest-client
@@ -119,10 +119,9 @@ files:
119
119
  - pending/test_multi.rb
120
120
  - pending/test_test_util.rb
121
121
  - rest-core.gemspec
122
- - task/.gitignore
123
- - task/gemgem.rb
124
122
  - test/test_auth_basic.rb
125
123
  - test/test_builder.rb
124
+ - test/test_cache.rb
126
125
  - test/test_client.rb
127
126
  - test/test_client_oauth1.rb
128
127
  - test/test_default_query.rb
@@ -162,6 +161,7 @@ summary: Modular Ruby clients interface for REST APIs
162
161
  test_files:
163
162
  - test/test_auth_basic.rb
164
163
  - test/test_builder.rb
164
+ - test/test_cache.rb
165
165
  - test/test_client.rb
166
166
  - test/test_client_oauth1.rb
167
167
  - test/test_default_query.rb
@@ -1 +0,0 @@
1
- *.rbc
@@ -1,257 +0,0 @@
1
-
2
- require 'pathname'
3
-
4
- module Gemgem
5
- class << self
6
- attr_accessor :dir, :spec
7
- end
8
-
9
- module_function
10
- def create
11
- yield(spec = Gem::Specification.new{ |s|
12
- s.authors = ['Lin Jen-Shin (godfat)']
13
- s.email = ['godfat (XD) godfat.org']
14
-
15
- s.description = description.join
16
- s.summary = description.first
17
-
18
- s.rubygems_version = Gem::VERSION
19
- s.date = Time.now.strftime('%Y-%m-%d')
20
- s.files = gem_files
21
- s.test_files = gem_files.grep(%r{^test/(.+?/)*test_.+?\.rb$})
22
- s.executables = Dir['bin/*'].map{ |f| File.basename(f) }
23
- s.require_paths = %w[lib]
24
- })
25
- spec.homepage ||= "https://github.com/godfat/#{spec.name}"
26
- spec
27
- end
28
-
29
- def readme
30
- path = %w[README.md README].find{ |name|
31
- File.exist?("#{Gemgem.dir}/#{name}")
32
- }
33
- @readme ||=
34
- if path
35
- ps = File.read(path).scan(/#+[^\n]+\n\n.+?(?=\n\n#+[^\n]+\n)/m)
36
- ps.inject({'HEADER' => ps.first}){ |r, s, i|
37
- r[s[/\w+/]] = s
38
- r
39
- }
40
- else
41
- {}
42
- end
43
- end
44
-
45
- def description
46
- @description ||= (readme['DESCRIPTION']||'').sub(/.+\n\n/, '').lines.to_a
47
- end
48
-
49
- def changes
50
- path = %w[CHANGES.md CHANGES].find{ |name|
51
- File.exist?("#{Gemgem.dir}/#{name}")
52
- }
53
- @changes ||=
54
- if path
55
- date = '\d+{4}\-\d+{2}\-\d{2}'
56
- File.read(path).match(
57
- /([^\n]+#{date}\n\n(.+?))(?=\n\n[^\n]+#{date}\n)/m)[1]
58
- else
59
- ''
60
- end
61
- end
62
-
63
- def ann_md
64
- "##{readme['HEADER'].sub(/([\w\-]+)/, "[\\1](#{spec.homepage})")}\n\n" \
65
- "##{readme['DESCRIPTION'][/[^\n]+\n\n[^\n]+/]}\n\n" \
66
- "### CHANGES:\n\n" \
67
- "###{changes}\n\n" \
68
- "##{readme['INSTALLATION']}\n\n" +
69
- if readme['SYNOPSIS'] then "##{readme['SYNOPSIS']}" else '' end
70
- end
71
-
72
- def ann_html
73
- gem 'nokogiri'
74
- gem 'kramdown'
75
-
76
- IO.popen('kramdown', 'r+') do |md|
77
- md.puts Gemgem.ann_md
78
- md.close_write
79
- require 'nokogiri'
80
- html = Nokogiri::XML.parse("<gemgem>#{md.read}</gemgem>")
81
- html.css('*').each{ |n| n.delete('id') }
82
- html.root.children.to_html
83
- end
84
- end
85
-
86
- def ann_email
87
- "#{readme['HEADER'].sub(/([\w\-]+)/, "\\1 <#{spec.homepage}>")}\n\n" \
88
- "#{readme['DESCRIPTION']}\n\n" \
89
- "#{readme['INSTALLATION']}\n\n" +
90
- if readme['SYNOPSIS'] then "##{readme['SYNOPSIS']}\n\n" else '' end +
91
- "## CHANGES:\n\n" \
92
- "##{changes}\n\n"
93
- end
94
-
95
- def gem_tag
96
- "#{spec.name}-#{spec.version}"
97
- end
98
-
99
- def write
100
- File.open("#{dir}/#{spec.name}.gemspec", 'w'){ |f|
101
- f << split_lines(spec.to_ruby) }
102
- end
103
-
104
- def split_lines ruby
105
- ruby.gsub(/(.+?)\[(.+?)\]/){ |s|
106
- if $2.index(',')
107
- "#{$1}[\n #{$2.split(',').map(&:strip).join(",\n ")}]"
108
- else
109
- s
110
- end
111
- }
112
- end
113
-
114
- def all_files
115
- @all_files ||= find_files(Pathname.new(dir)).map{ |file|
116
- if file.to_s =~ %r{\.git/}
117
- nil
118
- else
119
- file.to_s
120
- end
121
- }.compact.sort
122
- end
123
-
124
- def gem_files
125
- @gem_files ||= all_files - ignored_files
126
- end
127
-
128
- def ignored_files
129
- @ignored_file ||= all_files.select{ |path| ignore_patterns.find{ |ignore|
130
- path =~ ignore && !git_files.include?(path)}}
131
- end
132
-
133
- def git_files
134
- @git_files ||= if File.exist?("#{dir}/.git")
135
- `git ls-files`.split("\n")
136
- else
137
- []
138
- end
139
- end
140
-
141
- # protected
142
- def find_files path
143
- path.children.select(&:file?).map{|file| file.to_s[(dir.size+1)..-1]} +
144
- path.children.select(&:directory?).map{|dir| find_files(dir)}.flatten
145
- end
146
-
147
- def ignore_patterns
148
- @ignore_files ||= expand_patterns(
149
- File.read("#{dir}/.gitignore").split("\n").reject{ |pattern|
150
- pattern.strip == ''
151
- }).map{ |pattern| %r{^([^/]+/)*?#{Regexp.escape(pattern)}(/[^/]+)*?$} }
152
- end
153
-
154
- def expand_patterns pathes
155
- pathes.map{ |path|
156
- if path !~ /\*/
157
- path
158
- else
159
- expand_patterns(
160
- Dir[path] +
161
- Pathname.new(File.dirname(path)).children.select(&:directory?).
162
- map{ |prefix| "#{prefix}/#{File.basename(path)}" })
163
- end
164
- }.flatten
165
- end
166
- end
167
-
168
- namespace :gem do
169
-
170
- desc 'Install gem'
171
- task :install => [:build] do
172
- sh("#{Gem.ruby} -S gem install pkg/#{Gemgem.gem_tag}")
173
- end
174
-
175
- desc 'Build gem'
176
- task :build => [:spec] do
177
- sh("#{Gem.ruby} -S gem build #{Gemgem.spec.name}.gemspec")
178
- sh("mkdir -p pkg")
179
- sh("mv #{Gemgem.gem_tag}.gem pkg/")
180
- end
181
-
182
- desc 'Release gem'
183
- task :release => [:spec, :check, :build] do
184
- sh("git tag #{Gemgem.gem_tag}")
185
- sh("git push")
186
- sh("git push --tags")
187
- sh("#{Gem.ruby} -S gem push pkg/#{Gemgem.gem_tag}.gem")
188
- end
189
-
190
- task :check do
191
- ver = Gemgem.spec.version.to_s
192
-
193
- if ENV['VERSION'].nil?
194
- puts("\x1b[35mExpected " \
195
- "\x1b[33mVERSION\x1b[35m=\x1b[33m#{ver}\x1b[m")
196
- exit(1)
197
-
198
- elsif ENV['VERSION'] != ver
199
- puts("\x1b[35mExpected \x1b[33mVERSION\x1b[35m=\x1b[33m#{ver} " \
200
- "\x1b[35mbut got\n " \
201
- "\x1b[33mVERSION\x1b[35m=\x1b[33m#{ENV['VERSION']}\x1b[m")
202
- exit(2)
203
- end
204
- end
205
-
206
- end # of gem namespace
207
-
208
- desc 'Run tests in memory'
209
- task :test do
210
- require 'bacon'
211
- Bacon.extend(Bacon::TestUnitOutput)
212
- Bacon.summary_on_exit
213
- $LOAD_PATH.unshift('lib')
214
- Dir['./test/**/test_*.rb'].each{ |file| require file[0..-4] }
215
- end
216
-
217
- desc 'Run tests with shell'
218
- task 'test:shell', :RUBY_OPTS do |t, args|
219
- files = Dir['test/**/test_*.rb'].join(' ')
220
-
221
- cmd = [Gem.ruby, args[:RUBY_OPTS],
222
- '-I', 'lib', '-S', 'bacon', '--quiet', files]
223
-
224
- sh(cmd.compact.join(' '))
225
- end
226
-
227
- desc 'Generate ann markdown'
228
- task 'ann:md' => ['gem:spec'] do
229
- puts Gemgem.ann_md
230
- end
231
-
232
- desc 'Generate ann html'
233
- task 'ann:html' => ['gem:spec'] do
234
- puts Gemgem.ann_html
235
- end
236
-
237
- desc 'Generate ann email'
238
- task 'ann:email' => ['gem:spec'] do
239
- puts Gemgem.ann_email
240
- end
241
-
242
- desc 'Generate rdoc'
243
- task :doc => ['gem:spec'] do
244
- sh("yardoc -o rdoc --main README.md" \
245
- " --files #{Gemgem.spec.extra_rdoc_files.join(',')}")
246
- end
247
-
248
- desc 'Remove ignored files'
249
- task :clean => ['gem:spec'] do
250
- trash = "~/.Trash/#{Gemgem.spec.name}/"
251
- sh "mkdir -p #{trash}" unless File.exist?(File.expand_path(trash))
252
- Gemgem.ignored_files.each{ |file| sh "mv #{file} #{trash}" }
253
- end
254
-
255
- task :default do
256
- puts `#{Gem.ruby} -S #{$PROGRAM_NAME} -T`
257
- end