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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46f8af6ed22b7980942f7ec3def269395d1e65e9c7bfd06a96d161c31666997d
4
- data.tar.gz: 258e71f4b693c410c325e7a7751f8a2702e3b2db814752d0de39566fa1b94576
3
+ metadata.gz: 8d6b667252e1efbbe7114b4450c894f15e33721d7686c2455108e99a7211e385
4
+ data.tar.gz: 07f927d324da0be33031108bc85c564f1a62bd42de7b5a017cf3bfca05dee992
5
5
  SHA512:
6
- metadata.gz: cc2b8fbd4d25b795df95a47025b7465b3cfb6bd340008bebdfe527232a009bad959e25f9c4528b7eb4ce98f14f6148a47dd44e56b19b4d4c23187a2b4a24b56c
7
- data.tar.gz: 0d0f265ea93a3467718cbccd2ed93d85d0d98f9c84e53a821d3071b20243ca251e7670a788bd6e848f6381cee213c16ee8164dd27a32a2a28b77de5979a23326
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. Anyone who wishes to contribute is welcome.
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": 80
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
@@ -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.extract_client_info(client)
9
+ def self.parse_request_data(client)
10
10
  path, parameters = extract_url_parameters(client.gets.gsub("HTTP/1.1", ""))
11
- method_name = path.gsub("/", "_").strip!.downcase
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][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].gsub("\n", "").gsub("\r", "").gsub("\s", "") }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MacawFramework
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.4"
5
5
  end
@@ -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/http_status_code"
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 = nil)
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
- rescue StandardError
24
- @port ||= 8080
23
+ @bind = config["macaw"]["bind"]
24
+ rescue StandardError => e
25
+ @macaw_log.error(e.message)
25
26
  end
26
27
  @port ||= 8080
27
- @macaw_log ||= custom_log.nil? ? Logger.new($stdout) : custom_log
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
- loop do
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)
@@ -0,0 +1,3 @@
1
+ module LoggingAspect
2
+ def call_endpoint: -> Array[untyped]
3
+ end
@@ -1,7 +1,11 @@
1
1
  module MacawFramework
2
2
  class Macaw
3
+ @bind: string
4
+ @macaw_log: Logger
3
5
  @port: int
4
6
 
7
+ @server: Server
8
+
5
9
  def delete: -> nil
6
10
 
7
11
  def get: -> nil
data/sig/server.rbs ADDED
@@ -0,0 +1,16 @@
1
+ class Server
2
+ @bind: String
3
+ @macaw: MacawFramework::Macaw
4
+ @macaw_log: Logger
5
+ @port: Integer
6
+
7
+ @server: TCPServer
8
+
9
+ def close: -> nil
10
+
11
+ def run: -> nil
12
+
13
+ private
14
+
15
+ def call_endpoint: -> Array[untyped]
16
+ 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.3
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: 2022-12-14 00:00:00.000000000 Z
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/endpoint_not_mapped_error.rb
29
- - lib/macaw_framework/http_status_code.rb
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.3.26
61
+ rubygems_version: 3.4.10
58
62
  signing_key:
59
63
  specification_version: 4
60
64
  summary: A web framework still in development.