Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add versioning, cache, and base server #1

Merged
merged 1 commit into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,24 @@ Metrics/MethodLength:
Enabled: false

Metrics/ClassLength:
Max: 150
Exclude:
- "test/**/*"

Metrics/ModuleLength:
Max: 150
Exclude:
- "test/**/*"

Metrics/PerceivedComplexity:
Max: 10

Style/AsciiComments:
Enabled: false

Style/AccessModifierDeclarations:
Enabled: false

Style/Documentation:
Enabled: false

Expand Down
22 changes: 20 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,29 @@ source 'https://rubygems.org'
# Specify your gem's dependencies in lennarb.gemspec
gemspec

# [https://rubygems.org/gems/puma]
# Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for
# Ruby/Rack applications. Puma is intended for use in both development and
# production environments. In order to get the best throughput, it is highly
# recommended that you use a Ruby implementation with real threads like Rubinius
# or JRuby.
gem 'puma', '~> 6.4'
# [https://rubygems.org/gems/rack]
# Rack provides a minimal, modular, and adaptable interface for developing web
# applications in Ruby. By wrapping HTTP requests and responses in the simplest
# way possible, it unifies and distills the API for web servers, web frameworks,
# and software in between (the so-called middleware) into a single method call.
gem 'rack', '~> 3.0', '>= 3.0.8'
# [https://rubygems.org/gems/colorize]
# Colorize is a Ruby gem used to color text in terminals.
gem 'colorize', '~> 1.1'

group :development, :test do
gem 'debug'
# [https://rubygems.org/gems/rake]
# Rake is a Make-like program implemented in Ruby. Tasks and dependencies are
# specified in standard Ruby syntax.
gem 'rake', '~> 13.0'
gem 'rake', '~> 13.0', '>= 13.0.6'
# [https://rubygems.org/gems/rubocop]
# Automatic Ruby code style checking tool. Aims to enforce the
# community-driven Ruby Style Guide.
Expand All @@ -21,5 +39,5 @@ group :development, :test do
# [https://rubygems.org/gems/minitest]
# Minitest provides a complete suite of testing facilities supporting TDD,
# BDD, mocking, and benchmarking.
gem 'minitest', '~> 5.0'
gem 'minitest', '~> 5.20'
end
32 changes: 29 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,44 @@ PATH
remote: .
specs:
lennarb (0.1.0)
colorize (~> 1.1)
puma (~> 6.4)
rack (~> 3.0, >= 3.0.8)
rake (~> 13.0, >= 13.0.6)

GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
colorize (1.1.0)
debug (1.8.0)
irb (>= 1.5.0)
reline (>= 0.3.1)
io-console (0.6.0)
irb (1.9.0)
rdoc
reline (>= 0.3.8)
json (2.6.3)
language_server-protocol (3.17.0.3)
minitest (5.20.0)
nio4r (2.5.9)
parallel (1.23.0)
parser (3.2.2.4)
ast (~> 2.4.1)
racc
psych (5.1.1.1)
stringio
puma (6.4.0)
nio4r (~> 2.0)
racc (1.7.3)
rack (3.0.8)
rainbow (3.1.1)
rake (13.1.0)
rdoc (6.6.0)
psych (>= 4.0.0)
regexp_parser (2.8.2)
reline (0.4.0)
io-console (~> 0.5)
rexml (3.2.6)
rubocop (1.57.2)
json (~> 2.3)
Expand All @@ -35,16 +57,20 @@ GEM
rubocop-minitest (0.33.0)
rubocop (>= 1.39, < 2.0)
ruby-progressbar (1.13.0)
stringio (3.0.9)
unicode-display_width (2.5.0)

PLATFORMS
x86_64-linux

DEPENDENCIES
bundler
colorize (~> 1.1)
debug
lennarb!
minitest (~> 5.0)
rake (~> 13.0)
minitest (~> 5.20)
puma (~> 6.4)
rack (~> 3.0, >= 3.0.8)
rake (~> 13.0, >= 13.0.6)
rubocop (~> 1.57, >= 1.57.2)
rubocop-minitest (~> 0.33.0)

Expand Down
24 changes: 24 additions & 0 deletions LICENCE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
The MIT License (MIT)

Copyright (c) 2023, Aristóteles Coutinho costa

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
22 changes: 13 additions & 9 deletions lennarb.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,31 @@ require_relative 'lib/lennarb/version'
Gem::Specification.new do |spec|
spec.name = 'lennarb'
spec.version = Lennarb::VERSION
spec.license = 'MIT'
spec.authors = ['Aristóteles Coutinho']
spec.email = ['[email protected]']
spec.summary = "A modular web framework for Ruby."
spec.summary = 'A lightweight and experimental web framework for Ruby.'
spec.description = <<~EOF
Lenna is a lightweight, fast and easy framework for building modular
web applications and APIS with Ruby. Also, that's how I affectionately call my wife.
Lenna is a lightweight and experimental web framework for Ruby. It's designed to be modular and easy to use. Also, that's how I affectionately call my wife.
EOF
spec.homepage = 'https://rubygems.org/gems/lennarb'
spec.required_ruby_version = '>= 2.7.0'
spec.required_ruby_version = '>= 3.2.0'
spec.metadata['allowed_push_host'] = 'https://rubygems.org/gems/lennarb'
spec.metadata['homepage_uri'] = spec.homepage
spec.metadata['source_code_uri'] = 'https://github.com/aristotelesbr/lennarb'
spec.metadata['changelog_uri'] = 'https://github.com/aristotelesbr/lennarb/blob/master/CHANGELOG.md'
spec.files = Dir['lib/**/*'] + %w[MIT-LICENSE README.md SPEC.rdoc]
spec.extra_rdoc_files = ['README.md', 'CHANGELOG', 'CONTRIBUTING.md']
spec.files = Dir['lib/**/*']
spec.extra_rdoc_files = %w[README.md LICENCE CHANGELOG.md]
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']

spec.add_dependency 'puma', '~> 6.4'
spec.add_dependency 'rack', '~> 3.0', '>= 3.0.8'
spec.add_dependency 'rake', '~> 13.0', '>= 13.0.6'
spec.add_dependency 'colorize', '~> 1.1'
# Uncomment to register a new dependency of your gem
spec.add_development_dependency 'minitest', "~> 5.20"
spec.add_development_dependency 'bundler'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'minitest', '~> 5.20'
spec.add_development_dependency 'rake', '~> 13.0', '>= 13.0.6'
spec.metadata['rubygems_mfa_required'] = 'true'
end
52 changes: 52 additions & 0 deletions lib/lenna/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

# External dependencies
require 'puma'

# Internal dependencies
require_relative 'middleware/default/error_handler'
require_relative 'middleware/default/logging'
require_relative 'router'

module Lenna
# The base class is used to start the server.
class Base < Router
DEFAULT_PORT = 3000
private_constant :DEFAULT_PORT
DEFAULT_HOST = 'localhost'
private_constant :DEFAULT_HOST

# This method will start the server.
#
# @param port [Integer] The port to listen on (default: 3000)
# @param host [String] The host to listen on (default: '
# @return [void]
#
# @example
# app = Lenna::Base.new
# app.listen(8080)
# # => ⚡ Listening on localhost:8080
#
# or specify the host and port
#
# app = Lenna::Base.new
# app.listen(8000, host: '0.0.0.0')
# # => ⚡ Listening on 0.0.0.0:8000
#
# @api public
#
# @todo: Add Lenna::Server to handle the server logic
#
# @since 0.1.0
def listen(port = DEFAULT_PORT, host: DEFAULT_HOST, **)
puts "⚡ Listening on #{host}:#{port}"

# Add the logging middleware to the stack
use(Middleware::Default::Logging, Middleware::Default::ErrorHandler)

server = ::Puma::Server.new(self, **)
server.add_tcp_listener(host, port)
server.run.join
end
end
end
103 changes: 103 additions & 0 deletions lib/lenna/middleware/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# frozen_string_literal: true

module Lenna
module Middleware
# The MiddlewareManager class is responsible for managing the middlewares.
#
# @attr mutex [Mutex] the mutex used to synchronize the
# access to the global middlewares and
# the middleware chains cache.
# @attr global_middlewares [Array] the global middlewares
# @attr middleware_chains_cache [Hash] the middleware chains cache
#
# @note Middleware chains are cached by action.
# The middlewares that are added to a specific route are added to the
# global middlewares.
#
# @api private
#
# @since 0.1.0
class App
# @return [Mutex] the mutex used to synchronize the access to the global
attr_reader :global_middlewares

# @return [Hash] the middleware chains cache
attr_reader :middleware_chains_cache

# This method will initialize the global middlewares and the
# middleware chains cache.
#
# @return [void]
#
# @since 0.1.0
def initialize
@mutex = ::Mutex.new
@global_middlewares = []
@middleware_chains_cache = {}
end

# This method is used to add a middleware to the global middlewares.
# @param middlewares [Array] the middlewares to be used
# @return [void]
#
# @since 0.1.0
def use(middlewares)
@mutex.synchronize do
@global_middlewares += Array(middlewares)
@middleware_chains_cache = {}
end
end

# This method is used to fetch or build the middleware chain for the given
# action and route middlewares.
#
# @param action [Proc] the action to be executed
# @param route_middlewares [Array] the middlewares to be used
# @return [Proc] the middleware chain
#
# @see #build_middleware_chain
#
# @since 0.1.0
def fetch_or_build_middleware_chain(action, route_middlewares)
middleware_signature = action.object_id.to_s

@mutex.synchronize do
@middleware_chains_cache[middleware_signature] ||=
build_middleware_chain(action, route_middlewares)
end
end

# This method is used to build the middleware chain for the given action
# and middlewares.
#
# @param action [Proc] the action to be executed
# @param middlewares [Array] the middlewares to be used
# @return [Proc] the middleware chain
#
# @since 0.1.0
#
# @example Given the action:
# `->(req, res) { res << 'Hello' }` and the
# middlewares [mw1, mw2], the middleware
# chain will be:
# mw1 -> mw2 -> action
# The action will be the last middleware in the
# chain.
def build_middleware_chain(action, middlewares)
all_middlewares = @global_middlewares + Array(middlewares)

all_middlewares.reverse.reduce(action) do |next_middleware, middleware|
->(req, res) {
middleware.call(
req,
res,
-> {
next_middleware.call(req, res)
}
)
}
end
end
end
end
end
Loading