rakie 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8361c3cfc878d6edc052170cc5fbc5f555d173562c7325d9a939c567402e8b4f
4
- data.tar.gz: cc2f7fa419c2fcb750ec70305b942336974cd7e35ad336edc38436027f74e6dd
3
+ metadata.gz: 754086391c9ec9e2eb623d8d6a1ead9ca9188d49fb0c391b26c4a9e36db88236
4
+ data.tar.gz: 6ac34e0868c8bc1748e7f572266b4223a91f676d9d7c11b22fa5a7e10586713c
5
5
  SHA512:
6
- metadata.gz: 1b39af452c4d98899dd787a38ce1394b1781eaee28ab3761b46b824ec4b426f76c27d451da8ae1fb01545676dd572a19f9c9dae09839221a17a5ee807a151f72
7
- data.tar.gz: c7953919abc5572b8fba09a5afdf5e32034c0bc2e4fdbca4f649f67f9ec635a10cf49888a194ce67f17afda0424becd1e42467cd9b73e2a990670fd29e2c2c50
6
+ metadata.gz: 839acbd5155b745948a6144a37480b770e50f01ff4daf29146e39c8fb2e36cf0f434f10313577d3f7b1efcfd039317b036d9c87f96d6c57d09a25c9da6ba5e10
7
+ data.tar.gz: a844de019fa10103c96e7cc7ab4f7e78ffb2cd5a7c23b28be6483c35fbb7a5cfd1b51f4a1996fa053fa33e03d701af52682c0af306d92b3f2c2c98878aa7c4c6
data/lib/rakie.rb CHANGED
@@ -1,12 +1,24 @@
1
1
 
2
2
  module Rakie
3
-
3
+ NAME = "rakie"
4
+ MODE_DEV = 0
5
+ MODE_REL = 1
6
+
7
+ def self.current_mode
8
+ @current_mode ||= MODE_REL
9
+ end
4
10
  end
5
11
 
6
12
  require "rakie/channel"
7
13
  require "rakie/event"
14
+ require "rakie/proto"
15
+ require "rakie/http_proto"
16
+ require "rakie/websocket_proto"
8
17
  require "rakie/simple_server"
9
18
  require "rakie/http_server"
19
+ require "rakie/websocket"
20
+ require "rakie/websocket_server"
10
21
  require "rakie/tcp_channel"
11
22
  require "rakie/tcp_server_channel"
12
23
  require "rakie/log"
24
+ require "rakie/version"
data/lib/rakie/channel.rb CHANGED
@@ -2,6 +2,8 @@ module Rakie
2
2
  class Channel
3
3
  attr_accessor :delegate
4
4
 
5
+ DEFAULT_BUFFER_SIZE = 512 * 1024
6
+
5
7
  def initialize(io, delegate=nil)
6
8
  @io = io
7
9
  @read_buffer = String.new
@@ -14,7 +16,7 @@ module Rakie
14
16
  def on_read(io)
15
17
  begin
16
18
  loop do
17
- @read_buffer << io.read_nonblock(4096)
19
+ @read_buffer << io.read_nonblock(DEFAULT_BUFFER_SIZE)
18
20
  end
19
21
 
20
22
  rescue IO::EAGAINWaitReadable
@@ -32,6 +34,7 @@ module Rakie
32
34
  end
33
35
 
34
36
  if @delegate != nil
37
+ Log.debug("Channel handle on_recv")
35
38
  len = @delegate.on_recv(self, @read_buffer)
36
39
 
37
40
  if len > @read_buffer.length
@@ -39,7 +42,6 @@ module Rakie
39
42
  end
40
43
 
41
44
  @read_buffer = @read_buffer[len .. -1]
42
- Log.debug("Channel handle on_recv")
43
45
  end
44
46
 
45
47
  return Event::HANDLE_CONTINUED
@@ -90,10 +92,14 @@ module Rakie
90
92
  return Event::HANDLE_FINISHED
91
93
  end
92
94
 
93
- def on_close(io)
95
+ def on_detach(io)
94
96
  begin
95
97
  io.close
96
98
 
99
+ if @delegate
100
+ @delegate.on_close(self)
101
+ end
102
+
97
103
  rescue
98
104
  Log.debug("Channel is already closed")
99
105
  end
data/lib/rakie/event.rb CHANGED
@@ -50,7 +50,7 @@ module Rakie
50
50
 
51
51
  if handler != nil
52
52
  Log.debug("Event close #{new_io}")
53
- handler.on_close(new_io)
53
+ handler.on_detach(new_io)
54
54
  end
55
55
 
56
56
  @ios.delete(new_io)
@@ -105,11 +105,11 @@ module Rakie
105
105
  result = handler.on_read(io)
106
106
 
107
107
  if result == HANDLE_FINISHED
108
- @ios[io] = @ios[io] & ~READ_EVENT
108
+ @ios[io] &= ~READ_EVENT
109
109
  Log.debug("Event remove read #{io}")
110
110
 
111
111
  elsif result == HANDLE_FAILED
112
- handler.on_close(io)
112
+ handler.on_detach(io)
113
113
  Log.debug("Event close #{io}")
114
114
 
115
115
  @ios.delete(io)
@@ -130,11 +130,11 @@ module Rakie
130
130
  result = handler.on_write(io)
131
131
 
132
132
  if result == HANDLE_FINISHED
133
- @ios[io] = @ios[io] & ~WRITE_EVENT
133
+ @ios[io] &= ~WRITE_EVENT
134
134
  Log.debug("Event remove write #{io}")
135
135
 
136
136
  elsif result == HANDLE_FAILED
137
- handler.on_close(io)
137
+ handler.on_detach(io)
138
138
  Log.debug("Event close #{io}")
139
139
 
140
140
  @ios.delete(io)
@@ -168,19 +168,38 @@ module Rakie
168
168
  end
169
169
 
170
170
  def self.instance
171
- @instance ||= Event.new
171
+ @instance ||= Array.new(self.concurrent) { |i| Event.new }
172
172
  end
173
173
 
174
174
  def self.push(io, listener, type)
175
- self.instance.push(io, listener, type)
175
+ i = io.fileno % self.concurrent
176
+ inst = self.instance[i]
177
+ inst.push(io, listener, type)
178
+ # instance.push(io, listener, type)
176
179
  end
177
180
 
178
181
  def self.delete(io)
179
- self.instance.delete(io)
182
+ i = io.fileno % self.concurrent
183
+ inst = self.instance[i]
184
+ inst.delete(io)
185
+ # self.instance.delete(io)
180
186
  end
181
187
 
182
188
  def self.modify(io, listener, type)
183
- self.instance.modify(io, listener, type)
189
+ i = io.fileno % self.concurrent
190
+ inst = self.instance[i]
191
+ inst.modify(io, listener, type)
192
+ # self.instance.modify(io, listener, type)
193
+ end
194
+
195
+ # @return [Integer]
196
+ def self.concurrent
197
+ @concurrent ||= 2
198
+ end
199
+
200
+ # @param [Integer] count
201
+ def self.concurrent=(count)
202
+ @concurrent = count
184
203
  end
185
204
  end
186
205
  end
data/lib/rakie/http.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Rakie
2
+ class Http
3
+
4
+ end
5
+ end
@@ -0,0 +1,197 @@
1
+ module Rakie
2
+ class HttpMIME
3
+ TEXT = "text/plain"
4
+ HTML = "text/html"
5
+ JSON = "application/json"
6
+ end
7
+
8
+ class HttpRequest
9
+ include Proto
10
+
11
+ attr_accessor :head, :headers, :content
12
+
13
+ PARSE_HEAD = PARSE_BEGIN
14
+ PARSE_HEADERS = 1
15
+ PARSE_CONTENT = 2
16
+
17
+ class Head
18
+ attr_accessor :method, :path, :version
19
+
20
+ def initialize
21
+ @method = 'HEAD'
22
+ @path = '/'
23
+ @version = 'HTTP/1.1'
24
+ end
25
+ end
26
+
27
+ def initialize
28
+ @head = Head.new
29
+ @headers = {}
30
+ @content = ''
31
+ end
32
+
33
+ # @param [String] source
34
+ def parse_source_head(source)
35
+ offset = parse_offset
36
+
37
+ if eol_offset = source.index("\r\n")
38
+ head_s = source[0 .. eol_offset]
39
+ head_method, head_path, head_version = head_s.split(' ')
40
+
41
+ head.method = head_method
42
+ head.path = head_path
43
+ head.version = head_version
44
+
45
+ self.parse_state = PARSE_HEADERS
46
+ self.parse_offset = offset + 2
47
+
48
+ Log.debug("Rakie::HttpRequest parse source head ok")
49
+ return ParseStatus::CONTINUE
50
+ end
51
+
52
+ return ParseStatus::PENDING
53
+ end
54
+
55
+ def parse_source_header_item(header)
56
+ if semi_offset = header.index(':')
57
+ return [
58
+ header[0 .. (semi_offset - 1)].downcase,
59
+ header[(semi_offset + 1) .. -1].strip
60
+ ]
61
+ end
62
+
63
+ return nil
64
+ end
65
+
66
+ # @param [String] source
67
+ def parse_source_headers(source)
68
+ offset = parse_offset
69
+
70
+ while eol_offset = source.index("\r\n", offset)
71
+ header = source[offset .. (eol_offset - 1)]
72
+
73
+ if header.length == 0
74
+ self.parse_state = PARSE_CONTENT
75
+ self.parse_offset = eol_offset + 2
76
+
77
+ Log.debug("Rakie::HttpRequest parse source header done")
78
+ return ParseStatus::CONTINUE
79
+ end
80
+
81
+ header_key, header_value = self.parse_source_header_item(header)
82
+
83
+ if header_key
84
+ headers[header_key] = header_value
85
+ Log.debug("Rakie::HttpRequest parse source #{header_key}, #{header_value} header ok")
86
+ end
87
+
88
+ offset = eol_offset + 2
89
+ end
90
+
91
+ self.parse_offset = offset
92
+
93
+ return ParseStatus::PENDING
94
+ end
95
+
96
+ # @param [String] source
97
+ def parse_source_content(source)
98
+ len = headers["content-length"]
99
+ offset = parse_offset
100
+
101
+ if len == nil
102
+ return ParseStatus::COMPLETE
103
+ end
104
+
105
+ len = len.to_i
106
+
107
+ if source.length >= len + offset
108
+ self.content = source[offset .. (offset + len - 1)]
109
+ self.parse_state = PARSE_HEAD
110
+ self.parse_offset = offset + len
111
+
112
+ Log.debug("Rakie::HttpRequest parse source content ok")
113
+ return ParseStatus::COMPLETE
114
+ end
115
+
116
+ return ParseStatus::PENDING
117
+ end
118
+
119
+ # @param [String] source
120
+ # @return [Integer]
121
+ def deserialize(source)
122
+ current_state = parse_state
123
+
124
+ case current_state
125
+ when PARSE_HEAD
126
+ return parse_source_head(source)
127
+
128
+ when PARSE_HEADERS
129
+ return parse_source_headers(source)
130
+
131
+ when PARSE_CONTENT
132
+ return parse_source_content(source)
133
+ end
134
+ end
135
+
136
+ # @return [String]
137
+ def serialize
138
+ data = ""
139
+
140
+ data += "#{head.method} #{head.path} #{head.version}"
141
+ data += "\r\n"
142
+
143
+ headers_list = []
144
+ headers.each do |k, v|
145
+ headers_list << "#{k}: #{v}"
146
+ end
147
+
148
+ data += headers_list.join("\r\n")
149
+ data += "\r\n\r\n"
150
+
151
+ data += content
152
+ end
153
+ end
154
+
155
+ class HttpResponse
156
+ include Proto
157
+
158
+ PARSE_HEAD = 0
159
+ PARSE_HEADERS = 1
160
+ PARSE_CONTENT = 2
161
+
162
+ attr_accessor :head, :headers, :content
163
+
164
+ class Head
165
+ attr_accessor :version, :status, :message
166
+
167
+ def initialize
168
+ @version = 'HTTP/1.1'
169
+ @status = 200
170
+ @message = 'OK'
171
+ end
172
+ end
173
+
174
+ def initialize
175
+ @head = Head.new
176
+ @headers = {}
177
+ @content = ''
178
+ end
179
+
180
+ def serialize
181
+ data = ""
182
+
183
+ data += "#{head.version} #{head.status} #{head.message}"
184
+ data += "\r\n"
185
+
186
+ headers_list = []
187
+ headers.each do |k, v|
188
+ headers_list << "#{k}: #{v}"
189
+ end
190
+
191
+ data += headers_list.join("\r\n")
192
+ data += "\r\n\r\n"
193
+
194
+ data += content
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,127 @@
1
+ module Rakie
2
+ class HttpServer
3
+ class Session
4
+ # @return [HttpRequest]
5
+ attr_accessor :request
6
+
7
+ # @type [Array<HttpResponse>]
8
+ attr_accessor :responses
9
+
10
+ def initialize
11
+ @request = HttpRequest.new
12
+ @responses = []
13
+ end
14
+ end
15
+
16
+ attr_reader :channel, :opt
17
+
18
+ def initialize(delegate=nil)
19
+ @channel = TCPServerChannel.new('127.0.0.1', 10086, self)
20
+
21
+ # @type [Hash{Channel=>Session}]
22
+ @sessions = {}
23
+ @delegate = delegate
24
+ @opt = {}
25
+ end
26
+
27
+ def on_accept(channel)
28
+ channel.delegate = self
29
+ @sessions[channel] = Session.new
30
+ Log.debug("Rakie::HTTPServer accept client: #{channel}")
31
+ end
32
+
33
+ def on_recv(channel, data)
34
+ # Log.debug("Rakie::HTTPServer recv: #{data}")
35
+ session = @sessions[channel]
36
+
37
+ if session == nil
38
+ return 0
39
+ end
40
+
41
+ # @type [HttpRequest] request
42
+ request = session.request
43
+
44
+ if request.parse_status == ParseStatus::COMPLETE
45
+ request = HttpRequest.new
46
+ session.request = request
47
+ end
48
+
49
+ len = request.parse(data)
50
+
51
+ Log.debug("Rakie::HttpServer receive request: #{request.to_s}")
52
+
53
+ if request.parse_status == ParseStatus::COMPLETE
54
+ response = HttpResponse.new
55
+
56
+ if upgrade = request.headers["upgrade"]
57
+ if websocket_delegate = @opt[:websocket_delegate]
58
+ websocket_delegate.upgrade(request, response)
59
+ Log.debug("Rakie::HttpServer upgrade protocol")
60
+ end
61
+
62
+ elsif @delegate != nil
63
+ @delegate.handle(request, response)
64
+
65
+ else
66
+ response.headers["content-type"] = HttpMIME::HTML
67
+ response.content = "<html><body><h1>Rakie!</h1></body></html>"
68
+ end
69
+
70
+ if header_connection = request.headers["connection"]
71
+ response.headers["connection"] = header_connection
72
+ end
73
+
74
+ if response.content.length > 0
75
+ response.headers["content-length"] = response.content.length
76
+ end
77
+
78
+ response.headers["server"] = Rakie.full_version_s
79
+ session.responses << response
80
+ response_data = response.to_s
81
+
82
+ Log.debug("Rakie::HttpServer response: #{response_data}")
83
+
84
+ channel.write(response_data) # Response data
85
+
86
+ elsif request.parse_status == ParseStatus::ERROR
87
+ channel.close
88
+ @sessions.delete(channel)
89
+
90
+ Log.debug("Rakie::HttpServer: Illegal request")
91
+ return len
92
+ end
93
+
94
+ return len
95
+ end
96
+
97
+ def on_send(channel)
98
+ session = @sessions[channel]
99
+ # @type [HttpRequest]
100
+ last_response = session.responses.shift
101
+
102
+ if last_response
103
+ if connect_status = last_response.headers["connection"]
104
+ if connect_status.downcase == "close"
105
+ Log.debug("Rakie::HttpServer: send finish and close channel")
106
+ channel.close
107
+ @sessions.delete(channel)
108
+ end
109
+ end
110
+
111
+ if upgrade = last_response.headers["upgrade"]
112
+ websocket_delegate = @opt[:websocket_delegate]
113
+
114
+ if websocket_delegate
115
+ websocket_delegate.on_accept(channel)
116
+ @sessions.delete(channel)
117
+ return
118
+ end
119
+
120
+ Log.debug("Rakie::HttpServer: no websocket delegate and close channel")
121
+ channel.close
122
+ @sessions.delete(channel)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
data/lib/rakie/log.rb ADDED
@@ -0,0 +1,96 @@
1
+ module Rakie
2
+ class Log
3
+ attr_accessor :out, :level
4
+
5
+ @instance = nil
6
+
7
+ LEVEL_INFO = 0
8
+ LEVEL_ERROR = 1
9
+ LEVEL_DEBUG = 2
10
+
11
+ def initialize
12
+ @level = Rakie.current_mode == Rakie::MODE_DEV ? LEVEL_DEBUG : LEVEL_ERROR
13
+ @out = STDOUT
14
+ end
15
+
16
+ def level_text(level=nil)
17
+ unless level
18
+ level = @level
19
+ end
20
+
21
+ case level
22
+ when LEVEL_INFO
23
+ return "INFO"
24
+
25
+ when LEVEL_ERROR
26
+ return "ERROR"
27
+
28
+ when LEVEL_DEBUG
29
+ return "DEBUG"
30
+ end
31
+ end
32
+
33
+ def level_info?
34
+ @level >= LEVEL_INFO
35
+ end
36
+
37
+ def level_error?
38
+ @level >= LEVEL_ERROR
39
+ end
40
+
41
+ def level_debug?
42
+ @level >= LEVEL_DEBUG
43
+ end
44
+
45
+ def self.instance
46
+ @instance ||= Log.new
47
+ end
48
+
49
+ def self.info(message, who=nil)
50
+ unless self.instance.level_info?
51
+ return
52
+ end
53
+
54
+ if who
55
+ message = "#{who}: #{message}"
56
+ end
57
+
58
+ level = self.instance.level_text(LEVEL_INFO)
59
+ self.instance.out.write("[#{Time.now.to_s}][#{level}] #{message}\n")
60
+ end
61
+
62
+ def self.error(message, who=nil)
63
+ unless self.instance.level_error?
64
+ return
65
+ end
66
+
67
+ if who
68
+ message = "#{who}: #{message}"
69
+ end
70
+
71
+ level = self.instance.level_text(LEVEL_ERROR)
72
+ self.instance.out.write("[#{Time.now.to_s}][#{level}] #{message}\n")
73
+ end
74
+
75
+ def self.debug(message, who=nil)
76
+ unless self.instance.level_debug?
77
+ return
78
+ end
79
+
80
+ if who
81
+ message = "#{who}: #{message}"
82
+ end
83
+
84
+ level = self.instance.level_text(LEVEL_DEBUG)
85
+ self.instance.out.print("[#{Time.now.to_s}][#{level}] #{message}\n")
86
+ end
87
+
88
+ def self.level
89
+ self.instance.level
90
+ end
91
+
92
+ def self.level=(level)
93
+ self.instance.level = level
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,63 @@
1
+ module Rakie
2
+ class ParseStatus
3
+ ERROR = -1
4
+ CONTINUE = 0
5
+ PENDING = 1
6
+ COMPLETE = 2
7
+ end
8
+
9
+ module Proto
10
+ PARSE_BEGIN = 0
11
+
12
+ # @return [Integer]
13
+ def parse_status
14
+ @parse_status ||= ParseStatus::CONTINUE
15
+ end
16
+
17
+ # @return [Integer]
18
+ def parse_state
19
+ @parse_state ||= PARSE_BEGIN
20
+ end
21
+
22
+ # @param [Integer] state
23
+ def parse_state=(state)
24
+ @parse_state = state
25
+ # puts("Set state: #{@parse_state}")
26
+ end
27
+
28
+ # @return [Integer]
29
+ def parse_offset
30
+ @parse_offset ||= 0
31
+ end
32
+
33
+ # @param [Integer] offset
34
+ def parse_offset=(offset)
35
+ @parse_offset = offset
36
+ end
37
+
38
+ # @param [String] source
39
+ def parse(source)
40
+ status = ParseStatus::CONTINUE
41
+
42
+ while status == ParseStatus::CONTINUE
43
+ status = self.deserialize(source)
44
+ end
45
+
46
+ if status == ParseStatus::PENDING
47
+ status = ParseStatus::CONTINUE
48
+ end
49
+
50
+ offset = @parse_offset
51
+ @parse_status = status
52
+ @parse_offset = 0
53
+
54
+ return offset
55
+ end
56
+
57
+ # @param [Object] object
58
+ # @return [String]
59
+ def to_s
60
+ self.serialize
61
+ end
62
+ end
63
+ end
@@ -1,8 +1,24 @@
1
1
  module Rakie
2
2
  class TCPChannel < Channel
3
- def initialize(ip, port, delegate=nil)
4
- @io = TCPSocket.new(ip, port)
5
- super(@io, delegate)
3
+ LOCAL_HOST = '127.0.0.1'
4
+
5
+ # @param host [String]
6
+ # @param port [Integer]
7
+ # @param delegate [Object]
8
+ # @param socket [Socket]
9
+ # @overload initialize(host, port, delegate)
10
+ # @overload initialize(host, port)
11
+ # @overload initialize(host, port, delegate, socket)
12
+ def initialize(host=LOCAL_HOST, port=3001, delegate=nil, socket=nil)
13
+ if socket == nil
14
+ socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
15
+ socket.connect(Socket.pack_sockaddr_in(port, host))
16
+ end
17
+
18
+ @port = port
19
+ @host = host
20
+
21
+ super(socket, delegate)
6
22
  end
7
23
  end
8
24
  end
@@ -1,25 +1,41 @@
1
1
  module Rakie
2
- class TCPServerChannel < Channel
3
- def initialize(ip, port=nil, delegate=nil)
4
- io = nil
2
+ class TCPServerChannel < TCPChannel
3
+ # @param host [String]
4
+ # @param port [Integer]
5
+ # @param delegate [Object]
6
+ # @overload initialize(host, port, delegate)
7
+ # @overload initialize(host, port)
8
+ # @overload initialize(port)
9
+ def initialize(host=LOCAL_HOST, port=3001, delegate=nil)
10
+ socket = nil
5
11
 
6
12
  if port == nil
7
- port = ip
8
- io = TCPServer.new(ip)
9
-
10
- else
11
- io = TCPServer.new(ip, port)
13
+ port = host
14
+ host = LOCAL_HOST
12
15
  end
13
16
 
17
+ socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
18
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
19
+ socket.bind(Socket.pack_sockaddr_in(port, host))
20
+ socket.listen(255)
21
+
14
22
  @clients = []
15
23
 
16
- super(io, delegate)
24
+ super(host, port, delegate, socket)
17
25
  end
18
26
 
27
+ # @param io [Socket]
19
28
  def on_read(io)
20
29
  begin
21
- client_io, = io.accept_nonblock
22
- channel = Channel.new(client_io)
30
+ ret = io.accept_nonblock
31
+ # @type client_io [Socket]
32
+ client_io = ret[0]
33
+ # @type client_info [Addrinfo]
34
+ client_info = ret[1]
35
+ client_name_info = client_info.getnameinfo
36
+ client_host = client_name_info[0]
37
+ client_port = client_name_info[1]
38
+ channel = TCPChannel.new(client_host, client_port, nil, client_io)
23
39
 
24
40
  if @delegate != nil
25
41
  Log.debug("TCPServerChannel has delegate")
@@ -0,0 +1,11 @@
1
+ module Rakie
2
+ VERSION = [0, 0, 5]
3
+
4
+ def self.version_s
5
+ VERSION.join('.')
6
+ end
7
+
8
+ def self.full_version_s
9
+ "#{NAME} v#{self.version_s}"
10
+ end
11
+ end
@@ -0,0 +1,137 @@
1
+ module Rakie
2
+ class Websocket
3
+ attr_accessor :delegate, :client_side
4
+ attr_reader :channel
5
+
6
+ # @param [Rakie::TCPChannel] channel
7
+ def initialize(delegate=nil, channel=nil)
8
+ @delegate = delegate
9
+
10
+ if channel == nil
11
+ channel = TCPChannel.new('127.0.0.1', 10086, self)
12
+ end
13
+
14
+ @channel = channel
15
+
16
+ # @type [WebsocketMessage]
17
+ @recv_message = WebsocketMessage.new
18
+
19
+ # @type [Array<WebsocketMessage>]
20
+ @send_messages = []
21
+ @client_side = true
22
+ end
23
+
24
+ def on_recv(channel, data)
25
+ # Log.debug("Rakie::HTTPServer recv: #{data}")
26
+
27
+ # @type [WebsocketMessage] request
28
+ message = @recv_message
29
+
30
+ if message.parse_status == ParseStatus::COMPLETE
31
+ message = WebsocketMessage.new
32
+ @recv_message = message
33
+ end
34
+
35
+ len = message.parse(data)
36
+
37
+ Log.debug("Rakie::Websocket receive message: #{message.to_s}")
38
+
39
+ if message.parse_status == ParseStatus::COMPLETE
40
+ response = WebsocketMessage.new
41
+
42
+ if message.op_code == WebsocketMessage::OP_PING
43
+ response.fin = true
44
+ response.op_code = WebsocketMessage::OP_PONG
45
+ response.payload = "pong"
46
+
47
+ elsif message.op_code == WebsocketMessage::OP_PONG
48
+ response.fin = true
49
+ response.op_code = WebsocketMessage::OP_PING
50
+ response.payload = "ping"
51
+
52
+ elsif @delegate
53
+ @delegate.on_message(self, message.payload)
54
+ return len
55
+
56
+ else
57
+ response.fin = true
58
+ response.op_code = WebsocketMessage::OP_TEXT
59
+ response.payload = "Rakie!"
60
+ end
61
+
62
+ response_data = response.to_s
63
+
64
+ Log.debug("Rakie::Websocket response: #{response_data}")
65
+
66
+ channel.write(response_data) # Response data
67
+
68
+ elsif message.parse_status == ParseStatus::ERROR
69
+ channel.close
70
+
71
+ Log.debug("Rakie::Websocket: Illegal message")
72
+ return len
73
+ end
74
+
75
+ return len
76
+ end
77
+
78
+ def on_send(channel)
79
+ # @type [WebsocketMessage]
80
+ last_message = @send_messages.shift
81
+
82
+ if last_message
83
+ if last_message.op_code == WebsocketMessage::OP_CLOSE
84
+ Log.debug("Rakie::Websocket: send finish and close channel")
85
+ channel.close
86
+ end
87
+ end
88
+ end
89
+
90
+ def on_close(channel)
91
+ if @delegate
92
+ @delegate.on_disconnect(self)
93
+ end
94
+ end
95
+
96
+ def send(message, is_binary=false)
97
+ ws_message = WebsocketMessage.new
98
+ ws_message.fin = true
99
+ ws_message.op_code = WebsocketMessage::OP_TEXT
100
+ ws_message.payload = message
101
+
102
+ if is_binary
103
+ ws_message.op_code = WebsocketMessage::OP_BIN
104
+ end
105
+
106
+ if @client_side
107
+ ws_message.mask = true
108
+ ws_message.refresh_masking
109
+ end
110
+
111
+ send_message = ws_message.to_s
112
+
113
+ Log.debug("Rakie::Websocket send: #{send_message}")
114
+
115
+ @channel.write(send_message) # Response data
116
+ end
117
+
118
+ def close
119
+ ws_message = WebsocketMessage.new
120
+ ws_message.fin = true
121
+ ws_message.op_code = WebsocketMessage::OP_CLOSE
122
+
123
+ if @client_side
124
+ ws_message.mask = true
125
+ ws_message.refresh_masking
126
+ end
127
+
128
+ ws_message.payload = "close"
129
+
130
+ send_message = ws_message.to_s
131
+
132
+ Log.debug("Rakie::Websocket send close: #{send_message}")
133
+
134
+ @channel.write(send_message) # Response data
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,286 @@
1
+ module Rakie
2
+ class WebsocketBasicMessage
3
+ include Proto
4
+
5
+ FLAG_FIN = 1 << 7
6
+
7
+ OP_CONTINUE = 0x0
8
+ OP_TEXT = 0x1
9
+ OP_BIN = 0x2
10
+ OP_CLOSE = 0x8
11
+ OP_PING = 0x9
12
+ OP_PONG = 0xA
13
+
14
+ FLAG_MASK = 1 << 7
15
+
16
+ PARSE_OPERATION = PARSE_BEGIN
17
+ PARSE_LEN = 1
18
+ PARSE_EXT_LEN = 2
19
+ PARSE_MASKING = 3
20
+ PARSE_PAYLOAD = 4
21
+
22
+ attr_accessor :op_code, :mask, :length, :payload
23
+ attr_writer :fin
24
+
25
+ def initialize
26
+ @fin = false
27
+ @op_code = 0x0
28
+ @mask = false
29
+ @masking = []
30
+ @length = 0
31
+ @long_ext = false
32
+ @payload = ''
33
+ end
34
+
35
+ def fin?
36
+ @fin
37
+ end
38
+
39
+ # @param [String] source
40
+ def deserialize(source)
41
+ current_state = parse_state
42
+
43
+ case current_state
44
+ when PARSE_OPERATION
45
+ return parse_source_operation(source)
46
+
47
+ when PARSE_LEN
48
+ return parse_source_len(source)
49
+
50
+ when PARSE_EXT_LEN
51
+ return parse_source_ext_len(source)
52
+
53
+ when PARSE_MASKING
54
+ return parse_source_masking(source)
55
+
56
+ when PARSE_PAYLOAD
57
+ return parse_source_payload(source)
58
+ end
59
+ end
60
+
61
+ def serialize
62
+ data = ''
63
+
64
+ data += pack_source_operation
65
+ data += pack_source_len
66
+
67
+ if @mask
68
+ data += pack_source_masking
69
+ data += pack_source_masked_payload
70
+
71
+ return data
72
+ end
73
+
74
+ data += @payload
75
+
76
+ return data
77
+ end
78
+ end
79
+
80
+ class WebsocketMessage < WebsocketBasicMessage
81
+ # @param [String] source
82
+ def parse_source_operation(source)
83
+ offset = parse_offset
84
+
85
+ if source.length >= 1 + offset
86
+ byte = source[offset]
87
+ data = byte.unpack('C')[0]
88
+
89
+ if data & FLAG_FIN > 0
90
+ @fin = true
91
+ end
92
+
93
+ i = 3
94
+ code = 0x0
95
+
96
+ while i >= 0
97
+ code |= data & (1 << i)
98
+ i -= 1
99
+ end
100
+
101
+ @op_code = code
102
+
103
+ self.parse_state = PARSE_LEN
104
+ self.parse_offset = offset + 1
105
+
106
+ Log.debug("Rakie::WebsocketMessage parse source operation ok")
107
+ return ParseStatus::CONTINUE
108
+ end
109
+
110
+ return ParseStatus::PENDING
111
+ end
112
+
113
+ # @param [String] source
114
+ def parse_source_len(source)
115
+ offset = parse_offset
116
+
117
+ if source.length >= 1 + offset
118
+ byte = source[offset]
119
+ data = byte.unpack('C')[0]
120
+
121
+ if data & FLAG_MASK > 0
122
+ @mask = true
123
+ end
124
+
125
+ len = data & ~FLAG_MASK
126
+
127
+ Log.debug("Rakie::WebsocketMessage len: #{len}")
128
+
129
+ if len <= 125
130
+ @length = len
131
+
132
+ if @mask
133
+ self.parse_state = PARSE_MASKING
134
+ self.parse_offset = offset + 1
135
+
136
+ Log.debug("Rakie::WebsocketMessage parse source len ok")
137
+ return ParseStatus::CONTINUE
138
+ end
139
+
140
+ self.parse_state = PARSE_PAYLOAD
141
+ self.parse_offset = offset + 1
142
+
143
+ Log.debug("Rakie::WebsocketMessage parse source len ok")
144
+ return ParseStatus::CONTINUE
145
+ end
146
+
147
+ if len == 127
148
+ @long_ext = true
149
+ end
150
+
151
+ self.parse_state = PARSE_EXT_LEN
152
+ self.parse_offset = offset + 1
153
+
154
+ Log.debug("Rakie::WebsocketMessage parse source len ok")
155
+ return ParseStatus::CONTINUE
156
+ end
157
+
158
+ return ParseStatus::PENDING
159
+ end
160
+
161
+ # @param [String] source
162
+ def parse_source_ext_len(source)
163
+ offset = parse_offset
164
+ ext_len_size = 2
165
+ byte_format = 'S'
166
+
167
+ if @long_ext
168
+ ext_len_size = 8
169
+ byte_format = 'Q'
170
+ end
171
+
172
+ if source.length >= ext_len_size + offset
173
+ bytes = source[offset .. (offset + ext_len_size - 1)]
174
+ @length = bytes.unpack(byte_format + '>')[0]
175
+
176
+ if @mask
177
+ self.parse_state = PARSE_MASKING
178
+ self.parse_offset = offset + ext_len_size
179
+
180
+ Log.debug("Rakie::WebsocketMessage parse source ext len ok")
181
+ return ParseStatus::CONTINUE
182
+ end
183
+
184
+ self.parse_state = PARSE_PAYLOAD
185
+ self.parse_offset = offset + ext_len_size
186
+
187
+ Log.debug("Rakie::WebsocketMessage parse source ext len ok")
188
+ return ParseStatus::CONTINUE
189
+ end
190
+
191
+ return ParseStatus::PENDING
192
+ end
193
+
194
+ # @param [String] source
195
+ def parse_source_masking(source)
196
+ offset = parse_offset
197
+
198
+ if source.length >= 4 + offset
199
+ bytes = source[offset .. (offset + 4 - 1)]
200
+ @masking = bytes.unpack('C*')
201
+
202
+ self.parse_state = PARSE_PAYLOAD
203
+ self.parse_offset = offset + 4
204
+
205
+ Log.debug("Rakie::WebsocketMessage parse source masking ok")
206
+ return ParseStatus::CONTINUE
207
+ end
208
+
209
+ return ParseStatus::PENDING
210
+ end
211
+
212
+ # @param [String] source
213
+ def parse_source_payload(source)
214
+ offset = parse_offset
215
+
216
+ if source.length >= @length + offset
217
+ bytes = source[offset .. (offset + @length - 1)]
218
+
219
+ if @mask
220
+ bytes_list = bytes.unpack('C*')
221
+ masking = @masking
222
+ bytes_list_unmasked = bytes_list.map.with_index { |b, i| b ^ masking[i % 4] }
223
+
224
+ @payload = bytes_list_unmasked.pack('C*')
225
+
226
+ self.parse_state = PARSE_OPERATION
227
+ self.parse_offset = offset + @length
228
+
229
+ Log.debug("Rakie::WebsocketMessage parse source masked payload ok")
230
+ return ParseStatus::COMPLETE
231
+ end
232
+
233
+ @payload = bytes
234
+
235
+ self.parse_state = PARSE_OPERATION
236
+ self.parse_offset = offset + @length
237
+
238
+ Log.debug("Rakie::WebsocketMessage parse source payload ok")
239
+ return ParseStatus::COMPLETE
240
+ end
241
+
242
+ return ParseStatus::PENDING
243
+ end
244
+
245
+ def pack_source_operation
246
+ fin_bit = @fin ? 1 : 0
247
+ return [(fin_bit << 7) + @op_code].pack('C')
248
+ end
249
+
250
+ def pack_source_len
251
+ mask_bit = @mask ? 1 : 0
252
+
253
+ if @payload.length < 126
254
+ return [(mask_bit << 7) + @payload.length].pack('C')
255
+
256
+ elsif @payload.length < 65536
257
+ return [(mask_bit << 7) + 126, @payload.length].pack('CS>')
258
+ end
259
+
260
+ return [(mask_bit << 7) + 127, @payload.length].pack('CQ>')
261
+ end
262
+
263
+ def pack_source_masking
264
+ return @masking.pack('C*')
265
+ end
266
+
267
+ def pack_source_masked_payload
268
+ masking = @masking
269
+
270
+ if masking.empty?
271
+ return ''
272
+ end
273
+
274
+ bytes_list = @payload.unpack('C*')
275
+ bytes_list_masked = bytes_list.map.with_index { |b, i| b ^ masking[i % 4] }
276
+
277
+ return bytes_list_masked.pack('C*')
278
+ end
279
+
280
+ def refresh_masking
281
+ masking = []
282
+ 4.times { masking << rand(1 .. 255) }
283
+ @masking = masking
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,83 @@
1
+ module Rakie
2
+ class WebsocketServer < Websocket
3
+ # @param [Rakie::HttpServer] http_server
4
+ def initialize(delegate=nil, http_server=nil)
5
+ @delegate = delegate
6
+
7
+ if http_server == nil
8
+ http_server = HttpServer.new('127.0.0.1', 10086, self)
9
+ end
10
+
11
+ http_server.opt[:websocket_delegate] = self
12
+ @channel = http_server.channel
13
+
14
+ # @type [Array<WebsocketClient>]
15
+ @clients = {}
16
+ end
17
+
18
+ def on_accept(channel)
19
+ ws_client = Websocket.new(@delegate, channel)
20
+ ws_client.client_side = false
21
+
22
+ channel.delegate = self
23
+
24
+ if @delegate
25
+ @delegate.on_connect(ws_client)
26
+ return
27
+ end
28
+
29
+ @clients[channel] = ws_client
30
+ Log.debug("Rakie::WebsocketServer accept client: #{ws_client}")
31
+ end
32
+
33
+ # @param [HttpRequest] request
34
+ # @param [HttpResponse] response
35
+ # @return bool
36
+ def upgrade(request, response)
37
+ if websocket_key = request.headers["sec-websocket-key"]
38
+ digest_key = Digest::SHA1.base64digest(websocket_key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
39
+
40
+ response.head.status = 101
41
+ response.head.message = 'Switching Protocols'
42
+ response.headers["connection"] = "upgrade"
43
+ response.headers["upgrade"] = "websocket"
44
+ response.headers["sec-websocket-accept"] = digest_key
45
+ end
46
+ end
47
+
48
+ def on_recv(channel, data)
49
+ client = @clients[channel]
50
+
51
+ if client
52
+ client.on_recv(channel, data)
53
+ end
54
+ end
55
+
56
+ def on_send(channel)
57
+ client = @clients[channel]
58
+
59
+ if client
60
+ client.on_send(channel)
61
+ end
62
+ end
63
+
64
+ def on_close(channel)
65
+ client = @clients[channel]
66
+
67
+ if client
68
+ client.on_close(channel)
69
+ end
70
+ end
71
+
72
+ def send(message, is_binary=false); end
73
+
74
+ def close; end
75
+
76
+ def clients
77
+ @clients.values
78
+ end
79
+ end
80
+ end
81
+
82
+ require "digest"
83
+ require "base64"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rakie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakit Liang
@@ -20,9 +20,18 @@ files:
20
20
  - lib/rakie.rb
21
21
  - lib/rakie/channel.rb
22
22
  - lib/rakie/event.rb
23
+ - lib/rakie/http.rb
24
+ - lib/rakie/http_proto.rb
25
+ - lib/rakie/http_server.rb
26
+ - lib/rakie/log.rb
27
+ - lib/rakie/proto.rb
23
28
  - lib/rakie/simple_server.rb
24
29
  - lib/rakie/tcp_channel.rb
25
30
  - lib/rakie/tcp_server_channel.rb
31
+ - lib/rakie/version.rb
32
+ - lib/rakie/websocket.rb
33
+ - lib/rakie/websocket_proto.rb
34
+ - lib/rakie/websocket_server.rb
26
35
  - test/test_rakie.rb
27
36
  homepage: https://github.com/Jakitto/rakie
28
37
  licenses: