macaw_framework 1.3.0 → 1.3.1
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 +4 -4
- data/.rubocop.yml +7 -9
- data/CHANGELOG.md +4 -0
- data/Gemfile +9 -9
- data/README.md +24 -7
- data/Rakefile +6 -6
- data/lib/macaw_framework/aspects/logging_aspect.rb +3 -3
- data/lib/macaw_framework/aspects/prometheus_aspect.rb +1 -1
- data/lib/macaw_framework/core/common/server_base.rb +29 -27
- data/lib/macaw_framework/core/thread_server.rb +2 -2
- data/lib/macaw_framework/data_filters/log_data_filter.rb +9 -9
- data/lib/macaw_framework/data_filters/request_data_filtering.rb +16 -16
- data/lib/macaw_framework/data_filters/response_data_filter.rb +3 -3
- data/lib/macaw_framework/errors/endpoint_not_mapped_error.rb +1 -1
- data/lib/macaw_framework/middlewares/prometheus_middleware.rb +7 -7
- data/lib/macaw_framework/utils/http_status_code.rb +61 -61
- data/lib/macaw_framework/utils/supported_ssl_versions.rb +6 -6
- data/lib/macaw_framework/version.rb +1 -1
- data/lib/macaw_framework.rb +197 -82
- metadata +3 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: faee0746cfa34d7020272e9c6a3d24f12bff1c88ef3a5f8bf1f65be5dc7de8b2
         | 
| 4 | 
            +
              data.tar.gz: f3dfa0a71a12df9d0fdf07fcedcf9fe8cc29db81c20aa003482f5a465a330648
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 92b61e93524104407eba6c9d2621990a7cc6b68e0733a78fedc75cff6fc248601410344aa421dec9e79f77a52b391d7f2e102cb54f639a127ebeaf4b00c5f2fb
         | 
| 7 | 
            +
              data.tar.gz: 3eb62e30474e588dd0200f981aaa847ddf2725d7092d9c4387a0a5c63c2870f2ae87bd7f7998d0149dd00c592d144d0d6188464de6a473334ec94733967aef3d
         | 
    
        data/.rubocop.yml
    CHANGED
    
    | @@ -1,16 +1,8 @@ | |
| 1 1 | 
             
            AllCops:
         | 
| 2 | 
            -
              TargetRubyVersion:  | 
| 2 | 
            +
              TargetRubyVersion: 3.0
         | 
| 3 3 | 
             
              SuggestExtensions: false
         | 
| 4 4 | 
             
              NewCops: disable
         | 
| 5 5 |  | 
| 6 | 
            -
            Style/StringLiterals:
         | 
| 7 | 
            -
              Enabled: true
         | 
| 8 | 
            -
              EnforcedStyle: double_quotes
         | 
| 9 | 
            -
             | 
| 10 | 
            -
            Style/StringLiteralsInInterpolation:
         | 
| 11 | 
            -
              Enabled: true
         | 
| 12 | 
            -
              EnforcedStyle: double_quotes
         | 
| 13 | 
            -
             | 
| 14 6 | 
             
            Layout/LineLength:
         | 
| 15 7 | 
             
              Max: 120
         | 
| 16 8 |  | 
| @@ -31,3 +23,9 @@ Metrics/PerceivedComplexity: | |
| 31 23 |  | 
| 32 24 | 
             
            Metrics/ClassLength:
         | 
| 33 25 | 
             
              Enabled: false
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            Metrics/ModuleLength:
         | 
| 28 | 
            +
              Enabled: false
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            Naming/MemoizedInstanceVariableName:
         | 
| 31 | 
            +
              Enabled: false
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -143,3 +143,7 @@ | |
| 143 143 | 
             
            - Fixed a bug where errors were being logged with level INFO
         | 
| 144 144 | 
             
            - Improved error stack trace
         | 
| 145 145 |  | 
| 146 | 
            +
            ## [1.3.1]
         | 
| 147 | 
            +
            - Fixing bug where missing session configuration on `application.json` break the application
         | 
| 148 | 
            +
            - Including a Cache module for manual caching.
         | 
| 149 | 
            +
             | 
    
        data/Gemfile
    CHANGED
    
    | @@ -1,17 +1,17 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            source  | 
| 3 | 
            +
            source 'https://rubygems.org'
         | 
| 4 4 |  | 
| 5 5 | 
             
            gemspec
         | 
| 6 6 |  | 
| 7 | 
            -
            gem  | 
| 8 | 
            -
            gem  | 
| 7 | 
            +
            gem 'openssl'
         | 
| 8 | 
            +
            gem 'prometheus-client', '~> 4.1'
         | 
| 9 9 |  | 
| 10 10 | 
             
            group :test do
         | 
| 11 | 
            -
              gem  | 
| 12 | 
            -
              gem  | 
| 13 | 
            -
              gem  | 
| 14 | 
            -
              gem  | 
| 15 | 
            -
              gem  | 
| 16 | 
            -
              gem  | 
| 11 | 
            +
              gem 'minitest', '~> 5.0'
         | 
| 12 | 
            +
              gem 'rake', '~> 13.0'
         | 
| 13 | 
            +
              gem 'rubocop', '~> 1.21'
         | 
| 14 | 
            +
              gem 'simplecov', '~> 0.21.2'
         | 
| 15 | 
            +
              gem 'simplecov-json'
         | 
| 16 | 
            +
              gem 'simplecov_json_formatter', '~> 0.1.2'
         | 
| 17 17 | 
             
            end
         | 
    
        data/README.md
    CHANGED
    
    | @@ -18,7 +18,7 @@ provides developers with the essential tools to quickly build and deploy their a | |
| 18 18 | 
             
                    + [Configuration: Customize various aspects of the framework through the application.json configuration file, such as rate limiting, SSL support, and Prometheus integration](#configuration-customize-various-aspects-of-the-framework-through-the-applicationjson-configuration-file-such-as-rate-limiting-ssl-support-and-prometheus-integration)
         | 
| 19 19 | 
             
                    + [Monitoring: Easily monitor your application performance and metrics with built-in Prometheus support](#monitoring-easily-monitor-your-application-performance-and-metrics-with-built-in-prometheus-support)
         | 
| 20 20 | 
             
                    + [Routing for "public" Folder: Serve Static Assets](#routing-for-public-folder-serve-static-assets)
         | 
| 21 | 
            -
                    + [ | 
| 21 | 
            +
                    + [Periodic Jobs](#periodic-jobs)
         | 
| 22 22 | 
             
                    + [Tips](#tips)
         | 
| 23 23 | 
             
                * [Contributing](#contributing)
         | 
| 24 24 | 
             
                * [License](#license)
         | 
| @@ -51,7 +51,7 @@ We evaluated MacawFramework (Version 1.2.0) to assess its ability to handle simu | |
| 51 51 |  | 
| 52 52 | 
             
            MacawFramework is built to be highly compatible, since it uses only native Ruby code:
         | 
| 53 53 |  | 
| 54 | 
            -
            - **MRI**: MacawFramework is compatible with Matz's Ruby Interpreter (MRI), version  | 
| 54 | 
            +
            - **MRI**: MacawFramework is compatible with Matz's Ruby Interpreter (MRI), version 3.0.0 and onwards. If you are using this version or a more recent one, you should not encounter any compatibility issues.
         | 
| 55 55 |  | 
| 56 56 | 
             
            - **TruffleRuby**: TruffleRuby is another Ruby interpreter that is fully compatible with MacawFramework. This provides developers with more flexibility in their choice of Ruby interpreter.
         | 
| 57 57 |  | 
| @@ -104,6 +104,8 @@ m.start! | |
| 104 104 | 
             
            ### Caching: Improve performance by caching responses and configuring cache invalidation
         | 
| 105 105 |  | 
| 106 106 | 
             
            ```ruby
         | 
| 107 | 
            +
            m = MacawFramework::Macaw.new
         | 
| 108 | 
            +
             | 
| 107 109 | 
             
            m.get('/cached_data', cache: ["header_to_cache", "query_param_to_cache"]) do |context|
         | 
| 108 110 | 
             
              # Retrieve data
         | 
| 109 111 | 
             
            end
         | 
| @@ -111,6 +113,17 @@ end | |
| 111 113 |  | 
| 112 114 | 
             
            *Observation: To activate caching, you also have to set its properties in the `application.json` file. If you don't, the caching strategy will not work. See the Configuration section below for more details.*
         | 
| 113 115 |  | 
| 116 | 
            +
            Another method of cache is the manual cache via the `MacawFramework::Cache` class. You can manually
         | 
| 117 | 
            +
            call the `read` and `write` methods of this singleton to save and recover values inside your methods.
         | 
| 118 | 
            +
             | 
| 119 | 
            +
            ```ruby
         | 
| 120 | 
            +
            MacawFramework::Cache.write(:name, 'Maria', expires_in: 1800)
         | 
| 121 | 
            +
            # Your code
         | 
| 122 | 
            +
            MacawFramework::Cache.read(:name) # Maria
         | 
| 123 | 
            +
            ```
         | 
| 124 | 
            +
             | 
| 125 | 
            +
            Manual cache does not need any additional configuration.
         | 
| 126 | 
            +
             | 
| 114 127 | 
             
            ### Session management: Handle user sessions with server-side in-memory storage
         | 
| 115 128 |  | 
| 116 129 | 
             
            Session will only be enabled if it's configurations exists in the `application.json` file.
         | 
| @@ -123,6 +136,8 @@ a session id in the HTTP request. In the case of the client sending an ID of an | |
| 123 136 | 
             
            the framework will return a new session with a new ID.
         | 
| 124 137 |  | 
| 125 138 | 
             
            ```ruby
         | 
| 139 | 
            +
            m = MacawFramework::Macaw.new
         | 
| 140 | 
            +
             | 
| 126 141 | 
             
            m.get('/login') do |context|
         | 
| 127 142 | 
             
              # Authenticate user
         | 
| 128 143 | 
             
              context[:client][:user_id] = user_id
         | 
| @@ -195,17 +210,19 @@ be accessible at http://yourdomain.com/img/logo.png without any additional confi | |
| 195 210 |  | 
| 196 211 | 
             
            #### Caution: This is incompatible with most non-unix systems, such as Windows. If you are using a non-unix system, you will need to manually configure the "public" folder and use dir as nil to avoid problems.
         | 
| 197 212 |  | 
| 198 | 
            -
            ###  | 
| 213 | 
            +
            ### Periodic Jobs
         | 
| 199 214 |  | 
| 200 | 
            -
            Macaw Framework supports the declaration of  | 
| 215 | 
            +
            Macaw Framework supports the declaration of periodic jobs right in your application code. This feature allows developers to
         | 
| 201 216 | 
             
            define tasks that run at set intervals, starting after an optional delay. Each job runs in a separate thread, meaning
         | 
| 202 | 
            -
            your  | 
| 217 | 
            +
            your periodic jobs can execute in parallel without blocking the rest of your application.
         | 
| 203 218 |  | 
| 204 | 
            -
            Here's an example of how to declare a  | 
| 219 | 
            +
            Here's an example of how to declare a periodic job:
         | 
| 205 220 |  | 
| 206 221 | 
             
            ```ruby
         | 
| 222 | 
            +
            m = MacawFramework::Macaw.new
         | 
| 223 | 
            +
             | 
| 207 224 | 
             
            m.setup_job(interval: 5, start_delay: 5, job_name: "cron job 1") do
         | 
| 208 | 
            -
              puts "i'm a  | 
| 225 | 
            +
              puts "i'm a periodic job that runs every 5 secs!"
         | 
| 209 226 | 
             
            end
         | 
| 210 227 | 
             
            ```
         | 
| 211 228 |  | 
    
        data/Rakefile
    CHANGED
    
    | @@ -1,15 +1,15 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 3 | 
            +
            require 'bundler/gem_tasks'
         | 
| 4 | 
            +
            require 'rake/testtask'
         | 
| 5 5 |  | 
| 6 6 | 
             
            Rake::TestTask.new(:test) do |t|
         | 
| 7 | 
            -
              t.libs <<  | 
| 8 | 
            -
              t.libs <<  | 
| 9 | 
            -
              t.test_files = FileList[ | 
| 7 | 
            +
              t.libs << 'test'
         | 
| 8 | 
            +
              t.libs << 'lib'
         | 
| 9 | 
            +
              t.test_files = FileList['test/**/test_*.rb']
         | 
| 10 10 | 
             
            end
         | 
| 11 11 |  | 
| 12 | 
            -
            require  | 
| 12 | 
            +
            require 'rubocop/rake_task'
         | 
| 13 13 |  | 
| 14 14 | 
             
            RuboCop::RakeTask.new
         | 
| 15 15 |  | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: false
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require_relative  | 
| 3 | 
            +
            require 'logger'
         | 
| 4 | 
            +
            require_relative '../data_filters/log_data_filter'
         | 
| 5 5 |  | 
| 6 6 | 
             
            ##
         | 
| 7 7 | 
             
            # This Aspect is responsible for logging
         | 
| @@ -11,7 +11,7 @@ module LoggingAspect | |
| 11 11 | 
             
              def call_endpoint(logger, *args)
         | 
| 12 12 | 
             
                return super(*args) if logger.nil?
         | 
| 13 13 |  | 
| 14 | 
            -
                endpoint_name = args[1].split( | 
| 14 | 
            +
                endpoint_name = args[1].split('.')[1..].join('/')
         | 
| 15 15 | 
             
                logger.info("Request received for [#{endpoint_name}] from [#{args[-1]}]")
         | 
| 16 16 |  | 
| 17 17 | 
             
                begin
         | 
| @@ -13,7 +13,7 @@ module PrometheusAspect | |
| 13 13 | 
             
                ensure
         | 
| 14 14 | 
             
                  duration = (Time.now - start_time) * 1_000
         | 
| 15 15 |  | 
| 16 | 
            -
                  endpoint_name = args[2].split( | 
| 16 | 
            +
                  endpoint_name = args[2].split('.').join('/')
         | 
| 17 17 |  | 
| 18 18 | 
             
                  prometheus_middleware.request_duration_milliseconds.with_labels(endpoint: endpoint_name).observe(duration)
         | 
| 19 19 | 
             
                  prometheus_middleware.request_count.with_labels(endpoint: endpoint_name).increment
         | 
| @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative  | 
| 4 | 
            -
            require_relative  | 
| 5 | 
            -
            require_relative  | 
| 6 | 
            -
            require_relative  | 
| 7 | 
            -
            require_relative  | 
| 8 | 
            -
            require_relative  | 
| 9 | 
            -
            require_relative  | 
| 10 | 
            -
            require_relative  | 
| 11 | 
            -
            require  | 
| 3 | 
            +
            require_relative '../../middlewares/memory_invalidation_middleware'
         | 
| 4 | 
            +
            require_relative '../../middlewares/rate_limiter_middleware'
         | 
| 5 | 
            +
            require_relative '../../data_filters/response_data_filter'
         | 
| 6 | 
            +
            require_relative '../../errors/too_many_requests_error'
         | 
| 7 | 
            +
            require_relative '../../utils/supported_ssl_versions'
         | 
| 8 | 
            +
            require_relative '../../aspects/prometheus_aspect'
         | 
| 9 | 
            +
            require_relative '../../aspects/logging_aspect'
         | 
| 10 | 
            +
            require_relative '../../aspects/cache_aspect'
         | 
| 11 | 
            +
            require 'securerandom'
         | 
| 12 12 |  | 
| 13 13 | 
             
            ##
         | 
| 14 14 | 
             
            # Base module for Server classes. It contains
         | 
| @@ -29,7 +29,7 @@ module ServerBase | |
| 29 29 | 
             
                    headers: client_data[:headers],
         | 
| 30 30 | 
             
                    body: client_data[:body],
         | 
| 31 31 | 
             
                    params: client_data[:params],
         | 
| 32 | 
            -
                    client: @session | 
| 32 | 
            +
                    client: @session&.dig(session_id)&.dig(0)
         | 
| 33 33 | 
             
                  }
         | 
| 34 34 | 
             
                )
         | 
| 35 35 | 
             
              end
         | 
| @@ -46,7 +46,7 @@ module ServerBase | |
| 46 46 | 
             
                client_data = get_client_data(body, headers, parameters)
         | 
| 47 47 | 
             
                session_id = declare_client_session(client_data[:headers], @macaw.secure_header) if @macaw.session
         | 
| 48 48 |  | 
| 49 | 
            -
                @macaw_log&.info("Running #{path.gsub("\n",  | 
| 49 | 
            +
                @macaw_log&.info("Running #{path.gsub("\n", '').gsub("\r", '')}")
         | 
| 50 50 | 
             
                message, status, response_headers = call_endpoint(@prometheus_middleware, @macaw_log, @cache,
         | 
| 51 51 | 
             
                                                                  method_name, client_data, session_id, client.peeraddr[3])
         | 
| 52 52 | 
             
                response_headers ||= {}
         | 
| @@ -80,44 +80,46 @@ module ServerBase | |
| 80 80 | 
             
              end
         | 
| 81 81 |  | 
| 82 82 | 
             
              def set_rate_limiting
         | 
| 83 | 
            -
                return unless @macaw.config&.dig( | 
| 83 | 
            +
                return unless @macaw.config&.dig('macaw', 'rate_limiting')
         | 
| 84 84 |  | 
| 85 85 | 
             
                @rate_limit = RateLimiterMiddleware.new(
         | 
| 86 | 
            -
                  @macaw.config[ | 
| 87 | 
            -
                  @macaw.config[ | 
| 86 | 
            +
                  @macaw.config['macaw']['rate_limiting']['window'].to_i || 1,
         | 
| 87 | 
            +
                  @macaw.config['macaw']['rate_limiting']['max_requests'].to_i || 60
         | 
| 88 88 | 
             
                )
         | 
| 89 89 | 
             
              end
         | 
| 90 90 |  | 
| 91 91 | 
             
              def set_ssl
         | 
| 92 | 
            -
                ssl_config = @macaw.config[ | 
| 92 | 
            +
                ssl_config = @macaw.config['macaw']['ssl'] if @macaw.config&.dig('macaw', 'ssl')
         | 
| 93 93 | 
             
                ssl_config ||= nil
         | 
| 94 94 | 
             
                unless ssl_config.nil?
         | 
| 95 | 
            -
                  version_config = { min: ssl_config[ | 
| 95 | 
            +
                  version_config = { min: ssl_config['min'], max: ssl_config['max'] }
         | 
| 96 96 | 
             
                  @context = OpenSSL::SSL::SSLContext.new
         | 
| 97 97 | 
             
                  @context.min_version = SupportedSSLVersions::VERSIONS[version_config[:min]] unless version_config[:min].nil?
         | 
| 98 98 | 
             
                  @context.max_version = SupportedSSLVersions::VERSIONS[version_config[:max]] unless version_config[:max].nil?
         | 
| 99 | 
            -
                  @context.cert = OpenSSL::X509::Certificate.new(File.read(ssl_config[ | 
| 99 | 
            +
                  @context.cert = OpenSSL::X509::Certificate.new(File.read(ssl_config['cert_file_name']))
         | 
| 100 100 |  | 
| 101 | 
            -
                  if ssl_config[ | 
| 102 | 
            -
                    @context.key = OpenSSL::PKey::RSA.new(File.read(ssl_config[ | 
| 103 | 
            -
                  elsif ssl_config[ | 
| 104 | 
            -
                    @context.key = OpenSSL::PKey::EC.new(File.read(ssl_config[ | 
| 101 | 
            +
                  if ssl_config['key_type'] == 'RSA' || ssl_config['key_type'].nil?
         | 
| 102 | 
            +
                    @context.key = OpenSSL::PKey::RSA.new(File.read(ssl_config['key_file_name']))
         | 
| 103 | 
            +
                  elsif ssl_config['key_type'] == 'EC'
         | 
| 104 | 
            +
                    @context.key = OpenSSL::PKey::EC.new(File.read(ssl_config['key_file_name']))
         | 
| 105 105 | 
             
                  else
         | 
| 106 | 
            -
                    raise ArgumentError, "Unsupported SSL/TLS key type: #{ssl_config[ | 
| 106 | 
            +
                    raise ArgumentError, "Unsupported SSL/TLS key type: #{ssl_config['key_type']}"
         | 
| 107 107 | 
             
                  end
         | 
| 108 108 | 
             
                end
         | 
| 109 109 | 
             
                @context ||= nil
         | 
| 110 110 | 
             
              rescue IOError => e
         | 
| 111 | 
            -
                @macaw_log&.error("It was not possible to read files #{@macaw.config[ | 
| 112 | 
            -
            #{@macaw.config[ | 
| 111 | 
            +
                @macaw_log&.error("It was not possible to read files #{@macaw.config['macaw']['ssl']['cert_file_name']} and
         | 
| 112 | 
            +
            #{@macaw.config['macaw']['ssl']['key_file_name']}. Please assure the files exist and their names are correct.")
         | 
| 113 113 | 
             
                @macaw_log&.error(e.backtrace)
         | 
| 114 114 | 
             
                raise e
         | 
| 115 115 | 
             
              end
         | 
| 116 116 |  | 
| 117 117 | 
             
              def set_session
         | 
| 118 | 
            +
                return unless @macaw.session
         | 
| 119 | 
            +
             | 
| 118 120 | 
             
                @session ||= {}
         | 
| 119 | 
            -
                inv = if @macaw.config&.dig( | 
| 120 | 
            -
                        MemoryInvalidationMiddleware.new(@macaw.config[ | 
| 121 | 
            +
                inv = if @macaw.config&.dig('macaw', 'session', 'invalidation_time')
         | 
| 122 | 
            +
                        MemoryInvalidationMiddleware.new(@macaw.config['macaw']['session']['invalidation_time'])
         | 
| 121 123 | 
             
                      else
         | 
| 122 124 | 
             
                        MemoryInvalidationMiddleware.new
         | 
| 123 125 | 
             
                      end
         | 
| @@ -127,7 +129,7 @@ module ServerBase | |
| 127 129 | 
             
              def set_features
         | 
| 128 130 | 
             
                @is_shutting_down = false
         | 
| 129 131 | 
             
                set_rate_limiting
         | 
| 130 | 
            -
                set_session | 
| 132 | 
            +
                set_session
         | 
| 131 133 | 
             
                set_ssl
         | 
| 132 134 | 
             
              end
         | 
| 133 135 | 
             
            end
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: false
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 3 | 
            +
            require 'json'
         | 
| 4 4 |  | 
| 5 5 | 
             
            ##
         | 
| 6 6 | 
             
            # Module responsible for sanitizing log data
         | 
| @@ -10,7 +10,7 @@ module LogDataFilter | |
| 10 10 |  | 
| 11 11 | 
             
              def self.config
         | 
| 12 12 | 
             
                @config ||= begin
         | 
| 13 | 
            -
                  file_path =  | 
| 13 | 
            +
                  file_path = 'application.json'
         | 
| 14 14 | 
             
                  config = {
         | 
| 15 15 | 
             
                    max_length: DEFAULT_MAX_LENGTH,
         | 
| 16 16 | 
             
                    sensitive_fields: DEFAULT_SENSITIVE_FIELDS
         | 
| @@ -19,10 +19,10 @@ module LogDataFilter | |
| 19 19 | 
             
                  if File.exist?(file_path)
         | 
| 20 20 | 
             
                    json = JSON.parse(File.read(file_path))
         | 
| 21 21 |  | 
| 22 | 
            -
                    if json[ | 
| 23 | 
            -
                      log_config = json[ | 
| 24 | 
            -
                      config[:max_length] = log_config[ | 
| 25 | 
            -
                      config[:sensitive_fields] = log_config[ | 
| 22 | 
            +
                    if json['macaw'] && json['macaw']['log']
         | 
| 23 | 
            +
                      log_config = json['macaw']['log']
         | 
| 24 | 
            +
                      config[:max_length] = log_config['max_length'] if log_config['max_length']
         | 
| 25 | 
            +
                      config[:sensitive_fields] = log_config['sensitive_fields'] if log_config['sensitive_fields']
         | 
| 26 26 | 
             
                    end
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 |  | 
| @@ -31,11 +31,11 @@ module LogDataFilter | |
| 31 31 | 
             
              end
         | 
| 32 32 |  | 
| 33 33 | 
             
              def self.sanitize_for_logging(data, sensitive_fields: config[:sensitive_fields])
         | 
| 34 | 
            -
                return  | 
| 34 | 
            +
                return '' if data.nil?
         | 
| 35 35 |  | 
| 36 | 
            -
                data = data.to_s.force_encoding( | 
| 36 | 
            +
                data = data.to_s.force_encoding('UTF-8')
         | 
| 37 37 | 
             
                data = data.slice(0, config[:max_length])
         | 
| 38 | 
            -
                data = data.gsub( | 
| 38 | 
            +
                data = data.gsub('\\', '')
         | 
| 39 39 |  | 
| 40 40 | 
             
                sensitive_fields.each do |field|
         | 
| 41 41 | 
             
                  next unless data.include?(field.to_s)
         | 
| @@ -1,23 +1,23 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative  | 
| 3 | 
            +
            require_relative '../errors/endpoint_not_mapped_error'
         | 
| 4 4 |  | 
| 5 5 | 
             
            ##
         | 
| 6 6 | 
             
            # Module containing methods to filter Strings
         | 
| 7 7 | 
             
            module RequestDataFiltering
         | 
| 8 | 
            -
              VARIABLE_PATTERN = %r{:[^/]+} | 
| 8 | 
            +
              VARIABLE_PATTERN = %r{:[^/]+}
         | 
| 9 9 |  | 
| 10 10 | 
             
              ##
         | 
| 11 11 | 
             
              # Method responsible for extracting information
         | 
| 12 12 | 
             
              # provided by the client like Headers and Body
         | 
| 13 13 | 
             
              def self.parse_request_data(client, routes)
         | 
| 14 | 
            -
                path, parameters = extract_url_parameters(client.gets.gsub( | 
| 14 | 
            +
                path, parameters = extract_url_parameters(client.gets.gsub('HTTP/1.1', ''))
         | 
| 15 15 | 
             
                parameters = {} if parameters.nil?
         | 
| 16 16 |  | 
| 17 17 | 
             
                method_name = sanitize_method_name(path)
         | 
| 18 18 | 
             
                method_name = select_path(method_name, routes, parameters)
         | 
| 19 19 | 
             
                body_first_line, headers = extract_headers(client)
         | 
| 20 | 
            -
                body = extract_body(client, body_first_line, headers[ | 
| 20 | 
            +
                body = extract_body(client, body_first_line, headers['Content-Length'].to_i)
         | 
| 21 21 | 
             
                [path, method_name, headers, body, parameters]
         | 
| 22 22 | 
             
              end
         | 
| 23 23 |  | 
| @@ -26,8 +26,8 @@ module RequestDataFiltering | |
| 26 26 |  | 
| 27 27 | 
             
                selected_route = nil
         | 
| 28 28 | 
             
                routes.each do |route|
         | 
| 29 | 
            -
                  split_route = route.split( | 
| 30 | 
            -
                  split_name = method_name.split( | 
| 29 | 
            +
                  split_route = route.split('.')
         | 
| 30 | 
            +
                  split_name = method_name.split('.')
         | 
| 31 31 |  | 
| 32 32 | 
             
                  next unless split_route.length == split_name.length
         | 
| 33 33 | 
             
                  next unless match_path_with_route(split_name, split_route)
         | 
| @@ -56,15 +56,15 @@ module RequestDataFiltering | |
| 56 56 | 
             
              # Method responsible for sanitizing the method name
         | 
| 57 57 | 
             
              def self.sanitize_method_name(path)
         | 
| 58 58 | 
             
                path = extract_path(path)
         | 
| 59 | 
            -
                method_name = path.gsub( | 
| 60 | 
            -
                method_name.gsub!( | 
| 59 | 
            +
                method_name = path.gsub('/', '.').strip.downcase
         | 
| 60 | 
            +
                method_name.gsub!(' ', '')
         | 
| 61 61 | 
             
                method_name
         | 
| 62 62 | 
             
              end
         | 
| 63 63 |  | 
| 64 64 | 
             
              ##
         | 
| 65 65 | 
             
              # Method responsible for extracting the path from URI
         | 
| 66 66 | 
             
              def self.extract_path(path)
         | 
| 67 | 
            -
                path[0] ==  | 
| 67 | 
            +
                path[0] == '/' ? path[1..].gsub('/', '.') : path.gsub('/', '.')
         | 
| 68 68 | 
             
              end
         | 
| 69 69 |  | 
| 70 70 | 
             
              ##
         | 
| @@ -73,7 +73,7 @@ module RequestDataFiltering | |
| 73 73 | 
             
                header = client.gets.delete("\n").delete("\r")
         | 
| 74 74 | 
             
                headers = {}
         | 
| 75 75 | 
             
                while header.match(%r{[a-zA-Z0-9\-/*]*: [a-zA-Z0-9\-/*]})
         | 
| 76 | 
            -
                  split_header = header.split( | 
| 76 | 
            +
                  split_header = header.split(':')
         | 
| 77 77 | 
             
                  headers[split_header[0].strip] = split_header[1].strip
         | 
| 78 78 | 
             
                  header = client.gets.delete("\n").delete("\r")
         | 
| 79 79 | 
             
                end
         | 
| @@ -92,11 +92,11 @@ module RequestDataFiltering | |
| 92 92 | 
             
              def self.extract_url_parameters(http_first_line)
         | 
| 93 93 | 
             
                return http_first_line, nil unless http_first_line =~ /\?/
         | 
| 94 94 |  | 
| 95 | 
            -
                path_and_parameters = http_first_line.split( | 
| 95 | 
            +
                path_and_parameters = http_first_line.split('?', 2)
         | 
| 96 96 | 
             
                path = "#{path_and_parameters[0]} "
         | 
| 97 | 
            -
                parameters_array = path_and_parameters[1].split( | 
| 97 | 
            +
                parameters_array = path_and_parameters[1].split('&')
         | 
| 98 98 | 
             
                parameters_array.map! do |item|
         | 
| 99 | 
            -
                  split_item = item.split( | 
| 99 | 
            +
                  split_item = item.split('=')
         | 
| 100 100 | 
             
                  { sanitize_parameter_name(split_item[0]) => sanitize_parameter_value(split_item[1]) }
         | 
| 101 101 | 
             
                end
         | 
| 102 102 | 
             
                parameters = {}
         | 
| @@ -107,13 +107,13 @@ module RequestDataFiltering | |
| 107 107 | 
             
              ##
         | 
| 108 108 | 
             
              # Method responsible for sanitizing the parameter name
         | 
| 109 109 | 
             
              def self.sanitize_parameter_name(name)
         | 
| 110 | 
            -
                name.gsub(/[^\w\s]/,  | 
| 110 | 
            +
                name.gsub(/[^\w\s]/, '')
         | 
| 111 111 | 
             
              end
         | 
| 112 112 |  | 
| 113 113 | 
             
              ##
         | 
| 114 114 | 
             
              # Method responsible for sanitizing the parameter value
         | 
| 115 115 | 
             
              def self.sanitize_parameter_value(value)
         | 
| 116 | 
            -
                value.gsub(/[^\w\s]/,  | 
| 117 | 
            -
                value.gsub(/\s/,  | 
| 116 | 
            +
                value.gsub(/[^\w\s]/, '')
         | 
| 117 | 
            +
                value.gsub(/\s/, '')
         | 
| 118 118 | 
             
              end
         | 
| 119 119 | 
             
            end
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative  | 
| 3 | 
            +
            require_relative '../utils/http_status_code'
         | 
| 4 4 |  | 
| 5 5 | 
             
            ##
         | 
| 6 6 | 
             
            # Module responsible to filter and mount HTTP responses
         | 
| @@ -19,9 +19,9 @@ module ResponseDataFilter | |
| 19 19 | 
             
              end
         | 
| 20 20 |  | 
| 21 21 | 
             
              def self.mount_response_headers(headers)
         | 
| 22 | 
            -
                return  | 
| 22 | 
            +
                return '' if headers.nil?
         | 
| 23 23 |  | 
| 24 | 
            -
                response =  | 
| 24 | 
            +
                response = ''
         | 
| 25 25 | 
             
                headers.each do |key, value|
         | 
| 26 26 | 
             
                  response += "#{key}: #{value}\r\n"
         | 
| 27 27 | 
             
                end
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 3 | 
            +
            require 'prometheus/client'
         | 
| 4 | 
            +
            require 'prometheus/client/formats/text'
         | 
| 5 5 |  | 
| 6 6 | 
             
            ##
         | 
| 7 7 | 
             
            # Middleware responsible to configure prometheus
         | 
| @@ -14,20 +14,20 @@ class PrometheusMiddleware | |
| 14 14 |  | 
| 15 15 | 
             
                @request_duration_milliseconds = Prometheus::Client::Histogram.new(
         | 
| 16 16 | 
             
                  :request_duration_milliseconds,
         | 
| 17 | 
            -
                  docstring:  | 
| 17 | 
            +
                  docstring: 'The duration of each request in milliseconds',
         | 
| 18 18 | 
             
                  labels: [:endpoint],
         | 
| 19 19 | 
             
                  buckets: (100..1000).step(100).to_a + (2000..10_000).step(1000).to_a
         | 
| 20 20 | 
             
                )
         | 
| 21 21 |  | 
| 22 22 | 
             
                @request_count = Prometheus::Client::Counter.new(
         | 
| 23 23 | 
             
                  :request_count,
         | 
| 24 | 
            -
                  docstring:  | 
| 24 | 
            +
                  docstring: 'The total number of requests received',
         | 
| 25 25 | 
             
                  labels: [:endpoint]
         | 
| 26 26 | 
             
                )
         | 
| 27 27 |  | 
| 28 28 | 
             
                @response_count = Prometheus::Client::Counter.new(
         | 
| 29 29 | 
             
                  :response_count,
         | 
| 30 | 
            -
                  docstring:  | 
| 30 | 
            +
                  docstring: 'The total number of responses sent',
         | 
| 31 31 | 
             
                  labels: %i[endpoint status]
         | 
| 32 32 | 
             
                )
         | 
| 33 33 |  | 
| @@ -40,9 +40,9 @@ class PrometheusMiddleware | |
| 40 40 | 
             
              private
         | 
| 41 41 |  | 
| 42 42 | 
             
              def prometheus_endpoint(prometheus_registry, configurations, macaw)
         | 
| 43 | 
            -
                endpoint = configurations[ | 
| 43 | 
            +
                endpoint = configurations['macaw']['prometheus']['endpoint'] || '/metrics'
         | 
| 44 44 | 
             
                macaw.get(endpoint) do |_context|
         | 
| 45 | 
            -
                  [Prometheus::Client::Formats::Text.marshal(prometheus_registry), 200, {  | 
| 45 | 
            +
                  [Prometheus::Client::Formats::Text.marshal(prometheus_registry), 200, { 'Content-Type' => 'plaintext' }]
         | 
| 46 46 | 
             
                end
         | 
| 47 47 | 
             
              end
         | 
| 48 48 | 
             
            end
         | 
| @@ -7,66 +7,66 @@ module HttpStatusCode | |
| 7 7 | 
             
              ##
         | 
| 8 8 | 
             
              # Http Status Code Map
         | 
| 9 9 | 
             
              HTTP_STATUS_CODE_MAP = {
         | 
| 10 | 
            -
                100 =>  | 
| 11 | 
            -
                101 =>  | 
| 12 | 
            -
                102 =>  | 
| 13 | 
            -
                103 =>  | 
| 14 | 
            -
                200 =>  | 
| 15 | 
            -
                201 =>  | 
| 16 | 
            -
                202 =>  | 
| 17 | 
            -
                203 =>  | 
| 18 | 
            -
                204 =>  | 
| 19 | 
            -
                205 =>  | 
| 20 | 
            -
                206 =>  | 
| 21 | 
            -
                207 =>  | 
| 22 | 
            -
                208 =>  | 
| 23 | 
            -
                226 =>  | 
| 24 | 
            -
                300 =>  | 
| 25 | 
            -
                301 =>  | 
| 26 | 
            -
                302 =>  | 
| 27 | 
            -
                303 =>  | 
| 28 | 
            -
                304 =>  | 
| 29 | 
            -
                305 =>  | 
| 30 | 
            -
                307 =>  | 
| 31 | 
            -
                308 =>  | 
| 32 | 
            -
                400 =>  | 
| 33 | 
            -
                401 =>  | 
| 34 | 
            -
                402 =>  | 
| 35 | 
            -
                403 =>  | 
| 36 | 
            -
                404 =>  | 
| 37 | 
            -
                405 =>  | 
| 38 | 
            -
                406 =>  | 
| 39 | 
            -
                407 =>  | 
| 40 | 
            -
                408 =>  | 
| 41 | 
            -
                409 =>  | 
| 42 | 
            -
                410 =>  | 
| 43 | 
            -
                411 =>  | 
| 44 | 
            -
                412 =>  | 
| 45 | 
            -
                413 =>  | 
| 46 | 
            -
                414 =>  | 
| 47 | 
            -
                415 =>  | 
| 48 | 
            -
                416 =>  | 
| 49 | 
            -
                417 =>  | 
| 50 | 
            -
                421 =>  | 
| 51 | 
            -
                422 =>  | 
| 52 | 
            -
                423 =>  | 
| 53 | 
            -
                424 =>  | 
| 54 | 
            -
                425 =>  | 
| 55 | 
            -
                426 =>  | 
| 56 | 
            -
                428 =>  | 
| 57 | 
            -
                429 =>  | 
| 58 | 
            -
                431 =>  | 
| 59 | 
            -
                451 =>  | 
| 60 | 
            -
                500 =>  | 
| 61 | 
            -
                501 =>  | 
| 62 | 
            -
                502 =>  | 
| 63 | 
            -
                503 =>  | 
| 64 | 
            -
                504 =>  | 
| 65 | 
            -
                505 =>  | 
| 66 | 
            -
                506 =>  | 
| 67 | 
            -
                507 =>  | 
| 68 | 
            -
                508 =>  | 
| 69 | 
            -
                510 =>  | 
| 70 | 
            -
                511 =>  | 
| 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'
         | 
| 71 71 | 
             
              }.freeze
         | 
| 72 72 | 
             
            end
         | 
| @@ -1,13 +1,13 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require  | 
| 3 | 
            +
            require 'openssl'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module SupportedSSLVersions
         | 
| 6 6 | 
             
              VERSIONS = {
         | 
| 7 | 
            -
                 | 
| 8 | 
            -
                 | 
| 9 | 
            -
                 | 
| 10 | 
            -
                 | 
| 11 | 
            -
                 | 
| 7 | 
            +
                'SSL2' => OpenSSL::SSL::SSL2_VERSION,
         | 
| 8 | 
            +
                'SSL3' => OpenSSL::SSL::SSL3_VERSION,
         | 
| 9 | 
            +
                'TLS1.1' => OpenSSL::SSL::TLS1_1_VERSION,
         | 
| 10 | 
            +
                'TLS1.2' => OpenSSL::SSL::TLS1_2_VERSION,
         | 
| 11 | 
            +
                'TLS1.3' => OpenSSL::SSL::TLS1_3_VERSION
         | 
| 12 12 | 
             
              }.freeze
         | 
| 13 13 | 
             
            end
         | 
    
        data/lib/macaw_framework.rb
    CHANGED
    
    | @@ -1,18 +1,19 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative  | 
| 4 | 
            -
            require_relative  | 
| 5 | 
            -
            require_relative  | 
| 6 | 
            -
            require_relative  | 
| 7 | 
            -
            require_relative  | 
| 8 | 
            -
            require_relative  | 
| 9 | 
            -
            require_relative  | 
| 10 | 
            -
            require  | 
| 11 | 
            -
            require  | 
| 12 | 
            -
            require  | 
| 13 | 
            -
            require  | 
| 14 | 
            -
            require  | 
| 15 | 
            -
            require  | 
| 3 | 
            +
            require_relative 'macaw_framework/errors/endpoint_not_mapped_error'
         | 
| 4 | 
            +
            require_relative 'macaw_framework/middlewares/prometheus_middleware'
         | 
| 5 | 
            +
            require_relative 'macaw_framework/data_filters/request_data_filtering'
         | 
| 6 | 
            +
            require_relative 'macaw_framework/middlewares/memory_invalidation_middleware'
         | 
| 7 | 
            +
            require_relative 'macaw_framework/core/cron_runner'
         | 
| 8 | 
            +
            require_relative 'macaw_framework/core/thread_server'
         | 
| 9 | 
            +
            require_relative 'macaw_framework/version'
         | 
| 10 | 
            +
            require 'prometheus/client'
         | 
| 11 | 
            +
            require 'securerandom'
         | 
| 12 | 
            +
            require 'singleton'
         | 
| 13 | 
            +
            require 'pathname'
         | 
| 14 | 
            +
            require 'logger'
         | 
| 15 | 
            +
            require 'socket'
         | 
| 16 | 
            +
            require 'json'
         | 
| 16 17 |  | 
| 17 18 | 
             
            module MacawFramework
         | 
| 18 19 | 
             
              ##
         | 
| @@ -30,13 +31,7 @@ module MacawFramework | |
| 30 31 | 
             
                def initialize(custom_log: Logger.new($stdout), server: ThreadServer, dir: nil)
         | 
| 31 32 | 
             
                  apply_options(custom_log)
         | 
| 32 33 | 
             
                  create_endpoint_public_files(dir)
         | 
| 33 | 
            -
                   | 
| 34 | 
            -
                  @bind ||= "localhost"
         | 
| 35 | 
            -
                  @config ||= nil
         | 
| 36 | 
            -
                  @threads ||= 200
         | 
| 37 | 
            -
                  @endpoints_to_cache = []
         | 
| 38 | 
            -
                  @prometheus ||= nil
         | 
| 39 | 
            -
                  @prometheus_middleware ||= nil
         | 
| 34 | 
            +
                  setup_default_configs
         | 
| 40 35 | 
             
                  @server_class = server
         | 
| 41 36 | 
             
                end
         | 
| 42 37 |  | 
| @@ -45,14 +40,15 @@ module MacawFramework | |
| 45 40 | 
             
                # with the respective path.
         | 
| 46 41 | 
             
                # @param {String} path
         | 
| 47 42 | 
             
                # @param {Proc} block
         | 
| 48 | 
            -
                # @example
         | 
| 49 43 | 
             
                #
         | 
| 50 | 
            -
                #  | 
| 51 | 
            -
                # | 
| 52 | 
            -
                #    | 
| 53 | 
            -
                #  | 
| 44 | 
            +
                # @example
         | 
| 45 | 
            +
                #   macaw = MacawFramework::Macaw.new
         | 
| 46 | 
            +
                #   macaw.get("/hello") do |context|
         | 
| 47 | 
            +
                #     return "Hello World!", 200, { "Content-Type" => "text/plain" }
         | 
| 48 | 
            +
                #   end
         | 
| 49 | 
            +
                ##
         | 
| 54 50 | 
             
                def get(path, cache: [], &block)
         | 
| 55 | 
            -
                  map_new_endpoint( | 
| 51 | 
            +
                  map_new_endpoint('get', cache, path, &block)
         | 
| 56 52 | 
             
                end
         | 
| 57 53 |  | 
| 58 54 | 
             
                ##
         | 
| @@ -63,12 +59,13 @@ module MacawFramework | |
| 63 59 | 
             
                # @param {Proc} block
         | 
| 64 60 | 
             
                # @example
         | 
| 65 61 | 
             
                #
         | 
| 66 | 
            -
                # | 
| 67 | 
            -
                # | 
| 68 | 
            -
                # | 
| 69 | 
            -
                # | 
| 62 | 
            +
                #   macaw = MacawFramework::Macaw.new
         | 
| 63 | 
            +
                #   macaw.post("/hello") do |context|
         | 
| 64 | 
            +
                #     return "Hello World!", 200, { "Content-Type" => "text/plain" }
         | 
| 65 | 
            +
                #   end
         | 
| 66 | 
            +
                ##
         | 
| 70 67 | 
             
                def post(path, cache: [], &block)
         | 
| 71 | 
            -
                  map_new_endpoint( | 
| 68 | 
            +
                  map_new_endpoint('post', cache, path, &block)
         | 
| 72 69 | 
             
                end
         | 
| 73 70 |  | 
| 74 71 | 
             
                ##
         | 
| @@ -78,12 +75,13 @@ module MacawFramework | |
| 78 75 | 
             
                # @param {Proc} block
         | 
| 79 76 | 
             
                # @example
         | 
| 80 77 | 
             
                #
         | 
| 81 | 
            -
                # | 
| 82 | 
            -
                # | 
| 83 | 
            -
                # | 
| 84 | 
            -
                # | 
| 78 | 
            +
                #   macaw = MacawFramework::Macaw.new
         | 
| 79 | 
            +
                #   macaw.put("/hello") do |context|
         | 
| 80 | 
            +
                #     return "Hello World!", 200, { "Content-Type" => "text/plain" }
         | 
| 81 | 
            +
                #   end
         | 
| 82 | 
            +
                ##
         | 
| 85 83 | 
             
                def put(path, cache: [], &block)
         | 
| 86 | 
            -
                  map_new_endpoint( | 
| 84 | 
            +
                  map_new_endpoint('put', cache, path, &block)
         | 
| 87 85 | 
             
                end
         | 
| 88 86 |  | 
| 89 87 | 
             
                ##
         | 
| @@ -93,12 +91,13 @@ module MacawFramework | |
| 93 91 | 
             
                # @param {Proc} block
         | 
| 94 92 | 
             
                # @example
         | 
| 95 93 | 
             
                #
         | 
| 96 | 
            -
                # | 
| 97 | 
            -
                # | 
| 98 | 
            -
                # | 
| 99 | 
            -
                # | 
| 94 | 
            +
                #   macaw = MacawFramework::Macaw.new
         | 
| 95 | 
            +
                #   macaw.patch("/hello") do |context|
         | 
| 96 | 
            +
                #     return "Hello World!", 200, { "Content-Type" => "text/plain" }
         | 
| 97 | 
            +
                #   end
         | 
| 98 | 
            +
                ##
         | 
| 100 99 | 
             
                def patch(path, cache: [], &block)
         | 
| 101 | 
            -
                  map_new_endpoint( | 
| 100 | 
            +
                  map_new_endpoint('patch', cache, path, &block)
         | 
| 102 101 | 
             
                end
         | 
| 103 102 |  | 
| 104 103 | 
             
                ##
         | 
| @@ -108,26 +107,28 @@ module MacawFramework | |
| 108 107 | 
             
                # @param {Proc} block
         | 
| 109 108 | 
             
                # @example
         | 
| 110 109 | 
             
                #
         | 
| 111 | 
            -
                # | 
| 112 | 
            -
                # | 
| 113 | 
            -
                # | 
| 114 | 
            -
                # | 
| 110 | 
            +
                #   macaw = MacawFramework::Macaw.new
         | 
| 111 | 
            +
                #   macaw.delete("/hello") do |context|
         | 
| 112 | 
            +
                #     return "Hello World!", 200, { "Content-Type" => "text/plain" }
         | 
| 113 | 
            +
                #   end
         | 
| 114 | 
            +
                ##
         | 
| 115 115 | 
             
                def delete(path, cache: [], &block)
         | 
| 116 | 
            -
                  map_new_endpoint( | 
| 116 | 
            +
                  map_new_endpoint('delete', cache, path, &block)
         | 
| 117 117 | 
             
                end
         | 
| 118 118 |  | 
| 119 119 | 
             
                ##
         | 
| 120 | 
            -
                # Spawn and start a thread running the defined  | 
| 120 | 
            +
                # Spawn and start a thread running the defined periodic job.
         | 
| 121 121 | 
             
                # @param {Integer} interval
         | 
| 122 122 | 
             
                # @param {Integer?} start_delay
         | 
| 123 123 | 
             
                # @param {String} job_name
         | 
| 124 124 | 
             
                # @param {Proc} block
         | 
| 125 125 | 
             
                # @example
         | 
| 126 126 | 
             
                #
         | 
| 127 | 
            -
                # | 
| 128 | 
            -
                # | 
| 129 | 
            -
                # | 
| 130 | 
            -
                # | 
| 127 | 
            +
                #   macaw = MacawFramework::Macaw.new
         | 
| 128 | 
            +
                #   macaw.setup_job(interval: 60, start_delay: 60, job_name: "job 1") do
         | 
| 129 | 
            +
                #     puts "I'm a periodic job that runs every minute"
         | 
| 130 | 
            +
                #   end
         | 
| 131 | 
            +
                ##
         | 
| 131 132 | 
             
                def setup_job(interval: 60, start_delay: 0, job_name: "job_#{SecureRandom.uuid}", &block)
         | 
| 132 133 | 
             
                  @cron_runner ||= CronRunner.new(self)
         | 
| 133 134 | 
             
                  @jobs ||= []
         | 
| @@ -139,27 +140,27 @@ module MacawFramework | |
| 139 140 | 
             
                # Starts the web server
         | 
| 140 141 | 
             
                def start!
         | 
| 141 142 | 
             
                  if @macaw_log.nil?
         | 
| 142 | 
            -
                    puts( | 
| 143 | 
            +
                    puts('---------------------------------')
         | 
| 143 144 | 
             
                    puts("Starting server at port #{@port}")
         | 
| 144 145 | 
             
                    puts("Number of threads: #{@threads}")
         | 
| 145 | 
            -
                    puts( | 
| 146 | 
            +
                    puts('---------------------------------')
         | 
| 146 147 | 
             
                  else
         | 
| 147 | 
            -
                    @macaw_log.info( | 
| 148 | 
            +
                    @macaw_log.info('---------------------------------')
         | 
| 148 149 | 
             
                    @macaw_log.info("Starting server at port #{@port}")
         | 
| 149 150 | 
             
                    @macaw_log.info("Number of threads: #{@threads}")
         | 
| 150 | 
            -
                    @macaw_log.info( | 
| 151 | 
            +
                    @macaw_log.info('---------------------------------')
         | 
| 151 152 | 
             
                  end
         | 
| 152 153 | 
             
                  @server = @server_class.new(self, @endpoints_to_cache, @cache, @prometheus, @prometheus_middleware)
         | 
| 153 154 | 
             
                  server_loop(@server)
         | 
| 154 155 | 
             
                rescue Interrupt
         | 
| 155 156 | 
             
                  if @macaw_log.nil?
         | 
| 156 | 
            -
                    puts( | 
| 157 | 
            +
                    puts('Stopping server')
         | 
| 157 158 | 
             
                    @server.close
         | 
| 158 | 
            -
                    puts( | 
| 159 | 
            +
                    puts('Macaw stop flying for some seeds...')
         | 
| 159 160 | 
             
                  else
         | 
| 160 | 
            -
                    @macaw_log.info( | 
| 161 | 
            +
                    @macaw_log.info('Stopping server')
         | 
| 161 162 | 
             
                    @server.close
         | 
| 162 | 
            -
                    @macaw_log.info( | 
| 163 | 
            +
                    @macaw_log.info('Macaw stop flying for some seeds...')
         | 
| 163 164 | 
             
                  end
         | 
| 164 165 | 
             
                end
         | 
| 165 166 |  | 
| @@ -169,35 +170,63 @@ module MacawFramework | |
| 169 170 | 
             
                # you just want to keep cron jobs running, without
         | 
| 170 171 | 
             
                # mapping any HTTP endpoints.
         | 
| 171 172 | 
             
                def start_without_server!
         | 
| 172 | 
            -
                  @macaw_log.nil? ? puts( | 
| 173 | 
            +
                  @macaw_log.nil? ? puts('Application starting') : @macaw_log.info('Application starting')
         | 
| 173 174 | 
             
                  loop { sleep(3600) }
         | 
| 174 175 | 
             
                rescue Interrupt
         | 
| 175 | 
            -
                  @macaw_log.nil? ? puts( | 
| 176 | 
            +
                  @macaw_log.nil? ? puts('Macaw stop flying for some seeds.') : @macaw_log.info('Macaw stop flying for some seeds.')
         | 
| 176 177 | 
             
                end
         | 
| 177 178 |  | 
| 178 179 | 
             
                private
         | 
| 179 180 |  | 
| 181 | 
            +
                def setup_default_configs
         | 
| 182 | 
            +
                  @port ||= 8080
         | 
| 183 | 
            +
                  @bind ||= 'localhost'
         | 
| 184 | 
            +
                  @config ||= nil
         | 
| 185 | 
            +
                  @threads ||= 200
         | 
| 186 | 
            +
                  @endpoints_to_cache = []
         | 
| 187 | 
            +
                  @prometheus ||= nil
         | 
| 188 | 
            +
                  @prometheus_middleware ||= nil
         | 
| 189 | 
            +
                end
         | 
| 190 | 
            +
             | 
| 180 191 | 
             
                def apply_options(custom_log)
         | 
| 192 | 
            +
                  setup_basic_config(custom_log)
         | 
| 193 | 
            +
                  setup_session
         | 
| 194 | 
            +
                  setup_cache
         | 
| 195 | 
            +
                  setup_prometheus
         | 
| 196 | 
            +
                rescue StandardError => e
         | 
| 197 | 
            +
                  @macaw_log&.warn(e.message)
         | 
| 198 | 
            +
                end
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                def setup_cache
         | 
| 201 | 
            +
                  return if @config['macaw']['cache'].nil?
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                  @cache = MemoryInvalidationMiddleware.new(@config['macaw']['cache']['cache_invalidation'].to_i || 3_600)
         | 
| 204 | 
            +
                end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                def setup_session
         | 
| 207 | 
            +
                  @session = false
         | 
| 208 | 
            +
                  return if @config['macaw']['session'].nil?
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                  @session = true
         | 
| 211 | 
            +
                  @secure_header = @config['macaw']['session']['secure_header'] || 'X-Session-ID'
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                def setup_basic_config(custom_log)
         | 
| 181 215 | 
             
                  @routes = []
         | 
| 182 216 | 
             
                  @cached_methods = {}
         | 
| 183 217 | 
             
                  @macaw_log ||= custom_log
         | 
| 184 | 
            -
                  @config = JSON.parse(File.read( | 
| 185 | 
            -
                  @port = @config[ | 
| 186 | 
            -
                  @bind = @config[ | 
| 187 | 
            -
                  @ | 
| 188 | 
            -
             | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
                   | 
| 192 | 
            -
             | 
| 193 | 
            -
                   | 
| 194 | 
            -
             | 
| 195 | 
            -
                   | 
| 196 | 
            -
                  @prometheus = Prometheus::Client::Registry.new if @config["macaw"]["prometheus"]
         | 
| 197 | 
            -
                  @prometheus_middleware = PrometheusMiddleware.new if @config["macaw"]["prometheus"]
         | 
| 198 | 
            -
                  @prometheus_middleware.configure_prometheus(@prometheus, @config, self) if @config["macaw"]["prometheus"]
         | 
| 199 | 
            -
                rescue StandardError => e
         | 
| 200 | 
            -
                  @macaw_log&.warn(e.message)
         | 
| 218 | 
            +
                  @config = JSON.parse(File.read('application.json'))
         | 
| 219 | 
            +
                  @port = @config['macaw']['port'] || 8080
         | 
| 220 | 
            +
                  @bind = @config['macaw']['bind'] || 'localhost'
         | 
| 221 | 
            +
                  @threads = @config['macaw']['threads'] || 200
         | 
| 222 | 
            +
                end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                def setup_prometheus
         | 
| 225 | 
            +
                  return unless @config['macaw']['prometheus']
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                  @prometheus = Prometheus::Client::Registry.new
         | 
| 228 | 
            +
                  @prometheus_middleware = PrometheusMiddleware.new
         | 
| 229 | 
            +
                  @prometheus_middleware&.configure_prometheus(@prometheus, @config, self)
         | 
| 201 230 | 
             
                end
         | 
| 202 231 |  | 
| 203 232 | 
             
                def server_loop(server)
         | 
| @@ -208,10 +237,10 @@ module MacawFramework | |
| 208 237 | 
             
                  @endpoints_to_cache << "#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}" unless cache.empty?
         | 
| 209 238 | 
             
                  @cached_methods["#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}"] = cache unless cache.empty?
         | 
| 210 239 | 
             
                  path_clean = RequestDataFiltering.extract_path(path)
         | 
| 211 | 
            -
                  slash = path[0] ==  | 
| 240 | 
            +
                  slash = path[0] == '/' ? '' : '/'
         | 
| 212 241 | 
             
                  @macaw_log&.info("Defining #{prefix.upcase} endpoint at #{slash}#{path}")
         | 
| 213 242 | 
             
                  define_singleton_method("#{prefix}.#{path_clean}", block || lambda {
         | 
| 214 | 
            -
                    |context = { headers: {}, body:  | 
| 243 | 
            +
                    |context = { headers: {}, body: '', params: {} }|
         | 
| 215 244 | 
             
                  })
         | 
| 216 245 | 
             
                  @routes << "#{prefix}.#{path_clean}"
         | 
| 217 246 | 
             
                end
         | 
| @@ -219,8 +248,8 @@ module MacawFramework | |
| 219 248 | 
             
                def get_files_public_folder(dir)
         | 
| 220 249 | 
             
                  return [] if dir.nil?
         | 
| 221 250 |  | 
| 222 | 
            -
                  folder_path = Pathname.new(File.expand_path( | 
| 223 | 
            -
                  file_paths = folder_path.glob( | 
| 251 | 
            +
                  folder_path = Pathname.new(File.expand_path('public', dir))
         | 
| 252 | 
            +
                  file_paths = folder_path.glob('**/*').select(&:file?)
         | 
| 224 253 | 
             
                  file_paths.map { |path| "public/#{path.relative_path_from(folder_path)}" }
         | 
| 225 254 | 
             
                end
         | 
| 226 255 |  | 
| @@ -230,4 +259,90 @@ module MacawFramework | |
| 230 259 | 
             
                  end
         | 
| 231 260 | 
             
                end
         | 
| 232 261 | 
             
              end
         | 
| 262 | 
            +
             | 
| 263 | 
            +
              ##
         | 
| 264 | 
            +
              # This singleton class allows to manually cache
         | 
| 265 | 
            +
              # parameters and other data.
         | 
| 266 | 
            +
              class Cache
         | 
| 267 | 
            +
                include Singleton
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                attr_accessor :invalidation_frequency
         | 
| 270 | 
            +
             | 
| 271 | 
            +
                ##
         | 
| 272 | 
            +
                # Write a value to Cache memory.
         | 
| 273 | 
            +
                # Can be called statically or from an instance.
         | 
| 274 | 
            +
                # @param {String} tag
         | 
| 275 | 
            +
                # @param {Object} value
         | 
| 276 | 
            +
                # @param {Integer} expires_in Defaults to 3600.
         | 
| 277 | 
            +
                # @return nil
         | 
| 278 | 
            +
                #
         | 
| 279 | 
            +
                # @example
         | 
| 280 | 
            +
                #   MacawFramework::Cache.write("name", "Maria", expires_in: 7200)
         | 
| 281 | 
            +
                def self.write(tag, value, expires_in: 3600)
         | 
| 282 | 
            +
                  MacawFramework::Cache.instance.write(tag, value, expires_in: expires_in)
         | 
| 283 | 
            +
                end
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                ##
         | 
| 286 | 
            +
                # Write a value to Cache memory.
         | 
| 287 | 
            +
                # Can be called statically or from an instance.
         | 
| 288 | 
            +
                # @param {String} tag
         | 
| 289 | 
            +
                # @param {Object} value
         | 
| 290 | 
            +
                # @param {Integer} expires_in Defaults to 3600.
         | 
| 291 | 
            +
                # @return nil
         | 
| 292 | 
            +
                #
         | 
| 293 | 
            +
                # @example
         | 
| 294 | 
            +
                #   MacawFramework::Cache.write("name", "Maria", expires_in: 7200)
         | 
| 295 | 
            +
                def write(tag, value, expires_in: 3600)
         | 
| 296 | 
            +
                  if read(tag).nil?
         | 
| 297 | 
            +
                    @mutex.synchronize do
         | 
| 298 | 
            +
                      @cache.store(tag, { value: value, expires_in: Time.now + expires_in })
         | 
| 299 | 
            +
                    end
         | 
| 300 | 
            +
                  else
         | 
| 301 | 
            +
                    @cache[tag][:value] = value
         | 
| 302 | 
            +
                    @cache[tag][:expires_in] = Time.now + expires_in
         | 
| 303 | 
            +
                  end
         | 
| 304 | 
            +
                end
         | 
| 305 | 
            +
             | 
| 306 | 
            +
                ##
         | 
| 307 | 
            +
                # Read the value with the specified tag.
         | 
| 308 | 
            +
                # Can be called statically or from an instance.
         | 
| 309 | 
            +
                # @param {String} tag
         | 
| 310 | 
            +
                # @return {String|nil}
         | 
| 311 | 
            +
                #
         | 
| 312 | 
            +
                # @example
         | 
| 313 | 
            +
                #   MacawFramework::Cache.read("name") # Maria
         | 
| 314 | 
            +
                def self.read(tag) = MacawFramework::Cache.instance.read(tag)
         | 
| 315 | 
            +
             | 
| 316 | 
            +
                ##
         | 
| 317 | 
            +
                # Read the value with the specified tag.
         | 
| 318 | 
            +
                # Can be called statically or from an instance.
         | 
| 319 | 
            +
                # @param {String} tag
         | 
| 320 | 
            +
                # @return {String|nil}
         | 
| 321 | 
            +
                #
         | 
| 322 | 
            +
                # @example
         | 
| 323 | 
            +
                #   MacawFramework::Cache.read("name") # Maria
         | 
| 324 | 
            +
                def read(tag) = @cache.dig(tag, :value)
         | 
| 325 | 
            +
             | 
| 326 | 
            +
                private
         | 
| 327 | 
            +
             | 
| 328 | 
            +
                def initialize
         | 
| 329 | 
            +
                  @cache = {}
         | 
| 330 | 
            +
                  @mutex = Mutex.new
         | 
| 331 | 
            +
                  @invalidation_frequency = 60
         | 
| 332 | 
            +
                  invalidate_cache
         | 
| 333 | 
            +
                end
         | 
| 334 | 
            +
             | 
| 335 | 
            +
                def invalidate_cache
         | 
| 336 | 
            +
                  @invalidator = Thread.new(&method(:invalidation_process))
         | 
| 337 | 
            +
                end
         | 
| 338 | 
            +
             | 
| 339 | 
            +
                def invalidation_process
         | 
| 340 | 
            +
                  loop do
         | 
| 341 | 
            +
                    sleep @invalidation_frequency
         | 
| 342 | 
            +
                    @mutex.synchronize do
         | 
| 343 | 
            +
                      @cache.delete_if { |_, v| v[:expires_in] < Time.now }
         | 
| 344 | 
            +
                    end
         | 
| 345 | 
            +
                  end
         | 
| 346 | 
            +
                end
         | 
| 347 | 
            +
              end
         | 
| 233 348 | 
             
            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: 1.3. | 
| 4 | 
            +
              version: 1.3.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Aria Diniz
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024- | 
| 11 | 
            +
            date: 2024-09-07 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: prometheus-client
         | 
| @@ -84,7 +84,7 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 84 84 | 
             
              requirements:
         | 
| 85 85 | 
             
              - - ">="
         | 
| 86 86 | 
             
                - !ruby/object:Gem::Version
         | 
| 87 | 
            -
                  version:  | 
| 87 | 
            +
                  version: 3.0.0
         | 
| 88 88 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 89 89 | 
             
              requirements:
         | 
| 90 90 | 
             
              - - ">="
         |