spyder 0.2.0 → 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: b5ee9dba2fca697adc1456d6473a6a1f7e011108ce4d8baf0f7659c9886b85c0
4
- data.tar.gz: 719a5fe8d53574d442ddec5cbb666db6018526901be76ebf7ac665451da3c512
3
+ metadata.gz: 2e1ade7f9c9ee1b99448fc6a0fb8addcbbca80a2696581b1bb4fa05c27ea534a
4
+ data.tar.gz: 075d91b41bdd5a7264bfadd3e014ef67b7c311545417b1503c9a245935f12860
5
5
  SHA512:
6
- metadata.gz: 90c3f303a6d4c12abf9250ad13caba9cdf06632cfc4a68a317c7a8ccac47b2aa4c568c315581a03a353d9d0b15317a4dbe1e245ad9a1e70335a2406cb9f30ba3
7
- data.tar.gz: 471ab8a863891a2b27f236bb0cea65028e37e62ac12a387daa81a302d58adf5cf7f81c236b8d4953e9e15815a3b56c86a15bf497f7c207a1ac4c8fecfe2a1447
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
data/lib/spyder/router.rb CHANGED
@@ -9,6 +9,14 @@ module Spyder
9
9
  @fallback_stack = []
10
10
  end
11
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)
18
+ end
19
+
12
20
  def add_route(verb, matcher, &handler)
13
21
  matcher = Mustermann.new(matcher) if matcher.is_a?(String)
14
22
 
data/lib/spyder/server.rb CHANGED
@@ -1,88 +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, response = nil
44
- begin
45
- response = 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
- if response&.hijack
58
- response.hijack.call(client)
59
- else
60
- client.close rescue nil
61
- end
62
- ensure
63
- @tp_sync.synchronize { busy_threads -= 1 }
64
- end
83
+ client_socket = @server.accept
84
+ @tp_sync.synchronize do
85
+ @waiting_clients << client_socket
65
86
  end
66
- end
67
- end
68
87
 
69
- def process_request(request)
70
- mids = @middleware + [[RouterApp, @router]]
71
- app = nil
72
- loop do
73
- klass, args = mids.pop
74
- break unless klass
75
- app = klass.new(args, app)
88
+ @tp_write.write(1)
76
89
  end
77
-
78
- app.call({}, request)
79
90
  end
80
91
 
81
- def process_new_client(socket)
82
- verb, path, protocol = read_line(socket).split(' ')
92
+ def read_client_request(socket)
93
+ verb, path, http_version = read_line(socket).split(' ', 3)
83
94
  request = Request.new
84
95
  request.path = path
85
96
  request.verb = verb
97
+ request.http_version = http_version
86
98
  request.io = socket
87
99
 
88
100
  loop do
@@ -94,9 +106,7 @@ module Spyder
94
106
  request.add_header(name, value)
95
107
  end
96
108
 
97
- response = process_request(request)
98
-
99
- dispatch_response(socket, response)
109
+ request
100
110
  end
101
111
 
102
112
  def dispatch_response(socket, response)
@@ -104,6 +114,8 @@ module Spyder
104
114
  if !content_length && response.body && response.body.is_a?(String)
105
115
  content_length = response.body.length
106
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'
107
119
  end
108
120
 
109
121
  response.set_header('connection', 'close') unless response.headers.dict['connection']
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spyder
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0-beta1'
5
5
  end
@@ -9,14 +9,18 @@ module Spyder
9
9
  def initialize(paths, index: nil)
10
10
  @default_index = index
11
11
  @base_paths = Array(paths).map do |path|
12
- File.expand_path(File.join(Dir.pwd, path))
12
+ if path[0] == '/'
13
+ path
14
+ else
15
+ File.expand_path(File.join(Dir.pwd, path))
16
+ end
13
17
  end
14
18
  end
15
19
 
16
20
  def call(request)
17
21
  return unless request.verb == 'GET'
18
22
 
19
- input_path = request.path
23
+ input_path = request.path_info
20
24
  input_path = @default_index if request.path == '/' && @default_index
21
25
 
22
26
  req_path = safe_request_path(input_path)
data/lib/spyder.rb CHANGED
@@ -12,6 +12,7 @@ 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'
16
17
  require 'spyder/web_socket_streaming_buffer'
17
18
  require 'spyder/web_socket'
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.2.0
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: 2025-06-21 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
@@ -46,8 +59,10 @@ extra_rdoc_files: []
46
59
  files:
47
60
  - lib/examples/simplest_server/server.rb
48
61
  - lib/spyder.rb
62
+ - lib/spyder/basic_server.rb
49
63
  - lib/spyder/error.rb
50
64
  - lib/spyder/header_store.rb
65
+ - lib/spyder/rack_adapter.rb
51
66
  - lib/spyder/request.rb
52
67
  - lib/spyder/response.rb
53
68
  - lib/spyder/router.rb
@@ -60,7 +75,6 @@ homepage: https://github.com/andrepiske/spyder
60
75
  licenses:
61
76
  - MIT
62
77
  metadata: {}
63
- post_install_message:
64
78
  rdoc_options: []
65
79
  require_paths:
66
80
  - lib
@@ -75,8 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
89
  - !ruby/object:Gem::Version
76
90
  version: '0'
77
91
  requirements: []
78
- rubygems_version: 3.0.3.1
79
- signing_key:
92
+ rubygems_version: 4.0.23
80
93
  specification_version: 4
81
94
  summary: Spyder
82
95
  test_files: []