em-midori 0.3.0 → 0.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 +4 -4
- data/lib/midori.rb +1 -6
- data/lib/midori/api.rb +2 -4
- data/lib/midori/api_engine.rb +26 -6
- data/lib/midori/core_ext/configurable.rb +1 -3
- data/lib/midori/version.rb +1 -1
- data/tutorial/essentials/installation.md +13 -19
- data/tutorial/essentials/request_handling.md +2 -3
- data/tutorial/essentials/routing.md +2 -2
- data/tutorial/essentials/runner.md +59 -0
- metadata +5 -14
- data/lib/midori/core_ext/event_loop.rb +0 -118
- data/lib/midori/core_ext/promise.rb +0 -70
- data/lib/midori/core_ext/safe_require.rb +0 -13
- data/lib/midori/core_ext/timer.rb +0 -22
- data/lib/midori/extension/file.rb +0 -51
- data/lib/midori/extension/net.rb +0 -42
- data/lib/midori/extension/redic.rb +0 -99
- data/lib/midori/extension/sequel/mysql2.rb +0 -111
- data/lib/midori/extension/sequel/postgres.rb +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b72c4ed56a72fbec6c4ad3476c864725ff799cf8
|
4
|
+
data.tar.gz: f18ebee6e089fd9e981519c54ac24daa14a015d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a800b68b2d413aadff9c1fa63569e28adf8194806012d2ce7d016f280ee3f28c228b4a22a44a481aa412551186887f4235f264e74811e1e78f5a47d81386d125
|
7
|
+
data.tar.gz: 1d9d3288d00d8293a18ae4baa99943d25a34adcb0a11c82a97ed3e1e63b0b014762e0a8f359069dcc65399184bbcb70cf4091011885b28eb83cada0b9049ab8a
|
data/lib/midori.rb
CHANGED
@@ -1,22 +1,17 @@
|
|
1
1
|
require 'digest/sha1'
|
2
2
|
require 'stringio'
|
3
|
-
require 'nio'
|
4
3
|
require 'fiber'
|
5
4
|
require 'logger'
|
6
5
|
require 'http/parser'
|
7
6
|
require 'mustermann'
|
7
|
+
require 'murasaki'
|
8
8
|
require 'socket'
|
9
9
|
|
10
10
|
require_relative 'midori/core_ext/configurable'
|
11
11
|
require_relative 'midori/core_ext/string'
|
12
|
-
require_relative 'midori/core_ext/event_loop'
|
13
|
-
require_relative 'midori/core_ext/timer'
|
14
|
-
require_relative 'midori/core_ext/promise'
|
15
12
|
require_relative 'midori/core_ext/define_class'
|
16
13
|
require_relative 'midori/core_ext/proc'
|
17
|
-
require_relative 'midori/core_ext/safe_require'
|
18
14
|
|
19
|
-
require_relative 'midori/version'
|
20
15
|
require_relative 'midori/const'
|
21
16
|
require_relative 'midori/exception'
|
22
17
|
require_relative 'midori/env'
|
data/lib/midori/api.rb
CHANGED
@@ -339,7 +339,7 @@ class Midori::API
|
|
339
339
|
# @param [String, Regexp] path path definition
|
340
340
|
# @param [Proc] block process to run when route matched
|
341
341
|
# @return [nil] nil
|
342
|
-
def add_route(method, path, block)
|
342
|
+
private def add_route(method, path, block)
|
343
343
|
# Argument check
|
344
344
|
raise ArgumentError unless path.is_a?String
|
345
345
|
|
@@ -380,13 +380,11 @@ class Midori::API
|
|
380
380
|
end
|
381
381
|
end
|
382
382
|
|
383
|
-
def inherited(subclass)
|
383
|
+
private def inherited(subclass)
|
384
384
|
subclass.class_initialize
|
385
385
|
end
|
386
386
|
end
|
387
387
|
|
388
|
-
private_class_method :add_route, :inherited
|
389
|
-
|
390
388
|
# Constants of supported methods in route definition
|
391
389
|
METHODS = %w( delete
|
392
390
|
get
|
data/lib/midori/api_engine.rb
CHANGED
@@ -22,7 +22,7 @@ class Midori::APIEngine
|
|
22
22
|
end
|
23
23
|
|
24
24
|
# Merge all routes with a Depth-first search
|
25
|
-
def merge(prefix, root_api, middlewares)
|
25
|
+
private def merge(prefix, root_api, middlewares)
|
26
26
|
root_api.routes[:MOUNT].each do |mount|
|
27
27
|
root_api.routes.merge!(merge(mount[0], mount[1], root_api.scope_middlewares)) do |_key, old_val, new_val|
|
28
28
|
old_val + new_val
|
@@ -48,7 +48,6 @@ class Midori::APIEngine
|
|
48
48
|
params = route.path.params(request.path)
|
49
49
|
next unless params # Skip if not matched
|
50
50
|
request.params = params
|
51
|
-
route.middlewares.each { |middleware| request = middleware.before(request) }
|
52
51
|
clean_room = Midori::CleanRoom.new(request)
|
53
52
|
if request.websocket?
|
54
53
|
# Send 101 Switching Protocol
|
@@ -66,10 +65,12 @@ class Midori::APIEngine
|
|
66
65
|
Midori::Sandbox.run(clean_room, route.function, connection.eventsource)
|
67
66
|
return Midori::Response.new
|
68
67
|
else
|
68
|
+
request = middleware_exec(route.middlewares, clean_room, request)
|
69
|
+
return request if request.is_a? Midori::Response # Early stop
|
69
70
|
result = Midori::Sandbox.run(clean_room, route.function)
|
70
|
-
clean_room.body = result
|
71
|
-
response =
|
72
|
-
route.middlewares
|
71
|
+
clean_room.body = result
|
72
|
+
response = result.is_a?(Midori::Response) ? result : clean_room.raw_response
|
73
|
+
response = middleware_exec(route.middlewares, clean_room, request, response)
|
73
74
|
return response
|
74
75
|
end
|
75
76
|
end
|
@@ -85,5 +86,24 @@ class Midori::APIEngine
|
|
85
86
|
header
|
86
87
|
end
|
87
88
|
|
88
|
-
|
89
|
+
# Exec middlewares
|
90
|
+
private def middleware_exec(middlewares, clean_room, request, response=nil)
|
91
|
+
result = response.nil? ? request : response
|
92
|
+
middlewares.each do |middleware|
|
93
|
+
if response.nil?
|
94
|
+
result = Midori::Sandbox.run(
|
95
|
+
clean_room,
|
96
|
+
proc { |req| middleware.before(req) },
|
97
|
+
result)
|
98
|
+
else
|
99
|
+
result = Midori::Sandbox.run(
|
100
|
+
clean_room,
|
101
|
+
proc { |req, resp| middleware.after(req, resp) },
|
102
|
+
request,
|
103
|
+
result)
|
104
|
+
end
|
105
|
+
return result if response.nil? && result.is_a?(Midori::Response) # Early stop
|
106
|
+
end
|
107
|
+
result
|
108
|
+
end
|
89
109
|
end
|
@@ -20,13 +20,11 @@ module Configurable
|
|
20
20
|
self
|
21
21
|
end
|
22
22
|
|
23
|
-
private
|
24
|
-
|
25
23
|
# Dynamically defines a method on settings.
|
26
24
|
# @param [String] name method name
|
27
25
|
# @param [Proc] content method content
|
28
26
|
# @return [nil] nil
|
29
|
-
def define_singleton(name, content = Proc.new)
|
27
|
+
private def define_singleton(name, content = Proc.new)
|
30
28
|
singleton_class.class_eval do
|
31
29
|
undef_method(name) if method_defined? name
|
32
30
|
String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
|
data/lib/midori/version.rb
CHANGED
@@ -6,38 +6,32 @@ Open up a command line prompt. Any commands prefaced with a dollar sign `$` sh
|
|
6
6
|
|
7
7
|
```
|
8
8
|
$ ruby -v
|
9
|
-
ruby 2.4.
|
9
|
+
ruby 2.4.1p111
|
10
10
|
```
|
11
11
|
|
12
12
|
Generally, midori supports the following ruby interpreters:
|
13
13
|
|
14
|
-
- Ruby (MRI) **>= 2.
|
15
|
-
- JRuby >= **9.0.4.0**
|
14
|
+
- Ruby (MRI) **>= 2.2.6**
|
16
15
|
|
17
16
|
For every version released, it would be tested and **officially ensured** in the following environments:
|
18
17
|
|
19
|
-
- Ruby
|
20
|
-
- 2.
|
21
|
-
- 2.
|
22
|
-
- 2.
|
23
|
-
- 2.4.0
|
24
|
-
- JRuby
|
25
|
-
- 9.0.4.0, OpenJDK 7
|
26
|
-
- 9.0.4.0, OracleJDK 7
|
27
|
-
- 9.0.4.0, OracleJDK 8
|
18
|
+
- Ruby (MRI)
|
19
|
+
- 2.2.7
|
20
|
+
- 2.3.4
|
21
|
+
- 2.4.1
|
28
22
|
|
29
23
|
**Note: **
|
30
24
|
|
31
|
-
- **For JRuby users,
|
32
|
-
- **For macOS users, you may meet performance problem due to the
|
25
|
+
- **For JRuby users, due to some C extensions used in midori, it is still unable to run on the current version of JRuby.**
|
26
|
+
- **For macOS users, you may meet performance problem due to the issue of [nio4r](https://github.com/socketry/nio4r/issues/125). Very few people would use macOS in production, so this issue may not affect much. We would still be working hard on fixing it, but the issue wouldn't be a high priority one.**
|
33
27
|
|
34
|
-
It's hard to say that if it is possible for running on other ruby implementations like Rubinius, if you're in favor of supporting more ruby implementations, you could open a ticket [here](https://github.com/heckpsi-lab/em-midori/issues), and we are glad to discuss it.
|
28
|
+
It's hard to say that if it is possible for running on other ruby implementations like Rubinius or RubyMotion, if you're in favor of supporting more ruby implementations, you could open a ticket [here](https://github.com/heckpsi-lab/em-midori/issues), and we are glad to discuss it.
|
35
29
|
|
36
30
|
## Install with RubyGems
|
37
31
|
|
38
32
|
```
|
39
33
|
$ gem install em-midori
|
40
|
-
Successfully installed em-midori-0.
|
34
|
+
Successfully installed em-midori-0.3.0
|
41
35
|
1 gem installed
|
42
36
|
```
|
43
37
|
|
@@ -50,7 +44,7 @@ $ ruby -r "midori" -e "class A < Midori::API;end;Midori::Runner.new(A).start"
|
|
50
44
|
If you see the following message, then everything now works fine.
|
51
45
|
|
52
46
|
```
|
53
|
-
Midori 0.
|
47
|
+
Midori 0.3.0 is now running on 127.0.0.1:8080
|
54
48
|
```
|
55
49
|
|
56
50
|
## Use Bundler
|
@@ -60,7 +54,7 @@ Example `Gemfile` of basic usage as following:
|
|
60
54
|
```ruby
|
61
55
|
source 'https://rubygems.org'
|
62
56
|
gem 'bundler', '~> 1.0'
|
63
|
-
gem 'em-midori', '~> 0.
|
57
|
+
gem 'em-midori', '~> 0.3', require: 'midori'
|
64
58
|
```
|
65
59
|
|
66
60
|
and then running:
|
@@ -83,7 +77,7 @@ To include built-in extensions of midori you could make your `Gemfile` like:
|
|
83
77
|
```ruby
|
84
78
|
source 'https://rubygems.org'
|
85
79
|
gem 'bundler', '~> 1.0'
|
86
|
-
gem 'em-midori', '~> 0.
|
80
|
+
gem 'em-midori', '~> 0.3', require: %w'midori midori/extension/sequel'
|
87
81
|
```
|
88
82
|
|
89
83
|
Using bunlder could make dependency management much easier, which helps a lot in scaling project. To learn more about bundler, you could see docs [here](http://bundler.io/docs.html).
|
@@ -49,7 +49,7 @@ class ExampleAPI < Midori::API
|
|
49
49
|
end
|
50
50
|
|
51
51
|
get '/case_2' do
|
52
|
-
header['Example-Header'] = 'Example-Value'
|
52
|
+
@header['Example-Header'] = 'Example-Value'
|
53
53
|
'Hello'
|
54
54
|
# HTTP/1.1 200 OK
|
55
55
|
# Server: Midori/1.0
|
@@ -60,11 +60,10 @@ class ExampleAPI < Midori::API
|
|
60
60
|
|
61
61
|
get '/case_3' do
|
62
62
|
@status = 202
|
63
|
-
header['Example-Header'] = 'Example-Value'
|
63
|
+
@header['Example-Header'] = 'Example-Value'
|
64
64
|
Midori::Response.new(status: 200, header: {}, body: 'Hello') # Overrides everything else
|
65
65
|
# HTTP/1.1 200 OK
|
66
66
|
# Server: Midori/1.0
|
67
|
-
# Example-Header: Example-Value
|
68
67
|
#
|
69
68
|
# Hello
|
70
69
|
end
|
@@ -40,7 +40,7 @@ end
|
|
40
40
|
|
41
41
|
Routes are matched in the order they are defined. The first route that matches the request is invoked.
|
42
42
|
|
43
|
-
Midori not only supports the methods above, it supports
|
43
|
+
Midori not only supports the methods above, it supports almost every method provided in RFC standards. You could look it up in [API doc](http://www.rubydoc.info/gems/em-midori/Midori/API) for more details.
|
44
44
|
|
45
45
|
## Params
|
46
46
|
|
@@ -111,7 +111,7 @@ class ExampleAPI < Midori::API
|
|
111
111
|
end
|
112
112
|
```
|
113
113
|
|
114
|
-
midori also supports `EventSource
|
114
|
+
midori also supports `EventSource` connection as part of your route.
|
115
115
|
|
116
116
|
Here's a chatroom example using eventsource in midori:
|
117
117
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Runner
|
2
|
+
|
3
|
+
## Introdution
|
4
|
+
|
5
|
+
`Runner` is the container of midori server. You could create, start, stop midori instance by `Runner`.
|
6
|
+
|
7
|
+
`Runner` use `Midori::Configure` as its configuration by default.
|
8
|
+
|
9
|
+
## Examples
|
10
|
+
|
11
|
+
Here're some examples for common usages
|
12
|
+
|
13
|
+
### Port Binding
|
14
|
+
|
15
|
+
Start midori instance with port `4567` instead of the default `8080`.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'midori'
|
19
|
+
class API < Midori::API
|
20
|
+
get '/' do
|
21
|
+
'Hello World'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Midori::Configure.set :port, 4567
|
26
|
+
Midori::Runner.new(API).start
|
27
|
+
```
|
28
|
+
|
29
|
+
### Address Binding
|
30
|
+
|
31
|
+
Start midori instance listening to all IP addresses.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'midori'
|
35
|
+
class API < Midori::API
|
36
|
+
get '/' do
|
37
|
+
'Hello World'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Midori::Configure.set :bind, '0.0.0.0'
|
42
|
+
Midori::Runner.new(API).start
|
43
|
+
```
|
44
|
+
|
45
|
+
### Stop Midori
|
46
|
+
|
47
|
+
Stop midori instance when specified route been called.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
require 'midori'
|
51
|
+
$runner = nil
|
52
|
+
class API < Midori::API
|
53
|
+
get '/stop' do
|
54
|
+
$runner.stop
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
$runner = Midori::Runner.new(API).start
|
59
|
+
```
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-midori
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- HeckPsi Lab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-06-
|
11
|
+
date: 2017-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: murasaki
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: mustermann
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -71,20 +71,11 @@ files:
|
|
71
71
|
- lib/midori/const.rb
|
72
72
|
- lib/midori/core_ext/configurable.rb
|
73
73
|
- lib/midori/core_ext/define_class.rb
|
74
|
-
- lib/midori/core_ext/event_loop.rb
|
75
74
|
- lib/midori/core_ext/proc.rb
|
76
|
-
- lib/midori/core_ext/promise.rb
|
77
|
-
- lib/midori/core_ext/safe_require.rb
|
78
75
|
- lib/midori/core_ext/string.rb
|
79
|
-
- lib/midori/core_ext/timer.rb
|
80
76
|
- lib/midori/env.rb
|
81
77
|
- lib/midori/eventsource.rb
|
82
78
|
- lib/midori/exception.rb
|
83
|
-
- lib/midori/extension/file.rb
|
84
|
-
- lib/midori/extension/net.rb
|
85
|
-
- lib/midori/extension/redic.rb
|
86
|
-
- lib/midori/extension/sequel/mysql2.rb
|
87
|
-
- lib/midori/extension/sequel/postgres.rb
|
88
79
|
- lib/midori/logger.rb
|
89
80
|
- lib/midori/middleware.rb
|
90
81
|
- lib/midori/request.rb
|
@@ -1,118 +0,0 @@
|
|
1
|
-
##
|
2
|
-
# EventLoop Module, providing main loop for events
|
3
|
-
module EventLoop
|
4
|
-
class << self
|
5
|
-
# Config EvnetLoop, call by default if any other methods called
|
6
|
-
# @param [NIO::Selector] selector an event selector
|
7
|
-
def config(selector = NIO::Selector.new)
|
8
|
-
# Raw NIO Selector
|
9
|
-
@selector = selector
|
10
|
-
# Array of active timers
|
11
|
-
@timers = []
|
12
|
-
# Hash of io and its callback
|
13
|
-
@ios = Hash.new
|
14
|
-
# IO queue
|
15
|
-
@queue = Hash.new
|
16
|
-
end
|
17
|
-
|
18
|
-
# Add timer in event loop
|
19
|
-
# @param [EventLoop::Timer] timer timer to insert
|
20
|
-
# @return [nil] nil
|
21
|
-
def add_timer(timer)
|
22
|
-
config if @selector.nil?
|
23
|
-
timer.start_time = Time.now.to_f + timer.time
|
24
|
-
@timers << timer
|
25
|
-
nil
|
26
|
-
end
|
27
|
-
|
28
|
-
# Register I/O event with queue protection
|
29
|
-
# @param [IO] io io to register
|
30
|
-
# @param [Symbol] interest :r for read only, :w for write only, and :rw for both
|
31
|
-
# @yield what to run when io callbacks
|
32
|
-
# @return [nil] nil
|
33
|
-
def register(io, interest=(:rw), &callback)
|
34
|
-
config if @selector.nil?
|
35
|
-
if @queue[io.to_i].nil?
|
36
|
-
@queue[io.to_i] = Array.new
|
37
|
-
register_raw(io, interest, callback)
|
38
|
-
else
|
39
|
-
@queue[io.to_i] << [io, interest, callback]
|
40
|
-
end
|
41
|
-
nil
|
42
|
-
end
|
43
|
-
|
44
|
-
# Register I/O event directly, without any queue protection
|
45
|
-
# @param [IO] io io to register
|
46
|
-
# @param [Symbol] interest :r for read only, :w for write only, and :rw for both
|
47
|
-
# @param [Proc] callback what to run when io callbacks
|
48
|
-
# @return [nil] nil
|
49
|
-
def register_raw(io, interest=(:rw), callback)
|
50
|
-
config if @selector.nil?
|
51
|
-
@selector.register(io, interest)
|
52
|
-
@ios[io] = { callback: callback }
|
53
|
-
nil
|
54
|
-
end
|
55
|
-
|
56
|
-
# Deregister I/O event
|
57
|
-
# @param [IO] io io to deregister
|
58
|
-
# @return [nil] nil
|
59
|
-
def deregister(io)
|
60
|
-
fd = io.to_i
|
61
|
-
@selector.deregister(io)
|
62
|
-
@ios.delete(io)
|
63
|
-
next_register = @queue[fd].shift
|
64
|
-
next_register.nil? ? @queue.delete(fd) : register_raw(*next_register)
|
65
|
-
nil
|
66
|
-
end
|
67
|
-
|
68
|
-
# Run I/O selector once
|
69
|
-
# @return [nil] nil
|
70
|
-
def run_once
|
71
|
-
config if @selector.nil?
|
72
|
-
@selector.select(0.2) do |monitor| # Timeout for 0.2 secs
|
73
|
-
@ios[monitor.io][:callback].call(monitor)
|
74
|
-
end
|
75
|
-
timer_once
|
76
|
-
nil
|
77
|
-
end
|
78
|
-
|
79
|
-
# Run timer once
|
80
|
-
# @return [nil] nil
|
81
|
-
def timer_once
|
82
|
-
config if @selector.nil?
|
83
|
-
now_time = Time.now.to_f
|
84
|
-
@timers.delete_if do |timer|
|
85
|
-
if timer.start_time < now_time
|
86
|
-
timer.callback.call
|
87
|
-
true
|
88
|
-
end
|
89
|
-
end
|
90
|
-
nil
|
91
|
-
end
|
92
|
-
|
93
|
-
# Start the event loop
|
94
|
-
# @return [nil] nil
|
95
|
-
def start
|
96
|
-
return if running?
|
97
|
-
@stop = false
|
98
|
-
until @stop
|
99
|
-
run_once
|
100
|
-
end
|
101
|
-
@stop = nil
|
102
|
-
end
|
103
|
-
|
104
|
-
# Set the stop flag
|
105
|
-
# @return [nil] nil
|
106
|
-
def stop
|
107
|
-
@stop = true
|
108
|
-
nil
|
109
|
-
end
|
110
|
-
|
111
|
-
# Detect the stop flag
|
112
|
-
# @return [Boolean] return if eventloop is set to be stopped
|
113
|
-
def running?
|
114
|
-
@stop = true if @stop.nil?
|
115
|
-
!@stop
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
##
|
2
|
-
# Meta-programming String for Syntactic Sugars
|
3
|
-
# Referenced from {Qiita}[http://qiita.com/south37/items/99a60345b22ef395d424]
|
4
|
-
class Promise
|
5
|
-
# Init a Promise
|
6
|
-
# @param [Proc] callback an async method
|
7
|
-
def initialize(&callback)
|
8
|
-
@callback = callback
|
9
|
-
end
|
10
|
-
|
11
|
-
# Define what to do after a method callbacks
|
12
|
-
# @param [Proc] resolve what on callback
|
13
|
-
# @return [nil] nil
|
14
|
-
def then(&resolve)
|
15
|
-
@callback.call(resolve)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
module Kernel
|
20
|
-
# Make fiber as async chain
|
21
|
-
# @param [Fiber] fiber root of async chain
|
22
|
-
def async_fiber(fiber)
|
23
|
-
chain = proc do |result|
|
24
|
-
next unless result.is_a? Promise
|
25
|
-
result.then do |val|
|
26
|
-
chain.call(fiber.resume(val))
|
27
|
-
end
|
28
|
-
end
|
29
|
-
chain.call(fiber.resume)
|
30
|
-
end
|
31
|
-
|
32
|
-
# Define an async method
|
33
|
-
# @param [Symbol] method method name
|
34
|
-
# @yield async method
|
35
|
-
# @example
|
36
|
-
# async :hello do
|
37
|
-
# puts 'Hello'
|
38
|
-
# end
|
39
|
-
def async(method)
|
40
|
-
define_singleton_method method do |*args|
|
41
|
-
async_fiber(Fiber.new {yield(*args)})
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Block the I/O to wait for async method response
|
46
|
-
# @param [Promise] promise promise method
|
47
|
-
# @example
|
48
|
-
# result = await SQL.query('SELECT * FROM hello')
|
49
|
-
def await(promise)
|
50
|
-
result = Fiber.yield promise
|
51
|
-
if result.is_a? PromiseException
|
52
|
-
raise result.payload
|
53
|
-
end
|
54
|
-
result
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
|
59
|
-
##
|
60
|
-
# Exceptions for Promises
|
61
|
-
# @!attribute [r] payload
|
62
|
-
# @return [Exception] raw execption
|
63
|
-
class PromiseException < Exception
|
64
|
-
attr_reader :payload
|
65
|
-
# Init PromiseException with existed Exception
|
66
|
-
# @param [Exception] payload raw execption
|
67
|
-
def initialize(payload)
|
68
|
-
@payload = payload
|
69
|
-
end
|
70
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
module Kernel
|
2
|
-
# Raise error if Load Failed
|
3
|
-
# @param [String] file file to Load
|
4
|
-
# @param [String] prompt To prompt what when load error
|
5
|
-
# @return [Boolean] whether it has been required already
|
6
|
-
def safe_require(file, prompt)
|
7
|
-
begin
|
8
|
-
require file
|
9
|
-
rescue LoadError => _e
|
10
|
-
raise prompt
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
##
|
2
|
-
# Timer Object in EventLoop
|
3
|
-
# @!attribute [r] time
|
4
|
-
# @return [Float] timeout length
|
5
|
-
# @!attribute [r] callback
|
6
|
-
# @return [Proc] proc to call when callbacks
|
7
|
-
# @!attribute start_time
|
8
|
-
# @return [Float] when timer should callbacks
|
9
|
-
class EventLoop::Timer
|
10
|
-
|
11
|
-
attr_reader :time, :callback
|
12
|
-
attr_accessor :start_time
|
13
|
-
|
14
|
-
# Init a timer with a time period and callback
|
15
|
-
# @param [Float] time timeout length
|
16
|
-
# @yield proc to call when callbacks
|
17
|
-
def initialize(time, &callback)
|
18
|
-
@time = time
|
19
|
-
@callback = callback
|
20
|
-
@start_time = Float::INFINITY
|
21
|
-
end
|
22
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
##
|
2
|
-
# Midori Extension of File reading and writing
|
3
|
-
class Midori::File
|
4
|
-
# Init File object
|
5
|
-
# @param [Array] args same args like File.new
|
6
|
-
def initialize(*args)
|
7
|
-
@file = File.new(*args)
|
8
|
-
end
|
9
|
-
|
10
|
-
# read file
|
11
|
-
# @return [String] string readed
|
12
|
-
def read
|
13
|
-
await(Promise.new do |resolve|
|
14
|
-
data = ''
|
15
|
-
EventLoop.register(@file, :r) do
|
16
|
-
if @file.eof?
|
17
|
-
EventLoop.deregister(@file)
|
18
|
-
resolve.call(data)
|
19
|
-
else
|
20
|
-
data << @file.read_nonblock(16384)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end)
|
24
|
-
end
|
25
|
-
|
26
|
-
# write file
|
27
|
-
# @param [String] data string to be written
|
28
|
-
def write(data)
|
29
|
-
await(Promise.new do |resolve|
|
30
|
-
written = 0
|
31
|
-
EventLoop.register(@file, :w) do
|
32
|
-
written += @file.write_nonblock(data[written..-1])
|
33
|
-
if written == data.size
|
34
|
-
EventLoop.deregister(@file)
|
35
|
-
resolve.call(written)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end)
|
39
|
-
end
|
40
|
-
|
41
|
-
# raw file object
|
42
|
-
# @return [File] file
|
43
|
-
def raw
|
44
|
-
@file
|
45
|
-
end
|
46
|
-
|
47
|
-
# Close the file
|
48
|
-
def close
|
49
|
-
@file.close
|
50
|
-
end
|
51
|
-
end
|
data/lib/midori/extension/net.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
require 'net/protocol'
|
2
|
-
|
3
|
-
##
|
4
|
-
# Meta programming Net class for async HTTP and FTP connection
|
5
|
-
class Net::BufferedIO
|
6
|
-
# Wait till io finishes
|
7
|
-
# @param [Symbol] interest
|
8
|
-
def wait_io(interest)
|
9
|
-
await(Promise.new do |resolve|
|
10
|
-
io = @io.to_io
|
11
|
-
EventLoop.register(io, interest) do
|
12
|
-
EventLoop.deregister(io)
|
13
|
-
resolve.call(self)
|
14
|
-
end
|
15
|
-
end)
|
16
|
-
end
|
17
|
-
|
18
|
-
# Fill until the operation finishes
|
19
|
-
def rbuf_fill
|
20
|
-
loop do
|
21
|
-
case rv = @io.read_nonblock(BUFSIZE, exception: false)
|
22
|
-
when String
|
23
|
-
return @rbuf << rv
|
24
|
-
when :wait_readable
|
25
|
-
wait_io(:r)
|
26
|
-
# @io.to_io.wait_readable(@read_timeout) or raise Net::ReadTimeout
|
27
|
-
# continue looping
|
28
|
-
when :wait_writable
|
29
|
-
# OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable.
|
30
|
-
# http://www.openssl.org/support/faq.html#PROG10
|
31
|
-
# :nocov:
|
32
|
-
wait_io(:w)
|
33
|
-
# @io.to_io.wait_writable(@read_timeout) or raise Net::ReadTimeout
|
34
|
-
# continue looping
|
35
|
-
when nil
|
36
|
-
# callers do not care about backtrace, so avoid allocating for it
|
37
|
-
raise EOFError, 'end of file reached', []
|
38
|
-
# :nocov:
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,99 +0,0 @@
|
|
1
|
-
##
|
2
|
-
# Meta-programming hiredis for redis async extension
|
3
|
-
module Hiredis
|
4
|
-
require 'hiredis/connection'
|
5
|
-
# Overwrite with ruby implementation to hook IO
|
6
|
-
require 'hiredis/ruby/connection'
|
7
|
-
require 'hiredis/ruby/reader'
|
8
|
-
# Redis Connection
|
9
|
-
Connection = Ruby::Connection
|
10
|
-
# Redis Result Reader
|
11
|
-
Reader = Ruby::Reader
|
12
|
-
|
13
|
-
##
|
14
|
-
# Meta-programming hiredis for redis async extension
|
15
|
-
module Ruby
|
16
|
-
##
|
17
|
-
# Meta-programming hiredis for redis async extension
|
18
|
-
class Connection
|
19
|
-
# Do redis query
|
20
|
-
# @param [Array] args equal to Hiredis write args
|
21
|
-
def query(args)
|
22
|
-
await(Promise.new do |resolve|
|
23
|
-
read_flag = false
|
24
|
-
data = pre_write(args)
|
25
|
-
written = 0
|
26
|
-
EventLoop.register(@sock, :rw) do |monitor|
|
27
|
-
if read_flag && monitor.readable?
|
28
|
-
# Reading
|
29
|
-
_read(resolve, @sock)
|
30
|
-
end
|
31
|
-
if !read_flag && monitor.writable?
|
32
|
-
# Writing
|
33
|
-
written += @sock.write_nonblock(data[written..-1])
|
34
|
-
read_flag = true if written == string_size(data)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end)
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
def pre_write(args)
|
42
|
-
command = []
|
43
|
-
command << "*#{args.size}"
|
44
|
-
args.each do |arg|
|
45
|
-
arg = arg.to_s
|
46
|
-
command << "$#{string_size arg}"
|
47
|
-
command << arg
|
48
|
-
end
|
49
|
-
data = command.join(COMMAND_DELIMITER) + COMMAND_DELIMITER
|
50
|
-
data.force_encoding('binary') if data.respond_to?(:force_encoding)
|
51
|
-
data
|
52
|
-
end
|
53
|
-
|
54
|
-
def _read(resolve, sock)
|
55
|
-
@reader.feed @sock.read_nonblock(1024)
|
56
|
-
reply = @reader.gets
|
57
|
-
if reply
|
58
|
-
EventLoop.deregister(sock)
|
59
|
-
resolve.call(reply)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
require 'redic'
|
67
|
-
|
68
|
-
##
|
69
|
-
# Meta-programming Redic for redis async extension
|
70
|
-
class Redic
|
71
|
-
# Meta-programming Redic for redis async extension
|
72
|
-
class Client
|
73
|
-
# Connect redis, yield optional
|
74
|
-
def connect
|
75
|
-
establish_connection unless connected?
|
76
|
-
if block_given?
|
77
|
-
# Redic default yield
|
78
|
-
# :nocov:
|
79
|
-
@semaphore.synchronize do
|
80
|
-
yield
|
81
|
-
end
|
82
|
-
# :nocov:
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
# Call without thread lock
|
87
|
-
# @param [Array] args same params as Redic
|
88
|
-
def call(*args)
|
89
|
-
@connection.query(*args)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
# Call without thread lock
|
94
|
-
# @param [Array] args same params as Redic
|
95
|
-
def call(*args)
|
96
|
-
@client.connect
|
97
|
-
@client.call(args)
|
98
|
-
end
|
99
|
-
end
|
@@ -1,111 +0,0 @@
|
|
1
|
-
safe_require 'sequel', 'gem install sequel'
|
2
|
-
require 'sequel/adapters/mysql2'
|
3
|
-
|
4
|
-
# Management of MySQL Sockets
|
5
|
-
MYSQL_SOCKETS = {}
|
6
|
-
|
7
|
-
##
|
8
|
-
# Meta-programming Sequel for async extensions
|
9
|
-
module Sequel
|
10
|
-
# Midori Extension of sequel MySQL through meta programming
|
11
|
-
module Mysql2
|
12
|
-
# Midori Extension of sequel MySQL through meta programming
|
13
|
-
class Database
|
14
|
-
# Execute the given SQL on the given connection. If the :type
|
15
|
-
# option is :select, yield the result of the query, otherwise
|
16
|
-
# yield the connection if a block is given.
|
17
|
-
# @param [Mysql2::Client] conn connection to database
|
18
|
-
# @param [String] sql sql query
|
19
|
-
# @param [Hash] opts optional options
|
20
|
-
# @return [Mysql2::Result] MySQL results
|
21
|
-
def _execute(conn, sql, opts) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
22
|
-
begin
|
23
|
-
# :nocov:
|
24
|
-
stream = opts[:stream]
|
25
|
-
if NativePreparedStatements
|
26
|
-
if (args = opts[:arguments])
|
27
|
-
args = args.map{|arg| bound_variable_value(arg)}
|
28
|
-
end
|
29
|
-
|
30
|
-
case sql
|
31
|
-
when ::Mysql2::Statement
|
32
|
-
stmt = sql
|
33
|
-
when Dataset
|
34
|
-
sql = sql.sql
|
35
|
-
close_stmt = true
|
36
|
-
stmt = conn.prepare(sql)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
r = log_connection_yield((log_sql = opts[:log_sql]) ? sql + log_sql : sql, conn, args) do
|
41
|
-
if stmt
|
42
|
-
conn.query_options.merge!(cache_rows: true,
|
43
|
-
database_timezone: timezone,
|
44
|
-
application_timezone: Sequel.application_timezone,
|
45
|
-
stream: stream,
|
46
|
-
cast_booleans: convert_tinyint_to_bool)
|
47
|
-
stmt.execute(*args)
|
48
|
-
# :nocov:
|
49
|
-
else
|
50
|
-
if MYSQL_SOCKETS[conn.socket].nil?
|
51
|
-
MYSQL_SOCKETS[conn.socket] = IO::open(conn.socket)
|
52
|
-
end
|
53
|
-
socket = MYSQL_SOCKETS[conn.socket]
|
54
|
-
await(Promise.new do |resolve|
|
55
|
-
count = 0
|
56
|
-
EventLoop.register(socket, :rw) do
|
57
|
-
if (count == 0)
|
58
|
-
# Writable
|
59
|
-
count += 1
|
60
|
-
conn.query(sql,
|
61
|
-
database_timezone: timezone,
|
62
|
-
application_timezone: Sequel.application_timezone,
|
63
|
-
stream: stream,
|
64
|
-
async: true)
|
65
|
-
else
|
66
|
-
# Readable
|
67
|
-
begin
|
68
|
-
EventLoop.deregister(socket)
|
69
|
-
resolve.call(conn.async_result)
|
70
|
-
rescue ::Mysql2::Error => e
|
71
|
-
resolve.call(PromiseException.new(e))
|
72
|
-
next
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# :nocov:
|
81
|
-
if opts[:type] == :select
|
82
|
-
if r
|
83
|
-
if stream
|
84
|
-
begin
|
85
|
-
r2 = yield r
|
86
|
-
ensure
|
87
|
-
# If r2 is nil, it means the block did not exit normally,
|
88
|
-
# so the rest of the results must be drained to prevent
|
89
|
-
# "commands out of sync" errors.
|
90
|
-
r.each{} unless r2
|
91
|
-
end
|
92
|
-
else
|
93
|
-
yield r
|
94
|
-
end
|
95
|
-
end
|
96
|
-
elsif block_given?
|
97
|
-
yield conn
|
98
|
-
end
|
99
|
-
rescue ::Mysql2::Error => e
|
100
|
-
raise_error(e)
|
101
|
-
ensure
|
102
|
-
if stmt
|
103
|
-
conn.query_options.replace(conn.instance_variable_get(:@sequel_default_query_options))
|
104
|
-
stmt.close if close_stmt
|
105
|
-
end
|
106
|
-
# :nocov:
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
safe_require 'sequel', 'gem install sequel'
|
2
|
-
require 'sequel/adapters/postgres'
|
3
|
-
|
4
|
-
# Management of Postgres Sockets
|
5
|
-
POSTGRES_SOCKETS = {}
|
6
|
-
|
7
|
-
##
|
8
|
-
# Midori Extension of sequel postgres through meta programming
|
9
|
-
class Sequel::Postgres::Adapter
|
10
|
-
# Call a sql request asynchronously
|
11
|
-
# @param [String] sql sql request
|
12
|
-
# @param [Array] args args to send
|
13
|
-
# @return [Array] sql query result
|
14
|
-
def execute_query(sql, args)
|
15
|
-
@db.log_connection_yield(sql, self, args) do
|
16
|
-
if POSTGRES_SOCKETS[self].nil?
|
17
|
-
POSTGRES_SOCKETS[self] = IO::open(socket)
|
18
|
-
end
|
19
|
-
socket_object = POSTGRES_SOCKETS[self]
|
20
|
-
await(Promise.new do |resolve|
|
21
|
-
count = 0
|
22
|
-
EventLoop.register(socket_object, :rw) do
|
23
|
-
begin
|
24
|
-
if (count == 0)
|
25
|
-
# Writable
|
26
|
-
unless is_busy
|
27
|
-
send_query(sql)
|
28
|
-
count += 1
|
29
|
-
end
|
30
|
-
else
|
31
|
-
# Readable
|
32
|
-
EventLoop.deregister(socket_object)
|
33
|
-
resolve.call(get_result)
|
34
|
-
end
|
35
|
-
# For extra errors
|
36
|
-
# :nocov:
|
37
|
-
rescue => e
|
38
|
-
resolve.call(PromiseException.new(e))
|
39
|
-
# :nocov:
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|