spyder 0.1.2 → 0.3.0.pre.beta1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3417dd82aa219dd2526c0fb01be1f6e9c58723651bac784b2f082a4c8d3b5f20
4
- data.tar.gz: eca6629c49c0048f7304da5c71f8701d38b1a24ef4a160f923bf5267d0cea628
3
+ metadata.gz: 2e1ade7f9c9ee1b99448fc6a0fb8addcbbca80a2696581b1bb4fa05c27ea534a
4
+ data.tar.gz: 075d91b41bdd5a7264bfadd3e014ef67b7c311545417b1503c9a245935f12860
5
5
  SHA512:
6
- metadata.gz: d97926b4bc3a3064d7e6224c0bbb61ec2a225207f766802be5821db8d9b092eba60e92aec37b5012843e073999aa057c79e946551503e680dcf7c2e327013f32
7
- data.tar.gz: 51a1d5d5a6b4e8d7ba2598ddcf674e1acb224e35fa77a2c53f90a97f936709bc52274f9f92cef492d93f96ef3095758a7e946e63cc2db7720859a4c654096509
6
+ metadata.gz: f0cc009cdbf9d1ab3bf184be0214c6d1e0366b6d0823dc6df8c2b548db4e2b631d972f8e7a2dd2ec9fabfdcb2b6135e4ba1c79d3276d6bc5864005df73f32750
7
+ data.tar.gz: ba21a6413f6cf9ffe290806cd7ee7b96c877b592243d3549caaa374dd455a40037e2f240f41743c83fcc753f0cf97ad682892f2c0cfe4f96d3f99ad50f77bf5c
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spyder
4
+ class BasicServer
5
+ attr_accessor :router
6
+
7
+ def initialize(router)
8
+ @router = router
9
+ @middleware = []
10
+ end
11
+
12
+ def process_request(request)
13
+ mids = @middleware + [[RouterApp, @router]]
14
+ app = nil
15
+
16
+ loop do
17
+ klass, args = mids.pop
18
+ break unless klass
19
+ app = klass.new(args, app)
20
+ end
21
+
22
+ app.call({}, request)
23
+ end
24
+
25
+ def add_middleware(callable, args=[])
26
+ @middleware << [callable, args]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ require 'spyder'
3
+
4
+ module Spyder
5
+ class RackServer < BasicServer
6
+ attr_accessor :router
7
+
8
+ def initialize(router: Router.new)
9
+ super(router)
10
+ end
11
+
12
+ def call(env)
13
+ qs = env[Rack::QUERY_STRING]
14
+
15
+ request = Request.new
16
+ request.path = (!qs || qs.empty? ? env[Rack::PATH_INFO] : "#{env[Rack::PATH_INFO]}?#{qs}")
17
+ request.verb = env[Rack::REQUEST_METHOD]
18
+ request.http_version = env['HTTP_VERSION']
19
+ request.io = env['rack.input']
20
+
21
+ env.each do |key, value|
22
+ next unless key.start_with?('HTTP_') && key != 'HTTP_VERSION'
23
+
24
+ header_name = key[5..].downcase.tr('_', '-')
25
+ request.add_header(header_name, value)
26
+ end
27
+
28
+ response = process_request(request)
29
+
30
+ [
31
+ response.code,
32
+ # response.headers.ordered.map { |k, v| ["HTTP_#{k.tr('-', '_').upcase}", v] },
33
+ response.headers.ordered,
34
+ [response.body]
35
+ ]
36
+ end
37
+ end
38
+ end
@@ -7,7 +7,7 @@ module Spyder
7
7
  attr_accessor :verb
8
8
  attr_accessor :path
9
9
  attr_accessor :headers
10
- attr_accessor :protocol, :remote_address
10
+ attr_accessor :http_version, :remote_address
11
11
  attr_accessor :io
12
12
 
13
13
  def_delegators :@headers, :ordered, :dict, :add_header
@@ -73,6 +73,16 @@ module Spyder
73
73
  len ? io.read(Integer(len)) : io.read # FIXME:
74
74
  end
75
75
 
76
+ def debug_dump(include_body: false)
77
+ lines = ["verb=#{@verb}\npath=#{@path}\nhttp_version=#{@http_version}"]
78
+ lines << "has #{@headers.dict.length} headers"
79
+ @headers.dict.each do |name, value|
80
+ lines << "\t#{name} = #{value}"
81
+ end
82
+ lines << "has body: #{has_body? ? 'yes' : 'no'}"
83
+ lines.join("\n")
84
+ end
85
+
76
86
  def has_body?
77
87
  dict['transfer-encoding'] != nil || dict['content-length'] != nil
78
88
  end
@@ -4,6 +4,7 @@ module Spyder
4
4
  class Response
5
5
  attr_reader :code
6
6
  attr_accessor :body
7
+ attr_accessor :hijack
7
8
  attr_reader :headers
8
9
  attr_writer :reason_sentence
9
10
 
@@ -60,8 +61,10 @@ module Spyder
60
61
  ]
61
62
  end.freeze
62
63
 
63
- def initialize
64
- self.code = :ok
64
+ def initialize(code: :ok)
65
+ @body = nil
66
+ @hijack = false
67
+ self.code = code
65
68
  @headers = HeaderStore.new(:response)
66
69
  end
67
70
 
data/lib/spyder/router.rb CHANGED
@@ -2,10 +2,19 @@
2
2
 
3
3
  module Spyder
4
4
  class Router
5
- attr_accessor :fallback_route
5
+ attr_accessor :fallback_stack
6
6
 
7
7
  def initialize
8
8
  @routes = Hash.new { |k, v| k[v] = [] }
9
+ @fallback_stack = []
10
+ end
11
+
12
+ def add_route_method(verb, matcher, obj, method_name)
13
+ h = proc do |req, rq|
14
+ obj.public_send(method_name, req, rq)
15
+ end
16
+
17
+ add_route(verb, matcher, &h)
9
18
  end
10
19
 
11
20
  def add_route(verb, matcher, &handler)
@@ -14,6 +23,12 @@ module Spyder
14
23
  @routes[verb.to_s.upcase] << [matcher, handler]
15
24
  end
16
25
 
26
+ def add_fallback(callable = nil, &blk)
27
+ raise "Provide either a callable or a block, but not both" if callable && block_given?
28
+
29
+ @fallback_stack << (block_given? ? blk : callable)
30
+ end
31
+
17
32
  def call(ctx, request)
18
33
  only_path = request.path_info
19
34
 
@@ -26,7 +41,16 @@ module Spyder
26
41
  end
27
42
  end
28
43
 
29
- match_data ? handler.call(request, match_data) : Response.make_generic(:not_found)
44
+ response = match_data ? handler.call(request, match_data) : nil
45
+ return response if response.is_a?(Response)
46
+
47
+ fallback_stack.each do |blk|
48
+ response = blk.call(request)
49
+
50
+ return response if response.is_a?(Response)
51
+ end
52
+
53
+ Response.make_generic(:not_found)
30
54
  end
31
55
  end
32
56
 
data/lib/spyder/server.rb CHANGED
@@ -1,84 +1,100 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spyder
4
- class Server
4
+ class Server < BasicServer
5
5
  attr_accessor :router
6
6
 
7
7
  def initialize(bind, port, router: Router.new, max_threads: 4, tcp_backlog: 10)
8
+ super(router)
9
+
8
10
  @server = TCPServer.new(bind, port)
9
11
  @tcp_backlog = tcp_backlog
10
12
  @max_threads = max_threads
11
- @middleware = []
12
- @threads = []
13
+ @max_queue_size = max_threads * tcp_backlog
14
+ @tp_read, @tp_write = IO.pipe
13
15
  @tp_sync = Mutex.new
14
- @router = router
16
+ @waiting_clients = []
17
+ @threads = max_threads.times.map { create_request_handler_thread }
15
18
  end
16
19
 
17
- def add_middleware(callable, args)
18
- @middleware << [callable, args]
20
+ def create_request_handler_thread
21
+ Thread.new do
22
+ loop do
23
+ @tp_read.read(1)
24
+ client = nil
25
+
26
+ @tp_sync.synchronize do
27
+ client = @waiting_clients.shift
28
+ end
29
+
30
+ handle_new_client(client)
31
+ end
32
+ end
33
+ end
34
+
35
+ def handle_new_client(client)
36
+ error, response = nil
37
+
38
+ request = read_client_request(client)
39
+
40
+ begin
41
+ response = process_request(request)
42
+ rescue Exception => e
43
+ error = e
44
+ end
45
+
46
+ if error
47
+ puts "Request errored in spyder. Error dump:"
48
+ puts error.full_message
49
+ puts "Request dump:\n#{request.debug_dump}"
50
+
51
+ response = Response.make_generic :internal_server_error
52
+ end
53
+
54
+ dispatch_response(client, response)
55
+
56
+ if !error && response&.hijack
57
+ response.hijack.call(client)
58
+ else
59
+ client.close rescue nil
60
+ end
19
61
  end
20
62
 
21
63
  def start
22
- busy_threads = 0
23
64
  @server.listen(@tcp_backlog)
24
65
 
25
66
  loop do
26
67
  time_start = Process.clock_gettime(:CLOCK_MONOTONIC, :float_second)
27
68
  loop do
28
- current_busy = @tp_sync.synchronize { busy_threads }
29
- break if current_busy < @max_threads
69
+ queue_size = nil
70
+ @tp_sync.synchronize do
71
+ queue_size = @waiting_clients.length
72
+ end
73
+
74
+ break if queue_size < @max_queue_size # current_busy < @max_threads
30
75
  sleep(0)
31
76
  current_time = Process.clock_gettime(:CLOCK_MONOTONIC, :float_second)
32
77
  if (current_time - time_start) > 1.0
33
- # puts "Waiting a long time: #{(current_time - time_start)}"
78
+ puts "Waiting a long time: #{(current_time - time_start)}"
34
79
  sleep 0.2
35
80
  end
36
81
  end
37
82
 
38
- client = @server.accept
39
- @tp_sync.synchronize { busy_threads += 1 }
40
-
41
- Thread.new do
42
- begin
43
- error = nil
44
- begin
45
- process_new_client(client)
46
- rescue Exception => e
47
- error = e
48
- end
49
-
50
- if error
51
- puts error.full_message
52
-
53
- response = Response.make_generic :internal_server_error
54
- dispatch_response(client, response)
55
- end
56
-
57
- client.close rescue nil
58
- ensure
59
- @tp_sync.synchronize { busy_threads -= 1 }
60
- end
83
+ client_socket = @server.accept
84
+ @tp_sync.synchronize do
85
+ @waiting_clients << client_socket
61
86
  end
62
- end
63
- end
64
87
 
65
- def process_request(request)
66
- mids = @middleware + [[RouterApp, @router]]
67
- app = nil
68
- loop do
69
- klass, args = mids.pop
70
- break unless klass
71
- app = klass.new(args, app)
88
+ @tp_write.write(1)
72
89
  end
73
-
74
- app.call({}, request)
75
90
  end
76
91
 
77
- def process_new_client(socket)
78
- verb, path, protocol = read_line(socket).split(' ')
92
+ def read_client_request(socket)
93
+ verb, path, http_version = read_line(socket).split(' ', 3)
79
94
  request = Request.new
80
95
  request.path = path
81
96
  request.verb = verb
97
+ request.http_version = http_version
82
98
  request.io = socket
83
99
 
84
100
  loop do
@@ -90,24 +106,25 @@ module Spyder
90
106
  request.add_header(name, value)
91
107
  end
92
108
 
93
- response = process_request(request)
94
-
95
- dispatch_response(socket, response)
109
+ request
96
110
  end
97
111
 
98
112
  def dispatch_response(socket, response)
99
113
  content_length = response.headers.dict['content-length']
100
114
  if !content_length && response.body && response.body.is_a?(String)
101
115
  content_length = response.body.length
116
+ response.set_header 'content-length', content_length.to_s
117
+ elsif !content_length && (200..203) === response.code
118
+ response.set_header 'content-length', '0'
102
119
  end
103
120
 
121
+ response.set_header('connection', 'close') unless response.headers.dict['connection']
122
+
104
123
  begin
105
124
  socket.write("HTTP/1.1 #{response.code} #{response.reason_sentence.b}\r\n")
106
125
  response.headers.ordered.each do |name, value|
107
126
  socket.write("#{name.b}: #{value.b}\r\n")
108
127
  end
109
- socket.write("connection: close\r\n") # FIXME:
110
- socket.write("content-length: #{content_length}\r\n") if content_length
111
128
  socket.write("\r\n")
112
129
 
113
130
  if response.body
@@ -120,6 +137,8 @@ module Spyder
120
137
  # socket closed. So what?
121
138
  socket.close rescue nil
122
139
  end
140
+
141
+ response
123
142
  end
124
143
 
125
144
  def read_line(socket)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spyder
4
- VERSION = '0.1.2'
4
+ VERSION = '0.3.0-beta1'
5
5
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spyder
4
+ module Web
5
+ class FileServer
6
+ attr_reader :base_paths
7
+ attr_reader :default_index
8
+
9
+ def initialize(paths, index: nil)
10
+ @default_index = index
11
+ @base_paths = Array(paths).map do |path|
12
+ if path[0] == '/'
13
+ path
14
+ else
15
+ File.expand_path(File.join(Dir.pwd, path))
16
+ end
17
+ end
18
+ end
19
+
20
+ def call(request)
21
+ return unless request.verb == 'GET'
22
+
23
+ input_path = request.path_info
24
+ input_path = @default_index if request.path == '/' && @default_index
25
+
26
+ req_path = safe_request_path(input_path)
27
+ full_path = nil
28
+ return unless @base_paths.any? do |base|
29
+ fp = File.join(base, *req_path)
30
+ next unless File.file?(fp)
31
+ full_path = fp
32
+
33
+ true
34
+ end
35
+
36
+ st = File.readable?(full_path) ? File.stat(full_path) : nil
37
+
38
+ return unless st && %w[file link].include?(st.ftype)
39
+
40
+ etag = "\"#{"%xT-%x0" % [st.mtime, st.size]}\""
41
+
42
+ resp = serve_not_modified_response(request, st, etag)
43
+ return resp if resp
44
+
45
+ resp = Spyder::Response.new
46
+ resp.add_standard_headers
47
+ resp.set_header 'last-modified', st.mtime.httpdate
48
+ resp.set_header 'etag', etag
49
+ resp.set_header 'cache-control', 'public, must-revalidate, max-age=0'
50
+
51
+ File.open full_path do |fp|
52
+ mime = Marcel::MimeType.for(fp)
53
+ if mime == 'application/octet-stream' || mime == 'text/plain'
54
+ mime = Marcel::MimeType.for(name: req_path.last)
55
+ end
56
+
57
+ resp.set_header('content-type', mime) if mime
58
+
59
+ fp.rewind
60
+ resp.body = fp.read
61
+ end
62
+
63
+ resp
64
+ end
65
+
66
+ private
67
+
68
+ def serve_not_modified_response(request, st, etag)
69
+ if_etag = request.headers.dict['if-none-match']
70
+ if_modified_since = request.headers.dict['if-modified-since']
71
+
72
+ return unless if_etag || if_modified_since
73
+
74
+ resp = Spyder::Response.new
75
+ resp.add_standard_headers
76
+ resp.code = 304
77
+
78
+ return resp if if_etag && if_etag == etag
79
+
80
+ if_ms = if_modified_since ? Time.parse(if_modified_since) : nil
81
+
82
+ (if_ms && Time.at(st.mtime.to_i) <= if_ms) ? resp : nil
83
+ end
84
+
85
+ def safe_request_path(path)
86
+ path.split('/').reject do |component|
87
+ component == '..' || component == '' || component == '.'
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spyder
4
+ class WebSocket
5
+ WS_CONST = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
6
+
7
+ class Frame
8
+ def initialize
9
+ end
10
+
11
+ def decode(raw_data)
12
+ fin = raw_data[0] & 1
13
+ rsv = (raw_data[0] & 0b0111) >> 1
14
+ end
15
+ end
16
+
17
+ def initialize
18
+ @socket = nil
19
+ @on_start = nil
20
+ @on_message = nil
21
+ @on_close = nil
22
+
23
+ @streaming_buffer = WebSocketStreamingBuffer.new do |frame, mode, fragmented, last_fragment|
24
+ @on_message.call(frame, mode) unless fragmented
25
+ end
26
+ @streaming_buffer.on_close = proc do
27
+ @socket.close rescue nil
28
+ end
29
+ end
30
+
31
+ def send_text(data)
32
+ send_data(data, :text)
33
+ end
34
+
35
+ def send_binary(data)
36
+ send_data(data, :binary)
37
+ end
38
+
39
+ def send_data(data, mode)
40
+ data = data.b
41
+
42
+ length = data.length
43
+ buffer = String.new(encoding: 'ascii-8bit', capacity: length + 32)
44
+ buffer += ((1 << 7) | (mode == :binary ? 2 : 1)).chr
45
+ if length < 126
46
+ buffer += length.chr
47
+ elsif length <= 0xFFFF
48
+ buffer += [126, length].pack('CS>')
49
+ else
50
+ buffer += [127, length].pack('CQ>')
51
+ end
52
+
53
+ @socket.write(buffer)
54
+ @socket.write(data)
55
+ end
56
+
57
+ def on_close(&blk)
58
+ @on_close = blk
59
+ end
60
+
61
+ def on_message(&blk)
62
+ @on_message = blk
63
+ end
64
+
65
+ def on_start(&blk)
66
+ @on_start = blk
67
+ end
68
+
69
+ def hijacked!(socket)
70
+ @socket = socket
71
+ puts "websocket hijacked! #{socket}"
72
+ @thread = Thread.start do
73
+ self.threaded_start!
74
+ end
75
+ end
76
+
77
+ def self.upgrade_websocket_request(request)
78
+ ws_key = request.headers.dict['sec-websocket-key']
79
+ return unless ws_key && request.headers.dict['upgrade'] == 'websocket'
80
+
81
+ conns = request.headers.dict.fetch('connection', '').split(' ').map(&:strip)
82
+ return unless conns.include?('Upgrade')
83
+
84
+ ws_version = request.headers.dict['sec-websocket-version'] # expect: 13
85
+ return unless ws_version
86
+
87
+ protocols = request.headers.dict['sec-websocket-protocol']
88
+ protocols = protocols.split(',').map(&:strip) if protocols
89
+
90
+ extensions = request.headers.dict['sec-websocket-extensions']
91
+ extensions = extensions.split(';').map(&:strip) if extensions
92
+
93
+ decoded_key = Base64.strict_decode64(ws_key) rescue nil
94
+ return unless decoded_key&.length == 16
95
+
96
+ response_key = Base64.strict_encode64(
97
+ OpenSSL::Digest::SHA1.digest("#{ws_key}#{WS_CONST}")
98
+ )
99
+
100
+ ws = new
101
+
102
+ chosen_proto = yield(ws, protocols) if (block_given? && protocols)
103
+ chosen_proto = protocols.first if !chosen_proto && protocols
104
+
105
+ resp = Spyder::Response.new(code: 101)
106
+ resp.add_standard_headers
107
+ resp.set_header 'connection', 'Upgrade'
108
+ resp.set_header 'upgrade', 'websocket'
109
+ resp.set_header('sec-websocket-protocol', chosen_proto) if chosen_proto
110
+ resp.set_header 'sec-websocket-accept', response_key
111
+
112
+ resp.hijack = proc { |client_socket| ws.hijacked!(client_socket) }
113
+
114
+ [resp, ws]
115
+ end
116
+
117
+ def threaded_start!
118
+ @on_start.call if @on_start
119
+
120
+ while !@socket.closed? && !@socket.eof?
121
+ data = nil
122
+ begin
123
+ data = @socket.read_nonblock(16 * 1024)
124
+ rescue IO::WaitReadable
125
+ IO.select([@socket], [], [@socket])
126
+ end
127
+
128
+ next unless data
129
+
130
+ puts "ws: read #{data.length} bytes"
131
+ @streaming_buffer.feed(data)
132
+ end
133
+
134
+ @on_close.call unless @on_close
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spyder
4
+ class WebSocketStreamingBuffer
5
+ attr_reader :buffer
6
+ attr_accessor :on_close
7
+
8
+ def initialize(&frame_callback)
9
+ @frame_callback = frame_callback
10
+ @on_close = nil
11
+ @buffer = []
12
+ _reset
13
+ end
14
+
15
+ def feed(buf)
16
+ @buffer += buf.bytes
17
+
18
+ # printable = buf.bytes.map{ |x| x.to_s(16).rjust(2, '0').upcase }.join(' ')
19
+ # puts "Got #{buf.bytes.length} more bytes: [#{printable}]"
20
+
21
+ iterations = 0
22
+ loop do
23
+ _flush
24
+
25
+ iterations += 1
26
+ if iterations > 1_000
27
+ raise "Watchdog error: got stuck on a loop of #{iterations} iterations."
28
+ end
29
+
30
+ break if (@buffer.length == 0 && !@need_buffer_length) ||
31
+ (@need_buffer_length && @buffer.length < @need_buffer_length)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def _reset
38
+ @state = :initial
39
+ @mask = nil
40
+ @opcode = nil
41
+ @data_mode = nil
42
+ @payload_length = nil
43
+ @need_buffer_length = nil
44
+ @flag_fin = nil
45
+ @fragmented = false
46
+ @fragmented_total_size = nil
47
+ end
48
+
49
+ def _flush
50
+ send(:"_flush_#{@state}")
51
+ end
52
+
53
+ def _flush_data
54
+ return unless @buffer.length >= @payload_length
55
+
56
+ data = if @mask
57
+ (0...@payload_length).map do |i|
58
+ @buffer[i] ^ @mask[i & 3]
59
+ end
60
+ else
61
+ @buffer[0...@payload_length]
62
+ end
63
+
64
+ last_fragment = @fragmented && @flag_fin
65
+ @frame_callback.call(data, @data_mode, @fragmented, last_fragment)
66
+
67
+ @buffer.shift(@payload_length)
68
+
69
+ if @fragmented && !last_fragment
70
+ @need_buffer_length = 2
71
+ @opcode = nil
72
+ @payload_length = nil
73
+ @state = "initial"
74
+ else
75
+ _reset
76
+ end
77
+ end
78
+
79
+ def close_with_error(msg)
80
+ @socket.close rescue nil
81
+
82
+ puts "Closing websocket: #{msg}"
83
+
84
+ @on_close.call if @on_close
85
+
86
+ _reset
87
+
88
+ @state = :closed
89
+ @socket = nil
90
+ @buffer = nil
91
+ end
92
+
93
+ def _flush_readhdr
94
+ return unless @buffer.length >= @need_buffer_length
95
+
96
+ @flag_fin = ((@buffer[0] & 0x80) != 0)
97
+ @opcode = (@buffer[0] & 0xF)
98
+ data_len = (@buffer[1] & 0x7F)
99
+ mask_flag = ((@buffer[1] & 0x80) != 0)
100
+
101
+ is_control_frame = ((@opcode & 8) != 0)
102
+ if is_control_frame && (data_len > 125 || !@flag_fin)
103
+ # Control frames must not be fragmented and must be <= 125 bytes in size
104
+ return close_with_error("Control frame invalid state")
105
+ end
106
+
107
+ if @fragmented && !is_control_frame && @opcode != 0
108
+ # We're in the middle of a fragmented frame and received a non-control
109
+ # frame. That's an error.
110
+ return close_with_error("Got non-control frame during fragmented frame.")
111
+ end
112
+
113
+ if !@fragmented && !@flag_fin && !is_control_frame
114
+ @fragmented = true
115
+ @fragmented_total_size = 0
116
+ end
117
+
118
+ shift_length = 2
119
+
120
+ @payload_length = if data_len == 127
121
+ shift_length += 8
122
+ @buffer[2...10].pack('C*').unpack('Q>').first
123
+ elsif data_len == 126
124
+ shift_length += 2
125
+ @buffer[2...4].pack('C*').unpack('S>').first
126
+ else
127
+ shift_length += 0
128
+ data_len
129
+ end
130
+
131
+ if mask_flag
132
+ @mask = @buffer[shift_length...(shift_length + 4)].pack('C*').bytes
133
+ shift_length += 4
134
+ else
135
+ @mask = nil
136
+ end
137
+
138
+ @fragmented_total_size += @payload_length if @fragmented
139
+
140
+ fgr = nil
141
+ fgr = ", fgr=#{@fragmented_total_size}" if @fragmented
142
+ puts "frame(code=#{@opcode}, len=#{@payload_length}, fin=#{@flag_fin}#{fgr})"
143
+
144
+ @buffer.shift(shift_length)
145
+ @need_buffer_length = ((0..2) === @opcode ? @payload_length : nil)
146
+
147
+ case @opcode
148
+ when 0x0 # continuation
149
+ @state = "data"
150
+ when 0x1 # text frame
151
+ @state = "data"
152
+ @data_mode = :text
153
+ when 0x2 # binary frame
154
+ @state = "data"
155
+ @data_mode = :binary
156
+ when (0x3..0x7) # reserved
157
+ unexpected_opcode(@opcode)
158
+ when 0x8 # connection close
159
+ @on_close.call if @on_close
160
+
161
+ _reset
162
+ when 0x9 # ping
163
+ # TODO: send pong?
164
+ when 0xA # pong
165
+ puts "WS: got pong!"
166
+
167
+ _reset
168
+ else
169
+ unexpected_opcode(@opcode)
170
+ end
171
+ end
172
+
173
+ def _flush_initial
174
+ return unless @buffer.length >= 2
175
+
176
+ data_len = (@buffer[1] & 0x7F)
177
+ mask_flag = (@buffer[1] & 0x80)
178
+
179
+ @need_buffer_length = 2 + (mask_flag ? 4 : 0) + (
180
+ data_len == 127 ? 8 : (data_len == 126 ? 2 : 0)
181
+ )
182
+
183
+ @state = "readhdr"
184
+ _flush
185
+ end
186
+
187
+ def unexpected_opcode opcode
188
+ puts "WS: unexpected opcode #{opcode}"
189
+ exit 1
190
+ end
191
+ end
192
+ end
data/lib/spyder.rb CHANGED
@@ -12,4 +12,14 @@ require 'spyder/header_store'
12
12
  require 'spyder/request'
13
13
  require 'spyder/response'
14
14
  require 'spyder/router'
15
+ require 'spyder/basic_server'
15
16
  require 'spyder/server'
17
+ require 'spyder/web_socket_streaming_buffer'
18
+ require 'spyder/web_socket'
19
+
20
+ # spyder-web
21
+ require 'marcel'
22
+ require 'base64'
23
+ require 'openssl'
24
+
25
+ require 'spyder/web/file_server'
metadata CHANGED
@@ -1,15 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spyder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.3.0.pre.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - André D. Piske
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-11-23 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.3'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: mustermann
15
28
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +37,20 @@ dependencies:
24
37
  - - "~>"
25
38
  - !ruby/object:Gem::Version
26
39
  version: '3.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: marcel
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
27
54
  description: Spyder Web
28
55
  email: andrepiske@gmail.com
29
56
  executables: []
@@ -32,18 +59,22 @@ extra_rdoc_files: []
32
59
  files:
33
60
  - lib/examples/simplest_server/server.rb
34
61
  - lib/spyder.rb
62
+ - lib/spyder/basic_server.rb
35
63
  - lib/spyder/error.rb
36
64
  - lib/spyder/header_store.rb
65
+ - lib/spyder/rack_adapter.rb
37
66
  - lib/spyder/request.rb
38
67
  - lib/spyder/response.rb
39
68
  - lib/spyder/router.rb
40
69
  - lib/spyder/server.rb
41
70
  - lib/spyder/version.rb
71
+ - lib/spyder/web/file_server.rb
72
+ - lib/spyder/web_socket.rb
73
+ - lib/spyder/web_socket_streaming_buffer.rb
42
74
  homepage: https://github.com/andrepiske/spyder
43
75
  licenses:
44
76
  - MIT
45
77
  metadata: {}
46
- post_install_message:
47
78
  rdoc_options: []
48
79
  require_paths:
49
80
  - lib
@@ -58,8 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
89
  - !ruby/object:Gem::Version
59
90
  version: '0'
60
91
  requirements: []
61
- rubygems_version: 3.5.6
62
- signing_key:
92
+ rubygems_version: 4.0.23
63
93
  specification_version: 4
64
94
  summary: Spyder
65
95
  test_files: []