macaw_framework 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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.