macaw_framework 0.1.1 → 0.1.3

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: 46f8af6ed22b7980942f7ec3def269395d1e65e9c7bfd06a96d161c31666997d
4
+ data.tar.gz: 258e71f4b693c410c325e7a7751f8a2702e3b2db814752d0de39566fa1b94576
5
5
  SHA512:
6
- metadata.gz: 04ac3f80610994da626fef59bd5554bc2535c693357bca3fe8891170478cdf7b65af4552c83b8532c40ae704e6d50fafe9457afa2c03b6177080742e15e11611
7
- data.tar.gz: 245ef53c503e22eea1c2f5a3e4a8a8ccf48a7444cba36c393fed048973415d1ff4dea8bba53c3bb5dd892422898e0284ca8a6d3b3f70ddaed0c3823d191df792
6
+ metadata.gz: cc2b8fbd4d25b795df95a47025b7465b3cfb6bd340008bebdfe527232a009bad959e25f9c4528b7eb4ce98f14f6148a47dd44e56b19b4d4c23187a2b4a24b56c
7
+ data.tar.gz: 0d0f265ea93a3467718cbccd2ed93d85d0d98f9c84e53a821d3071b20243ca251e7670a788bd6e848f6381cee213c16ee8164dd27a32a2a28b77de5979a23326
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.6
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
@@ -4,6 +4,17 @@
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
17
+
18
+ ## [0.1.3] - 2022-12-13
19
+
20
+ - Adding logger gem to Macaw class to fix a bug on the application start
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
 
@@ -4,7 +4,7 @@
4
4
  # Error raised when the client calls
5
5
  # for a path that doesn't exist.
6
6
  class EndpointNotMappedError < StandardError
7
- def initialize(msg = 'Undefined endpoint')
7
+ def initialize(msg = "Undefined endpoint")
8
8
  super
9
9
  end
10
10
  end
@@ -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.3"
5
5
  end
@@ -1,8 +1,10 @@
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"
7
+ require "logger"
6
8
  require "socket"
7
9
  require "json"
8
10
 
@@ -12,7 +14,9 @@ module MacawFramework
12
14
  # starting the web server.
13
15
  class Macaw
14
16
  include(HttpStatusCode)
15
- def initialize
17
+ ##
18
+ # @param {Logger} custom_log
19
+ def initialize(custom_log = nil)
16
20
  begin
17
21
  config = JSON.parse(File.read("application.json"))
18
22
  @port = config["macaw"]["port"]
@@ -20,6 +24,7 @@ module MacawFramework
20
24
  @port ||= 8080
21
25
  end
22
26
  @port ||= 8080
27
+ @macaw_log ||= custom_log.nil? ? Logger.new($stdout) : custom_log
23
28
  end
24
29
 
25
30
  ##
@@ -29,8 +34,7 @@ module MacawFramework
29
34
  # @param {Proc} block
30
35
  # @return {Integer, String}
31
36
  def get(path, &block)
32
- path_clean = path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
33
- define_singleton_method("get_#{path_clean}", block)
37
+ map_new_endpoint("get", path, &block)
34
38
  end
35
39
 
36
40
  ##
@@ -40,8 +44,7 @@ module MacawFramework
40
44
  # @param {Proc} block
41
45
  # @return {String, Integer}
42
46
  def post(path, &block)
43
- path_clean = path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
44
- define_singleton_method("post_#{path_clean}", block)
47
+ map_new_endpoint("post", path, &block)
45
48
  end
46
49
 
47
50
  ##
@@ -51,8 +54,7 @@ module MacawFramework
51
54
  # @param {Proc} block
52
55
  # @return {String, Integer}
53
56
  def put(path, &block)
54
- path_clean = path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
55
- define_singleton_method("put_#{path_clean}", block)
57
+ map_new_endpoint("put", path, &block)
56
58
  end
57
59
 
58
60
  ##
@@ -62,8 +64,7 @@ module MacawFramework
62
64
  # @param {Proc} block
63
65
  # @return {String, Integer}
64
66
  def patch(path, &block)
65
- path_clean = path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
66
- define_singleton_method("patch_#{path_clean}", block)
67
+ map_new_endpoint("patch", path, &block)
67
68
  end
68
69
 
69
70
  ##
@@ -73,22 +74,33 @@ module MacawFramework
73
74
  # @param {Proc} block
74
75
  # @return {String, Integer}
75
76
  def delete(path, &block)
76
- path_clean = path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
77
- define_singleton_method("delete_#{path_clean}", block)
77
+ map_new_endpoint("delete", path, &block)
78
78
  end
79
79
 
80
80
  ##
81
81
  # Starts the web server
82
82
  def start!
83
+ @macaw_log.info("Starting server at port #{@port}")
84
+ time = Time.now
83
85
  server = TCPServer.open(@port)
84
- puts "Starting server at port #{@port}"
86
+ @macaw_log.info("Server started in #{Time.now - time} seconds.")
87
+ server_loop(server)
88
+ rescue Interrupt
89
+ @macaw_log.info("Stopping server")
90
+ server.close
91
+ @macaw_log.info("Macaw stop flying for some seeds...")
92
+ end
93
+
94
+ private
95
+
96
+ def server_loop(server)
85
97
  loop do
86
98
  Thread.start(server.accept) do |client|
87
- client.select
88
- method_name, headers, body = extract_client_info(client)
99
+ path, method_name, headers, body, parameters = RequestDataFiltering.extract_client_info(client)
89
100
  raise EndpointNotMappedError unless respond_to?(method_name)
90
101
 
91
- message, status = send(method_name, headers, body)
102
+ @macaw_log.info("Running #{path.gsub("\n", "").gsub("\r", "")}")
103
+ message, status = send(method_name, headers, body, parameters)
92
104
  status ||= 200
93
105
  message ||= "Ok"
94
106
  client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
@@ -96,44 +108,18 @@ module MacawFramework
96
108
  rescue EndpointNotMappedError
97
109
  client.print "HTTP/1.1 404 Not Found\r\n\r\n"
98
110
  client.close
99
- rescue StandardError
111
+ rescue StandardError => e
100
112
  client.print "HTTP/1.1 500 Internal Server Error\r\n\r\n"
113
+ @macaw_log.info("Error: #{e}")
101
114
  client.close
102
115
  end
103
116
  end
104
- rescue Interrupt
105
- puts "Macaw stop flying for some seeds..."
106
- end
107
-
108
- private
109
-
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
117
  end
133
118
 
134
- def extract_body(client, body_first_line, content_length)
135
- body = client.read(content_length)
136
- body_first_line << body.to_s
119
+ def map_new_endpoint(prefix, path, &block)
120
+ path_clean = RequestDataFiltering.extract_path(path)
121
+ @macaw_log.info("Defining #{prefix.upcase} endpoint at /#{path}")
122
+ define_singleton_method("#{prefix}_#{path_clean}", block)
137
123
  end
138
124
  end
139
125
  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.3
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-14 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