em-midori 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|