em-midori 0.0.9.2 → 0.0.9.3
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/em-midori.rb +1 -0
- data/lib/em-midori/api.rb +79 -96
- data/lib/em-midori/clean_room.rb +17 -0
- data/lib/em-midori/const.rb +6 -0
- data/lib/em-midori/define_class.rb +14 -1
- data/lib/em-midori/em_midori.rb +16 -7
- data/lib/em-midori/error.rb +10 -0
- data/lib/em-midori/eventsource.rb +6 -0
- data/lib/em-midori/middleware.rb +17 -0
- data/lib/em-midori/promise.rb +38 -16
- data/lib/em-midori/request.rb +18 -2
- data/lib/em-midori/response.rb +12 -0
- data/lib/em-midori/route.rb +9 -0
- data/lib/em-midori/server.rb +17 -0
- data/lib/em-midori/string.rb +9 -1
- data/lib/em-midori/version.rb +2 -1
- data/lib/em-midori/websocket.rb +28 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10e7c12fb4c851be82d6b1ebf1a5edbdfa8c3b77
|
4
|
+
data.tar.gz: 1e8c9cfe8c4cb0416f8e4f2f84ac2160e1825096
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a97cb0557f0865a4198415d9d72f3abf36b0b1929dda53b5fdcd0470c36d9ca99372da9deb3db60a510100052d36ae5e768456be6674d9ead089b4404d0f1e81
|
7
|
+
data.tar.gz: 7041791083c422d4b7929bcbb9fb97a43c37d82890333b61eba09a60fae44c355f2bd79c74c56b4dc44c0839d47e3793181f4e94b0a831a457899661218b35fd
|
data/lib/em-midori.rb
CHANGED
data/lib/em-midori/api.rb
CHANGED
@@ -3,165 +3,143 @@
|
|
3
3
|
class Midori::API
|
4
4
|
class << self
|
5
5
|
# Add GET method as a DSL for route definition
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# === Examples
|
11
|
-
# String as router
|
6
|
+
# @param [ String, Regexp ] path Accepts as part of path in route definition
|
7
|
+
# @yield what to run when route matched
|
8
|
+
# @return [ nil ] nil
|
9
|
+
# @example String as router
|
12
10
|
# get '/' do
|
13
11
|
# puts 'Hello World'
|
14
12
|
# end
|
15
13
|
#
|
16
|
-
# Regex as router
|
14
|
+
# @example Regex as router
|
17
15
|
# get /\/hello\/(.*?)/ do
|
18
16
|
# puts 'Hello World'
|
19
17
|
# end
|
20
18
|
def get(path, &block) end
|
21
19
|
|
22
20
|
# Add POST method as a DSL for route definition
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# === Examples
|
28
|
-
# String as router
|
21
|
+
# @param [ String, Regexp ] path Accepts as part of path in route definition
|
22
|
+
# @yield what to run when route matched
|
23
|
+
# @return [ nil ] nil
|
24
|
+
# @example String as router
|
29
25
|
# post '/' do
|
30
26
|
# puts 'Hello World'
|
31
27
|
# end
|
32
28
|
#
|
33
|
-
# Regex as router
|
29
|
+
# @example Regex as router
|
34
30
|
# post /\/hello\/(.*?)/ do
|
35
31
|
# puts 'Hello World'
|
36
32
|
# end
|
37
33
|
def post(path, &block) end
|
38
34
|
|
39
35
|
# Add PUT method as a DSL for route definition
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# === Examples
|
45
|
-
# String as router
|
36
|
+
# @param [ String, Regexp ] path Accepts as part of path in route definition
|
37
|
+
# @yield what to run when route matched
|
38
|
+
# @return [ nil ] nil
|
39
|
+
# @example String as router
|
46
40
|
# put '/' do
|
47
41
|
# puts 'Hello World'
|
48
42
|
# end
|
49
43
|
#
|
50
|
-
# Regex as router
|
44
|
+
# @example Regex as router
|
51
45
|
# put /\/hello\/(.*?)/ do
|
52
46
|
# puts 'Hello World'
|
53
47
|
# end
|
54
48
|
def put(path, &block) end
|
55
49
|
|
56
50
|
# Add DELETE method as a DSL for route definition
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# === Examples
|
62
|
-
# String as router
|
51
|
+
# @param [ String, Regexp ] path Accepts as part of path in route definition
|
52
|
+
# @yield what to run when route matched
|
53
|
+
# @return [ nil ] nil
|
54
|
+
# @example String as router
|
63
55
|
# delete '/' do
|
64
56
|
# puts 'Hello World'
|
65
57
|
# end
|
66
58
|
#
|
67
|
-
# Regex as router
|
59
|
+
# @example Regex as router
|
68
60
|
# delete /\/hello\/(.*?)/ do
|
69
61
|
# puts 'Hello World'
|
70
62
|
# end
|
71
63
|
def delete(path, &block) end
|
72
64
|
|
73
65
|
# Add OPTIONS method as a DSL for route definition
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
# nil
|
78
|
-
# === Examples
|
79
|
-
# String as router
|
66
|
+
# @param [ String, Regexp ] path Accepts as part of path in route definition
|
67
|
+
# @return [ nil ] nil
|
68
|
+
# @example String as router
|
80
69
|
# options '/' do
|
81
70
|
# puts 'Hello World'
|
82
71
|
# end
|
83
72
|
#
|
84
|
-
# Regex as router
|
73
|
+
# @example Regex as router
|
85
74
|
# options /\/hello\/(.*?)/ do
|
86
75
|
# puts 'Hello World'
|
87
76
|
# end
|
88
77
|
def options(path, &block) end
|
89
78
|
|
90
79
|
# Add LINK method as a DSL for route definition
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
# === Examples
|
96
|
-
# String as router
|
80
|
+
# @param [ String, Regexp ] path Accepts as part of path in route definition
|
81
|
+
# @yield what to run when route matched
|
82
|
+
# @return [ nil ] nil
|
83
|
+
# @example String as router
|
97
84
|
# link '/' do
|
98
85
|
# puts 'Hello World'
|
99
86
|
# end
|
100
87
|
#
|
101
|
-
# Regex as router
|
88
|
+
# @example Regex as router
|
102
89
|
# link /\/hello\/(.*?)/ do
|
103
90
|
# puts 'Hello World'
|
104
91
|
# end
|
105
92
|
def link(path, &block) end
|
106
93
|
|
107
94
|
# Add UNLINK method as a DSL for route definition
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
# === Examples
|
113
|
-
# String as router
|
95
|
+
# @param [ String, Regexp ] path Accepts as part of path in route definition
|
96
|
+
# @yield what to run when route matched
|
97
|
+
# @return [ nil ] nil
|
98
|
+
# @example String as router
|
114
99
|
# unlink '/' do
|
115
100
|
# puts 'Hello World'
|
116
101
|
# end
|
117
102
|
#
|
118
|
-
# Regex as router
|
103
|
+
# @example Regex as router
|
119
104
|
# unlink /\/hello\/(.*?)/ do
|
120
105
|
# puts 'Hello World'
|
121
106
|
# end
|
122
107
|
def unlink(path, &block) end
|
123
108
|
|
124
109
|
# Add WEBSOCKET method as a DSL for route definition
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
130
|
-
# String as router
|
131
|
-
# unlink '/' do
|
110
|
+
# @param [ String, Regexp ] path Accepts as part of path in route definition
|
111
|
+
# @yield what to run when route matched
|
112
|
+
# @return [ nil ] nil
|
113
|
+
# @example String as router
|
114
|
+
# websocket '/' do
|
132
115
|
# puts 'Hello World'
|
133
116
|
# end
|
134
117
|
#
|
135
|
-
# Regex as router
|
136
|
-
#
|
118
|
+
# @example Regex as router
|
119
|
+
# websocket /\/hello\/(.*?)/ do
|
137
120
|
# puts 'Hello World'
|
138
121
|
# end
|
139
122
|
def websocket(path, &block) end
|
140
123
|
|
141
124
|
# Add EVENTSOURCE method as a DSL for route definition
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
# === Examples
|
147
|
-
# String as router
|
148
|
-
# unlink '/' do
|
125
|
+
# @param [ String, Regexp ] path Accepts as part of path in route definition
|
126
|
+
# @return [ nil ] nil
|
127
|
+
# @example String as router
|
128
|
+
# eventsource '/' do
|
149
129
|
# puts 'Hello World'
|
150
130
|
# end
|
151
131
|
#
|
152
|
-
# Regex as router
|
153
|
-
#
|
132
|
+
# @example Regex as router
|
133
|
+
# eventsource /\/hello\/(.*?)/ do
|
154
134
|
# puts 'Hello World'
|
155
135
|
# end
|
156
136
|
def eventsource(path, &block) end
|
157
137
|
|
158
138
|
# Implementation of route DSL
|
159
|
-
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
#
|
163
|
-
# === Returns
|
164
|
-
# nil
|
139
|
+
# @param [ String ] method HTTP method
|
140
|
+
# @param [ String, Regexp ] path path definition
|
141
|
+
# @param [ Proc ] block process to run when route matched
|
142
|
+
# @return [ nil ] nil
|
165
143
|
def add_route(method, path, block)
|
166
144
|
if path.class == String
|
167
145
|
# Convert String to Regexp to provide performance boost (Precompiled Regexp)
|
@@ -173,10 +151,11 @@ class Midori::API
|
|
173
151
|
end
|
174
152
|
|
175
153
|
# Process after receive data from client
|
176
|
-
#
|
177
|
-
#
|
178
|
-
#
|
179
|
-
# [
|
154
|
+
# @param request [ Midori::Request ] Http Raw Request
|
155
|
+
# @param connection [ EM::Connection ] A connection created by EventMachine
|
156
|
+
# @yield what to run when route matched
|
157
|
+
# @return [ Midori::Response ] Http Response
|
158
|
+
# @raise [ Midori::Error::NotFound ] If no route matched
|
180
159
|
def receive(request, connection = nil)
|
181
160
|
@route.each do |route|
|
182
161
|
matched = match(route.method, route.path, request.method, request.path)
|
@@ -188,8 +167,6 @@ class Midori::API
|
|
188
167
|
end
|
189
168
|
middlewares.each { |middleware| request = middleware.before(request) }
|
190
169
|
clean_room = Midori::CleanRoom.new(request, middlewares, body_accept)
|
191
|
-
@helpers ||= []
|
192
|
-
@helpers.map { |block| clean_room.instance_exec(&block) }
|
193
170
|
if request.websocket?
|
194
171
|
# Send 101 Switching Protocol
|
195
172
|
connection.send_data Midori::Response.new(101, websocket_header(request.header['Sec-WebSocket-Key']), '')
|
@@ -211,15 +188,13 @@ class Midori::API
|
|
211
188
|
end
|
212
189
|
|
213
190
|
# Match route with given definition
|
214
|
-
#
|
215
|
-
#
|
216
|
-
#
|
217
|
-
#
|
218
|
-
#
|
219
|
-
# if not matched
|
220
|
-
#
|
221
|
-
# else returns an array of parameter string matched
|
222
|
-
# === Examples
|
191
|
+
# @param [ String ] method Accepts an HTTP/1.1 method like GET POST PUT ...
|
192
|
+
# @param [ Regexp ] path Precompiled route definition.
|
193
|
+
# @param [ String ] request_method HTTP Request Method
|
194
|
+
# @param [ String ] request_path HTTP Request Path
|
195
|
+
# @return [ Array ] matched parameters
|
196
|
+
# @return [ Boolean ] false if not matched
|
197
|
+
# @example match a route
|
223
198
|
# match('GET', /^\/user\/(.*?)\/order\/(.*?)$/, '/user/foo/order/bar') # => ['foo', 'bar']
|
224
199
|
def match(method, path, request_method, request_path)
|
225
200
|
if request_method == method
|
@@ -232,11 +207,9 @@ class Midori::API
|
|
232
207
|
end
|
233
208
|
|
234
209
|
# Convert String path to its Regexp equivalent
|
235
|
-
#
|
236
|
-
#
|
237
|
-
#
|
238
|
-
# Regexp equivalent
|
239
|
-
# === Examples
|
210
|
+
# @param [ String ] path String route definition
|
211
|
+
# @return [ Regexp ] Regexp equivalent
|
212
|
+
# @example
|
240
213
|
# convert_route('/user/:id/order/:order_id') # => Regexp
|
241
214
|
def convert_route(path)
|
242
215
|
path = '^' + path
|
@@ -246,13 +219,21 @@ class Midori::API
|
|
246
219
|
Regexp.new path
|
247
220
|
end
|
248
221
|
|
222
|
+
# Use a middleware in the all routes
|
223
|
+
# @param [Class] middleware Inherited from +Midori::Middleware+
|
224
|
+
# @return [nil] nil
|
249
225
|
def use(middleware, *args)
|
250
226
|
middleware = middleware.new(*args)
|
227
|
+
CleanRoom.class_exec { middleware.helper }
|
251
228
|
@middleware = [] if @middleware.nil?
|
252
229
|
@middleware << middleware
|
253
230
|
@body_accept = middleware.body_accept
|
231
|
+
nil
|
254
232
|
end
|
255
233
|
|
234
|
+
# Return websocket header with given key
|
235
|
+
# @param [String] key 'Sec-WebSocket-Key' in request header
|
236
|
+
# @return [Hash] header
|
256
237
|
def websocket_header(key)
|
257
238
|
{
|
258
239
|
'Upgrade' => 'websocket',
|
@@ -261,15 +242,17 @@ class Midori::API
|
|
261
242
|
}
|
262
243
|
end
|
263
244
|
|
245
|
+
# Helper block for defining methods in APIs
|
246
|
+
# @yield define what to run in CleanRoom
|
264
247
|
def helper(&block)
|
265
|
-
|
266
|
-
@helpers << block
|
248
|
+
Midori::CleanRoom.class_exec(&block)
|
267
249
|
end
|
268
250
|
end
|
269
251
|
|
270
252
|
private_class_method :add_route
|
271
253
|
|
272
|
-
|
254
|
+
# Constants of supported methods in route definition
|
255
|
+
METHODS = %w(get post put delete options link unlink websocket eventsource).freeze
|
273
256
|
|
274
257
|
# Magics to fill DSL methods through dynamically class method definition
|
275
258
|
METHODS.each do |method|
|
data/lib/em-midori/clean_room.rb
CHANGED
@@ -1,5 +1,14 @@
|
|
1
|
+
##
|
2
|
+
# This class is used to be sandbox of requests processing.
|
3
|
+
# @attr [Fixnum] code HTTP response code
|
4
|
+
# @attr [Hash] header HTTP response header
|
5
|
+
# @attr [Object] body HTTP response body. String could is accepted by default, but could leave for further process with +Midori::Midlleware+
|
6
|
+
# @attr [Midori::Request] request HTTP request
|
1
7
|
class Midori::CleanRoom
|
2
8
|
attr_accessor :code, :header, :body, :request
|
9
|
+
# @param [Midori::Request] request HTTP request
|
10
|
+
# @param [Array<Midori::Middleware>] middleware middlewares to run
|
11
|
+
# @param [Array<Class>] body_accept what class for body could last middleware accept by default
|
3
12
|
def initialize(request, middleware = [], body_accept = [String])
|
4
13
|
@status = 200
|
5
14
|
@header = Midori::Const::DEFAULT_HEADER.clone
|
@@ -9,15 +18,23 @@ class Midori::CleanRoom
|
|
9
18
|
@body_accept = body_accept
|
10
19
|
end
|
11
20
|
|
21
|
+
# Genenrate response from variables inside +Midori::CleanRoom+
|
22
|
+
# @return [Midori::Response] midori response
|
12
23
|
def raw_response
|
13
24
|
Midori::Response.new(@status, @header, @body)
|
14
25
|
end
|
15
26
|
|
27
|
+
# Add a middleware to a specific route
|
28
|
+
# @param [Class] middleware inherited form +Midori::Middleware+ class
|
29
|
+
# @param [Array<Object>] args for middleware initialize
|
30
|
+
# @return [nil] nil
|
16
31
|
def use(middleware, *args)
|
17
32
|
middleware = middleware.new(*args)
|
33
|
+
middleware.helper
|
18
34
|
@middleware = [] if @middleware.nil?
|
19
35
|
@middleware << middleware
|
20
36
|
@body_accept.replace middleware.body_accept
|
21
37
|
@request = middleware.before(request)
|
38
|
+
nil
|
22
39
|
end
|
23
40
|
end
|
data/lib/em-midori/const.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
##
|
2
|
+
# Module for store Midori Consts
|
1
3
|
module Midori::Const
|
4
|
+
# Hash table for converting numbers to HTTP/1.1 status code line
|
2
5
|
STATUS_CODE = {
|
3
6
|
100 => '100 Continue',
|
4
7
|
101 => '101 Switching Protocols',
|
@@ -32,6 +35,7 @@ module Midori::Const
|
|
32
35
|
415 => '415 Unsupported Media Type',
|
33
36
|
416 => '416 Requested range not satisfiable',
|
34
37
|
417 => '417 Expectation Failed',
|
38
|
+
451 => '451 ',
|
35
39
|
500 => '500 Internal Server Error',
|
36
40
|
501 => '501 Not Implemented',
|
37
41
|
502 => '502 Bad Gateway',
|
@@ -42,10 +46,12 @@ module Midori::Const
|
|
42
46
|
STATUS_CODE.default = '500 Internal Server Error'
|
43
47
|
STATUS_CODE.freeze
|
44
48
|
|
49
|
+
# Default header for Basic HTTP response
|
45
50
|
DEFAULT_HEADER = {
|
46
51
|
'Server' => "Midori/#{Midori::VERSION}"
|
47
52
|
}
|
48
53
|
|
54
|
+
# Default header for Evenrsource response
|
49
55
|
EVENTSOURCE_HEADER = {
|
50
56
|
'Content-Type' => 'text-event-stream',
|
51
57
|
'Cache-Control' => 'no-cache',
|
@@ -1,16 +1,29 @@
|
|
1
|
-
|
1
|
+
##
|
2
|
+
# Meta-programming Kernel for Syntactic Sugars
|
3
|
+
module Kernel
|
2
4
|
# This method is implemented to dynamically generate class with given name and template.
|
3
5
|
# Referenced from {Ruby China}[https://ruby-china.org/topics/17382]
|
6
|
+
# @param [String] name name of class
|
7
|
+
# @param [Class] ancestor class to be inherited
|
8
|
+
# @yield inner block to be inserted into class
|
9
|
+
# @return [Class] the class defined
|
4
10
|
def define_class(name, ancestor = Object)
|
5
11
|
Object.const_set(name, Class.new(ancestor))
|
6
12
|
Object.const_get(name).class_eval(&Proc.new) if block_given?
|
7
13
|
Object.const_get(name)
|
8
14
|
end
|
9
15
|
|
16
|
+
# Define a batch of error handler with given name
|
17
|
+
# @param [Array<Symbol>] args names to be defined
|
18
|
+
# @return [nil] nil
|
19
|
+
# @example
|
20
|
+
# define_error(:foo_error, :bar_error)
|
21
|
+
# => nil, FooError < StandardError and BarError < StandardError would be defined
|
10
22
|
def define_error(*args)
|
11
23
|
args.each do |arg|
|
12
24
|
class_name = arg.to_s.split('_').collect(&:capitalize).join
|
13
25
|
define_class(class_name, StandardError)
|
14
26
|
end
|
27
|
+
nil
|
15
28
|
end
|
16
29
|
end
|
data/lib/em-midori/em_midori.rb
CHANGED
@@ -1,18 +1,27 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
##
|
2
|
+
# The main module of Midori
|
3
3
|
module Midori
|
4
|
-
@logger = ::Logger.new(
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
@logger = ::Logger.new(STDOUT)
|
5
|
+
# Start Midori Server instance
|
6
|
+
# @note This is an async method, but no callback
|
7
|
+
# @param [Class] api Inherit from +Midori::API+
|
8
|
+
# @param [String] ip The ip address to bind
|
9
|
+
# @param [Fixnum] port Port number
|
10
|
+
# @param [Logger] logger Ruby logger
|
11
|
+
# @return [nil] nil
|
12
|
+
def self.run(api = Midori::API, ip = '127.0.0.1', port = 8081, logger = ::Logger.new(STDOUT))
|
9
13
|
@logger = logger
|
10
14
|
EventMachine.run do
|
11
15
|
@logger.info "Midori #{Midori::VERSION} is now running on #{ip}:#{port}".blue
|
12
16
|
@midori_server = EventMachine.start_server ip, port, Midori::Server, api, logger
|
13
17
|
end
|
18
|
+
nil
|
14
19
|
end
|
15
20
|
|
21
|
+
# Stop Midori Server instance
|
22
|
+
# @note This is an async method, but no callback
|
23
|
+
# @return [Boolean] [true] stop successfully
|
24
|
+
# @return [Boolean] [false] nothing to stop
|
16
25
|
def self.stop
|
17
26
|
if @midori_server.nil?
|
18
27
|
@logger.error 'Midori Server has NOT been started'.red
|
data/lib/em-midori/error.rb
CHANGED
@@ -1,10 +1,20 @@
|
|
1
|
+
##
|
2
|
+
# This module store errors to be handled in Midori
|
1
3
|
module Midori::Error
|
4
|
+
# No route matched
|
2
5
|
class NotFound < StandardError; end
|
6
|
+
# Midori doesn't support continuous frame of WebSockets yet
|
3
7
|
class ContinuousFrame < StandardError; end
|
8
|
+
# WebSocket OpCode not defined in RFC standards
|
4
9
|
class OpCodeError < StandardError; end
|
10
|
+
# Websocket request not masked
|
5
11
|
class NotMasked < StandardError; end
|
12
|
+
# Websocket frame has ended
|
6
13
|
class FrameEnd < StandardError; end
|
14
|
+
# Websocket Ping Pong size too large
|
7
15
|
class PingPongSizeTooLarge < StandardError; end
|
16
|
+
# Not sending String in EventSource
|
8
17
|
class EventSourceTypeError < StandardError; end
|
18
|
+
# Insert a not middleware class to middleware list
|
9
19
|
class MiddlewareError < StandardError; end
|
10
20
|
end
|
@@ -1,10 +1,16 @@
|
|
1
|
+
##
|
2
|
+
# This class provides methods for EventSource connection instance.
|
3
|
+
# @attr [EM::Connection] connection the connection instance of EventMachine
|
1
4
|
class Midori::EventSource
|
2
5
|
attr_accessor :connection
|
3
6
|
|
7
|
+
# @param [EM::Connection] connection the connection instance of EventMachine
|
4
8
|
def initialize(connection)
|
5
9
|
@connection = connection
|
6
10
|
end
|
7
11
|
|
12
|
+
# Send data and close the connection
|
13
|
+
# @param [String] data data to be sent
|
8
14
|
def send(data)
|
9
15
|
raise Midori::Error::EventSourceTypeError unless data.is_a?String
|
10
16
|
@connection.send_data(data.split("\n").map {|str| "data: #{str}\n"}.join + "\n")
|
data/lib/em-midori/middleware.rb
CHANGED
@@ -1,15 +1,32 @@
|
|
1
|
+
##
|
2
|
+
# Ancestor of all middlewares
|
1
3
|
class Midori::Middleware
|
4
|
+
# Init a middleware
|
2
5
|
def initialize
|
3
6
|
end
|
4
7
|
|
8
|
+
# run before processing a request
|
9
|
+
# @param [Midori::Request] request raw request
|
10
|
+
# @return [Midori::Request] request to be further processed
|
5
11
|
def before(request)
|
6
12
|
request
|
7
13
|
end
|
8
14
|
|
15
|
+
# run after processing a request
|
16
|
+
# @param [Midori::Request] _request raw request
|
17
|
+
# @param [Midori::Response] response raw response
|
18
|
+
# @return [Midori::Response] response to be further processed
|
9
19
|
def after(_request, response)
|
10
20
|
response
|
11
21
|
end
|
12
22
|
|
23
|
+
# code to be inserted inside CleanRoom
|
24
|
+
# @return [nil] nil
|
25
|
+
def helper
|
26
|
+
end
|
27
|
+
|
28
|
+
# Acceptable body
|
29
|
+
# @return [Array<Class>] array of acceptable type's class
|
13
30
|
def body_accept
|
14
31
|
[String]
|
15
32
|
end
|
data/lib/em-midori/promise.rb
CHANGED
@@ -1,30 +1,52 @@
|
|
1
|
+
##
|
2
|
+
# Meta-programming String for Syntactic Sugars
|
1
3
|
# Referenced from {Qiita}[http://qiita.com/south37/items/99a60345b22ef395d424]
|
2
4
|
class Promise
|
5
|
+
# @param [Proc] callback an async method
|
3
6
|
def initialize(callback)
|
4
7
|
@callback = callback
|
5
8
|
end
|
6
9
|
|
10
|
+
# Define what to do after a method callbacked
|
11
|
+
# @param [Proc] resolve what if callbacked
|
12
|
+
# @param [Proc] reject what if callback failed
|
13
|
+
# @return [nil] nil
|
7
14
|
def then(resolve = ->() {}, reject = ->() {})
|
8
15
|
@callback.call(resolve, reject)
|
9
16
|
end
|
10
17
|
end
|
11
18
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
19
|
+
module Kernel
|
20
|
+
# Logic dealing of async method
|
21
|
+
# @param [Fiber] fiber a fiber to call
|
22
|
+
def async_internal(fiber)
|
23
|
+
chain = lambda do |result|
|
24
|
+
return if result.class != Promise
|
25
|
+
result.then(lambda do |val|
|
26
|
+
chain.call(fiber.resume(val))
|
27
|
+
end)
|
28
|
+
end
|
29
|
+
chain.call(fiber.resume)
|
30
|
+
end
|
21
31
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
32
|
+
# Define an async method
|
33
|
+
# @param [Symbol] method_name method name
|
34
|
+
# @yield async method
|
35
|
+
# @example
|
36
|
+
# async :hello do
|
37
|
+
# puts 'Hello'
|
38
|
+
# end
|
39
|
+
def async(method_name)
|
40
|
+
define_singleton_method method_name, ->(*args) {
|
41
|
+
async_internal(Fiber.new { yield(*args) })
|
42
|
+
}
|
43
|
+
end
|
27
44
|
|
28
|
-
|
29
|
-
|
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
|
+
Fiber.yield promise
|
51
|
+
end
|
30
52
|
end
|
data/lib/em-midori/request.rb
CHANGED
@@ -1,8 +1,19 @@
|
|
1
|
+
##
|
2
|
+
# Request class for midori
|
3
|
+
# @attr [String] ip client ip address
|
4
|
+
# @attr [Fixnum] port client port
|
5
|
+
# @attr [String] protocol protocol version of HTTP request
|
6
|
+
# @attr [String] path request path
|
7
|
+
# @attr [String] query_string request query string
|
8
|
+
# @attr [Hash] header request header
|
9
|
+
# @attr [String] body request body
|
10
|
+
# @attr [Boolean] parsed whether the request parsed
|
1
11
|
class Midori::Request
|
2
12
|
attr_accessor :ip, :port,
|
3
13
|
:protocol, :method, :path, :query_string,
|
4
14
|
:header, :body, :parsed
|
5
15
|
|
16
|
+
# Init Request
|
6
17
|
def initialize
|
7
18
|
@parsed = false
|
8
19
|
@is_websocket = false
|
@@ -10,8 +21,7 @@ class Midori::Request
|
|
10
21
|
end
|
11
22
|
|
12
23
|
# Init an request with StringIO data
|
13
|
-
#
|
14
|
-
# * +data+ [+StringIO+] - Request data
|
24
|
+
# @param [StringIO+] data Request data
|
15
25
|
def parse(data)
|
16
26
|
@header = {}
|
17
27
|
|
@@ -46,14 +56,20 @@ class Midori::Request
|
|
46
56
|
@parsed = true
|
47
57
|
end
|
48
58
|
|
59
|
+
# Syntatic sugur for whether a request is parsed
|
60
|
+
# @return [Boolean] parsed or not
|
49
61
|
def parsed?
|
50
62
|
@parsed
|
51
63
|
end
|
52
64
|
|
65
|
+
# Syntatic sugur for whether a request is a websocket request
|
66
|
+
# @return [Boolean] websocket or not
|
53
67
|
def websocket?
|
54
68
|
@is_websocket
|
55
69
|
end
|
56
70
|
|
71
|
+
# Syntatic sugur for whether a request is an eventsource request
|
72
|
+
# @return [Boolean] eventsource or not
|
57
73
|
def eventsource?
|
58
74
|
@is_eventsource
|
59
75
|
end
|
data/lib/em-midori/response.rb
CHANGED
@@ -1,18 +1,30 @@
|
|
1
|
+
##
|
2
|
+
# Class for midori response
|
3
|
+
# @attr [String] HTTP response status
|
4
|
+
# @attr [Hash] HTTP response header
|
5
|
+
# @attr [String] HTTP response body
|
1
6
|
class Midori::Response
|
2
7
|
attr_accessor :status, :header, :body
|
3
8
|
|
9
|
+
# @param [Fixnum] code HTTP response code
|
10
|
+
# @param [Hash] header HTTP response header
|
11
|
+
# @param [String] body HTTP response body
|
4
12
|
def initialize(code = 200, header = Midori::Const::DEFAULT_HEADER.clone, body = '')
|
5
13
|
@status = Midori::Const::STATUS_CODE[code]
|
6
14
|
@header = header
|
7
15
|
@body = body
|
8
16
|
end
|
9
17
|
|
18
|
+
# Generate header string from hash
|
19
|
+
# @return [String] generated string
|
10
20
|
def generate_header
|
11
21
|
@header.map do |key, value|
|
12
22
|
"#{key}: #{value}\r\n"
|
13
23
|
end.join
|
14
24
|
end
|
15
25
|
|
26
|
+
# Convert response to raw string
|
27
|
+
# @return [String] generated string
|
16
28
|
def to_s
|
17
29
|
"HTTP/1.1 #{@status}\r\n#{generate_header}\r\n#{@body}"
|
18
30
|
end
|
data/lib/em-midori/route.rb
CHANGED
@@ -1,5 +1,14 @@
|
|
1
|
+
##
|
2
|
+
# Class for Midori route
|
3
|
+
# @attr [String] method HTTP method
|
4
|
+
# @attr [Regexp] path regex to match
|
5
|
+
# @attr [Proc] function what to do after matched
|
1
6
|
class Midori::Route
|
2
7
|
attr_accessor :method, :path, :function
|
8
|
+
|
9
|
+
# @param [String] method HTTP method
|
10
|
+
# @param [Regexp] path regex to match
|
11
|
+
# @param [Proc] function what to do after matched
|
3
12
|
def initialize(method, path, function)
|
4
13
|
@method = method
|
5
14
|
@path = path
|
data/lib/em-midori/server.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
|
+
##
|
2
|
+
# Logics to EventMachine TCP Server, running inside +EM::Connection+
|
3
|
+
# @attr [Midori::Request] request
|
4
|
+
# @attr [Class] api inherited from Midori::API
|
5
|
+
# @attr [Midori::WebSocket] websocket websocket instance
|
6
|
+
# @attr [Midori::EventSource] eventsource eventsource instance
|
1
7
|
module Midori::Server
|
2
8
|
attr_accessor :request, :api, :websocket, :eventsource
|
3
9
|
|
10
|
+
# @param [Class] api inherited from Midori::API
|
11
|
+
# @param [Logger] logger global logger
|
4
12
|
def initialize(api, logger)
|
5
13
|
@api = api
|
6
14
|
@logger = logger
|
@@ -9,6 +17,8 @@ module Midori::Server
|
|
9
17
|
@eventsource = Midori::EventSource.new(self)
|
10
18
|
end
|
11
19
|
|
20
|
+
# Logics of receiving data
|
21
|
+
# @param [String] data raw data
|
12
22
|
def receive_data(data)
|
13
23
|
->() { async_internal(Fiber.new do
|
14
24
|
start_time = Time.now
|
@@ -26,6 +36,8 @@ module Midori::Server
|
|
26
36
|
end) }.call
|
27
37
|
end
|
28
38
|
|
39
|
+
# Logics of receiving new request
|
40
|
+
# @param [String] data raw data
|
29
41
|
def receive_new_request(data)
|
30
42
|
begin
|
31
43
|
@request.parse(data)
|
@@ -44,6 +56,8 @@ module Midori::Server
|
|
44
56
|
end
|
45
57
|
end
|
46
58
|
|
59
|
+
# Logics of receiving WebSocket request
|
60
|
+
# @param [String] data raw data
|
47
61
|
def websocket_request(data)
|
48
62
|
@websocket.decode(data)
|
49
63
|
case @websocket.opcode
|
@@ -69,6 +83,9 @@ module Midori::Server
|
|
69
83
|
close_connection_after_writing
|
70
84
|
end
|
71
85
|
|
86
|
+
# To call a websocket event if it exist
|
87
|
+
# @param [Symbol] event event name
|
88
|
+
# @param [Array] args arg list
|
72
89
|
def call_event(event, args = [])
|
73
90
|
(-> { @websocket.instance_exec(*args, &@websocket.events[event]) }.call) unless @websocket.events[event].nil?
|
74
91
|
end
|
data/lib/em-midori/string.rb
CHANGED
@@ -1,20 +1,28 @@
|
|
1
|
+
##
|
2
|
+
# Meta-programming String for Syntactic Sugars
|
1
3
|
class String
|
4
|
+
# @param [Fixnum] color_code ANSI color code
|
5
|
+
# @return [String] colored string
|
2
6
|
def colorize(color_code)
|
3
7
|
"\e[#{color_code}m#{self}\e[0m"
|
4
8
|
end
|
5
9
|
|
10
|
+
# color the string with red color
|
6
11
|
def red
|
7
12
|
colorize(31)
|
8
13
|
end
|
9
14
|
|
15
|
+
# color the string with green color
|
10
16
|
def green
|
11
17
|
colorize(32)
|
12
18
|
end
|
13
|
-
|
19
|
+
|
20
|
+
# color the string with yellow color
|
14
21
|
def yellow
|
15
22
|
colorize(33)
|
16
23
|
end
|
17
24
|
|
25
|
+
# color the string with blue color
|
18
26
|
def blue
|
19
27
|
colorize(34)
|
20
28
|
end
|
data/lib/em-midori/version.rb
CHANGED
data/lib/em-midori/websocket.rb
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
##
|
2
2
|
# This class provides methods for WebSocket connection instance.
|
3
|
+
# @attr [Array<Fixnum>, String] msg message send from client
|
4
|
+
# @attr [Fixnum] opcode operation code of WebSocket
|
5
|
+
# @attr [Hash] events response for different event
|
6
|
+
# @attr [EM::Connection] connection raw EventMachine connection
|
3
7
|
class Midori::WebSocket
|
4
8
|
attr_accessor :msg, :opcode, :events, :connection
|
5
9
|
|
10
|
+
# @param [EM::Connection] connection raw EventMachine connection
|
6
11
|
def initialize(connection)
|
7
12
|
@events = {}
|
8
13
|
@connection = connection
|
9
14
|
end
|
10
15
|
|
16
|
+
# Decode raw data send from client
|
17
|
+
# @param [String] data raw data
|
11
18
|
def decode(data)
|
12
19
|
# Fin and Opcode
|
13
20
|
byte_tmp = data.getbyte
|
@@ -21,6 +28,8 @@ class Midori::WebSocket
|
|
21
28
|
decode_mask(data)
|
22
29
|
end
|
23
30
|
|
31
|
+
# Decode masked message send from client
|
32
|
+
# @param [String] data raw data
|
24
33
|
def decode_mask(data)
|
25
34
|
# Mask
|
26
35
|
byte_tmp = data.getbyte
|
@@ -38,10 +47,21 @@ class Midori::WebSocket
|
|
38
47
|
# data.bytes {|byte| puts byte.to_s(16)}
|
39
48
|
end
|
40
49
|
|
50
|
+
# API definition for events
|
51
|
+
# @param [Symbol] event event name(open, message, close, ping, pong)
|
52
|
+
# @yield what to do after event matched
|
53
|
+
# @example
|
54
|
+
# websocket '/websocket' do |ws|
|
55
|
+
# ws.on :message do |msg|
|
56
|
+
# puts msg
|
57
|
+
# end
|
58
|
+
# end
|
41
59
|
def on(event, &block) # open, message, close, ping, pong
|
42
60
|
@events[event] = block
|
43
61
|
end
|
44
62
|
|
63
|
+
# Send data
|
64
|
+
# @param [Array<Fixnum>, String] msg data to send
|
45
65
|
def send(msg)
|
46
66
|
output = []
|
47
67
|
if msg.is_a?String
|
@@ -56,19 +76,27 @@ class Midori::WebSocket
|
|
56
76
|
end
|
57
77
|
end
|
58
78
|
|
79
|
+
# Send a Ping request
|
80
|
+
# @param [String] str string to send
|
59
81
|
def ping(str)
|
60
82
|
heartbeat(0b10001001, str)
|
61
83
|
end
|
62
84
|
|
85
|
+
# Send a Pong request
|
86
|
+
# @param [String] str string to send
|
63
87
|
def pong(str)
|
64
88
|
heartbeat(0b10001010, str)
|
65
89
|
end
|
66
90
|
|
91
|
+
# Ancestor of ping pong
|
92
|
+
# @param [Fixnum] method opcode
|
93
|
+
# @param [String] str string to send
|
67
94
|
def heartbeat(method, str)
|
68
95
|
raise Midori::Error::PingPongSizeTooLarge if str.size > 125
|
69
96
|
@connection.send_data [method, str.size, str].pack("CCA#{str.size}")
|
70
97
|
end
|
71
98
|
|
99
|
+
# Close a websocket connection
|
72
100
|
def close
|
73
101
|
raise Midori::Error::FrameEnd
|
74
102
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-midori
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.9.
|
4
|
+
version: 0.0.9.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- HeckPsi Lab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: eventmachine
|