midori.rb 0.5.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/example.rb +17 -0
- data/example2.rb +19 -0
- data/ext/midori/websocket.c +73 -19
- data/lib/midori.rb +4 -4
- data/lib/midori/configure.rb +4 -1
- data/lib/midori/connection.rb +13 -21
- data/lib/midori/core_ext/{tcp_server.rb → socket.rb} +9 -2
- data/lib/midori/eventsource.rb +1 -0
- data/lib/midori/request.rb +23 -8
- data/lib/midori/response.rb +1 -1
- data/lib/midori/runner.rb +22 -13
- data/lib/midori/server.rb +50 -30
- data/lib/midori/version.rb +1 -1
- data/lib/midori/websocket.rb +19 -37
- metadata +15 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 39f2783e703297e0c67b6190fe38776e92397215917bb1883da0fb7b8e218590
|
4
|
+
data.tar.gz: 8a150f94267e00f92e06ee41d3dd356eb03dc13349c940bf20146bfe008ebf69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d2238695402c0b3cb064d52feadb6b71ef685dcc2b30f79cbdd7dd680de5d26bca3057abbb986bb611831c740f66e3ff7ee18bece2b675c51298012baa348a4
|
7
|
+
data.tar.gz: ecf3a5ae2f89baaa5215f1884ecfcd7972128e92f76f892c332f05b278be1d5fbc7a3862a8169a232025826df3e825636a55e09218ba7539bf44240a4494cd8a
|
data/example.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
$LOAD_PATH.unshift File.expand_path("./lib", __dir__)
|
3
|
+
|
4
|
+
require 'evt'
|
5
|
+
require 'midori'
|
6
|
+
|
7
|
+
Fiber.set_scheduler Evt::Scheduler.new
|
8
|
+
|
9
|
+
class HelloWorldAPI < Midori::API
|
10
|
+
get '/' do
|
11
|
+
'Ohayou Midori'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Fiber.schedule do
|
16
|
+
Midori::Runner.new(HelloWorldAPI).start
|
17
|
+
end
|
data/example2.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
$LOAD_PATH.unshift File.expand_path("./lib", __dir__)
|
3
|
+
|
4
|
+
require 'evt'
|
5
|
+
require 'midori'
|
6
|
+
require 'midori-contrib/redic'
|
7
|
+
|
8
|
+
Fiber.set_scheduler Evt::Scheduler.new
|
9
|
+
REDIS = Redic.new
|
10
|
+
|
11
|
+
class HelloWorldAPI < Midori::API
|
12
|
+
get '/' do
|
13
|
+
REDIS.call 'GET', 'foo'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Fiber.schedule do
|
18
|
+
Midori::Runner.new(HelloWorldAPI).start
|
19
|
+
end
|
data/ext/midori/websocket.c
CHANGED
@@ -1,32 +1,86 @@
|
|
1
1
|
#include <ruby.h>
|
2
|
+
#include <ruby/encoding.h>
|
2
3
|
|
3
4
|
VALUE Midori = Qnil;
|
5
|
+
VALUE MidoriException = Qnil;
|
4
6
|
VALUE MidoriWebSocket = Qnil;
|
5
7
|
|
8
|
+
VALUE ContinousFrameException = Qnil;
|
9
|
+
VALUE OpCodeException = Qnil;
|
10
|
+
VALUE NotMaskedException = Qnil;
|
11
|
+
|
6
12
|
void Init_midori_ext();
|
7
|
-
VALUE
|
13
|
+
VALUE method_midori_websocket_decode(VALUE self, VALUE data);
|
8
14
|
|
9
|
-
void Init_midori_ext()
|
15
|
+
void Init_midori_ext()
|
16
|
+
{
|
10
17
|
Midori = rb_define_module("Midori");
|
11
18
|
MidoriWebSocket = rb_define_class_under(Midori, "WebSocket", rb_cObject);
|
12
|
-
|
19
|
+
MidoriException = rb_define_module_under(Midori, "Exception");
|
20
|
+
ContinousFrameException = rb_const_get(MidoriException, rb_intern("ContinuousFrame"));
|
21
|
+
OpCodeException = rb_const_get(MidoriException, rb_intern("OpCodeError"));
|
22
|
+
NotMaskedException = rb_const_get(MidoriException, rb_intern("NotMasked"));
|
23
|
+
rb_define_method(MidoriWebSocket, "decode", method_midori_websocket_decode, 1);
|
13
24
|
}
|
14
25
|
|
15
|
-
VALUE
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
int mask_array
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
VALUE method_midori_websocket_decode(VALUE self, VALUE data)
|
27
|
+
{
|
28
|
+
int byte, opcode, i, n, fin;
|
29
|
+
char *result;
|
30
|
+
int *mask_array;
|
31
|
+
ID getbyte = rb_intern("getbyte");
|
32
|
+
ID close = rb_intern("close");
|
33
|
+
|
34
|
+
byte = NUM2INT(rb_funcall(data, getbyte, 0));
|
35
|
+
fin = byte & 0x80;
|
36
|
+
opcode = byte & 0x0f;
|
37
|
+
|
38
|
+
if (fin != 0x80)
|
39
|
+
rb_raise(ContinousFrameException, "Continous Frame hasn't been implemented yet");
|
40
|
+
|
41
|
+
rb_iv_set(self, "@opcode", INT2NUM(opcode));
|
42
|
+
if (opcode != 0x1 && opcode != 0x2 && opcode != 0x8 && opcode != 0x9 && opcode != 0xA)
|
43
|
+
rb_raise(OpCodeException, "OpCode %d not supported", opcode);
|
44
|
+
|
45
|
+
if (opcode == 0x8)
|
46
|
+
{
|
47
|
+
rb_funcall(self, close, 0);
|
48
|
+
}
|
49
|
+
|
50
|
+
byte = NUM2INT(rb_funcall(data, getbyte, 0));
|
51
|
+
if ((byte & 0x80) != 0x80)
|
52
|
+
{
|
53
|
+
rb_raise(NotMaskedException, "Messages from client MUST be masked");
|
54
|
+
}
|
55
|
+
|
56
|
+
n = byte & 0x7f;
|
57
|
+
result = (char *)xmalloc(n);
|
58
|
+
mask_array = (int *)xmalloc(4);
|
59
|
+
|
60
|
+
for (i = 0; i < 4; i++) {
|
61
|
+
mask_array[i] = NUM2INT(rb_funcall(data, getbyte, 0));
|
62
|
+
}
|
63
|
+
|
64
|
+
for (i = 0; i < n; i++)
|
65
|
+
{
|
66
|
+
result[i] = NUM2INT(rb_funcall(data, getbyte, 0)) ^ mask_array[i % 4];
|
30
67
|
}
|
31
|
-
|
68
|
+
|
69
|
+
if (opcode == 0x1 || opcode == 0x9 || opcode == 0xA)
|
70
|
+
{
|
71
|
+
rb_iv_set(self, "@msg", rb_enc_str_new(result, n, rb_utf8_encoding()));
|
72
|
+
}
|
73
|
+
else
|
74
|
+
{
|
75
|
+
VALUE result_arr = rb_ary_new2(n);
|
76
|
+
for (i = 0; i < n; i++)
|
77
|
+
{
|
78
|
+
rb_ary_store(result_arr, i, INT2NUM(result[i]));
|
79
|
+
}
|
80
|
+
rb_iv_set(self, "@msg", result_arr);
|
81
|
+
}
|
82
|
+
|
83
|
+
xfree(mask_array);
|
84
|
+
xfree(result);
|
85
|
+
return Qnil;
|
32
86
|
}
|
data/lib/midori.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
require 'cgi'
|
2
|
+
require 'evt'
|
2
3
|
require 'digest/sha1'
|
3
4
|
require 'stringio'
|
4
5
|
require 'fiber'
|
5
6
|
require 'logger'
|
6
|
-
require '
|
7
|
+
require 'mizu'
|
7
8
|
require 'mustermann'
|
8
|
-
require 'murasaki'
|
9
9
|
require 'socket'
|
10
10
|
|
11
|
-
require_relative 'midori_ext'
|
12
11
|
require_relative 'midori/core_ext/configurable'
|
13
12
|
require_relative 'midori/core_ext/define_class'
|
14
13
|
require_relative 'midori/core_ext/http_header'
|
15
14
|
require_relative 'midori/core_ext/proc'
|
15
|
+
require_relative 'midori/core_ext/socket'
|
16
16
|
require_relative 'midori/core_ext/string'
|
17
|
-
require_relative 'midori/core_ext/tcp_server'
|
18
17
|
|
19
18
|
require_relative 'midori/version'
|
20
19
|
|
@@ -37,3 +36,4 @@ require_relative 'midori/configure'
|
|
37
36
|
require_relative 'midori/runner'
|
38
37
|
require_relative 'midori/logger'
|
39
38
|
|
39
|
+
require_relative 'midori_ext'
|
data/lib/midori/configure.rb
CHANGED
@@ -4,7 +4,6 @@ class Midori::Configure
|
|
4
4
|
extend Configurable
|
5
5
|
|
6
6
|
set :logger, ::Logger.new(STDOUT)
|
7
|
-
set :protocol, :http
|
8
7
|
set :bind, '127.0.0.1'
|
9
8
|
set :port, 8080
|
10
9
|
set :route_type, :sinatra
|
@@ -14,4 +13,8 @@ class Midori::Configure
|
|
14
13
|
set :trusted_proxies, /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|
|
15
14
|
\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/ix
|
16
15
|
set :tcp_fast_open, true
|
16
|
+
set :socket_reuse_port, false
|
17
|
+
set :keep_alive, true
|
18
|
+
set :keep_alive_timeout, 75
|
19
|
+
set :keep_alive_requests, 1000
|
17
20
|
end
|
data/lib/midori/connection.rb
CHANGED
@@ -11,22 +11,18 @@ class Midori::Connection
|
|
11
11
|
# @param [IO] socket raw socket
|
12
12
|
def initialize(socket)
|
13
13
|
@registered = false
|
14
|
-
@socket = socket
|
15
|
-
@
|
14
|
+
@socket = socket[0]
|
15
|
+
@peer_addr = socket[1].ip_unpack
|
16
16
|
@close_flag = false
|
17
17
|
@buffer = ''
|
18
|
-
listen(socket)
|
19
18
|
end
|
20
19
|
|
21
20
|
# Register events of connection
|
22
|
-
# @param [
|
23
|
-
def listen
|
24
|
-
|
25
|
-
@
|
26
|
-
|
27
|
-
receive_data(monitor)
|
28
|
-
end
|
29
|
-
if monitor.writable?
|
21
|
+
# @param [Array] socket raw socket
|
22
|
+
def listen
|
23
|
+
Fiber.schedule do
|
24
|
+
until @socket.closed?
|
25
|
+
receive_data(@socket)
|
30
26
|
if !@buffer.empty?
|
31
27
|
send_buffer
|
32
28
|
elsif @close_flag
|
@@ -45,27 +41,23 @@ class Midori::Connection
|
|
45
41
|
end
|
46
42
|
|
47
43
|
# Send buffer immediately
|
44
|
+
# @return [nil] nil
|
48
45
|
private def send_buffer
|
49
|
-
|
50
|
-
written = @socket.write_nonblock(@buffer)
|
51
|
-
@buffer = @buffer.byteslice(written..-1)
|
52
|
-
end
|
46
|
+
@socket.write(@buffer) unless @socket.closed?
|
53
47
|
nil
|
54
|
-
rescue IO::EAGAINWaitWritable => _e
|
55
|
-
# :nocov:
|
56
|
-
# Unknown Reason Resource Conflict
|
57
|
-
nil
|
58
|
-
# :nocov:
|
59
48
|
end
|
60
49
|
|
61
50
|
# Close the connection
|
51
|
+
# @return [nil] nil
|
62
52
|
def close_connection
|
63
|
-
EventLoop.deregister @socket
|
64
53
|
@socket.close
|
54
|
+
nil
|
65
55
|
end
|
66
56
|
|
67
57
|
# Close the connection after writing
|
58
|
+
# @return [nil] nil
|
68
59
|
def close_connection_after_writing
|
69
60
|
@close_flag = true
|
61
|
+
nil
|
70
62
|
end
|
71
63
|
end
|
@@ -1,9 +1,16 @@
|
|
1
|
-
class
|
1
|
+
class Socket
|
2
2
|
def tcp_fast_open
|
3
3
|
# macOS devices option is DIFFERENT from Linux and FreeBSD
|
4
4
|
opt = (/darwin/ =~ RUBY_PLATFORM) ? 1 : 5
|
5
5
|
# Magic number 6 may refer to Socket::SOL_TCP or Socket::IPPROTO_TCP
|
6
|
-
|
6
|
+
setsockopt(6, Socket::TCP_FASTOPEN, opt)
|
7
|
+
true
|
8
|
+
rescue => _e
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def reuse_port
|
13
|
+
setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, 1)
|
7
14
|
true
|
8
15
|
rescue => _e
|
9
16
|
false
|
data/lib/midori/eventsource.rb
CHANGED
@@ -14,6 +14,7 @@ class Midori::EventSource
|
|
14
14
|
# @param [String] data data to be sent
|
15
15
|
def send(data)
|
16
16
|
raise Midori::Exception::EventSourceTypeError unless data.is_a? String
|
17
|
+
# TODO: implement envents by standard
|
17
18
|
@connection.send_data(data.split("\n").map {|str| "data: #{str}\n"}.join + "\n")
|
18
19
|
@connection.close_connection_after_writing
|
19
20
|
end
|
data/lib/midori/request.rb
CHANGED
@@ -26,15 +26,17 @@ class Midori::Request
|
|
26
26
|
@body_parsed = false
|
27
27
|
@is_websocket = false
|
28
28
|
@is_eventsource = false
|
29
|
-
@
|
29
|
+
@ip = ''
|
30
|
+
@port = 0
|
31
|
+
@parser = Mizu::Parser.new
|
30
32
|
@params = {}
|
31
33
|
@query_params = Hash.new(Array.new)
|
32
34
|
@cookie = {}
|
33
35
|
@body = ''
|
34
|
-
@parser.
|
35
|
-
@protocol = @parser.
|
36
|
-
@method = @parser.
|
37
|
-
@path = @parser.
|
36
|
+
@parser.on_complete do
|
37
|
+
@protocol = @parser.version
|
38
|
+
@method = @parser.method
|
39
|
+
@path = @parser.path
|
38
40
|
# Turn header into case-insensitive due to RFC 2.6 Chapter 4.2
|
39
41
|
# https://www.ietf.org/rfc/rfc2616.txt
|
40
42
|
@parser.headers.each { |key, value| @header[key] = value }
|
@@ -50,10 +52,22 @@ class Midori::Request
|
|
50
52
|
@path.gsub!(/\?(.*?)$/, '')
|
51
53
|
@method = @method.to_sym
|
52
54
|
@parsed = true
|
53
|
-
:stop
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
58
|
+
def reset!
|
59
|
+
@header = HTTPHeader.new
|
60
|
+
@parsed = false
|
61
|
+
@body_parsed = false
|
62
|
+
@is_websocket = false
|
63
|
+
@is_eventsource = false
|
64
|
+
@parser.reset!
|
65
|
+
@params = {}
|
66
|
+
@query_params = Hash.new(Array.new)
|
67
|
+
@cookie = {}
|
68
|
+
@body = ''
|
69
|
+
end
|
70
|
+
|
57
71
|
# Init an request with String data
|
58
72
|
# @param [String] data
|
59
73
|
# @return [nil] nil
|
@@ -62,8 +76,9 @@ class Midori::Request
|
|
62
76
|
if @parsed
|
63
77
|
@body += data
|
64
78
|
else
|
65
|
-
|
66
|
-
@
|
79
|
+
return nil if data.nil?
|
80
|
+
@parser << data
|
81
|
+
@body += data[@parser.offset..-1] if @parsed
|
67
82
|
end
|
68
83
|
|
69
84
|
# Set body parsed if body reaches content length
|
data/lib/midori/response.rb
CHANGED
@@ -21,7 +21,7 @@ class Midori::Response
|
|
21
21
|
# Generate header string from hash
|
22
22
|
# @return [String] generated string
|
23
23
|
def generate_header
|
24
|
-
@header['Content-Length'] = @body.bytesize if @header['Content-Length'].nil? &&
|
24
|
+
@header['Content-Length'] = @body.bytesize if @header['Content-Length'].nil? && @header['Upgrade'].nil? && @header['Content-Type'] != 'text/event-stream'
|
25
25
|
@header.map do |key, value|
|
26
26
|
"#{key}: #{value}\r\n"
|
27
27
|
end.join
|
data/lib/midori/runner.rb
CHANGED
@@ -28,25 +28,36 @@ class Midori::Runner
|
|
28
28
|
# Start the Midori server
|
29
29
|
# @note This is an async method, but no callback
|
30
30
|
def start
|
31
|
-
return false if running?
|
31
|
+
return false if running?
|
32
32
|
@logger.info "Midori #{Midori::VERSION} is now running on #{bind}:#{port}".blue
|
33
|
-
|
34
|
-
|
35
|
-
@logger.warn 'Failed to use TCP Fast Open feature on your OS'.yellow unless tfo
|
36
|
-
async_fiber(Fiber.new do
|
33
|
+
init_socket
|
34
|
+
Fiber.schedule do
|
37
35
|
@logger.info 'Midori is booting...'.blue
|
38
36
|
@before.call
|
39
37
|
@logger.info 'Midori is serving...'.blue
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
Fiber.schedule do
|
39
|
+
loop do
|
40
|
+
socket = @server.accept
|
41
|
+
connection = Midori::Connection.new(socket)
|
42
|
+
connection.server_initialize(@api, @logger)
|
43
|
+
connection.listen
|
44
|
+
end
|
44
45
|
end
|
45
|
-
end
|
46
|
-
EventLoop.start
|
46
|
+
end
|
47
47
|
nil
|
48
48
|
end
|
49
49
|
|
50
|
+
private def init_socket
|
51
|
+
@server = Socket.new Socket::AF_INET, Socket::SOCK_STREAM
|
52
|
+
@server.reuse_port if Midori::Configure.socket_reuse_port
|
53
|
+
@server.bind Addrinfo.tcp @bind, @port
|
54
|
+
@server.listen Socket::SOMAXCONN
|
55
|
+
if Midori::Configure.tcp_fast_open
|
56
|
+
tfo = @server.tcp_fast_open
|
57
|
+
@logger.warn 'Failed to use TCP Fast Open feature on your OS'.yellow unless tfo
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
50
61
|
# Stop the Midori server
|
51
62
|
# @note This is an async method, but no callback
|
52
63
|
# @return [Boolean] [true] stop successfully
|
@@ -54,10 +65,8 @@ class Midori::Runner
|
|
54
65
|
def stop
|
55
66
|
if running?
|
56
67
|
@logger.info 'Stopping Midori'.blue
|
57
|
-
EventLoop.deregister @server
|
58
68
|
@server.close
|
59
69
|
@server = nil
|
60
|
-
EventLoop.stop
|
61
70
|
true
|
62
71
|
else
|
63
72
|
@logger.error 'Midori Server has NOT been started'.red
|
data/lib/midori/server.rb
CHANGED
@@ -17,40 +17,43 @@ module Midori::Server
|
|
17
17
|
def server_initialize(api, logger)
|
18
18
|
@api = api
|
19
19
|
@logger = logger
|
20
|
+
|
20
21
|
@request = Midori::Request.new
|
21
22
|
@websocket = Midori::WebSocket.new(self)
|
22
23
|
@eventsource = Midori::EventSource.new(self)
|
24
|
+
|
25
|
+
# Add keep-alive parameters
|
26
|
+
@keep_alive_timer = nil
|
27
|
+
@keep_alive_count = 1
|
23
28
|
end
|
24
29
|
|
25
30
|
# Logic of receiving data
|
26
|
-
# @param [
|
27
|
-
def receive_data(
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end)
|
53
|
-
end.call
|
31
|
+
# @param [Scoket] the socket able to read
|
32
|
+
def receive_data(socket)
|
33
|
+
begin
|
34
|
+
@request.ip, @request.port = @peer_addr
|
35
|
+
if @request.parsed? && !(@request.body_parsed?)
|
36
|
+
data = socket.read @request.body.bytesize - @request.header['Content-Length']
|
37
|
+
else
|
38
|
+
data = socket.readline
|
39
|
+
end
|
40
|
+
|
41
|
+
if @request.websocket?
|
42
|
+
websocket_request(StringIO.new(data))
|
43
|
+
else
|
44
|
+
@request.parse(data)
|
45
|
+
receive_new_request if @request.parsed? && @request.body_parsed?
|
46
|
+
end
|
47
|
+
rescue EOFError, Errno::ENOTCONN => _e
|
48
|
+
close_connection
|
49
|
+
# Ignore client's disconnection
|
50
|
+
rescue => e
|
51
|
+
# :nocov:
|
52
|
+
# Leave for corner cases
|
53
|
+
close_connection
|
54
|
+
@logger.warn "#{@request.ip} - - #{e.class} #{e.backtrace.join("\n")}".yellow
|
55
|
+
# :nocov:
|
56
|
+
end
|
54
57
|
end
|
55
58
|
|
56
59
|
# Logic of receiving new request
|
@@ -59,7 +62,7 @@ module Midori::Server
|
|
59
62
|
start_time = Time.now
|
60
63
|
@response = @api.receive(request, self)
|
61
64
|
now_time = Time.now
|
62
|
-
@logger.info "#{@request.ip} - - \"#{@request.method} #{@request.path} HTTP/#{@request.protocol
|
65
|
+
@logger.info "#{@request.ip} - - \"#{@request.method} #{@request.path} HTTP/#{@request.protocol}\" #{@response.status} #{sprintf("%.6f", now_time.to_f - start_time.to_f)}".green
|
63
66
|
call_event(:open) if @request.websocket?
|
64
67
|
rescue Midori::Exception::NotFound => e
|
65
68
|
@response = Midori::Sandbox.capture(e)
|
@@ -68,9 +71,10 @@ module Midori::Server
|
|
68
71
|
@logger.error e.inspect.red
|
69
72
|
@logger.warn e.backtrace.join("\n").yellow
|
70
73
|
end
|
74
|
+
|
71
75
|
unless @request.websocket? || @request.eventsource?
|
72
76
|
send_data @response
|
73
|
-
|
77
|
+
proceed_keep_alive
|
74
78
|
end
|
75
79
|
end
|
76
80
|
|
@@ -109,4 +113,20 @@ module Midori::Server
|
|
109
113
|
def call_event(event, args = [])
|
110
114
|
-> { @websocket.instance_exec(*args, &@websocket.events[event]) }.call unless @websocket.events[event].nil?
|
111
115
|
end
|
116
|
+
|
117
|
+
private def proceed_keep_alive
|
118
|
+
# Detect if it should close connection
|
119
|
+
if !Midori::Configure.keep_alive || (@keep_alive_count >= Midori::Configure.keep_alive_requests)
|
120
|
+
close_connection_after_writing
|
121
|
+
return
|
122
|
+
end
|
123
|
+
# Add timeout for keep-alive
|
124
|
+
@keep_alive_count += 1
|
125
|
+
Fiber.schedule do
|
126
|
+
sleep Midori::Configure.keep_alive_timeout
|
127
|
+
close_connection
|
128
|
+
end
|
129
|
+
# Reset request
|
130
|
+
@request.reset!
|
131
|
+
end
|
112
132
|
end
|
data/lib/midori/version.rb
CHANGED
data/lib/midori/websocket.rb
CHANGED
@@ -15,40 +15,6 @@ class Midori::WebSocket
|
|
15
15
|
@connection = connection
|
16
16
|
end
|
17
17
|
|
18
|
-
# Decode raw data send from client
|
19
|
-
# @param [StringIO] data raw data
|
20
|
-
def decode(data)
|
21
|
-
# Fin and Opcode
|
22
|
-
byte_tmp = data.getbyte
|
23
|
-
fin = byte_tmp & 0b10000000
|
24
|
-
@opcode = byte_tmp & 0b00001111
|
25
|
-
# NOT Support Multiple Fragments
|
26
|
-
raise Midori::Exception::ContinuousFrame unless fin
|
27
|
-
raise Midori::Exception::OpCodeError unless [0x1, 0x2, 0x8, 0x9, 0xA].include? opcode
|
28
|
-
close if @opcode == 0x8 # Close Frame
|
29
|
-
# return if @opcode == 0x9 || @opcode == 0xA # Ping Pong
|
30
|
-
decode_mask(data)
|
31
|
-
end
|
32
|
-
|
33
|
-
# Decode masked message send from client
|
34
|
-
# @param [StringIO] data raw data
|
35
|
-
def decode_mask(data)
|
36
|
-
# Mask
|
37
|
-
byte_tmp = data.getbyte
|
38
|
-
is_masked = byte_tmp & 0b10000000
|
39
|
-
raise Midori::Exception::NotMasked unless is_masked == 128
|
40
|
-
# Payload
|
41
|
-
payload = byte_tmp & 0b01111111
|
42
|
-
mask = Array.new(4) { data.getbyte }
|
43
|
-
# Message
|
44
|
-
masked_msg = Array.new(payload) { data.getbyte }
|
45
|
-
@msg = self.mask(masked_msg, mask)
|
46
|
-
@msg = @msg.pack('C*').force_encoding('utf-8') if [0x1, 0x9, 0xA].include? opcode
|
47
|
-
# For debug
|
48
|
-
# data.rewind
|
49
|
-
# data.bytes {|byte| puts byte.to_s(16)}
|
50
|
-
end
|
51
|
-
|
52
18
|
# API definition for events
|
53
19
|
# @param [Symbol] event event name(open, message, close, ping, pong)
|
54
20
|
# @yield what to do after event matched
|
@@ -66,11 +32,27 @@ class Midori::WebSocket
|
|
66
32
|
# @param [Array<Integer>, String] msg data to send
|
67
33
|
def send(msg)
|
68
34
|
output = []
|
35
|
+
payload_length = []
|
36
|
+
if msg.size < 126
|
37
|
+
payload_length << msg.size
|
38
|
+
elsif msg.size < 65_536
|
39
|
+
payload_length << 126
|
40
|
+
payload_length.concat([msg.size].pack('n').unpack('C*'))
|
41
|
+
elsif msg.size < 2**63
|
42
|
+
payload_length << 127
|
43
|
+
payload_length.concat([msg.size >> 32, msg.size & 0xFFFFFFFF].pack('NN').unpack('C*'))
|
44
|
+
else
|
45
|
+
raise Midori::Exception::ContinuousFrame
|
46
|
+
end
|
47
|
+
|
69
48
|
if msg.is_a?String
|
70
|
-
output << 0b10000001
|
71
|
-
|
49
|
+
output << 0b10000001
|
50
|
+
output.concat payload_length
|
51
|
+
output.concat msg.unpack('C*')
|
52
|
+
@connection.send_data(output.pack('C*'))
|
72
53
|
elsif msg.is_a? Array
|
73
|
-
output << 0b10000010
|
54
|
+
output << 0b10000010
|
55
|
+
output.concat payload_length
|
74
56
|
output.concat msg
|
75
57
|
@connection.send_data(output.pack('C*'))
|
76
58
|
else
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: midori.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- HeckPsi Lab
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: evt
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.3.0
|
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: 0.
|
26
|
+
version: 0.3.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: mustermann
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,19 +39,19 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: mizu
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.
|
47
|
+
version: 0.1.2
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.
|
54
|
+
version: 0.1.2
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rake-compiler
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,6 +78,8 @@ files:
|
|
78
78
|
- ".editorconfig"
|
79
79
|
- ".github/ISSUE_TEMPLATE.md"
|
80
80
|
- LICENSE
|
81
|
+
- example.rb
|
82
|
+
- example2.rb
|
81
83
|
- ext/midori/extconf.rb
|
82
84
|
- ext/midori/websocket.c
|
83
85
|
- lib/midori.rb
|
@@ -91,8 +93,8 @@ files:
|
|
91
93
|
- lib/midori/core_ext/define_class.rb
|
92
94
|
- lib/midori/core_ext/http_header.rb
|
93
95
|
- lib/midori/core_ext/proc.rb
|
96
|
+
- lib/midori/core_ext/socket.rb
|
94
97
|
- lib/midori/core_ext/string.rb
|
95
|
-
- lib/midori/core_ext/tcp_server.rb
|
96
98
|
- lib/midori/env.rb
|
97
99
|
- lib/midori/eventsource.rb
|
98
100
|
- lib/midori/exception.rb
|
@@ -125,16 +127,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
127
|
requirements:
|
126
128
|
- - ">="
|
127
129
|
- !ruby/object:Gem::Version
|
128
|
-
version:
|
130
|
+
version: 3.0.0
|
129
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
132
|
requirements:
|
131
133
|
- - ">="
|
132
134
|
- !ruby/object:Gem::Version
|
133
135
|
version: '0'
|
134
136
|
requirements: []
|
135
|
-
|
136
|
-
|
137
|
-
signing_key:
|
137
|
+
rubygems_version: 3.2.2
|
138
|
+
signing_key:
|
138
139
|
specification_version: 4
|
139
140
|
summary: High performance ruby web framework
|
140
141
|
test_files: []
|