macaw_framework 0.1.0 → 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: 20f895377b3f0eea2494e091fab22b81734f6625d840ed2414c557db4db29ac2
4
- data.tar.gz: 0ce55bbbbb41a5da4ab1622c72ced7300bfc4cb8b75091702fffb21822415ce2
3
+ metadata.gz: 512b14576a1be6337300fc758b7be6acf48603fa605c0d60c21d494b2d411a10
4
+ data.tar.gz: 29f0115f9e93539c740e56834515d7d66dd768a2b5a8c5ef2668beb82d403fa5
5
5
  SHA512:
6
- metadata.gz: 033b20344e924fb6cf66a75df40dd18fcee89967738392529b7f6dbab02e7301af275156d81a5601222428a03ae9796af751b048ab09632debfe87fcc5c86516
7
- data.tar.gz: c2c09a27334e8904f2b7e714a6ac71df95140fe7e088ddcf23e71a29a125c0cc72cce2395939ce18d2e5ebb3998fb28c8a8a0a912de99c9e070004a0b78483a9
6
+ metadata.gz: aa4dd23ceaa6579ab7f0b77caa42b8745fb48f830aa8f5db9eb713bff5330d1879653209781e888c873b071c45b11afd5744282401d77c9c7609c0ed9f721e4b
7
+ data.tar.gz: 2cc30aff12830763c3e9a19c8597fa4cd5f8e8ec7bfc1201f5c367883e93c480cef243d2590725084b62922538e6e27798f53840df0660b8872e09be01336f8a
data/CHANGELOG.md CHANGED
@@ -3,3 +3,14 @@
3
3
  ## [0.1.0] - 2022-12-05
4
4
 
5
5
  - Initial release
6
+
7
+ ## [0.1.1] - 2022-12-06
8
+
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
@@ -35,9 +35,9 @@ Example of usage:
35
35
  require 'macaw_framework'
36
36
  require 'json'
37
37
 
38
- m = Macaw.new
38
+ m = MacawFramework::Macaw.new
39
39
 
40
- m.get('/hello_world') do
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.0"
4
+ VERSION = "0.1.2"
5
5
  end
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'macaw_framework/endpoint_not_mapped_error'
4
- require_relative 'macaw_framework/http_status_code'
5
- require_relative 'macaw_framework/version'
6
- require 'socket'
7
- require 'json'
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"
6
+ require_relative "macaw_framework/version"
7
+ require "socket"
8
+ require "json"
8
9
 
9
10
  module MacawFramework
10
11
  ##
@@ -12,7 +13,9 @@ 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
20
  config = JSON.parse(File.read('application.json'))
18
21
  @port = config['macaw']['port']
@@ -20,6 +23,7 @@ module MacawFramework
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
  ##
@@ -41,7 +46,8 @@ module MacawFramework
41
46
  # @return {String, Integer}
42
47
  def post(path, &block)
43
48
  path_clean = path[0] == '/' ? path[1..].gsub('/', '_') : path.gsub('/', '_')
44
- define_singleton_method("post_#{path_clean}", block)
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
  ##
@@ -52,7 +58,8 @@ module MacawFramework
52
58
  # @return {String, Integer}
53
59
  def put(path, &block)
54
60
  path_clean = path[0] == '/' ? path[1..].gsub('/', '_') : path.gsub('/', '_')
55
- define_singleton_method("put_#{path_clean}", block)
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
  ##
@@ -63,7 +70,8 @@ module MacawFramework
63
70
  # @return {String, Integer}
64
71
  def patch(path, &block)
65
72
  path_clean = path[0] == '/' ? path[1..].gsub('/', '_') : path.gsub('/', '_')
66
- define_singleton_method("patch_#{path_clean}", block)
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
  ##
@@ -74,20 +82,24 @@ module MacawFramework
74
82
  # @return {String, Integer}
75
83
  def delete(path, &block)
76
84
  path_clean = path[0] == '/' ? path[1..].gsub('/', '_') : path.gsub('/', '_')
77
- define_singleton_method("delete_#{path_clean}", block)
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
- method_name = extract_client_info(client)
98
+ path, method_name, headers, body, parameters = RequestDataFiltering.extract_client_info(client)
88
99
  raise EndpointNotMappedError unless respond_to?(method_name)
89
100
 
90
- message, status = send(method_name)
101
+ @macaw_log.info("Running #{path.gsub("\n", '').gsub("\r", '')}")
102
+ message, status = send(method_name, headers, body, parameters)
91
103
  status ||= 200
92
104
  message ||= 'Ok'
93
105
  client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
@@ -95,23 +107,22 @@ module MacawFramework
95
107
  rescue EndpointNotMappedError
96
108
  client.print "HTTP/1.1 404 Not Found\r\n\r\n"
97
109
  client.close
98
- rescue StandardError
110
+ rescue StandardError => e
99
111
  client.print "HTTP/1.1 500 Internal Server Error\r\n\r\n"
112
+ @macaw_log.info("Error: #{e}")
100
113
  client.close
101
114
  end
102
115
  end
116
+ rescue Interrupt
117
+ @macaw_log.info('Stopping server')
118
+ server.close
119
+ @macaw_log.info('Macaw stop flying for some seeds...')
103
120
  end
104
121
 
105
122
  private
106
123
 
107
- ##
108
- # Method for extracting headers and body from client request
109
- # STILL IN DEVELOPMENT
110
- # @todo finish implementation
111
- def extract_client_info(client)
112
- method_name = client.gets.gsub('HTTP/1.1', '').gsub('/', '_').strip!.downcase
113
- method_name.gsub!(' ', '')
114
- method_name
124
+ def map_new_endpoint(prefix, path, &block)
125
+ define_singleton_method("#{prefix}_#{path}", block)
115
126
  end
116
127
  end
117
128
  end
@@ -0,0 +1,3 @@
1
+ module HttpStatusCode
2
+ HTTP_STATUS_CODE_MAP: Hash[Integer, String]
3
+ end
@@ -16,6 +16,10 @@ module MacawFramework
16
16
 
17
17
  private
18
18
 
19
+ def extract_body: -> string
20
+
19
21
  def extract_client_info: -> nil
22
+
23
+ def extract_headers: -> string
20
24
  end
21
25
  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.0
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-06 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,13 +27,16 @@ 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
34
36
  licenses:
35
37
  - MIT
36
38
  metadata:
39
+ documentation_uri: https://rubydoc.info/gems/macaw_framework
37
40
  homepage_uri: https://github.com/ariasdiniz/macaw_framework
38
41
  source_code_uri: https://github.com/ariasdiniz/macaw_framework
39
42
  post_install_message:
@@ -44,7 +47,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
44
47
  requirements:
45
48
  - - ">="
46
49
  - !ruby/object:Gem::Version
47
- version: 2.6.0
50
+ version: 2.7.0
48
51
  required_rubygems_version: !ruby/object:Gem::Requirement
49
52
  requirements:
50
53
  - - ">="