macaw_framework 0.1.1 → 0.1.3

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: 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