macaw_framework 0.1.4 → 0.2.0

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: 8d6b667252e1efbbe7114b4450c894f15e33721d7686c2455108e99a7211e385
4
- data.tar.gz: 07f927d324da0be33031108bc85c564f1a62bd42de7b5a017cf3bfca05dee992
3
+ metadata.gz: b2191d627ee06b38338eeef5d88e16791ad96c9ac94fd3b4f8a917cb7a4fd1d6
4
+ data.tar.gz: 8572db3e03300d28013c29f2bc5238d2f2a4907b087cfc1fe60ff6a8b2369026
5
5
  SHA512:
6
- metadata.gz: db686066c7e051021c0d088b715a9509bc65f08ea07d53bf7790c786c2da6c27c85f6a47b2bf7f4c39521f78e9439a63152aa077108cc708e6758205dc2931fa
7
- data.tar.gz: 64e5602bab74ed50d94da1935222c9d86a4c7fce04026061d7ac2585ba146959eb9b108b2db142d15a4f658de8aa67f8cdeca3577096bad1add1192015f7f592
6
+ metadata.gz: 58cc6a8c9fe40d4f2fead09cf8188ac440dce1444b387a737283285ba1d823b9604ada962305d7c9279bfe0195215b891ecf417715dc755f1b027f75ffa07c13
7
+ data.tar.gz: 6e8b48c28f83a5eb33f5166c37dc1bbacc2831a3fd31f494e75a5ded27410d006475f12b684da06051b4a6ffe27a3067b1c8dfd5ab0acdaf12ecaba8b444ca60
data/.rubocop.yml CHANGED
@@ -16,4 +16,13 @@ Metrics/MethodLength:
16
16
  Max: 30
17
17
 
18
18
  Metrics/AbcSize:
19
- Max: 35
19
+ Enabled: false
20
+
21
+ Metrics/CyclomaticComplexity:
22
+ Enabled: false
23
+
24
+ Metrics/ParameterLists:
25
+ Max: 15
26
+
27
+ Metrics/PerceivedComplexity:
28
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -24,3 +24,13 @@
24
24
  - Adding log by aspect on endpoint calls to improve observability
25
25
  - Moving the server for a new separate class to respect single responsibility
26
26
  - Improved the data filtering middleware to sanitize inputs
27
+
28
+ ## [0.1.5] - 2023-04-16
29
+
30
+ - Adding support to path variables
31
+
32
+ ## [0.2.0] - 2023-04-22
33
+
34
+ - Adding middleware for integration with Prometheus to collect metrics
35
+ - Adding a simple caching mechanism that can be enabled separately for each endpoint
36
+ - Performance and functional optimizations
data/Gemfile CHANGED
@@ -10,3 +10,7 @@ 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"
15
+
16
+ gem "prometheus-client", "~> 4.1"
data/README.md CHANGED
@@ -1,7 +1,9 @@
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
- it is strongly advised to not use it for production purposes for now. Actualy it supports only HTTP. HTTPS and SSL
6
+ it is strongly advised to not use it for production purposes for now. Actually it supports only HTTP. and HTTPS/SSL
5
7
  support will be implemented soon. Anyone who wishes to contribute is welcome.
6
8
 
7
9
  ## Installation
@@ -17,7 +19,6 @@ If bundler is not being used to manage dependencies, install the gem by executin
17
19
  ## Usage
18
20
 
19
21
  The usage of the framework still very simple. Actually it support 5 HTTP verbs: GET, POST, PUT, PATCH and DELETE.
20
- For now, the framework can't resolve client request body and headers. The support for this will be included soon.
21
22
 
22
23
  The default server port is 8080. To choose a different port, create a file with the name `application.json`
23
24
  in the same directory of the script that will start the application with the following content:
@@ -26,11 +27,18 @@ in the same directory of the script that will start the application with the fol
26
27
  {
27
28
  "macaw": {
28
29
  "port": 8080,
29
- "bind": "localhost"
30
+ "bind": "localhost",
31
+ "threads": 10,
32
+ "cache": {
33
+ "cache_invalidation": 3600
34
+ }
30
35
  }
31
36
  }
32
37
  ```
33
38
 
39
+ Cache invalidation time should be specified in seconds. In order to enable caching, The application.json file
40
+ should exist in the app main directory and it need the `cache_invalidation` config set.
41
+
34
42
  Example of usage:
35
43
 
36
44
  ```ruby
@@ -39,17 +47,32 @@ require 'json'
39
47
 
40
48
  m = MacawFramework::Macaw.new
41
49
 
42
- m.get('/hello_world') do |headers, body, parameters|
50
+ m.get('/hello_world', cache: true) do |context|
51
+ context[:body] # Returns the request body as string
52
+ context[:params] # Returns query parameters and path variables as a hash
53
+ context[:headers] # Returns headers as a hash
54
+ return JSON.pretty_generate({ hello_message: 'Hello World!' }), 200
55
+ end
56
+
57
+ m.post('/hello_world/:path_variable') do |context|
58
+ context[:body] # Returns the request body as string
59
+ context[:params] # Returns query parameters and path variables as a hash
60
+ context[:headers] # Returns headers as a hash
61
+ context[:params][:path_variable] # The defined path variable can be found in :params
43
62
  return JSON.pretty_generate({ hello_message: 'Hello World!' }), 200
44
63
  end
45
64
 
46
65
  m.start!
47
66
  ```
48
67
 
49
- The above example will start a server and will create a GET endpoint at localhost/hello_world.
68
+ The example above starts a server and creates a GET endpoint at localhost/hello_world.
69
+
70
+ If prometheus is enabled, a get endpoint will be defined at path `/metrics` to collect prometheus metrics. This path
71
+ is configurable via the `application.json` file.
50
72
 
51
- The verb methods must always return a String or nil (Used as response) and a number corresponding the
52
- HTTP Status Code to be returned to the client.
73
+ The verb methods must always return a string or nil (used as the response) and a number corresponding to the HTTP status
74
+ code to be returned to the client. If an endpoint doesn't return a value or returns nil for both the string and the
75
+ code, a default 200 OK status will be sent as the response.
53
76
 
54
77
  ## Contributing
55
78
 
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Aspect that provide cache for the endpoints.
5
+ module CacheAspect
6
+ def call_endpoint(cache, endpoints_to_cache, *args)
7
+ return super(*args) unless endpoints_to_cache.include?(args[0]) && !cache.nil?
8
+ return cache.cache[args[2..].to_s.to_sym][0] unless cache.cache[args[2..].to_s.to_sym].nil?
9
+
10
+ response = super(*args)
11
+ cache.cache[args[2..].to_s.to_sym] = [response, Time.now]
12
+ response
13
+ end
14
+ end
@@ -8,9 +8,17 @@ require "logger"
8
8
  # in the framework.
9
9
  module LoggingAspect
10
10
  def call_endpoint(logger, *args)
11
- logger.info("Input of #{args[0]}: #{args}")
12
- response = super(*args)
13
- logger.info("Output of #{args[0]} #{response}")
11
+ endpoint_name = args[2].split(".")[1..].join("/")
12
+ logger.info("Request received for #{endpoint_name} with arguments: #{args[3..]}")
13
+
14
+ begin
15
+ response = super(*args)
16
+ logger.info("Response for #{endpoint_name}: #{response}")
17
+ rescue StandardError => e
18
+ logger.error("Error processing #{endpoint_name}: #{e.message}\n#{e.backtrace.join("\n")}")
19
+ raise e
20
+ end
21
+
14
22
  response
15
23
  end
16
24
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Aspect that provides application metrics using prometheus.
5
+ module PrometheusAspect
6
+ def call_endpoint(prometheus_middleware, *args)
7
+ return super(*args) if prometheus_middleware.nil?
8
+
9
+ start_time = Time.now
10
+
11
+ begin
12
+ response = super(*args)
13
+ ensure
14
+ duration = (Time.now - start_time) * 1_000
15
+
16
+ endpoint_name = args[3].split(".").join("/")
17
+
18
+ prometheus_middleware.request_duration_milliseconds.with_labels(endpoint: endpoint_name).observe(duration)
19
+ prometheus_middleware.request_count.with_labels(endpoint: endpoint_name).increment
20
+ if response
21
+ prometheus_middleware.response_count.with_labels(endpoint: endpoint_name,
22
+ status: response[1]).increment
23
+ end
24
+ end
25
+
26
+ response
27
+ end
28
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../aspects/prometheus_aspect"
4
+ require_relative "../aspects/logging_aspect"
5
+ require_relative "../utils/http_status_code"
6
+ require_relative "../aspects/cache_aspect"
7
+
8
+ ##
9
+ # Class responsible for providing a default
10
+ # webserver.
11
+ class Server
12
+ prepend CacheAspect
13
+ prepend LoggingAspect
14
+ prepend PrometheusAspect
15
+ include HttpStatusCode
16
+ # rubocop:disable Metrics/ParameterLists
17
+
18
+ ##
19
+ # Create a new instance of Server.
20
+ # @param {Macaw} macaw
21
+ # @param {Logger} logger
22
+ # @param {Integer} port
23
+ # @param {String} bind
24
+ # @param {Integer} num_threads
25
+ # @param {CachingMiddleware} cache
26
+ # @param {Prometheus::Client:Registry} prometheus
27
+ # @return {Server}
28
+ def initialize(macaw, logger, port, bind, num_threads, endpoints_to_cache = nil, cache = nil, prometheus = nil,
29
+ prometheus_middleware = nil)
30
+ @port = port
31
+ @bind = bind
32
+ @macaw = macaw
33
+ @macaw_log = logger
34
+ @num_threads = num_threads
35
+ @work_queue = Queue.new
36
+ @endpoints_to_cache = endpoints_to_cache || []
37
+ @cache = cache
38
+ @prometheus = prometheus
39
+ @prometheus_middleware = prometheus_middleware
40
+ @workers = []
41
+ end
42
+
43
+ # rubocop:enable Metrics/ParameterLists
44
+
45
+ ##
46
+ # Start running the webserver.
47
+ def run
48
+ @server = TCPServer.new(@bind, @port)
49
+ @num_threads.times do
50
+ @workers << Thread.new do
51
+ loop do
52
+ client = @work_queue.pop
53
+ break if client == :shutdown
54
+
55
+ handle_client(client)
56
+ end
57
+ end
58
+ end
59
+
60
+ loop do
61
+ @work_queue << @server.accept
62
+ rescue IOError, Errno::EBADF
63
+ break
64
+ end
65
+ end
66
+
67
+ ##
68
+ # Method Responsible for closing the TCP server.
69
+ def close
70
+ @server.close
71
+ @num_threads.times { @work_queue << :shutdown }
72
+ @workers.each(&:join)
73
+ end
74
+
75
+ private
76
+
77
+ def handle_client(client)
78
+ path, method_name, headers, body, parameters = RequestDataFiltering.parse_request_data(client, @macaw.routes)
79
+ raise EndpointNotMappedError unless @macaw.respond_to?(method_name)
80
+
81
+ @macaw_log.info("Running #{path.gsub("\n", "").gsub("\r", "")}")
82
+ message, status = call_endpoint(@prometheus_middleware, @macaw_log, @cache, @endpoints_to_cache,
83
+ method_name, headers, body, parameters)
84
+ status ||= 200
85
+ message ||= nil
86
+ client.puts "HTTP/1.1 #{status} #{HTTP_STATUS_CODE_MAP[status]} \r\n\r\n#{message}"
87
+ rescue EndpointNotMappedError
88
+ client.print "HTTP/1.1 404 Not Found\r\n\r\n"
89
+ rescue StandardError => e
90
+ client.print "HTTP/1.1 500 Internal Server Error\r\n\r\n"
91
+ @macaw_log.info("Error: #{e}")
92
+ ensure
93
+ client.close
94
+ end
95
+
96
+ def call_endpoint(name, headers, body, parameters)
97
+ @macaw.send(name.to_sym, { headers: headers, body: body, params: parameters })
98
+ end
99
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Middleware responsible for storing and
5
+ # invalidating cache.
6
+ class CachingMiddleware
7
+ attr_accessor :cache
8
+
9
+ def initialize(inv_time_seconds = 3_600)
10
+ @cache = {}
11
+ Thread.new do
12
+ loop do
13
+ sleep(1)
14
+ @cache.each_pair do |key, value|
15
+ @cache.delete(key) if Time.now - value[1] >= inv_time_seconds
16
+ end
17
+ end
18
+ end
19
+ sleep(2)
20
+ end
21
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prometheus/client"
4
+ require "prometheus/client/formats/text"
5
+
6
+ ##
7
+ # Middleware responsible to configure prometheus
8
+ # defining metrics and an endpoint to access them.
9
+ class PrometheusMiddleware
10
+ attr_accessor :request_duration_milliseconds, :request_count, :response_count
11
+
12
+ def configure_prometheus(prometheus_registry, configurations, macaw)
13
+ return nil unless prometheus_registry
14
+
15
+ @request_duration_milliseconds = Prometheus::Client::Histogram.new(
16
+ :request_duration_milliseconds,
17
+ docstring: "The duration of each request in milliseconds",
18
+ labels: [:endpoint],
19
+ buckets: (100..1000).step(100).to_a + (2000..10_000).step(1000).to_a
20
+ )
21
+
22
+ @request_count = Prometheus::Client::Counter.new(
23
+ :request_count,
24
+ docstring: "The total number of requests received",
25
+ labels: [:endpoint]
26
+ )
27
+
28
+ @response_count = Prometheus::Client::Counter.new(
29
+ :response_count,
30
+ docstring: "The total number of responses sent",
31
+ labels: %i[endpoint status]
32
+ )
33
+
34
+ prometheus_registry.register(@request_duration_milliseconds)
35
+ prometheus_registry.register(@request_count)
36
+ prometheus_registry.register(@response_count)
37
+ prometheus_endpoint(prometheus_registry, configurations, macaw)
38
+ end
39
+
40
+ private
41
+
42
+ def prometheus_endpoint(prometheus_registry, configurations, macaw)
43
+ endpoint = configurations["macaw"]["prometheus"]["endpoint"] || "/metrics"
44
+ macaw.get(endpoint) do |_context|
45
+ [Prometheus::Client::Formats::Text.marshal(prometheus_registry), 200]
46
+ end
47
+ end
48
+ end
@@ -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
  ##
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MacawFramework
4
- VERSION = "0.1.4"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "macaw_framework/errors/endpoint_not_mapped_error"
4
+ require_relative "macaw_framework/middlewares/prometheus_middleware"
4
5
  require_relative "macaw_framework/middlewares/request_data_filtering"
5
- require_relative "macaw_framework/middlewares/server"
6
+ require_relative "macaw_framework/middlewares/caching_middleware"
7
+ require_relative "macaw_framework/core/server"
6
8
  require_relative "macaw_framework/version"
9
+ require "prometheus/client"
7
10
  require "logger"
8
11
  require "socket"
9
12
  require "json"
@@ -13,20 +16,37 @@ module MacawFramework
13
16
  # Class responsible for creating endpoints and
14
17
  # starting the web server.
15
18
  class Macaw
19
+ ##
20
+ # Array containing the routes defined in the application
21
+ attr_reader :routes
22
+
16
23
  ##
17
24
  # @param {Logger} custom_log
18
25
  def initialize(custom_log: nil, server: Server)
19
26
  begin
27
+ @routes = []
20
28
  @macaw_log ||= custom_log.nil? ? Logger.new($stdout) : custom_log
21
29
  config = JSON.parse(File.read("application.json"))
22
- @port = config["macaw"]["port"]
23
- @bind = config["macaw"]["bind"]
30
+ @port = config["macaw"]["port"] || 8080
31
+ @bind = config["macaw"]["bind"] || "localhost"
32
+ @threads = config["macaw"]["threads"] || 5
33
+ unless config["macaw"]["cache"].nil?
34
+ @cache = CachingMiddleware.new(config["macaw"]["cache"]["cache_invalidation"].to_i || 3_600)
35
+ end
36
+ @prometheus = Prometheus::Client::Registry.new if config["macaw"]["prometheus"]
37
+ @prometheus_middleware = PrometheusMiddleware.new if config["macaw"]["prometheus"]
38
+ @prometheus_middleware.configure_prometheus(@prometheus, config, self) if config["macaw"]["prometheus"]
24
39
  rescue StandardError => e
25
40
  @macaw_log.error(e.message)
26
41
  end
27
42
  @port ||= 8080
28
43
  @bind ||= "localhost"
29
- @server = server.new(self, @macaw_log, @port, @bind)
44
+ @threads ||= 5
45
+ @endpoints_to_cache = []
46
+ @prometheus ||= nil
47
+ @prometheus_middleware ||= nil
48
+ @server = server.new(self, @macaw_log, @port, @bind, @threads, @endpoints_to_cache, @cache, @prometheus,
49
+ @prometheus_middleware)
30
50
  end
31
51
 
32
52
  ##
@@ -35,18 +55,19 @@ module MacawFramework
35
55
  # @param {String} path
36
56
  # @param {Proc} block
37
57
  # @return {Integer, String}
38
- def get(path, &block)
39
- map_new_endpoint("get", path, &block)
58
+ def get(path, cache: false, &block)
59
+ map_new_endpoint("get", cache, path, &block)
40
60
  end
41
61
 
42
62
  ##
43
63
  # Creates a POST endpoint associated
44
64
  # with the respective path.
45
65
  # @param {String} path
66
+ # @param {Boolean} cache
46
67
  # @param {Proc} block
47
68
  # @return {String, Integer}
48
- def post(path, &block)
49
- map_new_endpoint("post", path, &block)
69
+ def post(path, cache: false, &block)
70
+ map_new_endpoint("post", cache, path, &block)
50
71
  end
51
72
 
52
73
  ##
@@ -55,8 +76,8 @@ module MacawFramework
55
76
  # @param {String} path
56
77
  # @param {Proc} block
57
78
  # @return {String, Integer}
58
- def put(path, &block)
59
- map_new_endpoint("put", path, &block)
79
+ def put(path, cache: false, &block)
80
+ map_new_endpoint("put", cache, path, &block)
60
81
  end
61
82
 
62
83
  ##
@@ -65,8 +86,8 @@ module MacawFramework
65
86
  # @param {String} path
66
87
  # @param {Proc} block
67
88
  # @return {String, Integer}
68
- def patch(path, &block)
69
- map_new_endpoint("patch", path, &block)
89
+ def patch(path, cache: false, &block)
90
+ map_new_endpoint("patch", cache, path, &block)
70
91
  end
71
92
 
72
93
  ##
@@ -75,16 +96,17 @@ module MacawFramework
75
96
  # @param {String} path
76
97
  # @param {Proc} block
77
98
  # @return {String, Integer}
78
- def delete(path, &block)
79
- map_new_endpoint("delete", path, &block)
99
+ def delete(path, cache: false, &block)
100
+ map_new_endpoint("delete", cache, path, &block)
80
101
  end
81
102
 
82
103
  ##
83
104
  # Starts the web server
84
105
  def start!
106
+ @macaw_log.info("---------------------------------")
85
107
  @macaw_log.info("Starting server at port #{@port}")
86
- time = Time.now
87
- @macaw_log.info("Server started in #{Time.now - time} seconds.")
108
+ @macaw_log.info("Number of threads: #{@threads}")
109
+ @macaw_log.info("---------------------------------")
88
110
  server_loop(@server)
89
111
  rescue Interrupt
90
112
  @macaw_log.info("Stopping server")
@@ -98,10 +120,14 @@ module MacawFramework
98
120
  server.run
99
121
  end
100
122
 
101
- def map_new_endpoint(prefix, path, &block)
123
+ def map_new_endpoint(prefix, cache, path, &block)
124
+ @endpoints_to_cache << "#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}" if cache
102
125
  path_clean = RequestDataFiltering.extract_path(path)
103
126
  @macaw_log.info("Defining #{prefix.upcase} endpoint at /#{path}")
104
- define_singleton_method("#{prefix}_#{path_clean}", block)
127
+ define_singleton_method("#{prefix}.#{path_clean}", block || lambda {
128
+ |context = { headers: {}, body: "", params: {} }|
129
+ })
130
+ @routes << "#{prefix}.#{path_clean}"
105
131
  end
106
132
  end
107
133
  end
data/macaw_logo.png ADDED
Binary file
@@ -0,0 +1,5 @@
1
+ class CachingMiddleware
2
+ @cache: Hash[String, Array[string]]
3
+
4
+ attr_accessor cache: Hash[String, Array[string]]
5
+ end
@@ -1,11 +1,18 @@
1
1
  module MacawFramework
2
2
  class Macaw
3
3
  @bind: string
4
+ @cache: untyped
5
+ @endpoints_to_cache: Array[String]
4
6
  @macaw_log: Logger
5
7
  @port: int
6
8
 
9
+ @prometheus: untyped
7
10
  @server: Server
8
11
 
12
+ @threads: Integer
13
+
14
+ attr_reader routes: Array[String]
15
+
9
16
  def delete: -> nil
10
17
 
11
18
  def get: -> nil
@@ -0,0 +1,3 @@
1
+ module RequestDataFiltering
2
+ VARIABLE_PATTERN: Regexp
3
+ end
data/sig/server.rbs CHANGED
@@ -1,11 +1,22 @@
1
1
  class Server
2
2
  @bind: String
3
+ @cache: CachingMiddleware
4
+ @endpoints_to_cache: Array[String]
3
5
  @macaw: MacawFramework::Macaw
4
6
  @macaw_log: Logger
7
+ @num_threads: Integer
5
8
  @port: Integer
6
9
 
10
+ @prometheus: untyped
11
+ @prometheus_middleware: untyped
7
12
  @server: TCPServer
8
13
 
14
+ @threads: Integer
15
+
16
+ @work_queue: Thread::Queue
17
+
18
+ @workers: Array[Thread]
19
+
9
20
  def close: -> nil
10
21
 
11
22
  def run: -> nil
@@ -13,4 +24,6 @@ class Server
13
24
  private
14
25
 
15
26
  def call_endpoint: -> Array[untyped]
27
+
28
+ def handle_client: -> nil
16
29
  end
metadata CHANGED
@@ -1,15 +1,29 @@
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.2.0
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
12
- dependencies: []
11
+ date: 2023-04-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: prometheus-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.1'
13
27
  description: A project started for study purpose that I intend to keep working on.
14
28
  email:
15
29
  - aria.diniz.dev@gmail.com
@@ -25,16 +39,23 @@ files:
25
39
  - README.md
26
40
  - Rakefile
27
41
  - lib/macaw_framework.rb
42
+ - lib/macaw_framework/aspects/cache_aspect.rb
28
43
  - lib/macaw_framework/aspects/logging_aspect.rb
44
+ - lib/macaw_framework/aspects/prometheus_aspect.rb
45
+ - lib/macaw_framework/core/server.rb
29
46
  - lib/macaw_framework/errors/endpoint_not_mapped_error.rb
47
+ - lib/macaw_framework/middlewares/caching_middleware.rb
48
+ - lib/macaw_framework/middlewares/prometheus_middleware.rb
30
49
  - lib/macaw_framework/middlewares/request_data_filtering.rb
31
- - lib/macaw_framework/middlewares/server.rb
32
50
  - lib/macaw_framework/utils/http_status_code.rb
33
51
  - lib/macaw_framework/version.rb
52
+ - macaw_logo.png
53
+ - sig/caching_middleware.rbs
34
54
  - sig/http_status_code.rbs
35
55
  - sig/logging_aspect.rbs
36
56
  - sig/macaw_framework.rbs
37
57
  - sig/macaw_framework/macaw.rbs
58
+ - sig/request_data_filtering.rbs
38
59
  - sig/server.rbs
39
60
  homepage: https://github.com/ariasdiniz/macaw_framework
40
61
  licenses:
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../aspects/logging_aspect"
4
- require_relative "../utils/http_status_code"
5
-
6
- ##
7
- # Class responsible for providing a default
8
- # webserver.
9
- class Server
10
- prepend LoggingAspect
11
- include HttpStatusCode
12
-
13
- ##
14
- # Create a new instance of Server.
15
- # @param {Macaw} macaw
16
- # @param {Logger} logger
17
- # @param {Integer} port
18
- # @param {String} bind
19
- # @return {Server}
20
- def initialize(macaw, logger, port, bind)
21
- @port = port
22
- @bind = bind
23
- @macaw = macaw
24
- @macaw_log = logger
25
- end
26
-
27
- ##
28
- # Start running the webserver.
29
- def run
30
- @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
-
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
49
- end
50
- end
51
- end
52
-
53
- ##
54
- # Method Responsible for closing the TCP server.
55
- def close
56
- @server.close
57
- end
58
-
59
- private
60
-
61
- def call_endpoint(name, *arg_array)
62
- @macaw.send(name.to_sym, *arg_array)
63
- end
64
- end