low_loop 0.7.1 → 0.8.0
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/lib/connections/connection_manager.rb +69 -0
- data/lib/events/file_event.rb +1 -1
- data/lib/low_loop.rb +24 -81
- data/lib/responses/file_response.rb +3 -3
- data/lib/responses/response_factory.rb +31 -0
- data/lib/servers/file_server.rb +11 -15
- data/lib/version.rb +1 -1
- metadata +4 -3
- data/lib/factories/response_factory.rb +0 -27
- /data/lib/{requests → connections}/request_parser.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 17df935b57f5de96e6888dd4342ade7c60ec4639f441d0ab7f517c858b269a80
|
|
4
|
+
data.tar.gz: e1934e74675858f24231771c81cf05751c06582b251b80075d4177e8d47154b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 48d2c4a1c581f14f0eb5e9255de93354f76e1638e52a1d2157cfd958f752eec9207ce71e91e9eddbf2032990f052deae91b422793cb27ce48f362929a565a145
|
|
7
|
+
data.tar.gz: 6f45681e3d0d17d800ab9c21ad543005016c9b6c779e48f45afe3c6380f98056f151e41a19ee19f71e128d47baea9fb1990f1e48dfeed1008a95e29bb7ad96f4
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'io/wait'
|
|
4
|
+
|
|
5
|
+
require_relative 'request_parser'
|
|
6
|
+
require_relative '../responses/response_builder'
|
|
7
|
+
|
|
8
|
+
module Low
|
|
9
|
+
class ConnectionManager
|
|
10
|
+
DEFAULT_KEEP_ALIVE_TIMEOUT = 30
|
|
11
|
+
DEFAULT_REQUEST_TIMEOUT = 10
|
|
12
|
+
|
|
13
|
+
def initialize(config:)
|
|
14
|
+
@config = config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def handle_connection(socket:)
|
|
18
|
+
stream = RequestParser.create_stream(socket:)
|
|
19
|
+
keep_alive = true
|
|
20
|
+
version = nil
|
|
21
|
+
|
|
22
|
+
while keep_alive
|
|
23
|
+
break unless socket.wait_readable(keep_alive_timeout)
|
|
24
|
+
|
|
25
|
+
socket.timeout = request_timeout
|
|
26
|
+
begin
|
|
27
|
+
request = RequestParser.parse(stream:, host: config.host, port: config.port, version:)
|
|
28
|
+
rescue IO::TimeoutError
|
|
29
|
+
break
|
|
30
|
+
ensure
|
|
31
|
+
socket.timeout = nil
|
|
32
|
+
end
|
|
33
|
+
break if request.nil?
|
|
34
|
+
|
|
35
|
+
version ||= request.version
|
|
36
|
+
keep_alive = keep_alive?(request)
|
|
37
|
+
|
|
38
|
+
response_event = Events::RequestEvent.take(request:)
|
|
39
|
+
response = response_event.response
|
|
40
|
+
|
|
41
|
+
ResponseBuilder.respond(config:, socket:, response:, keep_alive:)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def keep_alive?(request)
|
|
46
|
+
tokens = (request.headers['connection'] || []).flat_map do |value|
|
|
47
|
+
value.split(',').map { |token| token.strip.downcase }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if request.version.to_s.downcase.include?('1.0')
|
|
51
|
+
tokens.include?('keep-alive')
|
|
52
|
+
else
|
|
53
|
+
!tokens.include?('close')
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def keep_alive_timeout
|
|
58
|
+
config.keep_alive_timeout || DEFAULT_KEEP_ALIVE_TIMEOUT
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def request_timeout
|
|
62
|
+
config.request_timeout || DEFAULT_REQUEST_TIMEOUT
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
attr_reader :config
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/events/file_event.rb
CHANGED
data/lib/low_loop.rb
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'async'
|
|
4
|
-
require 'io/wait'
|
|
5
4
|
require 'paint'
|
|
6
5
|
require 'socket'
|
|
7
6
|
require 'low_type'
|
|
8
7
|
require 'low_event'
|
|
9
8
|
require 'observers'
|
|
10
9
|
|
|
11
|
-
require_relative '
|
|
12
|
-
require_relative '
|
|
13
|
-
require_relative 'responses/response_builder'
|
|
10
|
+
require_relative 'responses/response_factory'
|
|
11
|
+
require_relative 'connections/connection_manager'
|
|
14
12
|
require_relative 'servers/file_server'
|
|
15
13
|
require_relative 'support/low_frame'
|
|
16
14
|
|
|
17
15
|
class LowLoop
|
|
18
16
|
include Observers
|
|
19
17
|
|
|
20
|
-
DEFAULT_KEEP_ALIVE_TIMEOUT = 30
|
|
21
|
-
DEFAULT_REQUEST_TIMEOUT = 10
|
|
22
|
-
|
|
23
18
|
attr_reader :config
|
|
24
19
|
|
|
25
20
|
def initialize(config:, router: nil, renderer: nil, show_output: true)
|
|
26
21
|
@config = config
|
|
22
|
+
@connection_manager = Low::ConnectionManager.new(config:)
|
|
27
23
|
@frame = LowFrame.new(renderer:, fps: 10, show_output:)
|
|
28
24
|
|
|
29
25
|
Low::Events::RequestEvent.define do |observers|
|
|
@@ -37,20 +33,13 @@ class LowLoop
|
|
|
37
33
|
server = start_server
|
|
38
34
|
|
|
39
35
|
Async do |task|
|
|
40
|
-
|
|
41
|
-
task.async do
|
|
42
|
-
loop do
|
|
43
|
-
@frame.render if @frame.renderer
|
|
44
|
-
sleep 0.1 # 10fps
|
|
45
|
-
end
|
|
46
|
-
end
|
|
36
|
+
render_frame(task)
|
|
47
37
|
|
|
48
|
-
# Request handler.
|
|
49
38
|
loop do
|
|
50
39
|
socket = server.accept
|
|
51
40
|
|
|
52
41
|
task.async do
|
|
53
|
-
handle_connection(socket)
|
|
42
|
+
@connection_manager.handle_connection(socket:)
|
|
54
43
|
rescue StandardError => e
|
|
55
44
|
render_error(e)
|
|
56
45
|
ensure
|
|
@@ -60,6 +49,20 @@ class LowLoop
|
|
|
60
49
|
end
|
|
61
50
|
end
|
|
62
51
|
|
|
52
|
+
# Fallback mode for when there's no dependencies and you want to know that the server is still working.
|
|
53
|
+
def mirror(event:)
|
|
54
|
+
request = event.request
|
|
55
|
+
response = tories::ResponseFactory.html(body: "Thank you for visiting #{request.path} with body: '#{request.body}'")
|
|
56
|
+
Low::Events::ResponseEvent.new(response:)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Consider LowLoop a value object in the context of Observers (there can only be one).
|
|
60
|
+
def ==(other) = other.class == self.class
|
|
61
|
+
def eql?(other) = self == other
|
|
62
|
+
def hash = [self.class].hash
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
63
66
|
def start_server
|
|
64
67
|
puts "Starting server @ #{config.host}:#{config.port}" unless config.matrix_mode
|
|
65
68
|
|
|
@@ -68,28 +71,17 @@ class LowLoop
|
|
|
68
71
|
server
|
|
69
72
|
end
|
|
70
73
|
|
|
71
|
-
def
|
|
72
|
-
|
|
74
|
+
def render_frame(task)
|
|
75
|
+
return unless @frame.renderer
|
|
76
|
+
|
|
77
|
+
task.async do
|
|
73
78
|
loop do
|
|
74
79
|
@frame.render
|
|
80
|
+
sleep 0.1 # 10fps
|
|
75
81
|
end
|
|
76
82
|
end
|
|
77
83
|
end
|
|
78
84
|
|
|
79
|
-
# Fallback mode for when there's no dependencies and you want to know that the server is still working.
|
|
80
|
-
def mirror(event:)
|
|
81
|
-
request = event.request
|
|
82
|
-
response = Low::Factories::ResponseFactory.html(body: "Thank you for visiting #{request.path} with body: '#{request.body}'")
|
|
83
|
-
Low::Events::ResponseEvent.new(response:)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Consider LowLoop a value object in the context of Observers (there can only be one).
|
|
87
|
-
def ==(other) = other.class == self.class
|
|
88
|
-
def eql?(other) = self == other
|
|
89
|
-
def hash = [self.class].hash
|
|
90
|
-
|
|
91
|
-
private
|
|
92
|
-
|
|
93
85
|
def render_error(error)
|
|
94
86
|
puts "\nException:"
|
|
95
87
|
puts Paint[error.message, :red]
|
|
@@ -104,53 +96,4 @@ class LowLoop
|
|
|
104
96
|
gets
|
|
105
97
|
end
|
|
106
98
|
end
|
|
107
|
-
|
|
108
|
-
def handle_connection(socket)
|
|
109
|
-
stream = Low::RequestParser.create_stream(socket:)
|
|
110
|
-
keep_alive = true
|
|
111
|
-
version = nil
|
|
112
|
-
|
|
113
|
-
while keep_alive
|
|
114
|
-
break unless socket.wait_readable(keep_alive_timeout)
|
|
115
|
-
|
|
116
|
-
socket.timeout = request_timeout
|
|
117
|
-
begin
|
|
118
|
-
request = Low::RequestParser.parse(stream:, host: config.host, port: config.port, version:)
|
|
119
|
-
rescue IO::TimeoutError
|
|
120
|
-
break
|
|
121
|
-
ensure
|
|
122
|
-
socket.timeout = nil
|
|
123
|
-
end
|
|
124
|
-
break if request.nil?
|
|
125
|
-
|
|
126
|
-
version ||= request.version
|
|
127
|
-
keep_alive = keep_alive?(request)
|
|
128
|
-
|
|
129
|
-
# TODO: Handle nil return value; create 500 status code response.
|
|
130
|
-
response_event = Low::Events::RequestEvent.take(request:)
|
|
131
|
-
response = response_event.response
|
|
132
|
-
|
|
133
|
-
Low::ResponseBuilder.respond(config:, socket:, response:, keep_alive:)
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def keep_alive?(request)
|
|
138
|
-
tokens = (request.headers['connection'] || []).flat_map do |value|
|
|
139
|
-
value.split(',').map { |token| token.strip.downcase }
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
if request.version.to_s.downcase.include?('1.0')
|
|
143
|
-
tokens.include?('keep-alive')
|
|
144
|
-
else
|
|
145
|
-
!tokens.include?('close')
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def keep_alive_timeout
|
|
150
|
-
config.keep_alive_timeout || DEFAULT_KEEP_ALIVE_TIMEOUT
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def request_timeout
|
|
154
|
-
config.request_timeout || DEFAULT_REQUEST_TIMEOUT
|
|
155
|
-
end
|
|
156
99
|
end
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '../
|
|
3
|
+
require_relative '../responses/response_factory'
|
|
4
4
|
|
|
5
5
|
module Low
|
|
6
6
|
class FileResponse
|
|
7
7
|
class << self
|
|
8
|
-
def
|
|
8
|
+
def request(event:)
|
|
9
9
|
file = event.file
|
|
10
10
|
|
|
11
11
|
if File.exist?(file.path)
|
|
12
|
-
response =
|
|
12
|
+
response = ResponseFactory.file(path: file.path, content_type: file.content_type)
|
|
13
13
|
return Events::ResponseEvent.new(response:).tap(&:branch)
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'protocol/http'
|
|
4
|
+
require 'protocol/http/body/file'
|
|
5
|
+
|
|
6
|
+
module Low
|
|
7
|
+
class ResponseFactory
|
|
8
|
+
class << self
|
|
9
|
+
def html(body:)
|
|
10
|
+
headers = Protocol::HTTP::Headers.new([['content-type', 'text/html']])
|
|
11
|
+
body = Protocol::HTTP::Body::Buffered.wrap(body.strip)
|
|
12
|
+
|
|
13
|
+
Protocol::HTTP::Response.new('HTTP/1.1', 200, headers, body)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def file(path:, content_type:)
|
|
17
|
+
headers = Protocol::HTTP::Headers.new([['content-type', content_type]])
|
|
18
|
+
file = File.open(path, 'rb')
|
|
19
|
+
body = Protocol::HTTP::Body::File.new(file)
|
|
20
|
+
|
|
21
|
+
Protocol::HTTP::Response.new('HTTP/1.1', 200, headers, body)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def status(code:)
|
|
25
|
+
headers = Protocol::HTTP::Headers.new([['content-type', 'text/html']])
|
|
26
|
+
|
|
27
|
+
Protocol::HTTP::Response.new('HTTP/1.1', code, headers, nil)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/servers/file_server.rb
CHANGED
|
@@ -18,8 +18,8 @@ module Low
|
|
|
18
18
|
observers(Events::FileEvent) << FileResponse
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def extension(
|
|
22
|
-
extension = File.extname(
|
|
21
|
+
def extension(file_path:)
|
|
22
|
+
extension = File.extname(file_path).delete_prefix('.')
|
|
23
23
|
|
|
24
24
|
return nil if extension == ''
|
|
25
25
|
return nil unless @content_types.key?(extension)
|
|
@@ -27,23 +27,19 @@ module Low
|
|
|
27
27
|
extension
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
filepath = Protocol::URL::Path.to_local_path(Protocol::URL[event.request.path].path)
|
|
30
|
+
def request(event: Events::RequestEvent)
|
|
31
|
+
file_path = event.request.path
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
return nil if extension.nil?
|
|
36
|
-
|
|
37
|
-
file = States::FileState.new(path: safe_path(filepath), content_type: @content_types[extension])
|
|
33
|
+
return nil unless file_path.include?('.')
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
url = Protocol::URL[file_path]
|
|
36
|
+
extension = extension(file_path: url.path.to_s)
|
|
37
|
+
return nil if extension.nil?
|
|
41
38
|
|
|
42
|
-
|
|
39
|
+
file_path = url.local_path(@web_root)
|
|
40
|
+
file = States::FileState.new(path: file_path, content_type: @content_types[extension])
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
safe_path = path.split('/').reject { |segment| ['.', '..'].include?(segment) }.join('/')
|
|
46
|
-
File.join(@web_root, safe_path)
|
|
42
|
+
Events::FileEvent.take(file:, request: event.request)
|
|
47
43
|
end
|
|
48
44
|
end
|
|
49
45
|
end
|
data/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: low_loop
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
@@ -129,12 +129,13 @@ executables: []
|
|
|
129
129
|
extensions: []
|
|
130
130
|
extra_rdoc_files: []
|
|
131
131
|
files:
|
|
132
|
+
- lib/connections/connection_manager.rb
|
|
133
|
+
- lib/connections/request_parser.rb
|
|
132
134
|
- lib/events/file_event.rb
|
|
133
|
-
- lib/factories/response_factory.rb
|
|
134
135
|
- lib/low_loop.rb
|
|
135
|
-
- lib/requests/request_parser.rb
|
|
136
136
|
- lib/responses/file_response.rb
|
|
137
137
|
- lib/responses/response_builder.rb
|
|
138
|
+
- lib/responses/response_factory.rb
|
|
138
139
|
- lib/servers/file_server.rb
|
|
139
140
|
- lib/states/file_state.rb
|
|
140
141
|
- lib/support/config_loader.rb
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'protocol/http'
|
|
4
|
-
require 'protocol/http/body/file'
|
|
5
|
-
|
|
6
|
-
module Low
|
|
7
|
-
module Factories
|
|
8
|
-
class ResponseFactory
|
|
9
|
-
class << self
|
|
10
|
-
def html(body:)
|
|
11
|
-
headers = Protocol::HTTP::Headers.new([['content-type', 'text/html']])
|
|
12
|
-
body = Protocol::HTTP::Body::Buffered.wrap(body.strip)
|
|
13
|
-
|
|
14
|
-
Protocol::HTTP::Response.new('HTTP/1.1', 200, headers, body)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def file(path:, content_type:)
|
|
18
|
-
headers = Protocol::HTTP::Headers.new([['content-type', content_type]])
|
|
19
|
-
file = File.open(path, 'rb')
|
|
20
|
-
body = Protocol::HTTP::Body::File.new(file)
|
|
21
|
-
|
|
22
|
-
Protocol::HTTP::Response.new('HTTP/1.1', 200, headers, body)
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
File without changes
|