macaw_framework 0.1.3 → 0.1.4
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/CHANGELOG.md +6 -0
- data/README.md +4 -2
- data/lib/macaw_framework/aspects/logging_aspect.rb +16 -0
- data/lib/macaw_framework/{request_data_filtering.rb → middlewares/request_data_filtering.rb} +26 -5
- data/lib/macaw_framework/middlewares/server.rb +64 -0
- data/lib/macaw_framework/version.rb +1 -1
- data/lib/macaw_framework.rb +13 -31
- data/sig/logging_aspect.rbs +3 -0
- data/sig/macaw_framework/macaw.rbs +4 -0
- data/sig/server.rbs +16 -0
- metadata +10 -6
- /data/lib/macaw_framework/{endpoint_not_mapped_error.rb → errors/endpoint_not_mapped_error.rb} +0 -0
- /data/lib/macaw_framework/{http_status_code.rb → utils/http_status_code.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: 8d6b667252e1efbbe7114b4450c894f15e33721d7686c2455108e99a7211e385
|
|
4
|
+
data.tar.gz: 07f927d324da0be33031108bc85c564f1a62bd42de7b5a017cf3bfca05dee992
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db686066c7e051021c0d088b715a9509bc65f08ea07d53bf7790c786c2da6c27c85f6a47b2bf7f4c39521f78e9439a63152aa077108cc708e6758205dc2931fa
|
|
7
|
+
data.tar.gz: 64e5602bab74ed50d94da1935222c9d86a4c7fce04026061d7ac2585ba146959eb9b108b2db142d15a4f658de8aa67f8cdeca3577096bad1add1192015f7f592
|
data/CHANGELOG.md
CHANGED
|
@@ -18,3 +18,9 @@
|
|
|
18
18
|
## [0.1.3] - 2022-12-13
|
|
19
19
|
|
|
20
20
|
- Adding logger gem to Macaw class to fix a bug on the application start
|
|
21
|
+
|
|
22
|
+
## [0.1.4] - 2023-04-09
|
|
23
|
+
|
|
24
|
+
- Adding log by aspect on endpoint calls to improve observability
|
|
25
|
+
- Moving the server for a new separate class to respect single responsibility
|
|
26
|
+
- Improved the data filtering middleware to sanitize inputs
|
data/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# MacawFramework
|
|
2
2
|
|
|
3
3
|
This is a framework for developing web applications. Please have in mind that this is still a work in progress and
|
|
4
|
-
it is strongly advised to not use it for production purposes for now.
|
|
4
|
+
it is strongly advised to not use it for production purposes for now. Actualy it supports only HTTP. HTTPS and SSL
|
|
5
|
+
support will be implemented soon. Anyone who wishes to contribute is welcome.
|
|
5
6
|
|
|
6
7
|
## Installation
|
|
7
8
|
|
|
@@ -24,7 +25,8 @@ in the same directory of the script that will start the application with the fol
|
|
|
24
25
|
```json
|
|
25
26
|
{
|
|
26
27
|
"macaw": {
|
|
27
|
-
"port":
|
|
28
|
+
"port": 8080,
|
|
29
|
+
"bind": "localhost"
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# This Aspect is responsible for logging
|
|
7
|
+
# the input and output of every endpoint called
|
|
8
|
+
# in the framework.
|
|
9
|
+
module LoggingAspect
|
|
10
|
+
def call_endpoint(logger, *args)
|
|
11
|
+
logger.info("Input of #{args[0]}: #{args}")
|
|
12
|
+
response = super(*args)
|
|
13
|
+
logger.info("Output of #{args[0]} #{response}")
|
|
14
|
+
response
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/macaw_framework/{request_data_filtering.rb → middlewares/request_data_filtering.rb}
RENAMED
|
@@ -6,15 +6,23 @@ module RequestDataFiltering
|
|
|
6
6
|
##
|
|
7
7
|
# Method responsible for extracting information
|
|
8
8
|
# provided by the client like Headers and Body
|
|
9
|
-
def self.
|
|
9
|
+
def self.parse_request_data(client)
|
|
10
10
|
path, parameters = extract_url_parameters(client.gets.gsub("HTTP/1.1", ""))
|
|
11
|
-
method_name = path
|
|
12
|
-
method_name.gsub!(" ", "")
|
|
11
|
+
method_name = sanitize_method_name(path)
|
|
13
12
|
body_first_line, headers = extract_headers(client)
|
|
14
13
|
body = extract_body(client, body_first_line, headers["Content-Length"].to_i)
|
|
15
14
|
[path, method_name, headers, body, parameters]
|
|
16
15
|
end
|
|
17
16
|
|
|
17
|
+
##
|
|
18
|
+
# Method responsible for sanitizing the method name
|
|
19
|
+
def self.sanitize_method_name(path)
|
|
20
|
+
path = extract_path(path)
|
|
21
|
+
method_name = path.gsub("/", "_").strip.downcase
|
|
22
|
+
method_name.gsub!(" ", "")
|
|
23
|
+
method_name
|
|
24
|
+
end
|
|
25
|
+
|
|
18
26
|
##
|
|
19
27
|
# Method responsible for extracting the path from URI
|
|
20
28
|
def self.extract_path(path)
|
|
@@ -28,7 +36,7 @@ module RequestDataFiltering
|
|
|
28
36
|
headers = {}
|
|
29
37
|
while header.match(%r{[a-zA-Z0-9\-/*]*: [a-zA-Z0-9\-/*]})
|
|
30
38
|
split_header = header.split(":")
|
|
31
|
-
headers[split_header[0]] = split_header[1]
|
|
39
|
+
headers[split_header[0].strip] = split_header[1].strip
|
|
32
40
|
header = client.gets.delete("\n").delete("\r")
|
|
33
41
|
end
|
|
34
42
|
[header, headers]
|
|
@@ -51,10 +59,23 @@ module RequestDataFiltering
|
|
|
51
59
|
parameters_array = path_and_parameters[1].split("&")
|
|
52
60
|
parameters_array.map! do |item|
|
|
53
61
|
split_item = item.split("=")
|
|
54
|
-
{ split_item[0] => split_item[1]
|
|
62
|
+
{ sanitize_parameter_name(split_item[0]) => sanitize_parameter_value(split_item[1]) }
|
|
55
63
|
end
|
|
56
64
|
parameters = {}
|
|
57
65
|
parameters_array.each { |item| parameters.merge!(item) }
|
|
58
66
|
[path, parameters]
|
|
59
67
|
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# Method responsible for sanitizing the parameter name
|
|
71
|
+
def self.sanitize_parameter_name(name)
|
|
72
|
+
name.gsub(/[^\w\s]/, "")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
##
|
|
76
|
+
# Method responsible for sanitizing the parameter value
|
|
77
|
+
def self.sanitize_parameter_value(value)
|
|
78
|
+
value.gsub(/[^\w\s]/, "")
|
|
79
|
+
value.gsub(/[\r\n\s]/, "")
|
|
80
|
+
end
|
|
60
81
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../aspects/logging_aspect"
|
|
4
|
+
require_relative "../utils/http_status_code"
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
# Class responsible for providing a default
|
|
8
|
+
# webserver.
|
|
9
|
+
class Server
|
|
10
|
+
prepend LoggingAspect
|
|
11
|
+
include HttpStatusCode
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Create a new instance of Server.
|
|
15
|
+
# @param {Macaw} macaw
|
|
16
|
+
# @param {Logger} logger
|
|
17
|
+
# @param {Integer} port
|
|
18
|
+
# @param {String} bind
|
|
19
|
+
# @return {Server}
|
|
20
|
+
def initialize(macaw, logger, port, bind)
|
|
21
|
+
@port = port
|
|
22
|
+
@bind = bind
|
|
23
|
+
@macaw = macaw
|
|
24
|
+
@macaw_log = logger
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Start running the webserver.
|
|
29
|
+
def run
|
|
30
|
+
@server = TCPServer.new(@bind, @port)
|
|
31
|
+
loop do
|
|
32
|
+
Thread.start(@server.accept) do |client|
|
|
33
|
+
path, method_name, headers, body, parameters = RequestDataFiltering.parse_request_data(client)
|
|
34
|
+
raise EndpointNotMappedError unless @macaw.respond_to?(method_name)
|
|
35
|
+
|
|
36
|
+
@macaw_log.info("Running #{path.gsub("\n", "").gsub("\r", "")}")
|
|
37
|
+
message, status = call_endpoint(@macaw_log, method_name, headers, body, parameters)
|
|
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
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# Method Responsible for closing the TCP server.
|
|
55
|
+
def close
|
|
56
|
+
@server.close
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def call_endpoint(name, *arg_array)
|
|
62
|
+
@macaw.send(name.to_sym, *arg_array)
|
|
63
|
+
end
|
|
64
|
+
end
|
data/lib/macaw_framework.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "macaw_framework/endpoint_not_mapped_error"
|
|
4
|
-
require_relative "macaw_framework/request_data_filtering"
|
|
5
|
-
require_relative "macaw_framework/
|
|
3
|
+
require_relative "macaw_framework/errors/endpoint_not_mapped_error"
|
|
4
|
+
require_relative "macaw_framework/middlewares/request_data_filtering"
|
|
5
|
+
require_relative "macaw_framework/middlewares/server"
|
|
6
6
|
require_relative "macaw_framework/version"
|
|
7
7
|
require "logger"
|
|
8
8
|
require "socket"
|
|
@@ -13,18 +13,20 @@ module MacawFramework
|
|
|
13
13
|
# Class responsible for creating endpoints and
|
|
14
14
|
# starting the web server.
|
|
15
15
|
class Macaw
|
|
16
|
-
include(HttpStatusCode)
|
|
17
16
|
##
|
|
18
17
|
# @param {Logger} custom_log
|
|
19
|
-
def initialize(custom_log
|
|
18
|
+
def initialize(custom_log: nil, server: Server)
|
|
20
19
|
begin
|
|
20
|
+
@macaw_log ||= custom_log.nil? ? Logger.new($stdout) : custom_log
|
|
21
21
|
config = JSON.parse(File.read("application.json"))
|
|
22
22
|
@port = config["macaw"]["port"]
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
@bind = config["macaw"]["bind"]
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
@macaw_log.error(e.message)
|
|
25
26
|
end
|
|
26
27
|
@port ||= 8080
|
|
27
|
-
@
|
|
28
|
+
@bind ||= "localhost"
|
|
29
|
+
@server = server.new(self, @macaw_log, @port, @bind)
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
##
|
|
@@ -82,38 +84,18 @@ module MacawFramework
|
|
|
82
84
|
def start!
|
|
83
85
|
@macaw_log.info("Starting server at port #{@port}")
|
|
84
86
|
time = Time.now
|
|
85
|
-
server = TCPServer.open(@port)
|
|
86
87
|
@macaw_log.info("Server started in #{Time.now - time} seconds.")
|
|
87
|
-
server_loop(server)
|
|
88
|
+
server_loop(@server)
|
|
88
89
|
rescue Interrupt
|
|
89
90
|
@macaw_log.info("Stopping server")
|
|
90
|
-
server.close
|
|
91
|
+
@server.close
|
|
91
92
|
@macaw_log.info("Macaw stop flying for some seeds...")
|
|
92
93
|
end
|
|
93
94
|
|
|
94
95
|
private
|
|
95
96
|
|
|
96
97
|
def server_loop(server)
|
|
97
|
-
|
|
98
|
-
Thread.start(server.accept) do |client|
|
|
99
|
-
path, method_name, headers, body, parameters = RequestDataFiltering.extract_client_info(client)
|
|
100
|
-
raise EndpointNotMappedError unless respond_to?(method_name)
|
|
101
|
-
|
|
102
|
-
@macaw_log.info("Running #{path.gsub("\n", "").gsub("\r", "")}")
|
|
103
|
-
message, status = send(method_name, headers, body, parameters)
|
|
104
|
-
status ||= 200
|
|
105
|
-
message ||= "Ok"
|
|
106
|
-
client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
|
|
107
|
-
client.close
|
|
108
|
-
rescue EndpointNotMappedError
|
|
109
|
-
client.print "HTTP/1.1 404 Not Found\r\n\r\n"
|
|
110
|
-
client.close
|
|
111
|
-
rescue StandardError => e
|
|
112
|
-
client.print "HTTP/1.1 500 Internal Server Error\r\n\r\n"
|
|
113
|
-
@macaw_log.info("Error: #{e}")
|
|
114
|
-
client.close
|
|
115
|
-
end
|
|
116
|
-
end
|
|
98
|
+
server.run
|
|
117
99
|
end
|
|
118
100
|
|
|
119
101
|
def map_new_endpoint(prefix, path, &block)
|
data/sig/server.rbs
ADDED
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.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aria Diniz
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-04-09 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:
|
|
@@ -25,13 +25,17 @@ files:
|
|
|
25
25
|
- README.md
|
|
26
26
|
- Rakefile
|
|
27
27
|
- lib/macaw_framework.rb
|
|
28
|
-
- lib/macaw_framework/
|
|
29
|
-
- lib/macaw_framework/
|
|
30
|
-
- lib/macaw_framework/request_data_filtering.rb
|
|
28
|
+
- lib/macaw_framework/aspects/logging_aspect.rb
|
|
29
|
+
- lib/macaw_framework/errors/endpoint_not_mapped_error.rb
|
|
30
|
+
- lib/macaw_framework/middlewares/request_data_filtering.rb
|
|
31
|
+
- lib/macaw_framework/middlewares/server.rb
|
|
32
|
+
- lib/macaw_framework/utils/http_status_code.rb
|
|
31
33
|
- lib/macaw_framework/version.rb
|
|
32
34
|
- sig/http_status_code.rbs
|
|
35
|
+
- sig/logging_aspect.rbs
|
|
33
36
|
- sig/macaw_framework.rbs
|
|
34
37
|
- sig/macaw_framework/macaw.rbs
|
|
38
|
+
- sig/server.rbs
|
|
35
39
|
homepage: https://github.com/ariasdiniz/macaw_framework
|
|
36
40
|
licenses:
|
|
37
41
|
- MIT
|
|
@@ -54,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
54
58
|
- !ruby/object:Gem::Version
|
|
55
59
|
version: '0'
|
|
56
60
|
requirements: []
|
|
57
|
-
rubygems_version: 3.
|
|
61
|
+
rubygems_version: 3.4.10
|
|
58
62
|
signing_key:
|
|
59
63
|
specification_version: 4
|
|
60
64
|
summary: A web framework still in development.
|
/data/lib/macaw_framework/{endpoint_not_mapped_error.rb → errors/endpoint_not_mapped_error.rb}
RENAMED
|
File without changes
|
|
File without changes
|