low_loop 0.2.1 → 0.3.1

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: 7d01329530968986952de86dbb5f82d6e4fc7d2c64111176fca14c403ffd483b
4
- data.tar.gz: fc7475b5dafdf37c2e63d6a95f206c363a7a4476cd161f04c725cf04feb5fa13
3
+ metadata.gz: 3bd2e532e273f35fcbcd9d8e9f0f4b0d9261e536d293908026371412ec11573e
4
+ data.tar.gz: 39b83d8235d14d0d7359ea742f1e9c3d4ab4bdd1e0cceca01006950d2d942cc6
5
5
  SHA512:
6
- metadata.gz: 514b047cf407d3960a37745b166f29c299f4d9976ff717d70c9f44c8f7db5a18fb65072df0c343224b13c81c3c320a7354a44ea05684b5793c9ec35f54039139
7
- data.tar.gz: 02d577dfe82a415b723060e9c2ed4c2517c40d34db71a33e4e71cb1ae55efd4aa9f2d11293e4327fdacc2e7c497e74573b370889bf0f6b0dd79b0ea9db719679
6
+ metadata.gz: 17b1927035f2900208d1cbe6399b7814ec812f19afa1b22734e83995fd7e8f5bbf5d6724403553c55a83ef0779cf7b7bcaa56e56763ee8e2b67814980fab2b53
7
+ data.tar.gz: 29b8756fbb3957e9ab50a5748391b745ca244af98cea79ae2d3e9ed8955ec07aa3f7aad9d339a6154270e004b2058592272a8db132717354c6f5af200245f017
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'low_event'
4
+
5
+ module Low
6
+ module Events
7
+ class FileEvent < LowEvent
8
+ attr_reader :file, :request
9
+
10
+ # TODO: For RouteEvent/FileEvent parse and provide query params as attributes on the event.
11
+ def initialize(file:, request: nil)
12
+ super()
13
+
14
+ @file = file
15
+ @request = request
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,15 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'protocol/http'
4
+ require 'protocol/http/body/file'
4
5
 
5
6
  module Low
6
- class ResponseFactory
7
- class << self
8
- def response(body:)
9
- headers = Protocol::HTTP::Headers.new(['content-type', 'text/html'])
10
- body = Protocol::HTTP::Body::Buffered.wrap(body)
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)
11
13
 
12
- Protocol::HTTP::Response.new('http/1.1', 200, headers, body)
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
13
24
  end
14
25
  end
15
26
  end
data/lib/low_loop.rb CHANGED
@@ -7,66 +7,65 @@ require 'low_event'
7
7
  require 'observers'
8
8
 
9
9
  require_relative 'factories/response_factory'
10
- require_relative 'request_parser'
11
- require_relative 'response_builder'
10
+ require_relative 'requests/request_parser'
11
+ require_relative 'responses/response_builder'
12
12
 
13
- module Low
14
- class Loop
15
- include Observers
13
+ class LowLoop
14
+ include Observers
16
15
 
17
- attr_reader :config
16
+ attr_reader :config
18
17
 
19
- def initialize(config:, router: nil)
20
- @config = config
18
+ def initialize(config:, dependencies: [])
19
+ @config = config
21
20
 
22
- observers.push(self, action: :mirror) if config.mirror_mode
23
- observers << FileServer.new
24
- observers << router if router
21
+ dependencies.each do |dependency|
22
+ observers << dependency
25
23
  end
26
24
 
27
- def start
28
- server = start_server
25
+ observers.push(self, action: :mirror) if config.mirror_mode
26
+ end
27
+
28
+ def start
29
+ server = start_server
29
30
 
30
- Fiber.set_scheduler(Async::Scheduler.new)
31
+ Fiber.set_scheduler(Async::Scheduler.new)
31
32
 
32
- Fiber.schedule do
33
- loop do
34
- socket = server.accept
33
+ Fiber.schedule do
34
+ loop do
35
+ socket = server.accept
35
36
 
36
- Fiber.schedule do
37
- request = RequestParser.parse(socket:, host: config.host, port: config.port)
38
- response_event = take(event: Events::RequestEvent.new(request:))
39
- response = response_event.response
37
+ Fiber.schedule do
38
+ request = Low::RequestParser.parse(socket:, host: config.host, port: config.port)
39
+ response_event = take(event: Low::Events::RequestEvent.new(request:))
40
+ response = response_event.response
40
41
 
41
- ResponseBuilder.respond(config:, socket:, response:)
42
- rescue StandardError => e
43
- puts e.message
44
- ensure
45
- socket&.close
46
- end
42
+ Low::ResponseBuilder.respond(config:, socket:, response:)
43
+ rescue StandardError => e
44
+ puts e.message
45
+ ensure
46
+ socket&.close
47
47
  end
48
48
  end
49
49
  end
50
+ end
50
51
 
51
- def start_server
52
- puts "Starting server @ #{config.host}:#{config.port}" unless config.matrix_mode
53
-
54
- server = TCPServer.new(config.host, config.port)
55
- server.listen(10)
56
- server
57
- end
52
+ def start_server
53
+ puts "Starting server @ #{config.host}:#{config.port}" unless config.matrix_mode
58
54
 
59
- def mirror(event:)
60
- request = event.request
61
- response = Events::ResponseFactory.response(body: "Thank you for visiting #{request.path} with body: '#{request.body}'")
62
- Events::ResponseEvent.new(response:)
63
- end
55
+ server = TCPServer.new(config.host, config.port)
56
+ server.listen(10)
57
+ server
58
+ end
64
59
 
65
- # Consider LowLoop a value object in the context of Observers (there can only be one).
66
- def ==(other) = other.class == self.class
67
- def eql?(other) = self == other
68
- def hash = [self.class].hash
60
+ # Fallback mode for when there's no dependencies and you want to know that the server is still working.
61
+ def mirror(event:)
62
+ request = event.request
63
+ response = Low::Factories::ResponseFactory.html(body: "Thank you for visiting #{request.path} with body: '#{request.body}'")
64
+ Low::Events::ResponseEvent.new(response:)
69
65
  end
70
- end
71
66
 
72
- LowLoop = Low::Loop
67
+ # Consider LowLoop a value object in the context of Observers (there can only be one).
68
+ def ==(other) = other.class == self.class
69
+ def eql?(other) = self == other
70
+ def hash = [self.class].hash
71
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../factories/response_factory'
4
+
5
+ module Low
6
+ class FileResponse
7
+ class << self
8
+ def handle(event:)
9
+ file = event.file
10
+
11
+ if File.exist?(file.path)
12
+ response = Factories::ResponseFactory.file(path: file.path, content_type: file.content_type)
13
+ return Events::ResponseEvent.new(response:)
14
+ end
15
+
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
@@ -7,8 +7,19 @@ module Low
7
7
  def respond(config:, socket:, response:)
8
8
  socket.puts "#{response.version} #{response.status}\r\n"
9
9
  socket.puts config.port.nil? ? "Host: #{config.host}\r\n" : "Host: #{config.host}:#{config.port}\r\n"
10
+
11
+ response.headers.fields.each_slice(2) do |key, value|
12
+ socket.puts "#{key}: #{value}\r\n"
13
+ end
14
+
10
15
  socket.puts "\r\n"
11
- socket.puts(response.body.read)
16
+
17
+ if response.body.respond_to?(:file)
18
+ IO.copy_stream(response.body.file, socket)
19
+ else
20
+ socket.puts(response.body.read)
21
+ end
22
+
12
23
  socket.close
13
24
  end
14
25
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'protocol/url'
4
+
5
+ require_relative '../events/file_event'
6
+ require_relative '../responses/file_response'
7
+ require_relative '../states/file_state'
8
+
9
+ module Low
10
+ class FileServer
11
+ include LowType
12
+ include Observers
13
+
14
+ def initialize(web_root:, content_types:)
15
+ @web_root = web_root
16
+ @content_types = content_types.transform_keys(&:to_s)
17
+
18
+ observers(Events::FileEvent) << FileResponse
19
+ end
20
+
21
+ def extension(filepath:)
22
+ extension = File.extname(filepath).delete_prefix('.')
23
+
24
+ return nil if extension == ''
25
+ return nil unless @content_types.key?(extension)
26
+
27
+ extension
28
+ end
29
+
30
+ # TODO: Define type: Events::RequestEvent
31
+ def handle(event:)
32
+ filepath = Protocol::URL[event.request.path].path
33
+
34
+ extension = extension(filepath:)
35
+ return nil if extension.nil?
36
+
37
+ file = States::FileState.new(path: safe_path(filepath), content_type: @content_types[extension])
38
+
39
+ Events::FileEvent.trigger(file:, request: event.request)
40
+ end
41
+
42
+ private
43
+
44
+ def safe_path(path)
45
+ safe_path = path.split('/').reject { |segment| ['.', '..'].include?(segment) }.join('/')
46
+ File.join(@web_root, safe_path)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Low
4
+ module States
5
+ FileState = Data.define(:path, :content_type)
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConfigSupport
4
+ class << self
5
+ def parse_boolean(value)
6
+ return true if value == '1'
7
+ return false if value == '0'
8
+
9
+ nil
10
+ end
11
+ end
12
+ end
data/lib/version.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Low
4
- LOOP_VERSION = '0.2.1'
4
+ module Loop
5
+ VERSION = '0.3.1'
6
+ end
5
7
  end
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.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -65,6 +65,20 @@ dependencies:
65
65
  - - ">="
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: protocol-url
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
68
82
  - !ruby/object:Gem::Dependency
69
83
  name: low_event
70
84
  requirement: !ruby/object:Gem::Requirement
@@ -101,10 +115,15 @@ executables: []
101
115
  extensions: []
102
116
  extra_rdoc_files: []
103
117
  files:
118
+ - lib/events/file_event.rb
104
119
  - lib/factories/response_factory.rb
105
120
  - lib/low_loop.rb
106
- - lib/request_parser.rb
107
- - lib/response_builder.rb
121
+ - lib/requests/request_parser.rb
122
+ - lib/responses/file_response.rb
123
+ - lib/responses/response_builder.rb
124
+ - lib/servers/file_server.rb
125
+ - lib/states/file_state.rb
126
+ - lib/support/config_support.rb
108
127
  - lib/version.rb
109
128
  homepage: https://github.com/low-rb/low_loop
110
129
  licenses: []