macaw_framework 0.1.1 → 0.1.2

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: 8d2934b530ed442004aaecbd13430dc250fe2fde52c2b9abf2e0f95eed6c606a
4
- data.tar.gz: 8989aaff7cf4be554e18edb4cee3928b918c263b35cbb9886f9df94ef8a5b0f9
3
+ metadata.gz: 512b14576a1be6337300fc758b7be6acf48603fa605c0d60c21d494b2d411a10
4
+ data.tar.gz: 29f0115f9e93539c740e56834515d7d66dd768a2b5a8c5ef2668beb82d403fa5
5
5
  SHA512:
6
- metadata.gz: 04ac3f80610994da626fef59bd5554bc2535c693357bca3fe8891170478cdf7b65af4552c83b8532c40ae704e6d50fafe9457afa2c03b6177080742e15e11611
7
- data.tar.gz: 245ef53c503e22eea1c2f5a3e4a8a8ccf48a7444cba36c393fed048973415d1ff4dea8bba53c3bb5dd892422898e0284ca8a6d3b3f70ddaed0c3823d191df792
6
+ metadata.gz: aa4dd23ceaa6579ab7f0b77caa42b8745fb48f830aa8f5db9eb713bff5330d1879653209781e888c873b071c45b11afd5744282401d77c9c7609c0ed9f721e4b
7
+ data.tar.gz: 2cc30aff12830763c3e9a19c8597fa4cd5f8e8ec7bfc1201f5c367883e93c480cef243d2590725084b62922538e6e27798f53840df0660b8872e09be01336f8a
data/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@
4
4
 
5
5
  - Initial release
6
6
 
7
- ## [0.1.0] - 2022-12-06
7
+ ## [0.1.1] - 2022-12-06
8
8
 
9
9
  - Adding support for headers and body
10
+
11
+ ## [0.1.2] - 2022-12-10
12
+
13
+ - Adding support to URL parameters
14
+ - Adding logs to the framework activity
15
+ - Removing undefined Status Codes from http_status_code hash
16
+ - Moving methods from Macaw class to RequestDataFiltering module, respecting SOLID
data/README.md CHANGED
@@ -37,7 +37,7 @@ require 'json'
37
37
 
38
38
  m = MacawFramework::Macaw.new
39
39
 
40
- m.get('/hello_world') do |headers, body|
40
+ m.get('/hello_world') do |headers, body, parameters|
41
41
  return JSON.pretty_generate({ hello_message: 'Hello World!' }), 200
42
42
  end
43
43
 
@@ -7,69 +7,66 @@ module HttpStatusCode
7
7
  ##
8
8
  # Http Status Code Map
9
9
  HTTP_STATUS_CODE_MAP = {
10
- 100 => 'Continue',
11
- 101 => 'Switching Protocols',
12
- 102 => 'Processing',
13
- 103 => 'Early Hints',
14
- 200 => 'OK',
15
- 201 => 'Created',
16
- 202 => 'Accepted',
17
- 203 => 'Non-Authoritative Information',
18
- 204 => 'No Content',
19
- 205 => 'Reset Content',
20
- 206 => 'Partial Content',
21
- 207 => 'Multi-Status',
22
- 208 => 'Already Reported',
23
- 226 => 'IM Used',
24
- 300 => 'Multiple Choices',
25
- 301 => 'Moved Permanently',
26
- 302 => 'Found',
27
- 303 => 'See Other',
28
- 304 => 'Not Modified',
29
- 305 => 'Use Proxy',
30
- 307 => 'Temporary Redirect',
31
- 308 => 'Permanent Redirect',
32
- 400 => 'Bad Request',
33
- 401 => 'Unauthorized',
34
- 402 => 'Payment Required',
35
- 403 => 'Forbidden',
36
- 404 => 'Not Found',
37
- 405 => 'Method Not Allowed',
38
- 406 => 'Not Acceptable',
39
- 407 => 'Proxy Authentication Required',
40
- 408 => 'Request Timeout',
41
- 409 => 'Conflict',
42
- 410 => 'Gone',
43
- 411 => 'Length Required',
44
- 412 => 'Precondition Failed',
45
- 413 => 'Content Too Large',
46
- 414 => 'URI Too Long',
47
- 415 => 'Unsupported Media Type',
48
- 416 => 'Range Not Satisfiable',
49
- 417 => 'Expectation Failed',
50
- 421 => 'Misdirected Request',
51
- 422 => 'Unprocessable Content',
52
- 423 => 'Locked',
53
- 424 => 'Failed Dependency',
54
- 425 => 'Too Early',
55
- 426 => 'Upgrade Required',
56
- 427 => 'Unassigned',
57
- 428 => 'Precondition Required',
58
- 429 => 'Too Many Requests',
59
- 430 => 'Unassigned',
60
- 431 => 'Request Header Fields Too Large',
61
- 451 => 'Unavailable For Legal Reasons',
62
- 500 => 'Internal Server Error',
63
- 501 => 'Not Implemented',
64
- 502 => 'Bad Gateway',
65
- 503 => 'Service Unavailable',
66
- 504 => 'Gateway Timeout',
67
- 505 => 'HTTP Version Not Supported',
68
- 506 => 'Variant Also Negotiates',
69
- 507 => 'Insufficient Storage',
70
- 508 => 'Loop Detected',
71
- 509 => 'Unassigned',
72
- 510 => 'Not Extended (OBSOLETED)',
73
- 511 => 'Network Authentication Required'
10
+ 100 => "Continue",
11
+ 101 => "Switching Protocols",
12
+ 102 => "Processing",
13
+ 103 => "Early Hints",
14
+ 200 => "OK",
15
+ 201 => "Created",
16
+ 202 => "Accepted",
17
+ 203 => "Non-Authoritative Information",
18
+ 204 => "No Content",
19
+ 205 => "Reset Content",
20
+ 206 => "Partial Content",
21
+ 207 => "Multi-Status",
22
+ 208 => "Already Reported",
23
+ 226 => "IM Used",
24
+ 300 => "Multiple Choices",
25
+ 301 => "Moved Permanently",
26
+ 302 => "Found",
27
+ 303 => "See Other",
28
+ 304 => "Not Modified",
29
+ 305 => "Use Proxy",
30
+ 307 => "Temporary Redirect",
31
+ 308 => "Permanent Redirect",
32
+ 400 => "Bad Request",
33
+ 401 => "Unauthorized",
34
+ 402 => "Payment Required",
35
+ 403 => "Forbidden",
36
+ 404 => "Not Found",
37
+ 405 => "Method Not Allowed",
38
+ 406 => "Not Acceptable",
39
+ 407 => "Proxy Authentication Required",
40
+ 408 => "Request Timeout",
41
+ 409 => "Conflict",
42
+ 410 => "Gone",
43
+ 411 => "Length Required",
44
+ 412 => "Precondition Failed",
45
+ 413 => "Content Too Large",
46
+ 414 => "URI Too Long",
47
+ 415 => "Unsupported Media Type",
48
+ 416 => "Range Not Satisfiable",
49
+ 417 => "Expectation Failed",
50
+ 421 => "Misdirected Request",
51
+ 422 => "Unprocessable Content",
52
+ 423 => "Locked",
53
+ 424 => "Failed Dependency",
54
+ 425 => "Too Early",
55
+ 426 => "Upgrade Required",
56
+ 428 => "Precondition Required",
57
+ 429 => "Too Many Requests",
58
+ 431 => "Request Header Fields Too Large",
59
+ 451 => "Unavailable For Legal Reasons",
60
+ 500 => "Internal Server Error",
61
+ 501 => "Not Implemented",
62
+ 502 => "Bad Gateway",
63
+ 503 => "Service Unavailable",
64
+ 504 => "Gateway Timeout",
65
+ 505 => "HTTP Version Not Supported",
66
+ 506 => "Variant Also Negotiates",
67
+ 507 => "Insufficient Storage",
68
+ 508 => "Loop Detected",
69
+ 510 => "Not Extended (OBSOLETED)",
70
+ 511 => "Network Authentication Required"
74
71
  }.freeze
75
72
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Module containing methods to filter Strings
5
+ module RequestDataFiltering
6
+ ##
7
+ # Method responsible for extracting information
8
+ # provided by the client like Headers and Body
9
+ def self.extract_client_info(client)
10
+ path, parameters = extract_url_parameters(client.gets.gsub('HTTP/1.1', ''))
11
+ method_name = path.gsub('/', '_').strip!.downcase
12
+ method_name.gsub!(' ', '')
13
+ body_first_line, headers = extract_headers(client)
14
+ body = extract_body(client, body_first_line, headers['Content-Length'].to_i)
15
+ [path, method_name, headers, body, parameters]
16
+ end
17
+
18
+ ##
19
+ # Method responsible for extracting the path from URI
20
+ def self.extract_path(path)
21
+ path[0] == '/' ? path[1..].gsub('/', '_') : path.gsub('/', '_')
22
+ end
23
+
24
+ ##
25
+ # Method responsible for extracting the headers from request
26
+ def self.extract_headers(client)
27
+ header = client.gets.delete("\n").delete("\r")
28
+ headers = {}
29
+ 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][1..]
32
+ header = client.gets.delete("\n").delete("\r")
33
+ end
34
+ [header, headers]
35
+ end
36
+
37
+ ##
38
+ # Method responsible for extracting the body from request
39
+ def self.extract_body(client, body_first_line, content_length)
40
+ body = client.read(content_length)
41
+ body_first_line << body.to_s
42
+ end
43
+
44
+ ##
45
+ # Method responsible for extracting the parameters from URI
46
+ def self.extract_url_parameters(http_first_line)
47
+ return http_first_line, nil unless http_first_line =~ /\?/
48
+
49
+ path_and_parameters = http_first_line.split('?', 2)
50
+ path = "#{path_and_parameters[0]} "
51
+ parameters_array = path_and_parameters[1].split('&')
52
+ parameters_array.map! do |item|
53
+ split_item = item.split('=')
54
+ { split_item[0] => split_item[1].gsub("\n", '').gsub("\r", '').gsub("\s", '') }
55
+ end
56
+ parameters = {}
57
+ parameters_array.each { |item| parameters.merge!(item) }
58
+ [path, parameters]
59
+ end
60
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MacawFramework
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "macaw_framework/endpoint_not_mapped_error"
4
+ require_relative "macaw_framework/request_data_filtering"
4
5
  require_relative "macaw_framework/http_status_code"
5
6
  require_relative "macaw_framework/version"
6
7
  require "socket"
@@ -12,14 +13,17 @@ module MacawFramework
12
13
  # starting the web server.
13
14
  class Macaw
14
15
  include(HttpStatusCode)
15
- def initialize
16
+ ##
17
+ # @param {Logger} custom_log
18
+ def initialize(custom_log = nil)
16
19
  begin
17
- config = JSON.parse(File.read("application.json"))
18
- @port = config["macaw"]["port"]
20
+ config = JSON.parse(File.read('application.json'))
21
+ @port = config['macaw']['port']
19
22
  rescue StandardError
20
23
  @port ||= 8080
21
24
  end
22
25
  @port ||= 8080
26
+ @macaw_log ||= custom_log.nil? ? Logger.new($stdout) : custom_log
23
27
  end
24
28
 
25
29
  ##
@@ -29,8 +33,9 @@ module MacawFramework
29
33
  # @param {Proc} block
30
34
  # @return {Integer, String}
31
35
  def get(path, &block)
32
- path_clean = path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
33
- define_singleton_method("get_#{path_clean}", block)
36
+ path_clean = RequestDataFiltering.extract_path(path)
37
+ @macaw_log.info("Defining GET endpoint at #{path_clean}")
38
+ map_new_endpoint('get', path_clean, &block)
34
39
  end
35
40
 
36
41
  ##
@@ -40,8 +45,9 @@ module MacawFramework
40
45
  # @param {Proc} block
41
46
  # @return {String, Integer}
42
47
  def post(path, &block)
43
- path_clean = path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
44
- define_singleton_method("post_#{path_clean}", block)
48
+ path_clean = path[0] == '/' ? path[1..].gsub('/', '_') : path.gsub('/', '_')
49
+ @macaw_log.info("Defining POST endpoint at #{path_clean}")
50
+ map_new_endpoint('post', path_clean, &block)
45
51
  end
46
52
 
47
53
  ##
@@ -51,8 +57,9 @@ module MacawFramework
51
57
  # @param {Proc} block
52
58
  # @return {String, Integer}
53
59
  def put(path, &block)
54
- path_clean = path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
55
- define_singleton_method("put_#{path_clean}", block)
60
+ path_clean = path[0] == '/' ? path[1..].gsub('/', '_') : path.gsub('/', '_')
61
+ @macaw_log.info("Defining PUT endpoint at #{path_clean}")
62
+ map_new_endpoint('put', path_clean, &block)
56
63
  end
57
64
 
58
65
  ##
@@ -62,8 +69,9 @@ module MacawFramework
62
69
  # @param {Proc} block
63
70
  # @return {String, Integer}
64
71
  def patch(path, &block)
65
- path_clean = path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
66
- define_singleton_method("patch_#{path_clean}", block)
72
+ path_clean = path[0] == '/' ? path[1..].gsub('/', '_') : path.gsub('/', '_')
73
+ @macaw_log.info("Defining PATCH endpoint at #{path_clean}")
74
+ map_new_endpoint('patch', path_clean, &block)
67
75
  end
68
76
 
69
77
  ##
@@ -73,67 +81,48 @@ module MacawFramework
73
81
  # @param {Proc} block
74
82
  # @return {String, Integer}
75
83
  def delete(path, &block)
76
- path_clean = path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
77
- define_singleton_method("delete_#{path_clean}", block)
84
+ path_clean = path[0] == '/' ? path[1..].gsub('/', '_') : path.gsub('/', '_')
85
+ @macaw_log.info("Defining DELETE endpoint at #{path_clean}")
86
+ map_new_endpoint('delete', path_clean, &block)
78
87
  end
79
88
 
80
89
  ##
81
90
  # Starts the web server
82
91
  def start!
92
+ @macaw_log.info("Starting server at port #{@port}")
93
+ time = Time.now
83
94
  server = TCPServer.open(@port)
84
- puts "Starting server at port #{@port}"
95
+ @macaw_log.info("Server started in #{Time.now - time} seconds.")
85
96
  loop do
86
97
  Thread.start(server.accept) do |client|
87
- client.select
88
- method_name, headers, body = extract_client_info(client)
98
+ path, method_name, headers, body, parameters = RequestDataFiltering.extract_client_info(client)
89
99
  raise EndpointNotMappedError unless respond_to?(method_name)
90
100
 
91
- message, status = send(method_name, headers, body)
101
+ @macaw_log.info("Running #{path.gsub("\n", '').gsub("\r", '')}")
102
+ message, status = send(method_name, headers, body, parameters)
92
103
  status ||= 200
93
- message ||= "Ok"
104
+ message ||= 'Ok'
94
105
  client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
95
106
  client.close
96
107
  rescue EndpointNotMappedError
97
108
  client.print "HTTP/1.1 404 Not Found\r\n\r\n"
98
109
  client.close
99
- rescue StandardError
110
+ rescue StandardError => e
100
111
  client.print "HTTP/1.1 500 Internal Server Error\r\n\r\n"
112
+ @macaw_log.info("Error: #{e}")
101
113
  client.close
102
114
  end
103
115
  end
104
116
  rescue Interrupt
105
- puts "Macaw stop flying for some seeds..."
117
+ @macaw_log.info('Stopping server')
118
+ server.close
119
+ @macaw_log.info('Macaw stop flying for some seeds...')
106
120
  end
107
121
 
108
122
  private
109
123
 
110
- ##
111
- # Method responsible for extracting information
112
- # provided by the client like Headers and Body
113
- def extract_client_info(client)
114
- method_name = client.gets.gsub("HTTP/1.1", "").gsub("/", "_").strip!.downcase
115
- method_name.gsub!(" ", "")
116
- body_first_line, headers = extract_headers(client)
117
- body = extract_body(client, body_first_line, headers["Content-Length"].to_i)
118
- [method_name, headers, body]
119
- end
120
-
121
- ##
122
- # Extract application headers
123
- def extract_headers(client)
124
- header = client.gets.delete("\n").delete("\r")
125
- headers = {}
126
- while header.match(%r{[a-zA-Z0-9\-/*]*: [a-zA-Z0-9\-/*]})
127
- split_header = header.split(":")
128
- headers[split_header[0]] = split_header[1][1..]
129
- header = client.gets.delete("\n").delete("\r")
130
- end
131
- [header, headers]
132
- end
133
-
134
- def extract_body(client, body_first_line, content_length)
135
- body = client.read(content_length)
136
- body_first_line << body.to_s
124
+ def map_new_endpoint(prefix, path, &block)
125
+ define_singleton_method("#{prefix}_#{path}", block)
137
126
  end
138
127
  end
139
128
  end
@@ -0,0 +1,3 @@
1
+ module HttpStatusCode
2
+ HTTP_STATUS_CODE_MAP: Hash[Integer, String]
3
+ 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.1
4
+ version: 0.1.2
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-07 00:00:00.000000000 Z
11
+ date: 2022-12-11 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:
@@ -27,7 +27,9 @@ files:
27
27
  - lib/macaw_framework.rb
28
28
  - lib/macaw_framework/endpoint_not_mapped_error.rb
29
29
  - lib/macaw_framework/http_status_code.rb
30
+ - lib/macaw_framework/request_data_filtering.rb
30
31
  - lib/macaw_framework/version.rb
32
+ - sig/http_status_code.rbs
31
33
  - sig/macaw_framework.rbs
32
34
  - sig/macaw_framework/macaw.rbs
33
35
  homepage: https://github.com/ariasdiniz/macaw_framework