low_loop 0.0.0 → 0.1.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/factories/response_factory.rb +16 -0
- data/lib/low_loop.rb +49 -0
- data/lib/request_parser.rb +64 -0
- data/lib/response_builder.rb +16 -0
- data/lib/version.rb +2 -2
- metadata +76 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0cf78d69ff18cc8ec9d07e93acb8c3bffce11e38e83667b8ffe852fa842561e0
|
|
4
|
+
data.tar.gz: 9f96d239ec4b1c80823260b3d3718859528f6189d611247f6f6f7e4e34a23082
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a8a845e855e664055b2676d665bdb40d133f4595d4eacc6d0eea11b028b3c992f0c83a82527aaec0f840e3efcd598138e076ce8ef1f95541e118c2439ad6f33
|
|
7
|
+
data.tar.gz: 4dfc52e4259769ff99efba99195f8561689c8b62a1354bd724dc84ba964ed6a057501d3a1b9d3729c404902918a11648ee08d7e3528c13f22c691a1c4772bc4e
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'protocol/http'
|
|
4
|
+
|
|
5
|
+
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)
|
|
11
|
+
|
|
12
|
+
Protocol::HTTP::Response.new('http/1.1', 200, headers, body)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/low_loop.rb
CHANGED
|
@@ -1,2 +1,51 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'async'
|
|
4
|
+
require 'socket'
|
|
5
|
+
require 'low_type'
|
|
6
|
+
require 'low_event' # TODO: Move RequestEvent and ResponseEvent into this gem.
|
|
7
|
+
require 'observers'
|
|
8
|
+
|
|
9
|
+
require_relative 'factories/response_factory'
|
|
10
|
+
require_relative 'request_parser'
|
|
11
|
+
require_relative 'response_builder'
|
|
12
|
+
|
|
13
|
+
module Low
|
|
14
|
+
class Loop
|
|
15
|
+
extend Observers
|
|
16
|
+
observable
|
|
17
|
+
|
|
18
|
+
def start(config:)
|
|
19
|
+
server = TCPServer.new(config.host, config.port)
|
|
20
|
+
server.listen(10)
|
|
21
|
+
puts "Server@#{config.host}:#{config.port}" if config.matrix_mode
|
|
22
|
+
|
|
23
|
+
Fiber.set_scheduler(Async::Scheduler.new)
|
|
24
|
+
|
|
25
|
+
Fiber.schedule do
|
|
26
|
+
loop do
|
|
27
|
+
socket = server.accept
|
|
28
|
+
|
|
29
|
+
Fiber.schedule do
|
|
30
|
+
request = RequestParser.parse(socket:, host: config.host, port: config.port)
|
|
31
|
+
|
|
32
|
+
if config.mirror_mode
|
|
33
|
+
response = Low::ResponseFactory.response(body: "Thank you for visiting #{request.path} with '#{request.body}'")
|
|
34
|
+
else
|
|
35
|
+
response_event = LowLoop.take RequestEvent.new(request:)
|
|
36
|
+
response = response_event.response
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
ResponseBuilder.respond(config:, socket:, response:)
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
puts e.message
|
|
42
|
+
ensure
|
|
43
|
+
socket&.close
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
LowLoop = Low::Loop
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'io/stream'
|
|
4
|
+
require 'async/http'
|
|
5
|
+
require 'protocol/http'
|
|
6
|
+
|
|
7
|
+
module Low
|
|
8
|
+
class RequestParser
|
|
9
|
+
include LowType
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
def parse(socket: TCPSocket, host: String, port: Integer) -> { ::Protocol::HTTP::Request }
|
|
13
|
+
stream = IO::Stream(socket)
|
|
14
|
+
protocol = Async::HTTP::Protocol::HTTP.default.protocol_for(stream)
|
|
15
|
+
|
|
16
|
+
method, path, = parse_request(stream:)
|
|
17
|
+
headers = parse_headers(stream:)
|
|
18
|
+
body = parse_body(stream:, method:)
|
|
19
|
+
|
|
20
|
+
::Protocol::HTTP::Request.new('http', "#{host}:#{port}", method, path, protocol::VERSION, headers, body)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# HTTP Request format:
|
|
26
|
+
#
|
|
27
|
+
# :verb :path HTTP/1.1\r\n
|
|
28
|
+
# :header_1\r\n
|
|
29
|
+
# :header_2\r\n
|
|
30
|
+
# :header_3\r\n
|
|
31
|
+
# \r\n
|
|
32
|
+
# :body
|
|
33
|
+
#
|
|
34
|
+
def parse_request(stream:) # TODO: Handle type for namespaced "IO:Stream".
|
|
35
|
+
request_line = stream.gets || raise(StandardError, 'EOF')
|
|
36
|
+
|
|
37
|
+
method, full_path, _http_version = request_line.strip.split(' ', 3)
|
|
38
|
+
path, query = full_path.split('?', 2)
|
|
39
|
+
|
|
40
|
+
[method, full_path, path, query]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# TODO: Handle namespaced stream type "IO:Stream".
|
|
44
|
+
def parse_headers(stream:) -> { ::Protocol::HTTP::Headers }
|
|
45
|
+
fields = []
|
|
46
|
+
|
|
47
|
+
while (line = stream.gets.strip)
|
|
48
|
+
break if line.strip.empty?
|
|
49
|
+
|
|
50
|
+
key, value = line.split(/:\s/, 2)
|
|
51
|
+
fields << [key, value]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
::Protocol::HTTP::Headers.new(fields)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def parse_body(stream:, method:)
|
|
58
|
+
return nil unless %w[POST PUT].include?(method)
|
|
59
|
+
|
|
60
|
+
stream.read
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Low
|
|
4
|
+
class ResponseBuilder
|
|
5
|
+
class << self
|
|
6
|
+
# TODO: Use Async wherever we can where it doesn't have "Task" requirement.
|
|
7
|
+
def respond(config:, socket:, response:)
|
|
8
|
+
socket.puts "#{response.version} #{response.status}\r\n"
|
|
9
|
+
socket.puts config.port.nil? ? "Host: #{config.host}\r\n" : "Host: #{config.host}:#{config.port}\r\n"
|
|
10
|
+
socket.puts "\r\n"
|
|
11
|
+
socket.puts(response.body.read)
|
|
12
|
+
socket.close
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
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.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
@@ -9,6 +9,76 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: async
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: async-http
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: io-stream
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: protocol-http
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: low_event
|
|
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'
|
|
12
82
|
- !ruby/object:Gem::Dependency
|
|
13
83
|
name: low_type
|
|
14
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -23,15 +93,18 @@ dependencies:
|
|
|
23
93
|
- - "~>"
|
|
24
94
|
- !ruby/object:Gem::Version
|
|
25
95
|
version: '1.0'
|
|
26
|
-
description: An asynchronous
|
|
27
|
-
|
|
96
|
+
description: An asynchronous server that creates events for your event-driven application
|
|
97
|
+
to use
|
|
28
98
|
email:
|
|
29
99
|
- maediprichard@gmail.com
|
|
30
100
|
executables: []
|
|
31
101
|
extensions: []
|
|
32
102
|
extra_rdoc_files: []
|
|
33
103
|
files:
|
|
104
|
+
- lib/factories/response_factory.rb
|
|
34
105
|
- lib/low_loop.rb
|
|
106
|
+
- lib/request_parser.rb
|
|
107
|
+
- lib/response_builder.rb
|
|
35
108
|
- lib/version.rb
|
|
36
109
|
homepage: https://github.com/low-rb/low_loop
|
|
37
110
|
licenses: []
|