macaw_framework 0.1.4 → 0.1.5

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: 8d6b667252e1efbbe7114b4450c894f15e33721d7686c2455108e99a7211e385
4
- data.tar.gz: 07f927d324da0be33031108bc85c564f1a62bd42de7b5a017cf3bfca05dee992
3
+ metadata.gz: d900a18480da4792dfa03608ca8c85c0d0ba4084a3430c39008f4ca026ae784c
4
+ data.tar.gz: e3bc35f7b78bc0a6e995c0abd906879dda434e4c12306d3d55713c35dc45065c
5
5
  SHA512:
6
- metadata.gz: db686066c7e051021c0d088b715a9509bc65f08ea07d53bf7790c786c2da6c27c85f6a47b2bf7f4c39521f78e9439a63152aa077108cc708e6758205dc2931fa
7
- data.tar.gz: 64e5602bab74ed50d94da1935222c9d86a4c7fce04026061d7ac2585ba146959eb9b108b2db142d15a4f658de8aa67f8cdeca3577096bad1add1192015f7f592
6
+ metadata.gz: 6ac4eb646bb2510fbc67ddec9ccfca45bea8dfcb6833f9e55b60f0f18d5edebaf392645c53dae7745eec797c926af1700c273516682bd6f10e66c2b018f1cea6
7
+ data.tar.gz: 0ecb2e16a9f1762833751bb403d824e04571c4f3b79f01355ffb6f1f29eb29c234befc759c7d76d7b939f05d5f9f3c252d8a5828198e9c7674b7854fe2edcdf1
data/.rubocop.yml CHANGED
@@ -17,3 +17,6 @@ Metrics/MethodLength:
17
17
 
18
18
  Metrics/AbcSize:
19
19
  Max: 35
20
+
21
+ Metrics/CyclomaticComplexity:
22
+ Max: 10
data/Gemfile CHANGED
@@ -10,3 +10,5 @@ gem "rake", "~> 13.0"
10
10
  gem "minitest", "~> 5.0"
11
11
 
12
12
  gem "rubocop", "~> 1.21"
13
+
14
+ gem "simplecov", "~> 0.22.0"
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # MacawFramework
2
2
 
3
+ <img src="macaw_logo.png" alt= “” style="width: 30%;height: 30%;margin-left: 35%">
4
+
3
5
  This is a framework for developing web applications. Please have in mind that this is still a work in progress and
4
6
  it is strongly advised to not use it for production purposes for now. Actualy it supports only HTTP. HTTPS and SSL
5
7
  support will be implemented soon. Anyone who wishes to contribute is welcome.
@@ -26,7 +28,8 @@ in the same directory of the script that will start the application with the fol
26
28
  {
27
29
  "macaw": {
28
30
  "port": 8080,
29
- "bind": "localhost"
31
+ "bind": "localhost",
32
+ "threads": 10
30
33
  }
31
34
  }
32
35
  ```
@@ -39,7 +42,10 @@ require 'json'
39
42
 
40
43
  m = MacawFramework::Macaw.new
41
44
 
42
- m.get('/hello_world') do |headers, body, parameters|
45
+ m.get('/hello_world') do |context|
46
+ context[:body] # Returns the request body as string
47
+ context[:params] # Returns query parameters and path variables as a hash
48
+ context[:headers] # Returns headers as a hash
43
49
  return JSON.pretty_generate({ hello_message: 'Hello World!' }), 200
44
50
  end
45
51
 
@@ -1,24 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../errors/endpoint_not_mapped_error"
4
+
3
5
  ##
4
6
  # Module containing methods to filter Strings
5
7
  module RequestDataFiltering
8
+ VARIABLE_PATTERN = %r{:[^/]+}.freeze
9
+
6
10
  ##
7
11
  # Method responsible for extracting information
8
12
  # provided by the client like Headers and Body
9
- def self.parse_request_data(client)
13
+ def self.parse_request_data(client, routes)
10
14
  path, parameters = extract_url_parameters(client.gets.gsub("HTTP/1.1", ""))
15
+ parameters = {} if parameters.nil?
16
+
11
17
  method_name = sanitize_method_name(path)
18
+ method_name = select_path(method_name, routes, parameters)
12
19
  body_first_line, headers = extract_headers(client)
13
20
  body = extract_body(client, body_first_line, headers["Content-Length"].to_i)
14
21
  [path, method_name, headers, body, parameters]
15
22
  end
16
23
 
24
+ def self.select_path(method_name, routes, parameters)
25
+ return method_name if routes.include?(method_name)
26
+
27
+ selected_route = nil
28
+ routes.each do |route|
29
+ split_route = route.split(".")
30
+ split_name = method_name.split(".")
31
+
32
+ next unless split_route.length == split_name.length
33
+ next unless match_path_with_route(split_name, split_route)
34
+
35
+ selected_route = route
36
+ split_route.each_with_index do |var, index|
37
+ parameters[var[1..].to_sym] = split_name[index] if var =~ VARIABLE_PATTERN
38
+ end
39
+ break
40
+ end
41
+
42
+ raise EndpointNotMappedError if selected_route.nil?
43
+
44
+ selected_route
45
+ end
46
+
47
+ def self.match_path_with_route(split_path, split_route)
48
+ split_route.each_with_index do |var, index|
49
+ return false if var != split_path[index] && !var.match?(VARIABLE_PATTERN)
50
+ end
51
+
52
+ true
53
+ end
54
+
17
55
  ##
18
56
  # Method responsible for sanitizing the method name
19
57
  def self.sanitize_method_name(path)
20
58
  path = extract_path(path)
21
- method_name = path.gsub("/", "_").strip.downcase
59
+ method_name = path.gsub("/", ".").strip.downcase
22
60
  method_name.gsub!(" ", "")
23
61
  method_name
24
62
  end
@@ -26,7 +64,7 @@ module RequestDataFiltering
26
64
  ##
27
65
  # Method responsible for extracting the path from URI
28
66
  def self.extract_path(path)
29
- path[0] == "/" ? path[1..].gsub("/", "_") : path.gsub("/", "_")
67
+ path[0] == "/" ? path[1..].gsub("/", ".") : path.gsub("/", ".")
30
68
  end
31
69
 
32
70
  ##
@@ -16,49 +16,70 @@ class Server
16
16
  # @param {Logger} logger
17
17
  # @param {Integer} port
18
18
  # @param {String} bind
19
+ # @param {Integer} num_threads
19
20
  # @return {Server}
20
- def initialize(macaw, logger, port, bind)
21
+ def initialize(macaw, logger, port, bind, num_threads)
21
22
  @port = port
22
23
  @bind = bind
23
24
  @macaw = macaw
24
25
  @macaw_log = logger
26
+ @num_threads = num_threads
27
+ @work_queue = Queue.new
28
+ @workers = []
25
29
  end
26
30
 
27
31
  ##
28
32
  # Start running the webserver.
29
33
  def run
30
34
  @server = TCPServer.new(@bind, @port)
31
- loop do
32
- Thread.start(@server.accept) do |client|
33
- path, method_name, headers, body, parameters = RequestDataFiltering.parse_request_data(client)
34
- raise EndpointNotMappedError unless @macaw.respond_to?(method_name)
35
+ @num_threads.times do
36
+ @workers << Thread.new do
37
+ loop do
38
+ client = @work_queue.pop
39
+ break if client == :shutdown
35
40
 
36
- @macaw_log.info("Running #{path.gsub("\n", "").gsub("\r", "")}")
37
- message, status = call_endpoint(@macaw_log, method_name, headers, body, parameters)
38
- status ||= 200
39
- message ||= "Ok"
40
- client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
41
- client.close
42
- rescue EndpointNotMappedError
43
- client.print "HTTP/1.1 404 Not Found\r\n\r\n"
44
- client.close
45
- rescue StandardError => e
46
- client.print "HTTP/1.1 500 Internal Server Error\r\n\r\n"
47
- @macaw_log.info("Error: #{e}")
48
- client.close
41
+ handle_client(client)
42
+ end
49
43
  end
50
44
  end
45
+
46
+ loop do
47
+ @work_queue << @server.accept
48
+ rescue IOError, Errno::EBADF
49
+ break
50
+ end
51
51
  end
52
52
 
53
53
  ##
54
54
  # Method Responsible for closing the TCP server.
55
55
  def close
56
56
  @server.close
57
+ @num_threads.times { @work_queue << :shutdown }
58
+ @workers.each(&:join)
57
59
  end
58
60
 
59
61
  private
60
62
 
61
- def call_endpoint(name, *arg_array)
62
- @macaw.send(name.to_sym, *arg_array)
63
+ def handle_client(client)
64
+ path, method_name, headers, body, parameters = RequestDataFiltering.parse_request_data(client, @macaw.routes)
65
+ raise EndpointNotMappedError unless @macaw.respond_to?(method_name)
66
+
67
+ @macaw_log.info("Running #{path.gsub("\n", "").gsub("\r", "")}")
68
+ message, status = call_endpoint(@macaw_log, method_name, headers, body, parameters)
69
+ status ||= 200
70
+ message ||= "Ok"
71
+ client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
72
+ client.close
73
+ rescue EndpointNotMappedError
74
+ client.print "HTTP/1.1 404 Not Found\r\n\r\n"
75
+ client.close
76
+ rescue StandardError => e
77
+ client.print "HTTP/1.1 500 Internal Server Error\r\n\r\n"
78
+ @macaw_log.info("Error: #{e}")
79
+ client.close
80
+ end
81
+
82
+ def call_endpoint(name, headers, body, parameters)
83
+ @macaw.send(name.to_sym, { headers: headers, body: body, params: parameters })
63
84
  end
64
85
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MacawFramework
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.5"
5
5
  end
@@ -13,20 +13,27 @@ module MacawFramework
13
13
  # Class responsible for creating endpoints and
14
14
  # starting the web server.
15
15
  class Macaw
16
+ ##
17
+ # Array containing the routes defined in the application
18
+ attr_reader :routes
19
+
16
20
  ##
17
21
  # @param {Logger} custom_log
18
22
  def initialize(custom_log: nil, server: Server)
19
23
  begin
24
+ @routes = []
20
25
  @macaw_log ||= custom_log.nil? ? Logger.new($stdout) : custom_log
21
26
  config = JSON.parse(File.read("application.json"))
22
27
  @port = config["macaw"]["port"]
23
28
  @bind = config["macaw"]["bind"]
29
+ @threads = config["macaw"]["threads"].to_i
24
30
  rescue StandardError => e
25
31
  @macaw_log.error(e.message)
26
32
  end
27
33
  @port ||= 8080
28
34
  @bind ||= "localhost"
29
- @server = server.new(self, @macaw_log, @port, @bind)
35
+ @threads ||= 5
36
+ @server = server.new(self, @macaw_log, @port, @bind, @threads)
30
37
  end
31
38
 
32
39
  ##
@@ -82,9 +89,10 @@ module MacawFramework
82
89
  ##
83
90
  # Starts the web server
84
91
  def start!
92
+ @macaw_log.info("---------------------------------")
85
93
  @macaw_log.info("Starting server at port #{@port}")
86
- time = Time.now
87
- @macaw_log.info("Server started in #{Time.now - time} seconds.")
94
+ @macaw_log.info("Number of threads: #{@threads}")
95
+ @macaw_log.info("---------------------------------")
88
96
  server_loop(@server)
89
97
  rescue Interrupt
90
98
  @macaw_log.info("Stopping server")
@@ -101,7 +109,10 @@ module MacawFramework
101
109
  def map_new_endpoint(prefix, path, &block)
102
110
  path_clean = RequestDataFiltering.extract_path(path)
103
111
  @macaw_log.info("Defining #{prefix.upcase} endpoint at /#{path}")
104
- define_singleton_method("#{prefix}_#{path_clean}", block)
112
+ define_singleton_method("#{prefix}.#{path_clean}", block || lambda {
113
+ |context = { headers: {}, body: "", params: {} }|
114
+ })
115
+ @routes << "#{prefix}.#{path_clean}"
105
116
  end
106
117
  end
107
118
  end
data/macaw_logo.png ADDED
Binary file
@@ -6,6 +6,10 @@ module MacawFramework
6
6
 
7
7
  @server: Server
8
8
 
9
+ @threads: Integer
10
+
11
+ attr_reader routes: Array[String]
12
+
9
13
  def delete: -> nil
10
14
 
11
15
  def get: -> nil
@@ -0,0 +1,3 @@
1
+ module RequestDataFiltering
2
+ VARIABLE_PATTERN: Regexp
3
+ end
data/sig/server.rbs CHANGED
@@ -2,10 +2,17 @@ class Server
2
2
  @bind: String
3
3
  @macaw: MacawFramework::Macaw
4
4
  @macaw_log: Logger
5
+ @num_threads: Integer
5
6
  @port: Integer
6
7
 
7
8
  @server: TCPServer
8
9
 
10
+ @threads: Integer
11
+
12
+ @work_queue: Thread::Queue
13
+
14
+ @workers: Array[Thread]
15
+
9
16
  def close: -> nil
10
17
 
11
18
  def run: -> nil
@@ -13,4 +20,6 @@ class Server
13
20
  private
14
21
 
15
22
  def call_endpoint: -> Array[untyped]
23
+
24
+ def handle_client: -> nil
16
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.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aria Diniz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-04-09 00:00:00.000000000 Z
11
+ date: 2023-04-16 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:
@@ -31,10 +31,12 @@ files:
31
31
  - lib/macaw_framework/middlewares/server.rb
32
32
  - lib/macaw_framework/utils/http_status_code.rb
33
33
  - lib/macaw_framework/version.rb
34
+ - macaw_logo.png
34
35
  - sig/http_status_code.rbs
35
36
  - sig/logging_aspect.rbs
36
37
  - sig/macaw_framework.rbs
37
38
  - sig/macaw_framework/macaw.rbs
39
+ - sig/request_data_filtering.rbs
38
40
  - sig/server.rbs
39
41
  homepage: https://github.com/ariasdiniz/macaw_framework
40
42
  licenses: