adelnor 0.0.6 → 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: dd3a506fee52615e09d8aed4c85bea4a9dfdc58567a373b40b5960d903dc621f
4
- data.tar.gz: b69d40e33b17d24932f6feee996116c785660e9519b9062a95de258bca85d0b7
3
+ metadata.gz: 9b8a98417998eb15f466baf73a5943c808ad1efe160bc87b268464b1b9096bdc
4
+ data.tar.gz: 8edef2ff4470feefa0273c29b03c03da7245ee48de5df6fad9121798bf10d568
5
5
  SHA512:
6
- metadata.gz: f2f5ea6b2e64cdb6f9f5447a7590d73fa5bcb1b251b3e438b56bd9bc44a8112a9dc95bbf8492a871f29c61c4dc15b6604673517a3d06035d1234fbcedbfec8e0
7
- data.tar.gz: 99c4805e7b08d6948021d03fdee3c5c9dd311485e2d2bfde2c773241cfb900ce548fe8a5297044ecd9fbc43a3abc06faca0862fc626af620278740a16874a8ec
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
@@ -7,12 +7,13 @@
7
7
  ![Build](https://github.com/leandronsp/adelnor/actions/workflows/build.yml/badge.svg)
8
8
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop)
9
9
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-community-brightgreen.svg)](https://rubystyle.guide)
10
- ``` .___ .__
11
- _____ __| _/____ | | ____ ___________
12
- \__ \ / __ |/ __ \| | / \ / _ \_ __ \
13
- / __ \_/ /_/ \ ___/| |_| | ( <_> ) | \/
14
- (____ /\____ |\___ >____/___| /\____/|__|
15
- \/ \/ \/ \/
10
+ ```
11
+ ___ _______ _______ __ .__ __. ______ .______
12
+ / \ | \ | ____|| | | \ | | / __ \ | _ \
13
+ / ^ \ | .--. || |__ | | | \| | | | | | | |_) |
14
+ / /_\ \ | | | || __| | | | . ` | | | | | | /
15
+ / _____ \ | '--' || |____ | `----.| |\ | | `--' | | |\ \----.
16
+ /__/ \__\ |_______/ |_______||_______||__| \__| \______/ | _| `._____|
16
17
  ```
17
18
 
18
19
  [adelnor](https://rubygems.org/gems/adelnor) is a dead simple, yet Rack-compatible, HTTP server written in Ruby.
@@ -56,4 +57,25 @@ $ make bundle.install
56
57
  $ make sample.server
57
58
  ```
58
59
 
60
+ ## Handling concurrency
61
+
62
+ Currently Adelnor allows to run the server using different concurrency strategies, one at a time.
63
+
64
+ ```ruby
65
+ require './lib/adelnor/server'
66
+
67
+ app = -> (env) do
68
+ [200, { 'Content-Type' => 'text/html' }, 'Hello world!']
69
+ end
70
+
71
+ # Threaded mode
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
79
+ ```
80
+
59
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.6'
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
@@ -21,6 +21,8 @@ module Adelnor
21
21
  lines = @message.split(/\r\n/)
22
22
  headline = lines.shift
23
23
 
24
+ return self unless headline
25
+
24
26
  parse_headline!(headline)
25
27
  parse_headers!(lines)
26
28
 
@@ -19,6 +19,6 @@ module Adelnor
19
19
  private
20
20
 
21
21
  def headers_as_string = @headers.reduce('', &method(:acc_header_string))
22
- def acc_header_string(acc, (key, value)) = acc += "#{key}: #{value}\r\n"
22
+ def acc_header_string(acc, (key, value)) = acc + "#{key}: #{value}\r\n"
23
23
  end
24
24
  end
@@ -1,92 +1,37 @@
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
12
- def initialize(rack_app, port)
11
+ def initialize(rack_app, port, options = {})
13
12
  @rack_app = rack_app
14
13
  @port = port
15
- @socket = Socket.new(:INET, :STREAM)
16
- addr = Socket.sockaddr_in(@port, '0.0.0.0')
17
-
18
- @socket.bind(addr)
19
- @socket.listen(2)
20
-
21
- puts welcome_message
14
+ @options = options
22
15
  end
23
16
 
24
17
  def self.run(*args)
25
18
  new(*args).run
26
19
  end
27
20
 
28
- def rack_data(request)
29
- {
30
- 'REQUEST_METHOD' => request.request_method,
31
- 'PATH_INFO' => request.path_info,
32
- 'QUERY_STRING' => request.query_string,
33
- 'SERVER_PORT' => @port,
34
- 'SERVER_NAME' => request.headers['Host'],
35
- 'CONTENT_LENGTH' => request.content_length,
36
- 'HTTP_COOKIE' => request.headers['Cookie'],
37
- 'rack.input' => StringIO.new(request.body)
38
- }.merge(request.headers)
39
- end
40
-
41
21
  def run
42
- loop do
43
- client, = @socket.accept
44
-
45
- handle(client)
46
-
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
- message += client.gets
65
- puts
66
- puts "[#{Time.now}] #{message}"
67
-
68
- while (line = client.gets)
69
- break if line == "\r\n"
70
-
71
- message += line
72
- end
73
-
74
- message
75
- end
76
-
77
- def welcome_message
78
- <<~WELCOME
79
- |\---/|
80
- | o_o |
81
- \_^_/
82
-
83
- adelnor HTTP server
84
-
85
- ------------------------------------------------
86
-
87
- Adelnor is running at http://0.0.0.0:#{@port}
88
- Listening to HTTP connections
89
- 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)
90
35
  end
91
36
  end
92
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.6
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leandro Proença
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-22 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,15 +49,20 @@ 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:
44
63
  - MIT
45
64
  metadata: {}
46
- post_install_message:
65
+ post_install_message:
47
66
  rdoc_options: []
48
67
  require_paths:
49
68
  - lib
@@ -58,8 +77,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
77
  - !ruby/object:Gem::Version
59
78
  version: '0'
60
79
  requirements: []
61
- rubygems_version: 3.3.3
62
- signing_key:
80
+ rubygems_version: 3.4.10
81
+ signing_key:
63
82
  specification_version: 4
64
83
  summary: Adelnor HTTP server
65
84
  test_files: []