midori.rb 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/midori/websocket.c +59 -31
- data/lib/midori.rb +2 -2
- data/lib/midori/eventsource.rb +1 -0
- data/lib/midori/request.rb +20 -8
- data/lib/midori/response.rb +1 -1
- data/lib/midori/server.rb +2 -2
- data/lib/midori/version.rb +1 -1
- data/lib/midori/websocket.rb +19 -40
- metadata +6 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2adfa1f5075c05406725691e2e254c7ac1e0f3ff9aba2e8787d9567323681654
|
4
|
+
data.tar.gz: f6f22a1df53862dbc956f53b188a7e4b023c6ef85e58f879c4fcdaefb0539e38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab4f42932ef1851b8529c6eaa3d3a10d8a21cdd97e3702e94717a0bb8076ab981cd352d275e700f93203f4c790b75993b066e7c59f6b2c4fa6aa39f5479bd210
|
7
|
+
data.tar.gz: cab7f16b86eaa3e532ab5d40265e057c7b998d4122d002db14690e78e243191c2888795a77364a45bcd3112f6524ef194e71eb239b44026831a4ce789dd5381b
|
data/ext/midori/websocket.c
CHANGED
@@ -2,57 +2,85 @@
|
|
2
2
|
#include <ruby/encoding.h>
|
3
3
|
|
4
4
|
VALUE Midori = Qnil;
|
5
|
+
VALUE MidoriException = Qnil;
|
5
6
|
VALUE MidoriWebSocket = Qnil;
|
6
7
|
|
8
|
+
VALUE ContinousFrameException = Qnil;
|
9
|
+
VALUE OpCodeException = Qnil;
|
10
|
+
VALUE NotMaskedException = Qnil;
|
11
|
+
|
7
12
|
void Init_midori_ext();
|
8
|
-
VALUE
|
9
|
-
VALUE method_midori_websocket_mask_str(VALUE self, VALUE payload, VALUE mask);
|
13
|
+
VALUE method_midori_websocket_decode(VALUE self, VALUE data);
|
10
14
|
|
11
15
|
void Init_midori_ext()
|
12
16
|
{
|
13
17
|
Midori = rb_define_module("Midori");
|
14
18
|
MidoriWebSocket = rb_define_class_under(Midori, "WebSocket", rb_cObject);
|
15
|
-
|
16
|
-
|
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);
|
17
24
|
}
|
18
25
|
|
19
|
-
VALUE
|
26
|
+
VALUE method_midori_websocket_decode(VALUE self, VALUE data)
|
20
27
|
{
|
21
|
-
|
22
|
-
|
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");
|
23
33
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
NUM2INT(rb_ary_entry(mask, 2)),
|
28
|
-
NUM2INT(rb_ary_entry(mask, 3))};
|
34
|
+
byte = NUM2INT(rb_funcall(data, getbyte, 0));
|
35
|
+
fin = byte & 0x80;
|
36
|
+
opcode = byte & 0x0f;
|
29
37
|
|
30
|
-
|
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)
|
31
46
|
{
|
32
|
-
|
33
|
-
m = mask_array[i % 4];
|
34
|
-
rb_ary_store(unmasked, i, INT2NUM(p ^ m));
|
47
|
+
rb_funcall(self, close, 0);
|
35
48
|
}
|
36
|
-
return unmasked;
|
37
|
-
}
|
38
49
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
+
}
|
43
55
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
+
}
|
49
63
|
|
50
64
|
for (i = 0; i < n; i++)
|
51
65
|
{
|
52
|
-
|
53
|
-
|
54
|
-
|
66
|
+
result[i] = NUM2INT(rb_funcall(data, getbyte, 0)) ^ mask_array[i % 4];
|
67
|
+
}
|
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);
|
55
81
|
}
|
56
82
|
|
57
|
-
|
83
|
+
xfree(mask_array);
|
84
|
+
xfree(result);
|
85
|
+
return Qnil;
|
58
86
|
}
|
data/lib/midori.rb
CHANGED
@@ -3,12 +3,11 @@ require 'digest/sha1'
|
|
3
3
|
require 'stringio'
|
4
4
|
require 'fiber'
|
5
5
|
require 'logger'
|
6
|
-
require '
|
6
|
+
require 'mizu'
|
7
7
|
require 'mustermann'
|
8
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'
|
@@ -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/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,15 @@ class Midori::Request
|
|
26
26
|
@body_parsed = false
|
27
27
|
@is_websocket = false
|
28
28
|
@is_eventsource = false
|
29
|
-
@parser =
|
29
|
+
@parser = Mizu::Parser.new
|
30
30
|
@params = {}
|
31
31
|
@query_params = Hash.new(Array.new)
|
32
32
|
@cookie = {}
|
33
33
|
@body = ''
|
34
|
-
@parser.
|
35
|
-
@protocol = @parser.
|
36
|
-
@method = @parser.
|
37
|
-
@path = @parser.
|
34
|
+
@parser.on_complete do
|
35
|
+
@protocol = @parser.version
|
36
|
+
@method = @parser.method
|
37
|
+
@path = @parser.path
|
38
38
|
# Turn header into case-insensitive due to RFC 2.6 Chapter 4.2
|
39
39
|
# https://www.ietf.org/rfc/rfc2616.txt
|
40
40
|
@parser.headers.each { |key, value| @header[key] = value }
|
@@ -50,10 +50,22 @@ class Midori::Request
|
|
50
50
|
@path.gsub!(/\?(.*?)$/, '')
|
51
51
|
@method = @method.to_sym
|
52
52
|
@parsed = true
|
53
|
-
:stop
|
54
53
|
end
|
55
54
|
end
|
56
55
|
|
56
|
+
def reset!
|
57
|
+
@header = HTTPHeader.new
|
58
|
+
@parsed = false
|
59
|
+
@body_parsed = false
|
60
|
+
@is_websocket = false
|
61
|
+
@is_eventsource = false
|
62
|
+
@parser.reset!
|
63
|
+
@params = {}
|
64
|
+
@query_params = Hash.new(Array.new)
|
65
|
+
@cookie = {}
|
66
|
+
@body = ''
|
67
|
+
end
|
68
|
+
|
57
69
|
# Init an request with String data
|
58
70
|
# @param [String] data
|
59
71
|
# @return [nil] nil
|
@@ -62,8 +74,8 @@ class Midori::Request
|
|
62
74
|
if @parsed
|
63
75
|
@body += data
|
64
76
|
else
|
65
|
-
|
66
|
-
@body += data[offset..-1] if @parsed
|
77
|
+
@parser << data
|
78
|
+
@body += data[@parser.offset..-1] if @parsed
|
67
79
|
end
|
68
80
|
|
69
81
|
# 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/server.rb
CHANGED
@@ -59,7 +59,7 @@ module Midori::Server
|
|
59
59
|
start_time = Time.now
|
60
60
|
@response = @api.receive(request, self)
|
61
61
|
now_time = Time.now
|
62
|
-
@logger.info "#{@request.ip} - - \"#{@request.method} #{@request.path} HTTP/#{@request.protocol
|
62
|
+
@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
63
|
call_event(:open) if @request.websocket?
|
64
64
|
rescue Midori::Exception::NotFound => e
|
65
65
|
@response = Midori::Sandbox.capture(e)
|
@@ -125,6 +125,6 @@ module Midori::Server
|
|
125
125
|
end
|
126
126
|
EventLoop.add_timer(@keep_alive_timer)
|
127
127
|
# Reset request
|
128
|
-
@request
|
128
|
+
@request.reset!
|
129
129
|
end
|
130
130
|
end
|
data/lib/midori/version.rb
CHANGED
data/lib/midori/websocket.rb
CHANGED
@@ -15,43 +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
|
-
if [0x1, 0x9, 0xA].include? opcode
|
46
|
-
@msg = mask_str(masked_msg, mask)
|
47
|
-
else # [0x2]
|
48
|
-
@msg = mask(masked_msg, mask)
|
49
|
-
end
|
50
|
-
# For debug
|
51
|
-
# data.rewind
|
52
|
-
# data.bytes {|byte| puts byte.to_s(16)}
|
53
|
-
end
|
54
|
-
|
55
18
|
# API definition for events
|
56
19
|
# @param [Symbol] event event name(open, message, close, ping, pong)
|
57
20
|
# @yield what to do after event matched
|
@@ -69,11 +32,27 @@ class Midori::WebSocket
|
|
69
32
|
# @param [Array<Integer>, String] msg data to send
|
70
33
|
def send(msg)
|
71
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
|
+
|
72
48
|
if msg.is_a?String
|
73
|
-
output << 0b10000001
|
74
|
-
|
49
|
+
output << 0b10000001
|
50
|
+
output.concat payload_length
|
51
|
+
output.concat msg.unpack('C*')
|
52
|
+
@connection.send_data(output.pack('C*'))
|
75
53
|
elsif msg.is_a? Array
|
76
|
-
output << 0b10000010
|
54
|
+
output << 0b10000010
|
55
|
+
output.concat payload_length
|
77
56
|
output.concat msg
|
78
57
|
@connection.send_data(output.pack('C*'))
|
79
58
|
else
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: midori.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- HeckPsi Lab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: murasaki
|
@@ -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
|
@@ -132,8 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
132
|
- !ruby/object:Gem::Version
|
133
133
|
version: '0'
|
134
134
|
requirements: []
|
135
|
-
|
136
|
-
rubygems_version: 2.7.7
|
135
|
+
rubygems_version: 3.0.1
|
137
136
|
signing_key:
|
138
137
|
specification_version: 4
|
139
138
|
summary: High performance ruby web framework
|