macaw_framework 0.1.1 → 0.1.2

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