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