adelnor 0.0.7 → 0.0.8
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 +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +14 -0
- data/README.md +10 -1
- data/adelnor.gemspec +3 -2
- data/lib/adelnor/async_server.rb +29 -0
- data/lib/adelnor/base_server.rb +98 -0
- data/lib/adelnor/clustered_server.rb +33 -0
- data/lib/adelnor/ractor_server.rb +91 -0
- data/lib/adelnor/server.rb +18 -110
- data/lib/adelnor/threaded_server.rb +38 -0
- data/lib/adelnor/version.rb +1 -1
- metadata +25 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b8a98417998eb15f466baf73a5943c808ad1efe160bc87b268464b1b9096bdc
|
4
|
+
data.tar.gz: 8edef2ff4470feefa0273c29b03c03da7245ee48de5df6fad9121798bf10d568
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b5dc9aa3a65158ee1b1d7cc988136b852b7125f646e5f90a1b6154a37c6d2d989f6b1523a391e7c97b76a8934b7dd9b84944e586d57a7236f465f05cd152184
|
7
|
+
data.tar.gz: dcd17f72dfe0279174f4d4e10c48c7cdac44e0ccfa073241715be14ed7fa075ef85cccb1fa7ec3df84fcf968a93714928887f8a4b8dfc69fdf87d07c056efbcc
|
data/Gemfile
CHANGED
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
|
-
##
|
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.
|
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 '
|
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
|
data/lib/adelnor/server.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
require_relative './
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
data/lib/adelnor/version.rb
CHANGED
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.
|
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-
|
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: '
|
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: '
|
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:
|