macaw_framework 0.1.4 → 0.1.5
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/.rubocop.yml +3 -0
- data/Gemfile +2 -0
- data/README.md +8 -2
- data/lib/macaw_framework/middlewares/request_data_filtering.rb +41 -3
- data/lib/macaw_framework/middlewares/server.rb +41 -20
- data/lib/macaw_framework/version.rb +1 -1
- data/lib/macaw_framework.rb +15 -4
- data/macaw_logo.png +0 -0
- data/sig/macaw_framework/macaw.rbs +4 -0
- data/sig/request_data_filtering.rbs +3 -0
- data/sig/server.rbs +9 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d900a18480da4792dfa03608ca8c85c0d0ba4084a3430c39008f4ca026ae784c
|
4
|
+
data.tar.gz: e3bc35f7b78bc0a6e995c0abd906879dda434e4c12306d3d55713c35dc45065c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ac4eb646bb2510fbc67ddec9ccfca45bea8dfcb6833f9e55b60f0f18d5edebaf392645c53dae7745eec797c926af1700c273516682bd6f10e66c2b018f1cea6
|
7
|
+
data.tar.gz: 0ecb2e16a9f1762833751bb403d824e04571c4f3b79f01355ffb6f1f29eb29c234befc759c7d76d7b939f05d5f9f3c252d8a5828198e9c7674b7854fe2edcdf1
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# MacawFramework
|
2
2
|
|
3
|
+
<img src="macaw_logo.png" alt= “” style="width: 30%;height: 30%;margin-left: 35%">
|
4
|
+
|
3
5
|
This is a framework for developing web applications. Please have in mind that this is still a work in progress and
|
4
6
|
it is strongly advised to not use it for production purposes for now. Actualy it supports only HTTP. HTTPS and SSL
|
5
7
|
support will be implemented soon. Anyone who wishes to contribute is welcome.
|
@@ -26,7 +28,8 @@ in the same directory of the script that will start the application with the fol
|
|
26
28
|
{
|
27
29
|
"macaw": {
|
28
30
|
"port": 8080,
|
29
|
-
"bind": "localhost"
|
31
|
+
"bind": "localhost",
|
32
|
+
"threads": 10
|
30
33
|
}
|
31
34
|
}
|
32
35
|
```
|
@@ -39,7 +42,10 @@ require 'json'
|
|
39
42
|
|
40
43
|
m = MacawFramework::Macaw.new
|
41
44
|
|
42
|
-
m.get('/hello_world') do |
|
45
|
+
m.get('/hello_world') do |context|
|
46
|
+
context[:body] # Returns the request body as string
|
47
|
+
context[:params] # Returns query parameters and path variables as a hash
|
48
|
+
context[:headers] # Returns headers as a hash
|
43
49
|
return JSON.pretty_generate({ hello_message: 'Hello World!' }), 200
|
44
50
|
end
|
45
51
|
|
@@ -1,24 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "../errors/endpoint_not_mapped_error"
|
4
|
+
|
3
5
|
##
|
4
6
|
# Module containing methods to filter Strings
|
5
7
|
module RequestDataFiltering
|
8
|
+
VARIABLE_PATTERN = %r{:[^/]+}.freeze
|
9
|
+
|
6
10
|
##
|
7
11
|
# Method responsible for extracting information
|
8
12
|
# provided by the client like Headers and Body
|
9
|
-
def self.parse_request_data(client)
|
13
|
+
def self.parse_request_data(client, routes)
|
10
14
|
path, parameters = extract_url_parameters(client.gets.gsub("HTTP/1.1", ""))
|
15
|
+
parameters = {} if parameters.nil?
|
16
|
+
|
11
17
|
method_name = sanitize_method_name(path)
|
18
|
+
method_name = select_path(method_name, routes, parameters)
|
12
19
|
body_first_line, headers = extract_headers(client)
|
13
20
|
body = extract_body(client, body_first_line, headers["Content-Length"].to_i)
|
14
21
|
[path, method_name, headers, body, parameters]
|
15
22
|
end
|
16
23
|
|
24
|
+
def self.select_path(method_name, routes, parameters)
|
25
|
+
return method_name if routes.include?(method_name)
|
26
|
+
|
27
|
+
selected_route = nil
|
28
|
+
routes.each do |route|
|
29
|
+
split_route = route.split(".")
|
30
|
+
split_name = method_name.split(".")
|
31
|
+
|
32
|
+
next unless split_route.length == split_name.length
|
33
|
+
next unless match_path_with_route(split_name, split_route)
|
34
|
+
|
35
|
+
selected_route = route
|
36
|
+
split_route.each_with_index do |var, index|
|
37
|
+
parameters[var[1..].to_sym] = split_name[index] if var =~ VARIABLE_PATTERN
|
38
|
+
end
|
39
|
+
break
|
40
|
+
end
|
41
|
+
|
42
|
+
raise EndpointNotMappedError if selected_route.nil?
|
43
|
+
|
44
|
+
selected_route
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.match_path_with_route(split_path, split_route)
|
48
|
+
split_route.each_with_index do |var, index|
|
49
|
+
return false if var != split_path[index] && !var.match?(VARIABLE_PATTERN)
|
50
|
+
end
|
51
|
+
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
17
55
|
##
|
18
56
|
# Method responsible for sanitizing the method name
|
19
57
|
def self.sanitize_method_name(path)
|
20
58
|
path = extract_path(path)
|
21
|
-
method_name = path.gsub("/", "
|
59
|
+
method_name = path.gsub("/", ".").strip.downcase
|
22
60
|
method_name.gsub!(" ", "")
|
23
61
|
method_name
|
24
62
|
end
|
@@ -26,7 +64,7 @@ module RequestDataFiltering
|
|
26
64
|
##
|
27
65
|
# Method responsible for extracting the path from URI
|
28
66
|
def self.extract_path(path)
|
29
|
-
path[0] == "/" ? path[1..].gsub("/", "
|
67
|
+
path[0] == "/" ? path[1..].gsub("/", ".") : path.gsub("/", ".")
|
30
68
|
end
|
31
69
|
|
32
70
|
##
|
@@ -16,49 +16,70 @@ class Server
|
|
16
16
|
# @param {Logger} logger
|
17
17
|
# @param {Integer} port
|
18
18
|
# @param {String} bind
|
19
|
+
# @param {Integer} num_threads
|
19
20
|
# @return {Server}
|
20
|
-
def initialize(macaw, logger, port, bind)
|
21
|
+
def initialize(macaw, logger, port, bind, num_threads)
|
21
22
|
@port = port
|
22
23
|
@bind = bind
|
23
24
|
@macaw = macaw
|
24
25
|
@macaw_log = logger
|
26
|
+
@num_threads = num_threads
|
27
|
+
@work_queue = Queue.new
|
28
|
+
@workers = []
|
25
29
|
end
|
26
30
|
|
27
31
|
##
|
28
32
|
# Start running the webserver.
|
29
33
|
def run
|
30
34
|
@server = TCPServer.new(@bind, @port)
|
31
|
-
|
32
|
-
Thread.
|
33
|
-
|
34
|
-
|
35
|
+
@num_threads.times do
|
36
|
+
@workers << Thread.new do
|
37
|
+
loop do
|
38
|
+
client = @work_queue.pop
|
39
|
+
break if client == :shutdown
|
35
40
|
|
36
|
-
|
37
|
-
|
38
|
-
status ||= 200
|
39
|
-
message ||= "Ok"
|
40
|
-
client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
|
41
|
-
client.close
|
42
|
-
rescue EndpointNotMappedError
|
43
|
-
client.print "HTTP/1.1 404 Not Found\r\n\r\n"
|
44
|
-
client.close
|
45
|
-
rescue StandardError => e
|
46
|
-
client.print "HTTP/1.1 500 Internal Server Error\r\n\r\n"
|
47
|
-
@macaw_log.info("Error: #{e}")
|
48
|
-
client.close
|
41
|
+
handle_client(client)
|
42
|
+
end
|
49
43
|
end
|
50
44
|
end
|
45
|
+
|
46
|
+
loop do
|
47
|
+
@work_queue << @server.accept
|
48
|
+
rescue IOError, Errno::EBADF
|
49
|
+
break
|
50
|
+
end
|
51
51
|
end
|
52
52
|
|
53
53
|
##
|
54
54
|
# Method Responsible for closing the TCP server.
|
55
55
|
def close
|
56
56
|
@server.close
|
57
|
+
@num_threads.times { @work_queue << :shutdown }
|
58
|
+
@workers.each(&:join)
|
57
59
|
end
|
58
60
|
|
59
61
|
private
|
60
62
|
|
61
|
-
def
|
62
|
-
|
63
|
+
def handle_client(client)
|
64
|
+
path, method_name, headers, body, parameters = RequestDataFiltering.parse_request_data(client, @macaw.routes)
|
65
|
+
raise EndpointNotMappedError unless @macaw.respond_to?(method_name)
|
66
|
+
|
67
|
+
@macaw_log.info("Running #{path.gsub("\n", "").gsub("\r", "")}")
|
68
|
+
message, status = call_endpoint(@macaw_log, method_name, headers, body, parameters)
|
69
|
+
status ||= 200
|
70
|
+
message ||= "Ok"
|
71
|
+
client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
|
72
|
+
client.close
|
73
|
+
rescue EndpointNotMappedError
|
74
|
+
client.print "HTTP/1.1 404 Not Found\r\n\r\n"
|
75
|
+
client.close
|
76
|
+
rescue StandardError => e
|
77
|
+
client.print "HTTP/1.1 500 Internal Server Error\r\n\r\n"
|
78
|
+
@macaw_log.info("Error: #{e}")
|
79
|
+
client.close
|
80
|
+
end
|
81
|
+
|
82
|
+
def call_endpoint(name, headers, body, parameters)
|
83
|
+
@macaw.send(name.to_sym, { headers: headers, body: body, params: parameters })
|
63
84
|
end
|
64
85
|
end
|
data/lib/macaw_framework.rb
CHANGED
@@ -13,20 +13,27 @@ module MacawFramework
|
|
13
13
|
# Class responsible for creating endpoints and
|
14
14
|
# starting the web server.
|
15
15
|
class Macaw
|
16
|
+
##
|
17
|
+
# Array containing the routes defined in the application
|
18
|
+
attr_reader :routes
|
19
|
+
|
16
20
|
##
|
17
21
|
# @param {Logger} custom_log
|
18
22
|
def initialize(custom_log: nil, server: Server)
|
19
23
|
begin
|
24
|
+
@routes = []
|
20
25
|
@macaw_log ||= custom_log.nil? ? Logger.new($stdout) : custom_log
|
21
26
|
config = JSON.parse(File.read("application.json"))
|
22
27
|
@port = config["macaw"]["port"]
|
23
28
|
@bind = config["macaw"]["bind"]
|
29
|
+
@threads = config["macaw"]["threads"].to_i
|
24
30
|
rescue StandardError => e
|
25
31
|
@macaw_log.error(e.message)
|
26
32
|
end
|
27
33
|
@port ||= 8080
|
28
34
|
@bind ||= "localhost"
|
29
|
-
@
|
35
|
+
@threads ||= 5
|
36
|
+
@server = server.new(self, @macaw_log, @port, @bind, @threads)
|
30
37
|
end
|
31
38
|
|
32
39
|
##
|
@@ -82,9 +89,10 @@ module MacawFramework
|
|
82
89
|
##
|
83
90
|
# Starts the web server
|
84
91
|
def start!
|
92
|
+
@macaw_log.info("---------------------------------")
|
85
93
|
@macaw_log.info("Starting server at port #{@port}")
|
86
|
-
|
87
|
-
@macaw_log.info("
|
94
|
+
@macaw_log.info("Number of threads: #{@threads}")
|
95
|
+
@macaw_log.info("---------------------------------")
|
88
96
|
server_loop(@server)
|
89
97
|
rescue Interrupt
|
90
98
|
@macaw_log.info("Stopping server")
|
@@ -101,7 +109,10 @@ module MacawFramework
|
|
101
109
|
def map_new_endpoint(prefix, path, &block)
|
102
110
|
path_clean = RequestDataFiltering.extract_path(path)
|
103
111
|
@macaw_log.info("Defining #{prefix.upcase} endpoint at /#{path}")
|
104
|
-
define_singleton_method("#{prefix}
|
112
|
+
define_singleton_method("#{prefix}.#{path_clean}", block || lambda {
|
113
|
+
|context = { headers: {}, body: "", params: {} }|
|
114
|
+
})
|
115
|
+
@routes << "#{prefix}.#{path_clean}"
|
105
116
|
end
|
106
117
|
end
|
107
118
|
end
|
data/macaw_logo.png
ADDED
Binary file
|
data/sig/server.rbs
CHANGED
@@ -2,10 +2,17 @@ class Server
|
|
2
2
|
@bind: String
|
3
3
|
@macaw: MacawFramework::Macaw
|
4
4
|
@macaw_log: Logger
|
5
|
+
@num_threads: Integer
|
5
6
|
@port: Integer
|
6
7
|
|
7
8
|
@server: TCPServer
|
8
9
|
|
10
|
+
@threads: Integer
|
11
|
+
|
12
|
+
@work_queue: Thread::Queue
|
13
|
+
|
14
|
+
@workers: Array[Thread]
|
15
|
+
|
9
16
|
def close: -> nil
|
10
17
|
|
11
18
|
def run: -> nil
|
@@ -13,4 +20,6 @@ class Server
|
|
13
20
|
private
|
14
21
|
|
15
22
|
def call_endpoint: -> Array[untyped]
|
23
|
+
|
24
|
+
def handle_client: -> nil
|
16
25
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: macaw_framework
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aria Diniz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-16 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A project started for study purpose that I intend to keep working on.
|
14
14
|
email:
|
@@ -31,10 +31,12 @@ files:
|
|
31
31
|
- lib/macaw_framework/middlewares/server.rb
|
32
32
|
- lib/macaw_framework/utils/http_status_code.rb
|
33
33
|
- lib/macaw_framework/version.rb
|
34
|
+
- macaw_logo.png
|
34
35
|
- sig/http_status_code.rbs
|
35
36
|
- sig/logging_aspect.rbs
|
36
37
|
- sig/macaw_framework.rbs
|
37
38
|
- sig/macaw_framework/macaw.rbs
|
39
|
+
- sig/request_data_filtering.rbs
|
38
40
|
- sig/server.rbs
|
39
41
|
homepage: https://github.com/ariasdiniz/macaw_framework
|
40
42
|
licenses:
|