grape-async 0.1.1
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 +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +36 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +150 -0
- data/Guardfile +9 -0
- data/LICENSE +22 -0
- data/README.md +39 -0
- data/Rakefile +7 -0
- data/examples/config.ru +36 -0
- data/examples/endpoint_faker.ru +4 -0
- data/grape-async.gemspec +29 -0
- data/lib/.DS_Store +0 -0
- data/lib/grape-async/api.rb +11 -0
- data/lib/grape-async/endpoint.rb +34 -0
- data/lib/grape-async/middleware/async.rb +71 -0
- data/lib/grape-async.rb +4 -0
- data/spec/factories.rb +3 -0
- data/spec/lib/grape-async/api_spec.rb +39 -0
- data/spec/lib/grape-async/endpoint_spec.rb +35 -0
- data/spec/lib/grape-async/middleware/async_spec.rb +201 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/endpoint_faker.rb +80 -0
- metadata +171 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a2cf22a8d1c6cd20c87812b76a57015baea1ac77
|
4
|
+
data.tar.gz: 89c81fe6b0184768d9698132c7a7110427a716a3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 21d76e693615f041d142a9d88a15e55127fae2d2e6b498a1933ba6efb4689f9a7e5dd550800dc4fc64afc2dd1f7d302fb62ce2ee85f714a935b713253d367bce
|
7
|
+
data.tar.gz: 8ccdf1fd37da79969d9545bc431c36ccdd694875131544263b1d2c86ee5a14511ce95802792651cdc055165b0d566a7cf3b18ee7f736b365883a1862c2b48880
|
data/.DS_Store
ADDED
Binary file
|
data/.gitignore
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
/log/*
|
12
|
+
|
13
|
+
## Specific to RubyMotion:
|
14
|
+
.dat*
|
15
|
+
.repl_history
|
16
|
+
build/
|
17
|
+
|
18
|
+
## Documentation cache and generated files:
|
19
|
+
/.yardoc/
|
20
|
+
/_yardoc/
|
21
|
+
/doc/
|
22
|
+
/rdoc/
|
23
|
+
|
24
|
+
## Environment normalisation:
|
25
|
+
/.bundle/
|
26
|
+
/vendor/bundle
|
27
|
+
/lib/bundler/man/
|
28
|
+
|
29
|
+
# for a library or gem, you might want to ignore these files since the code is
|
30
|
+
# intended to run in multiple environments; otherwise, check them in:
|
31
|
+
# Gemfile.lock
|
32
|
+
# .ruby-version
|
33
|
+
# .ruby-gemset
|
34
|
+
|
35
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
36
|
+
.rvmrc
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
grape-async (0.1.0)
|
5
|
+
activesupport
|
6
|
+
eventmachine
|
7
|
+
grape
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activesupport (4.2.5)
|
13
|
+
i18n (~> 0.7)
|
14
|
+
json (~> 1.7, >= 1.7.7)
|
15
|
+
minitest (~> 5.1)
|
16
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
17
|
+
tzinfo (~> 1.1)
|
18
|
+
axiom-types (0.1.1)
|
19
|
+
descendants_tracker (~> 0.0.4)
|
20
|
+
ice_nine (~> 0.11.0)
|
21
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
22
|
+
builder (3.2.2)
|
23
|
+
capybara (2.5.0)
|
24
|
+
mime-types (>= 1.16)
|
25
|
+
nokogiri (>= 1.3.3)
|
26
|
+
rack (>= 1.0.0)
|
27
|
+
rack-test (>= 0.5.4)
|
28
|
+
xpath (~> 2.0)
|
29
|
+
coderay (1.1.0)
|
30
|
+
coercible (1.0.0)
|
31
|
+
descendants_tracker (~> 0.0.1)
|
32
|
+
daemons (1.2.3)
|
33
|
+
descendants_tracker (0.0.4)
|
34
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
35
|
+
diff-lcs (1.2.5)
|
36
|
+
equalizer (0.0.11)
|
37
|
+
eventmachine (1.0.8)
|
38
|
+
ffi (1.9.10)
|
39
|
+
formatador (0.2.5)
|
40
|
+
grape (0.14.0)
|
41
|
+
activesupport
|
42
|
+
builder
|
43
|
+
hashie (>= 2.1.0)
|
44
|
+
multi_json (>= 1.3.2)
|
45
|
+
multi_xml (>= 0.5.2)
|
46
|
+
rack (>= 1.3.0)
|
47
|
+
rack-accept
|
48
|
+
rack-mount
|
49
|
+
virtus (>= 1.0.0)
|
50
|
+
guard (2.13.0)
|
51
|
+
formatador (>= 0.2.4)
|
52
|
+
listen (>= 2.7, <= 4.0)
|
53
|
+
lumberjack (~> 1.0)
|
54
|
+
nenv (~> 0.1)
|
55
|
+
notiffany (~> 0.0)
|
56
|
+
pry (>= 0.9.12)
|
57
|
+
shellany (~> 0.0)
|
58
|
+
thor (>= 0.18.1)
|
59
|
+
guard-compat (1.2.1)
|
60
|
+
guard-rspec (4.6.4)
|
61
|
+
guard (~> 2.1)
|
62
|
+
guard-compat (~> 1.1)
|
63
|
+
rspec (>= 2.99.0, < 4.0)
|
64
|
+
hashie (3.4.3)
|
65
|
+
i18n (0.7.0)
|
66
|
+
ice_nine (0.11.1)
|
67
|
+
json (1.8.3)
|
68
|
+
listen (3.0.5)
|
69
|
+
rb-fsevent (>= 0.9.3)
|
70
|
+
rb-inotify (>= 0.9)
|
71
|
+
lumberjack (1.0.9)
|
72
|
+
method_source (0.8.2)
|
73
|
+
mime-types (3.0)
|
74
|
+
mime-types-data (~> 3.2015)
|
75
|
+
mime-types-data (3.2015.1120)
|
76
|
+
mini_portile2 (2.0.0)
|
77
|
+
minitest (5.8.3)
|
78
|
+
multi_json (1.11.2)
|
79
|
+
multi_xml (0.5.5)
|
80
|
+
nenv (0.2.0)
|
81
|
+
nokogiri (1.6.7.1)
|
82
|
+
mini_portile2 (~> 2.0.0.rc2)
|
83
|
+
notiffany (0.0.8)
|
84
|
+
nenv (~> 0.1)
|
85
|
+
shellany (~> 0.0)
|
86
|
+
pry (0.10.3)
|
87
|
+
coderay (~> 1.1.0)
|
88
|
+
method_source (~> 0.8.1)
|
89
|
+
slop (~> 3.4)
|
90
|
+
puma (2.15.3)
|
91
|
+
rack (1.6.4)
|
92
|
+
rack-accept (0.4.5)
|
93
|
+
rack (>= 0.4)
|
94
|
+
rack-mount (0.8.3)
|
95
|
+
rack (>= 1.0.0)
|
96
|
+
rack-test (0.6.3)
|
97
|
+
rack (>= 1.0)
|
98
|
+
rake (10.4.2)
|
99
|
+
rb-fsevent (0.9.6)
|
100
|
+
rb-inotify (0.9.5)
|
101
|
+
ffi (>= 0.5.0)
|
102
|
+
rspec (3.4.0)
|
103
|
+
rspec-core (~> 3.4.0)
|
104
|
+
rspec-expectations (~> 3.4.0)
|
105
|
+
rspec-mocks (~> 3.4.0)
|
106
|
+
rspec-core (3.4.1)
|
107
|
+
rspec-support (~> 3.4.0)
|
108
|
+
rspec-expectations (3.4.0)
|
109
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
110
|
+
rspec-support (~> 3.4.0)
|
111
|
+
rspec-mocks (3.4.0)
|
112
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
113
|
+
rspec-support (~> 3.4.0)
|
114
|
+
rspec-support (3.4.1)
|
115
|
+
shellany (0.0.1)
|
116
|
+
slop (3.6.0)
|
117
|
+
thin (1.6.4)
|
118
|
+
daemons (~> 1.0, >= 1.0.9)
|
119
|
+
eventmachine (~> 1.0, >= 1.0.4)
|
120
|
+
rack (~> 1.0)
|
121
|
+
thor (0.19.1)
|
122
|
+
thread_safe (0.3.5)
|
123
|
+
tzinfo (1.2.2)
|
124
|
+
thread_safe (~> 0.1)
|
125
|
+
virtus (1.0.5)
|
126
|
+
axiom-types (~> 0.1)
|
127
|
+
coercible (~> 1.0)
|
128
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
129
|
+
equalizer (~> 0.0, >= 0.0.9)
|
130
|
+
xpath (2.0.0)
|
131
|
+
nokogiri (~> 1.3)
|
132
|
+
|
133
|
+
PLATFORMS
|
134
|
+
ruby
|
135
|
+
|
136
|
+
DEPENDENCIES
|
137
|
+
bundler
|
138
|
+
capybara
|
139
|
+
grape-async!
|
140
|
+
guard
|
141
|
+
guard-rspec
|
142
|
+
pry
|
143
|
+
puma
|
144
|
+
rack-test
|
145
|
+
rake
|
146
|
+
rspec
|
147
|
+
thin
|
148
|
+
|
149
|
+
BUNDLED WITH
|
150
|
+
1.10.6
|
data/Guardfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard :rspec, cmd: 'bundle exec rspec', after_all_pass: false do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^spec/support/.+$}) { "spec" }
|
7
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
8
|
+
watch('spec/spec_helper.rb') { "spec" }
|
9
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Stuart
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+

|
2
|
+
|
3
|
+
# Async endpoints for Grape APIs
|
4
|
+
|
5
|
+
Enable asyncronous endpoints to avoid blocking slow requests within EventMachine or Threads.
|
6
|
+
This can be used with any Ruby server supporting `env['async.callback']` or
|
7
|
+
`env['rack.hijack']` (the Rack Hijack API).
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'grape', '>= 0.14.0'
|
15
|
+
gem 'grape-async'
|
16
|
+
```
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
class API < Grape::API
|
22
|
+
use Grape::Async
|
23
|
+
async
|
24
|
+
get do
|
25
|
+
# code to run asyncronously ...
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
The `async` directive accepts either `:em` for EventMachine based async or `:threaded` for thread based async.
|
31
|
+
The default is `:threaded`.
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
1. Fork it ( https://github.com/stuart/grape-async/fork )
|
36
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
37
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
38
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
39
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/examples/config.ru
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require '../lib/grape-async'
|
2
|
+
|
3
|
+
class API < Grape::API
|
4
|
+
use Grape::Middleware::Async
|
5
|
+
|
6
|
+
async :em
|
7
|
+
desc "Get status using EM async timer"
|
8
|
+
get :em do
|
9
|
+
puts "Sleeping..."
|
10
|
+
EM.add_timer(2) do
|
11
|
+
puts "Awake!"
|
12
|
+
present({ status: 'ok'})
|
13
|
+
done
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
async :threaded
|
18
|
+
desc "Get status using Threaded async timer"
|
19
|
+
get :thread do
|
20
|
+
puts "Sleeping..."
|
21
|
+
sleep(2)
|
22
|
+
puts "Awake!"
|
23
|
+
present({ status: 'ok'})
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Get status using sync timer"
|
27
|
+
get :sync do
|
28
|
+
puts "Sleeping..."
|
29
|
+
sleep(2)
|
30
|
+
puts "Awake!"
|
31
|
+
present({ status: 'ok'})
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
run API.new
|
data/grape-async.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "grape-async"
|
7
|
+
spec.version = '0.1.1'
|
8
|
+
spec.platform = Gem::Platform::RUBY
|
9
|
+
spec.authors = ["Lachlan Laycock"]
|
10
|
+
spec.email = ["l.laycock@stuart.com"]
|
11
|
+
spec.description = %q{Async endpoints for Grape APIs}
|
12
|
+
spec.summary = %q{Enable asyncronous endpoints to avoid blocking slow requests within EventMachine or Threads}
|
13
|
+
spec.homepage = "https://github.com/stuart/grape-async"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "grape", '~> 0.14'
|
22
|
+
spec.add_dependency "eventmachine", '~> 1.0'
|
23
|
+
spec.add_dependency "activesupport", '~> 4.2'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 3.0'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
spec.add_development_dependency 'rack-test', '~> 0.5'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
29
|
+
end
|
data/lib/.DS_Store
ADDED
Binary file
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Endpoint
|
5
|
+
|
6
|
+
class DeferrableResp
|
7
|
+
include EventMachine::Deferrable
|
8
|
+
end
|
9
|
+
|
10
|
+
def deferred_resp
|
11
|
+
if async_route?(:em)
|
12
|
+
@deferred_resp ||= DeferrableResp.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def async_route?(method = nil)
|
17
|
+
async_settings = route_setting(:async) || {}
|
18
|
+
async = async_settings.fetch(:async, false)
|
19
|
+
async_method = async_settings.fetch(:async_method, :threaded)
|
20
|
+
if method
|
21
|
+
async && async_method == method.to_sym
|
22
|
+
else
|
23
|
+
async
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def done
|
28
|
+
if deferred_resp.is_a?(DeferrableResp)
|
29
|
+
deferred_resp.set_deferred_status :succeeded
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Grape
|
2
|
+
module Middleware
|
3
|
+
class Async < Grape::Middleware::Base
|
4
|
+
|
5
|
+
def call!(env)
|
6
|
+
@env = env
|
7
|
+
if endpoint.async_route? && !async_io.nil?
|
8
|
+
if endpoint.async_route?(:em)
|
9
|
+
proc = lambda {
|
10
|
+
EM.next_tick do
|
11
|
+
super
|
12
|
+
endpoint.deferred_resp.callback do
|
13
|
+
resp = endpoint.file || [endpoint.body]
|
14
|
+
async_call [endpoint.status, endpoint.header, resp]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
}
|
18
|
+
if !EM.reactor_running?
|
19
|
+
EM.run do
|
20
|
+
proc.call
|
21
|
+
end
|
22
|
+
else
|
23
|
+
proc.call
|
24
|
+
end
|
25
|
+
else
|
26
|
+
Thread.new do
|
27
|
+
result = super
|
28
|
+
async_call result
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
[-1, {}, []] # Return async response
|
34
|
+
|
35
|
+
else
|
36
|
+
super
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def async_call(result)
|
42
|
+
if @env['async.callback']
|
43
|
+
async_io.call result
|
44
|
+
elsif @env['rack.hijack']
|
45
|
+
begin
|
46
|
+
if result.last.is_a?(Rack::BodyProxy)
|
47
|
+
async_io << result.last.body.first
|
48
|
+
result.last.close unless result.last.closed?
|
49
|
+
elsif result.last.is_a?(Array)
|
50
|
+
async_io << result.last.first
|
51
|
+
end
|
52
|
+
ensure
|
53
|
+
EM.stop if endpoint.async_route?(:em)
|
54
|
+
@env['rack.hijack'].close
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def async_io
|
60
|
+
@env['async.callback'] || begin
|
61
|
+
@env.key?('rack.hijack') ? @env['rack.hijack'].call : nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def endpoint
|
66
|
+
@env[Grape::Env::API_ENDPOINT]
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/grape-async.rb
ADDED
data/spec/factories.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::API do
|
4
|
+
|
5
|
+
subject { Class.new(Grape::API) }
|
6
|
+
|
7
|
+
describe ".async" do
|
8
|
+
|
9
|
+
before do
|
10
|
+
subject.get('/async') { 'ok' }
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:route_settings) { subject.route_setting(:async) }
|
14
|
+
|
15
|
+
it "should define route settings" do
|
16
|
+
subject.async
|
17
|
+
expect(route_settings).to be_a(Hash)
|
18
|
+
expect(route_settings).to have_key(:async)
|
19
|
+
expect(route_settings).to have_key(:async_method)
|
20
|
+
expect(route_settings[:async]).to be_truthy
|
21
|
+
expect(route_settings[:async_method]).to eql(:threaded)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should not define route params" do
|
25
|
+
expect(route_settings).to be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
context "specified with eventmachine" do
|
29
|
+
|
30
|
+
it "should define route params" do
|
31
|
+
subject.async :em
|
32
|
+
expect(route_settings[:async_method]).to eql(:em)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Grape::Endpoint do
|
4
|
+
|
5
|
+
subject { Class.new(Grape::API) }
|
6
|
+
|
7
|
+
let(:endpoint) { subject.endpoints.first }
|
8
|
+
|
9
|
+
describe "#async_route?" do
|
10
|
+
|
11
|
+
it "should return true" do
|
12
|
+
subject.async
|
13
|
+
subject.get('/async') { 'ok' }
|
14
|
+
expect(endpoint.async_route?).to be_truthy
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return false" do
|
18
|
+
subject.get('/sync') { 'ok' }
|
19
|
+
expect(endpoint.async_route?).to be_falsy
|
20
|
+
end
|
21
|
+
|
22
|
+
context "specified with eventmachine" do
|
23
|
+
|
24
|
+
it "should return true" do
|
25
|
+
subject.async :em
|
26
|
+
subject.get('/async') { 'ok' }
|
27
|
+
expect(endpoint.async_route?).to be_truthy
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'thin'
|
4
|
+
require 'puma'
|
5
|
+
|
6
|
+
describe Grape::Middleware::Async do
|
7
|
+
|
8
|
+
def setup_async_obj!
|
9
|
+
async_obj = double(:async_obj)
|
10
|
+
allow(async_obj).to receive(:call)
|
11
|
+
allow_any_instance_of(Grape::Middleware::Async).to receive(:async_io) {
|
12
|
+
async_obj
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:host) { 'localhost' }
|
17
|
+
let(:port) { 3333 }
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
Spec::Support::EndpointFaker::FakerAPI.clear_requests!
|
21
|
+
end
|
22
|
+
|
23
|
+
context "server async requests", type: :feature do
|
24
|
+
|
25
|
+
let(:reqs_tracker) { Spec::Support::EndpointFaker::FakerAPI.requests }
|
26
|
+
let(:async_responses) { %w(start start start done done done) }
|
27
|
+
let(:sync_responses) { %w(start done start done start done) }
|
28
|
+
|
29
|
+
shared_examples "async requests" do
|
30
|
+
|
31
|
+
let(:route) { '/async' }
|
32
|
+
|
33
|
+
it "should make 3 thread based async requests" do
|
34
|
+
threads = []
|
35
|
+
3.times do
|
36
|
+
threads << Thread.new {
|
37
|
+
`curl -s http://#{host}:#{port}#{route}`
|
38
|
+
}
|
39
|
+
end
|
40
|
+
threads.each(&:join)
|
41
|
+
expect(reqs_tracker).to eql(async_responses)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
shared_examples "sync requests" do
|
47
|
+
|
48
|
+
let(:route) { '/sync' }
|
49
|
+
|
50
|
+
it "should make sync 3 requests" do
|
51
|
+
threads = []
|
52
|
+
3.times do
|
53
|
+
threads << Thread.new {
|
54
|
+
`curl -s http://#{host}:#{port}#{route}`
|
55
|
+
}
|
56
|
+
end
|
57
|
+
threads.each(&:join)
|
58
|
+
expect(reqs_tracker).to eql(sync_responses)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
context "using the thin server" do
|
64
|
+
|
65
|
+
before(:all) do
|
66
|
+
@server = Thread.new {
|
67
|
+
Thin::Server.start('localhost', 3333) { run Spec::Support::EndpointFaker::FakerAPI }
|
68
|
+
}
|
69
|
+
sleep(1)
|
70
|
+
end
|
71
|
+
|
72
|
+
after(:all) do
|
73
|
+
@server.kill if @server.is_a?(Thread) and @server.alive?
|
74
|
+
end
|
75
|
+
|
76
|
+
context "sync endpoints are run as sync" do
|
77
|
+
it_behaves_like "sync requests"
|
78
|
+
end
|
79
|
+
|
80
|
+
context "threaded async endpoints are run as async" do
|
81
|
+
it_behaves_like "async requests"
|
82
|
+
end
|
83
|
+
|
84
|
+
context "EM async endpoints are run as async" do
|
85
|
+
it_behaves_like "async requests" do
|
86
|
+
let(:route) { '/async_em' }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
context "using the puma server" do
|
93
|
+
|
94
|
+
before(:all) do
|
95
|
+
@server = Thread.new {
|
96
|
+
app = Spec::Support::EndpointFaker::FakerAPI.new
|
97
|
+
Puma::Server.new(app).tap do |s|
|
98
|
+
s.add_tcp_listener 'localhost', 3333
|
99
|
+
end.run
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
after(:all) do
|
104
|
+
@server.kill if @server.is_a?(Thread) and @server.alive?
|
105
|
+
end
|
106
|
+
|
107
|
+
context "sync endpoints are run as sync" do
|
108
|
+
it_behaves_like "sync requests"
|
109
|
+
end
|
110
|
+
|
111
|
+
context "threaded async endpoints are run as async" do
|
112
|
+
it_behaves_like "async requests"
|
113
|
+
end
|
114
|
+
|
115
|
+
context "EM async endpoints are run as async" do
|
116
|
+
it_behaves_like "async requests" do
|
117
|
+
let(:route) { '/async_em' }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
context "using the passenger server" do
|
124
|
+
|
125
|
+
pending "Requires passenger support for Capybara"
|
126
|
+
# it_behaves_like "async requests"
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
context "using the webrick server" do
|
131
|
+
|
132
|
+
before(:all) do
|
133
|
+
@server = Thread.new {
|
134
|
+
Rack::Handler::WEBrick.run(Spec::Support::EndpointFaker::FakerAPI.new, :Port => 3333)
|
135
|
+
}
|
136
|
+
sleep(1)
|
137
|
+
end
|
138
|
+
|
139
|
+
after(:all) do
|
140
|
+
@server.kill if @server.is_a?(Thread) and @server.alive?
|
141
|
+
end
|
142
|
+
|
143
|
+
context "sync endpoints are run as sync" do
|
144
|
+
it_behaves_like "sync requests"
|
145
|
+
end
|
146
|
+
|
147
|
+
context "async endpoints are run as sync" do
|
148
|
+
let(:route) { '/async' }
|
149
|
+
it_behaves_like "sync requests"
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "#call!" do
|
157
|
+
|
158
|
+
let(:app) {
|
159
|
+
Rack::Builder.new do
|
160
|
+
run Spec::Support::EndpointFaker::FakerAPI.new
|
161
|
+
end
|
162
|
+
}
|
163
|
+
|
164
|
+
context "async endpoints" do
|
165
|
+
it "should pass through the async middleware" do
|
166
|
+
expect_any_instance_of(Grape::Middleware::Async).to receive(:call!).and_return([-1, {}, []])
|
167
|
+
get '/async'
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should receive async response" do
|
171
|
+
setup_async_obj!
|
172
|
+
get '/async'
|
173
|
+
expect(last_response.status).to eq(-1)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should NOT receive async response without server asysnc support" do
|
177
|
+
get '/async'
|
178
|
+
expect(last_response.status).to eq(200)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context "sync endpoints" do
|
183
|
+
it "should pass through the async middleware" do
|
184
|
+
expect_any_instance_of(Grape::Middleware::Async).to receive(:call!).and_return([200, {}, ['ok']])
|
185
|
+
get '/sync'
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should NOT receive async response" do
|
189
|
+
get '/sync'
|
190
|
+
expect(last_response.status).to eq(200)
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should NOT receive async response without server asysnc support" do
|
194
|
+
get '/sync'
|
195
|
+
expect(last_response.status).to eq(200)
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'support'))
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'bundler'
|
7
|
+
require 'pry'
|
8
|
+
require 'rack/test'
|
9
|
+
require 'grape-async'
|
10
|
+
require 'endpoint_faker'
|
11
|
+
|
12
|
+
Bundler.setup :default, :test
|
13
|
+
|
14
|
+
RSpec.configure do |config|
|
15
|
+
config.include Rack::Test::Methods
|
16
|
+
config.raise_errors_for_deprecations!
|
17
|
+
config.drb = true
|
18
|
+
config.color = true
|
19
|
+
# config.order = :random
|
20
|
+
config.mock_with :rspec
|
21
|
+
|
22
|
+
config.before(:suite) do
|
23
|
+
end
|
24
|
+
|
25
|
+
config.before(:each) do
|
26
|
+
Grape::Util::InheritableSetting.reset_global!
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'pry'
|
3
|
+
|
4
|
+
module Spec
|
5
|
+
module Support
|
6
|
+
class EndpointFaker
|
7
|
+
|
8
|
+
class FakerAPI < Grape::API
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def requests
|
13
|
+
@@requests ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def clear_requests!
|
17
|
+
requests.clear
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
logger = Logger.new('log/app.log')
|
23
|
+
use Grape::Middleware::Async
|
24
|
+
use Rack::CommonLogger
|
25
|
+
|
26
|
+
helpers do
|
27
|
+
def logger
|
28
|
+
API.logger
|
29
|
+
end
|
30
|
+
|
31
|
+
def log_before!
|
32
|
+
FakerAPI.requests << "start"
|
33
|
+
end
|
34
|
+
|
35
|
+
def log_done!
|
36
|
+
FakerAPI.requests << "done"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
async
|
41
|
+
get :async do
|
42
|
+
log_before!
|
43
|
+
sleep(1)
|
44
|
+
present({ status: 'ok'})
|
45
|
+
log_done!
|
46
|
+
end
|
47
|
+
|
48
|
+
async :em
|
49
|
+
get :async_em do
|
50
|
+
log_before!
|
51
|
+
EM.add_timer(1) do
|
52
|
+
present({ status: 'ok'})
|
53
|
+
done
|
54
|
+
log_done!
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
get :sync do
|
59
|
+
log_before!
|
60
|
+
present({ status: 'ok'})
|
61
|
+
log_done!
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize(app, endpoint = FakerAPI.endpoints.first)
|
67
|
+
@app = app
|
68
|
+
@endpoint = endpoint
|
69
|
+
end
|
70
|
+
|
71
|
+
def call(env)
|
72
|
+
@endpoint.instance_exec do
|
73
|
+
@request = Grape::Request.new(env.dup)
|
74
|
+
end
|
75
|
+
|
76
|
+
@app.call(env.merge('api.endpoint' => @endpoint))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grape-async
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lachlan Laycock
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: grape
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.14'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: eventmachine
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rack-test
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.5'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.5'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.0'
|
111
|
+
description: Async endpoints for Grape APIs
|
112
|
+
email:
|
113
|
+
- l.laycock@stuart.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".DS_Store"
|
119
|
+
- ".gitignore"
|
120
|
+
- Gemfile
|
121
|
+
- Gemfile.lock
|
122
|
+
- Guardfile
|
123
|
+
- LICENSE
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- examples/config.ru
|
127
|
+
- examples/endpoint_faker.ru
|
128
|
+
- grape-async.gemspec
|
129
|
+
- lib/.DS_Store
|
130
|
+
- lib/grape-async.rb
|
131
|
+
- lib/grape-async/api.rb
|
132
|
+
- lib/grape-async/endpoint.rb
|
133
|
+
- lib/grape-async/middleware/async.rb
|
134
|
+
- spec/factories.rb
|
135
|
+
- spec/lib/grape-async/api_spec.rb
|
136
|
+
- spec/lib/grape-async/endpoint_spec.rb
|
137
|
+
- spec/lib/grape-async/middleware/async_spec.rb
|
138
|
+
- spec/spec_helper.rb
|
139
|
+
- spec/support/endpoint_faker.rb
|
140
|
+
homepage: https://github.com/stuart/grape-async
|
141
|
+
licenses:
|
142
|
+
- MIT
|
143
|
+
metadata: {}
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
requirements: []
|
159
|
+
rubyforge_project:
|
160
|
+
rubygems_version: 2.4.5.1
|
161
|
+
signing_key:
|
162
|
+
specification_version: 4
|
163
|
+
summary: Enable asyncronous endpoints to avoid blocking slow requests within EventMachine
|
164
|
+
or Threads
|
165
|
+
test_files:
|
166
|
+
- spec/factories.rb
|
167
|
+
- spec/lib/grape-async/api_spec.rb
|
168
|
+
- spec/lib/grape-async/endpoint_spec.rb
|
169
|
+
- spec/lib/grape-async/middleware/async_spec.rb
|
170
|
+
- spec/spec_helper.rb
|
171
|
+
- spec/support/endpoint_faker.rb
|