adelnor 0.0.7 → 0.0.8

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: 5bbe7f5e96936b78536738d717ea5babeb525aec4ecc202774e6564095d21663
4
- data.tar.gz: 8da6cf7523a6d8f5d520fe6eb1c8e02320b53e79f631b0e36d0401a29f093f47
3
+ metadata.gz: 9b8a98417998eb15f466baf73a5943c808ad1efe160bc87b268464b1b9096bdc
4
+ data.tar.gz: 8edef2ff4470feefa0273c29b03c03da7245ee48de5df6fad9121798bf10d568
5
5
  SHA512:
6
- metadata.gz: b46502cfb371089998df7cf3d98221224df23ebb77cfd6f368d9d3b21df8ef8244a8cde14207c8b511146011fcd6766498483288ade45f263fdb00b4c7ed3927
7
- data.tar.gz: d7fdfbe381e3c56c4c6bfdc7109afdd10ee9bd05c7d95d5da012fe671f3b547763a3bb916e519c2228038a1a6be7c473236c5bb623662e0ff7b8f7672209349b
6
+ metadata.gz: 1b5dc9aa3a65158ee1b1d7cc988136b852b7125f646e5f90a1b6154a37c6d2d989f6b1523a391e7c97b76a8934b7dd9b84944e586d57a7236f465f05cd152184
7
+ data.tar.gz: dcd17f72dfe0279174f4d4e10c48c7cdac44e0ccfa073241715be14ed7fa075ef85cccb1fa7ec3df84fcf968a93714928887f8a4b8dfc69fdf87d07c056efbcc
data/Gemfile CHANGED
@@ -2,5 +2,6 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'async'
5
6
  gem 'rack'
6
7
  gem 'rubocop', require: false
data/Gemfile.lock CHANGED
@@ -2,6 +2,17 @@ GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
4
  ast (2.4.2)
5
+ async (2.6.4)
6
+ console (~> 1.10)
7
+ fiber-annotation
8
+ io-event (~> 1.1)
9
+ timers (~> 4.1)
10
+ console (1.23.2)
11
+ fiber-annotation
12
+ fiber-local
13
+ fiber-annotation (0.2.0)
14
+ fiber-local (1.0.0)
15
+ io-event (1.3.2)
5
16
  parallel (1.22.1)
6
17
  parser (3.1.2.0)
7
18
  ast (~> 2.4.1)
@@ -21,12 +32,15 @@ GEM
21
32
  rubocop-ast (1.18.0)
22
33
  parser (>= 3.1.1.0)
23
34
  ruby-progressbar (1.11.0)
35
+ timers (4.3.5)
24
36
  unicode-display_width (2.1.0)
25
37
 
26
38
  PLATFORMS
27
39
  aarch64-linux
40
+ x86_64-linux
28
41
 
29
42
  DEPENDENCIES
43
+ async
30
44
  rack
31
45
  rubocop
32
46
 
data/README.md CHANGED
@@ -57,7 +57,9 @@ $ make bundle.install
57
57
  $ make sample.server
58
58
  ```
59
59
 
60
- ## Using Thread Pool
60
+ ## Handling concurrency
61
+
62
+ Currently Adelnor allows to run the server using different concurrency strategies, one at a time.
61
63
 
62
64
  ```ruby
63
65
  require './lib/adelnor/server'
@@ -66,7 +68,14 @@ app = -> (env) do
66
68
  [200, { 'Content-Type' => 'text/html' }, 'Hello world!']
67
69
  end
68
70
 
71
+ # Threaded mode
69
72
  Adelnor::Server.run app, 3000, thread_pool: 5
73
+
74
+ # Clusterd mode (forking)
75
+ Adelnor::Server.run app, 3000, workers: 2
76
+
77
+ # Async mode
78
+ Adelnor::Server.run app, 3000, async: true
70
79
  ```
71
80
 
72
81
  Open `http://localhost:3000`
data/adelnor.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'adelnor'
5
- spec.version = '0.0.7'
5
+ spec.version = '0.0.8'
6
6
  spec.summary = 'Adelnor HTTP server'
7
7
  spec.description = 'A dead simple, yet Rack-compatible, HTTP server written in Ruby'
8
8
  spec.authors = ['Leandro Proença']
@@ -11,7 +11,8 @@ Gem::Specification.new do |spec|
11
11
  spec.homepage = 'https://github.com/leandronsp/adelnor'
12
12
  spec.license = 'MIT'
13
13
 
14
- spec.add_runtime_dependency 'rack'
14
+ spec.add_runtime_dependency 'async', '~> 2.6'
15
+ spec.add_runtime_dependency 'rack', '~> 2.2'
15
16
 
16
17
  spec.required_ruby_version = '~> 3.0'
17
18
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './base_server'
4
+ require 'async'
5
+ require 'async/scheduler'
6
+
7
+ module Adelnor
8
+ class AsyncServer < BaseServer
9
+ def initialize(rack_app, port, options = {})
10
+ super(rack_app, port, options)
11
+
12
+ scheduler = Async::Scheduler.new
13
+ Fiber.set_scheduler(scheduler)
14
+ end
15
+
16
+ def run
17
+ Async do |task|
18
+ loop do
19
+ client, = @socket.accept
20
+
21
+ task.async do
22
+ handle(client)
23
+ client.close
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'rack'
5
+ require 'stringio'
6
+
7
+ require_relative './request'
8
+ require_relative './response'
9
+
10
+ module Adelnor
11
+ class BaseServer
12
+ def initialize(rack_app, port, options = {})
13
+ @rack_app = rack_app
14
+ @port = port
15
+ @options = options
16
+ @socket = Socket.new(:INET, :STREAM)
17
+ addr = Socket.sockaddr_in(@port, '0.0.0.0')
18
+
19
+ @socket.bind(addr)
20
+ @socket.listen(2)
21
+
22
+ puts welcome_message
23
+ end
24
+
25
+ def self.run(*args)
26
+ new(*args).run
27
+ end
28
+
29
+ def rack_data(request)
30
+ {
31
+ 'REQUEST_METHOD' => request.request_method,
32
+ 'PATH_INFO' => request.path_info,
33
+ 'QUERY_STRING' => request.query_string,
34
+ 'SERVER_PORT' => @port,
35
+ 'SERVER_NAME' => request.headers['Host'],
36
+ 'CONTENT_LENGTH' => request.content_length,
37
+ 'HTTP_COOKIE' => request.headers['Cookie'],
38
+ 'rack.input' => StringIO.new(request.body)
39
+ }.merge(request.headers)
40
+ end
41
+
42
+ def run
43
+ loop do
44
+ client, = @socket.accept
45
+
46
+ handle(client)
47
+ client.close
48
+ end
49
+ end
50
+
51
+ def handle(client)
52
+ read_request_message(client)
53
+ .then { |message| Request.build(message) }
54
+ .tap { |request| request.parse_body!(client) }
55
+ .then { |request| rack_data(request) }
56
+ .then { |data| @rack_app.call(data) }
57
+ .then { |result| Response.build(*result) }
58
+ .then { |response| client.puts(response) }
59
+ end
60
+
61
+ def read_request_message(client)
62
+ message = ''
63
+
64
+ if (line = client.gets)
65
+ message += line
66
+ end
67
+
68
+ puts "\n[#{Time.now}] #{message}"
69
+
70
+ while (line = client.gets)
71
+ break if line == "\r\n"
72
+
73
+ message += line
74
+ end
75
+
76
+ message
77
+ end
78
+
79
+ def welcome_message
80
+ thread_pool_message = "Running with thread pool of #{@options[:thread_pool]} threads" if @options[:thread_pool]
81
+
82
+ <<~WELCOME
83
+ |\---/|
84
+ | o_o |
85
+ \_^_/
86
+
87
+ adelnor HTTP server
88
+
89
+ ------------------------------------------------
90
+
91
+ Adelnor is running at http://0.0.0.0:#{@port}
92
+ Listening to HTTP connections
93
+
94
+ #{thread_pool_message}
95
+ WELCOME
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './base_server'
4
+
5
+ module Adelnor
6
+ class ClusteredServer < BaseServer
7
+ def initialize(rack_app, port, options = {})
8
+ super(rack_app, port, options)
9
+
10
+ @workers = options[:workers]
11
+ end
12
+
13
+ def run
14
+ @workers.times do
15
+ fork { handle_worker }
16
+ end
17
+
18
+ Process.waitall
19
+ end
20
+
21
+ def handle_worker
22
+ pid = Process.pid
23
+ puts "[#{pid}] Worker started"
24
+
25
+ loop do
26
+ client, = @socket.accept
27
+
28
+ handle(client)
29
+ client.close
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:disable all
3
+
4
+ require_relative './base_server'
5
+
6
+ module Adelnor
7
+ class RactorServer < BaseServer
8
+ def initialize(rack_app, port, options = {})
9
+ @ractors = options[:ractors]
10
+
11
+ @rack_app = rack_app
12
+ @port = port
13
+ @options = options
14
+
15
+ @queue = Ractor.new do
16
+ loop do
17
+ Ractor.yield(Ractor.receive, move: true)
18
+ end
19
+ end
20
+
21
+ @listener = Ractor.new(@queue, @port) do |queue, port|
22
+ socket = Socket.new(:INET, :STREAM)
23
+ addr = Socket.sockaddr_in(port, '0.0.0.0')
24
+
25
+ socket.bind(addr)
26
+ socket.listen(2)
27
+
28
+ loop do
29
+ client, = socket.accept
30
+
31
+ queue.send(client, move: true)
32
+ end
33
+ end
34
+
35
+ puts welcome_message
36
+ end
37
+
38
+ def run
39
+ ractors = @ractors.times.map do
40
+ Ractor.new(@queue, @rack_app, @port) do |queue, rack_app, port|
41
+ rid = Ractor.current.object_id
42
+ puts "[#{rid}] Ractor started"
43
+
44
+ loop do
45
+ client = queue.take
46
+ message = ''
47
+
48
+ if (line = client.gets)
49
+ message += line
50
+ end
51
+
52
+ puts "\n[#{Time.now}] #{message}"
53
+
54
+ while (line = client.gets)
55
+ break if line == "\r\n"
56
+
57
+ message += line
58
+ end
59
+
60
+ rack_data = lambda do |request|
61
+ {
62
+ 'REQUEST_METHOD' => request.request_method,
63
+ 'PATH_INFO' => request.path_info,
64
+ 'QUERY_STRING' => request.query_string,
65
+ 'SERVER_PORT' => port,
66
+ 'SERVER_NAME' => request.headers['Host'],
67
+ 'CONTENT_LENGTH' => request.content_length,
68
+ 'HTTP_COOKIE' => request.headers['Cookie'],
69
+ 'rack.input' => StringIO.new(request.body)
70
+ }
71
+ end
72
+
73
+ Request.build(message)
74
+ .tap { |request| request.parse_body!(client) }
75
+ .then { |request| rack_data.call(request).merge(request.headers) }
76
+ .then { |data| rack_app.call(data) }
77
+ .then { |result| Response.build(*result) }
78
+ .then { |response| client.puts(response) }
79
+
80
+ client.close
81
+ end
82
+ end
83
+ end
84
+
85
+ loop do
86
+ Ractor.select(@listener, *ractors)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ # rubocop:enable all
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'socket'
4
- require 'rack'
5
- require 'stringio'
6
-
7
- require_relative './request'
8
- require_relative './response'
3
+ require_relative './base_server'
4
+ require_relative './threaded_server'
5
+ require_relative './clustered_server'
6
+ require_relative './ractor_server'
7
+ require_relative './async_server'
9
8
 
10
9
  module Adelnor
11
10
  class Server
@@ -13,117 +12,26 @@ module Adelnor
13
12
  @rack_app = rack_app
14
13
  @port = port
15
14
  @options = options
16
- @socket = Socket.new(:INET, :STREAM)
17
- addr = Socket.sockaddr_in(@port, '0.0.0.0')
18
-
19
- @socket.bind(addr)
20
- @socket.listen(2)
21
-
22
- @thread_queue = Queue.new if @options[:thread_pool]
23
- puts welcome_message
24
15
  end
25
16
 
26
17
  def self.run(*args)
27
18
  new(*args).run
28
19
  end
29
20
 
30
- def rack_data(request)
31
- {
32
- 'REQUEST_METHOD' => request.request_method,
33
- 'PATH_INFO' => request.path_info,
34
- 'QUERY_STRING' => request.query_string,
35
- 'SERVER_PORT' => @port,
36
- 'SERVER_NAME' => request.headers['Host'],
37
- 'CONTENT_LENGTH' => request.content_length,
38
- 'HTTP_COOKIE' => request.headers['Cookie'],
39
- 'rack.input' => StringIO.new(request.body)
40
- }.merge(request.headers)
41
- end
42
-
43
21
  def run
44
- if (pool_size = @options[:thread_pool])
45
- return run_with_thread_pool(pool_size)
46
- end
47
-
48
- loop do
49
- client, = @socket.accept
50
-
51
- handle(client)
52
- client.close
53
- end
54
- end
55
-
56
- def run_with_thread_pool(pool_size)
57
- pool_size.times do
58
- handle_request_in_thread
59
- end
60
-
61
- loop do
62
- client, = @socket.accept
63
-
64
- @thread_queue.push(client)
65
- end
66
- end
67
-
68
- def handle_request_in_thread
69
- Thread.new do
70
- tid = Thread.current.object_id
71
-
72
- puts "[#{tid}] Thread started"
73
- loop do
74
- client = @thread_queue.pop
75
-
76
- handle(client)
77
- client.close
78
- end
79
- end
80
- end
81
-
82
- def handle(client)
83
- read_request_message(client)
84
- .then { |message| Request.build(message) }
85
- .tap { |request| request.parse_body!(client) }
86
- .then { |request| rack_data(request) }
87
- .then { |data| @rack_app.call(data) }
88
- .then { |result| Response.build(*result) }
89
- .then { |response| client.puts(response) }
90
- end
91
-
92
- def read_request_message(client)
93
- message = ''
94
-
95
- if (line = client.gets)
96
- message += line
97
- end
98
-
99
- puts "\n[#{Time.now}] #{message}"
100
-
101
- while (line = client.gets)
102
- break if line == "\r\n"
103
-
104
- message += line
105
- end
106
-
107
- message
108
- end
109
-
110
- def welcome_message
111
- thread_pool_message = "Running with thread pool of #{@options[:thread_pool]} threads" if @options[:thread_pool]
112
-
113
- <<~WELCOME
114
- |\---/|
115
- | o_o |
116
- \_^_/
117
-
118
- adelnor HTTP server
119
-
120
- ------------------------------------------------
121
-
122
- Adelnor is running at http://0.0.0.0:#{@port}
123
- Listening to HTTP connections
124
-
125
- #{thread_pool_message}
126
- WELCOME
22
+ handler_klass = if @options[:thread_pool]
23
+ ThreadedServer
24
+ elsif @options[:workers]
25
+ ClusteredServer
26
+ # elsif @options[:ractors]
27
+ # RactorServer
28
+ elsif @options[:async]
29
+ AsyncServer
30
+ else
31
+ BaseServer
32
+ end
33
+
34
+ handler_klass.run(@rack_app, @port, @options)
127
35
  end
128
36
  end
129
37
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './base_server'
4
+
5
+ module Adelnor
6
+ class ThreadedServer < BaseServer
7
+ def initialize(rack_app, port, options = {})
8
+ super(rack_app, port, options)
9
+
10
+ @thread_queue = Queue.new
11
+ @pool_size = options[:thread_pool]
12
+ end
13
+
14
+ def run
15
+ @pool_size.times do
16
+ Thread.new { handle_thread }
17
+ end
18
+
19
+ loop do
20
+ client, = @socket.accept
21
+
22
+ @thread_queue.push(client)
23
+ end
24
+ end
25
+
26
+ def handle_thread
27
+ tid = Thread.current.object_id
28
+
29
+ puts "[#{tid}] Thread started"
30
+ loop do
31
+ client = @thread_queue.pop
32
+
33
+ handle(client)
34
+ client.close
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Adelnor
4
- VERSION = '0.0.6'
4
+ VERSION = '0.0.8'
5
5
  end
metadata CHANGED
@@ -1,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adelnor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leandro Proença
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-05 00:00:00.000000000 Z
11
+ date: 2023-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: async
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.6'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rack
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
- - - ">="
31
+ - - "~>"
18
32
  - !ruby/object:Gem::Version
19
- version: '0'
33
+ version: '2.2'
20
34
  type: :runtime
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
- - - ">="
38
+ - - "~>"
25
39
  - !ruby/object:Gem::Version
26
- version: '0'
40
+ version: '2.2'
27
41
  description: A dead simple, yet Rack-compatible, HTTP server written in Ruby
28
42
  email: leandronsp@gmail.com
29
43
  executables: []
@@ -35,9 +49,14 @@ files:
35
49
  - README.md
36
50
  - adelnor.gemspec
37
51
  - lib/adelnor.rb
52
+ - lib/adelnor/async_server.rb
53
+ - lib/adelnor/base_server.rb
54
+ - lib/adelnor/clustered_server.rb
55
+ - lib/adelnor/ractor_server.rb
38
56
  - lib/adelnor/request.rb
39
57
  - lib/adelnor/response.rb
40
58
  - lib/adelnor/server.rb
59
+ - lib/adelnor/threaded_server.rb
41
60
  - lib/adelnor/version.rb
42
61
  homepage: https://github.com/leandronsp/adelnor
43
62
  licenses: