macaw_framework 0.1.2 → 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/.rubocop.yml +7 -1
- data/CHANGELOG.md +10 -0
- data/README.md +4 -2
- data/lib/macaw_framework/aspects/logging_aspect.rb +16 -0
- data/lib/macaw_framework/{endpoint_not_mapped_error.rb → errors/endpoint_not_mapped_error.rb} +1 -1
- data/lib/macaw_framework/{request_data_filtering.rb → middlewares/request_data_filtering.rb} +33 -12
- data/lib/macaw_framework/middlewares/server.rb +64 -0
- data/lib/macaw_framework/version.rb +1 -1
- data/lib/macaw_framework.rb +29 -50
- 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/{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/.rubocop.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
AllCops:
|
2
|
-
TargetRubyVersion: 2.
|
2
|
+
TargetRubyVersion: 2.7
|
3
3
|
|
4
4
|
Style/StringLiterals:
|
5
5
|
Enabled: true
|
@@ -11,3 +11,9 @@ Style/StringLiteralsInInterpolation:
|
|
11
11
|
|
12
12
|
Layout/LineLength:
|
13
13
|
Max: 120
|
14
|
+
|
15
|
+
Metrics/MethodLength:
|
16
|
+
Max: 30
|
17
|
+
|
18
|
+
Metrics/AbcSize:
|
19
|
+
Max: 35
|
data/CHANGELOG.md
CHANGED
@@ -14,3 +14,13 @@
|
|
14
14
|
- Adding logs to the framework activity
|
15
15
|
- Removing undefined Status Codes from http_status_code hash
|
16
16
|
- Moving methods from Macaw class to RequestDataFiltering module, respecting SOLID
|
17
|
+
|
18
|
+
## [0.1.3] - 2022-12-13
|
19
|
+
|
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,19 +6,27 @@ module RequestDataFiltering
|
|
6
6
|
##
|
7
7
|
# Method responsible for extracting information
|
8
8
|
# provided by the client like Headers and Body
|
9
|
-
def self.
|
10
|
-
path, parameters = extract_url_parameters(client.gets.gsub(
|
11
|
-
method_name = path
|
12
|
-
method_name.gsub!(' ', '')
|
9
|
+
def self.parse_request_data(client)
|
10
|
+
path, parameters = extract_url_parameters(client.gets.gsub("HTTP/1.1", ""))
|
11
|
+
method_name = sanitize_method_name(path)
|
13
12
|
body_first_line, headers = extract_headers(client)
|
14
|
-
body = extract_body(client, body_first_line, headers[
|
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)
|
21
|
-
path[0] ==
|
29
|
+
path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
|
22
30
|
end
|
23
31
|
|
24
32
|
##
|
@@ -27,8 +35,8 @@ module RequestDataFiltering
|
|
27
35
|
header = client.gets.delete("\n").delete("\r")
|
28
36
|
headers = {}
|
29
37
|
while header.match(%r{[a-zA-Z0-9\-/*]*: [a-zA-Z0-9\-/*]})
|
30
|
-
split_header = header.split(
|
31
|
-
headers[split_header[0]] = split_header[1]
|
38
|
+
split_header = header.split(":")
|
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]
|
@@ -46,15 +54,28 @@ module RequestDataFiltering
|
|
46
54
|
def self.extract_url_parameters(http_first_line)
|
47
55
|
return http_first_line, nil unless http_first_line =~ /\?/
|
48
56
|
|
49
|
-
path_and_parameters = http_first_line.split(
|
57
|
+
path_and_parameters = http_first_line.split("?", 2)
|
50
58
|
path = "#{path_and_parameters[0]} "
|
51
|
-
parameters_array = path_and_parameters[1].split(
|
59
|
+
parameters_array = path_and_parameters[1].split("&")
|
52
60
|
parameters_array.map! do |item|
|
53
|
-
split_item = item.split(
|
54
|
-
{ split_item[0] => split_item[1]
|
61
|
+
split_item = item.split("=")
|
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,9 +1,10 @@
|
|
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
|
+
require "logger"
|
7
8
|
require "socket"
|
8
9
|
require "json"
|
9
10
|
|
@@ -12,18 +13,20 @@ module MacawFramework
|
|
12
13
|
# Class responsible for creating endpoints and
|
13
14
|
# starting the web server.
|
14
15
|
class Macaw
|
15
|
-
include(HttpStatusCode)
|
16
16
|
##
|
17
17
|
# @param {Logger} custom_log
|
18
|
-
def initialize(custom_log
|
18
|
+
def initialize(custom_log: nil, server: Server)
|
19
19
|
begin
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@
|
20
|
+
@macaw_log ||= custom_log.nil? ? Logger.new($stdout) : custom_log
|
21
|
+
config = JSON.parse(File.read("application.json"))
|
22
|
+
@port = config["macaw"]["port"]
|
23
|
+
@bind = config["macaw"]["bind"]
|
24
|
+
rescue StandardError => e
|
25
|
+
@macaw_log.error(e.message)
|
24
26
|
end
|
25
27
|
@port ||= 8080
|
26
|
-
@
|
28
|
+
@bind ||= "localhost"
|
29
|
+
@server = server.new(self, @macaw_log, @port, @bind)
|
27
30
|
end
|
28
31
|
|
29
32
|
##
|
@@ -33,9 +36,7 @@ module MacawFramework
|
|
33
36
|
# @param {Proc} block
|
34
37
|
# @return {Integer, String}
|
35
38
|
def get(path, &block)
|
36
|
-
|
37
|
-
@macaw_log.info("Defining GET endpoint at #{path_clean}")
|
38
|
-
map_new_endpoint('get', path_clean, &block)
|
39
|
+
map_new_endpoint("get", path, &block)
|
39
40
|
end
|
40
41
|
|
41
42
|
##
|
@@ -45,9 +46,7 @@ module MacawFramework
|
|
45
46
|
# @param {Proc} block
|
46
47
|
# @return {String, Integer}
|
47
48
|
def post(path, &block)
|
48
|
-
|
49
|
-
@macaw_log.info("Defining POST endpoint at #{path_clean}")
|
50
|
-
map_new_endpoint('post', path_clean, &block)
|
49
|
+
map_new_endpoint("post", path, &block)
|
51
50
|
end
|
52
51
|
|
53
52
|
##
|
@@ -57,9 +56,7 @@ module MacawFramework
|
|
57
56
|
# @param {Proc} block
|
58
57
|
# @return {String, Integer}
|
59
58
|
def put(path, &block)
|
60
|
-
|
61
|
-
@macaw_log.info("Defining PUT endpoint at #{path_clean}")
|
62
|
-
map_new_endpoint('put', path_clean, &block)
|
59
|
+
map_new_endpoint("put", path, &block)
|
63
60
|
end
|
64
61
|
|
65
62
|
##
|
@@ -69,9 +66,7 @@ module MacawFramework
|
|
69
66
|
# @param {Proc} block
|
70
67
|
# @return {String, Integer}
|
71
68
|
def patch(path, &block)
|
72
|
-
|
73
|
-
@macaw_log.info("Defining PATCH endpoint at #{path_clean}")
|
74
|
-
map_new_endpoint('patch', path_clean, &block)
|
69
|
+
map_new_endpoint("patch", path, &block)
|
75
70
|
end
|
76
71
|
|
77
72
|
##
|
@@ -81,9 +76,7 @@ module MacawFramework
|
|
81
76
|
# @param {Proc} block
|
82
77
|
# @return {String, Integer}
|
83
78
|
def delete(path, &block)
|
84
|
-
|
85
|
-
@macaw_log.info("Defining DELETE endpoint at #{path_clean}")
|
86
|
-
map_new_endpoint('delete', path_clean, &block)
|
79
|
+
map_new_endpoint("delete", path, &block)
|
87
80
|
end
|
88
81
|
|
89
82
|
##
|
@@ -91,38 +84,24 @@ module MacawFramework
|
|
91
84
|
def start!
|
92
85
|
@macaw_log.info("Starting server at port #{@port}")
|
93
86
|
time = Time.now
|
94
|
-
server = TCPServer.open(@port)
|
95
87
|
@macaw_log.info("Server started in #{Time.now - time} seconds.")
|
96
|
-
|
97
|
-
Thread.start(server.accept) do |client|
|
98
|
-
path, method_name, headers, body, parameters = RequestDataFiltering.extract_client_info(client)
|
99
|
-
raise EndpointNotMappedError unless respond_to?(method_name)
|
100
|
-
|
101
|
-
@macaw_log.info("Running #{path.gsub("\n", '').gsub("\r", '')}")
|
102
|
-
message, status = send(method_name, headers, body, parameters)
|
103
|
-
status ||= 200
|
104
|
-
message ||= 'Ok'
|
105
|
-
client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
|
106
|
-
client.close
|
107
|
-
rescue EndpointNotMappedError
|
108
|
-
client.print "HTTP/1.1 404 Not Found\r\n\r\n"
|
109
|
-
client.close
|
110
|
-
rescue StandardError => e
|
111
|
-
client.print "HTTP/1.1 500 Internal Server Error\r\n\r\n"
|
112
|
-
@macaw_log.info("Error: #{e}")
|
113
|
-
client.close
|
114
|
-
end
|
115
|
-
end
|
88
|
+
server_loop(@server)
|
116
89
|
rescue Interrupt
|
117
|
-
@macaw_log.info(
|
118
|
-
server.close
|
119
|
-
@macaw_log.info(
|
90
|
+
@macaw_log.info("Stopping server")
|
91
|
+
@server.close
|
92
|
+
@macaw_log.info("Macaw stop flying for some seeds...")
|
120
93
|
end
|
121
94
|
|
122
95
|
private
|
123
96
|
|
97
|
+
def server_loop(server)
|
98
|
+
server.run
|
99
|
+
end
|
100
|
+
|
124
101
|
def map_new_endpoint(prefix, path, &block)
|
125
|
-
|
102
|
+
path_clean = RequestDataFiltering.extract_path(path)
|
103
|
+
@macaw_log.info("Defining #{prefix.upcase} endpoint at /#{path}")
|
104
|
+
define_singleton_method("#{prefix}_#{path_clean}", block)
|
126
105
|
end
|
127
106
|
end
|
128
107
|
end
|
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.
|
File without changes
|