Skip to content
Open
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
32 changes: 32 additions & 0 deletions app/controllers/tests_controller.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,43 @@
require 'pry'
class TestsController < Simpler::Controller

# HTML (шаблон). GET /tests
def index
@time = Time.now
end

# curl.exe -X POST http://localhost:9292/tests
def create
headers['Content-Type'] = 'text/plain'
headers['X-Custom-Header'] = 'SimplerFramework'
status 201 # В логах будет статус 201, если не указать status то будет дефольтный 200-й
render plain: "Test created successfully!"
end

# Для проверки plain. GET /tests/plain
def plain
headers['Content-Type'] = 'text/plain; charset=utf-8'
render plain: "Plain text response"
end

# Для проверки json. GET /tests/json
def json
render json: { message: "Hello from JSON", data: [1, 2, 3], time: Time.now }
end

def server_error
status 500 # В логах будет статус 500
render json: { error: "Internal server error", code: 500 }
end

def show
test_id = params[:id]
@test = Test.all.find {|t| t.id == test_id }

if @test
render json: {message: "Test with id #{test_id} is found! #{@test.title} - #{@test.level} "}
else
render json: {error: "Test with id #{test_id} is not found"}
end
end
end
2 changes: 2 additions & 0 deletions config.ru
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require_relative 'config/environment'
require_relative 'lib/simpler/middleware/logger'

use Simpler::Middleware::Logger
run Simpler.application
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
Simpler.application.routes do
get '/tests', 'tests#index'
post '/tests', 'tests#create'
get '/tests/plain', 'tests#plain'
get '/tests/json', 'tests#json'
get '/tests/server_error', 'tests#server_error'
get '/tests/:id', 'tests#show'
end
3 changes: 1 addition & 2 deletions lib/simpler.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
require 'pathname'
require_relative 'simpler/application'
require_relative 'simpler/response_renderer'

module Simpler

class << self
def application
Application.instance
Expand All @@ -12,5 +12,4 @@ def root
Pathname.new(File.expand_path('..', __dir__))
end
end

end
4 changes: 4 additions & 0 deletions lib/simpler/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ def routes(&block)

def call(env)
route = @router.route_for(env)
# Если маршрут не найден то выдаём клиенту ошибку 404
unless route
return [404, {'Content-Type' => 'text/plain'}, [' 404 Not Found']]
end
controller = route.controller.new(env)
action = route.action

Expand Down
61 changes: 41 additions & 20 deletions lib/simpler/controller.rb
Original file line number Diff line number Diff line change
@@ -1,54 +1,75 @@
require_relative 'view'
require_relative 'response_renderer'

module Simpler
class Controller

attr_reader :name, :request, :response

def initialize(env)
@name = extract_name
@request = Rack::Request.new(env)
@response = Rack::Response.new
@env = env
@render_options = nil
end

def make_response(action)
@request.env['simpler.controller'] = self
@request.env['simpler.action'] = action

set_default_headers
@env['simpler.controller'] = self
@env['simpler.action'] = action
@env['simpler.controller_binding'] = binding
send(action)
write_response

@response.finish
end

def status(code)
@response.status = code
end

def headers
@response.headers
end

private

def extract_name
self.class.name.match('(?<name>.+)Controller')[:name].downcase
end

def set_default_headers
@response['Content-Type'] = 'text/html'
end

def write_response
body = render_body

def write_response
renderer = ResponseRenderer.new(@response, @env)

if @render_options
body = renderer.render(@render_options)
else
# Например, если в action-е index не указан render json: или render plain:
# то автоматический рендерится views/users/index.html.erb. Поэтому передаем nil
body = renderer.render(nil)
end

@response.write(body)
end

def render_body
View.new(@request.env).render(binding)
end

def params
@request.params
request_params = @request.params || {}
route_params = @env['simpler.route_params'] || {}

converted_route_params = route_params.transform_values do |value|
if value.is_a?(String) && value.match?(/^\d+$/)
# Переводим в integer, т.к Sequel ожидает integer
# Если не перевести то получим Sequel::Error: Invalid filter expression: "1"
value.to_i
else
value
end
end

request_params.merge(converted_route_params)
end

def render(template)
@request.env['simpler.template'] = template
def render(options)
@render_options = options
end

end
end
72 changes: 72 additions & 0 deletions lib/simpler/middleware/logger.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require 'logger'
require 'fileutils'

module Simpler
module Middleware
class Logger
LOG_FILE = 'log/app.log'

def initialize(app)
@app = app
ensure_log_directory
@logger = setup_logger
end

def call(env)
status, headers, body = @app.call(env)

log_request(env)
log_response(status, headers, env)

[status, headers, body]
end

private

def ensure_log_directory
FileUtils.mkdir_p(File.dirname(LOG_FILE))
end

def setup_logger
::Logger.new(LOG_FILE)
end

def log_request(env)
request = Rack::Request.new(env)
controller = env['simpler.controller']
action = env['simpler.action']

url = build_full_url(env)
handler = controller && action ? "#{controller.class.name}##{action}" : 'Unknown'
parameters = collect_parameters(request, env)

@logger.info("Request: #{env['REQUEST_METHOD']} #{url}")
@logger.info("Handler: #{handler}")
@logger.info("Parameters: #{parameters}")
end

def log_response(status, headers, env)
status_text = Rack::Utils::HTTP_STATUS_CODES[status] || 'Unknown'
content_type = headers['Content-Type'] || 'unknown'
template = env['simpler.template'] || ''
template_part = template.empty? ? '' : " #{template}"

@logger.info("Response: #{status} #{status_text} [#{content_type}]#{template_part}")
end

def build_full_url(env)
path = env['PATH_INFO']
query = env['QUERY_STRING']
query.empty? ? path : "#{path}?#{query}"
end

def collect_parameters(request, env)
params = {}
params.merge!(request.GET)
params.merge!(request.POST) if request.post?
params.merge!(env['simpler.route_params'] || {})
params
end
end
end
end
64 changes: 64 additions & 0 deletions lib/simpler/response_renderer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require 'json'

module Simpler
class ResponseRenderer
CONTENT_TYPES = {
html: 'text/html',
plain: 'text/plain',
json: 'application/json'
}.freeze

def initialize(response, env)
@response = response
@env = env
end

def render(render_options)
format = detect_format(render_options)
set_content_type(format)
generate_body(format, render_options)
end

private

def detect_format(options)
return :plain if options.is_a?(Hash) && options.key?(:plain)
return :json if options.is_a?(Hash) && options.key?(:json)
:html # по умалчанию, при nil
end

def set_content_type(format)
@response['Content-Type'] = CONTENT_TYPES[format]
end

def generate_body(format, options)
case format
when :plain
options[:plain].to_s
when :json
JSON.generate(options[:json])
when :html
render_template(options)
end
end

def render_template(options)
template = extract_template(options)
@env['simpler.template'] = template if template

# Создаем и рендерим view только для HTML
View.new(@env).render(@env['simpler.controller_binding'])
end

def extract_template(options)
case options
when String, Symbol
options.to_s
when Hash
options[:template]
else
nil
end
end
end
end
12 changes: 9 additions & 3 deletions lib/simpler/router.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ def post(path, route_point)
end

def route_for(env)
method = env['REQUEST_METHOD'].downcase.to_sym
path = env['PATH_INFO']

@routes.find { |route| route.match?(method, path) }
method = env['REQUEST_METHOD'].downcase.to_sym
route = @routes.find { |route| route.match?(method, path) } # Найдем подходящий маршрут

if route
params = route.extract_params(path) # Извлекаем параметры из найденного маршрута
env['simpler.route_params'] = params
end

route
end

private
Expand Down
25 changes: 22 additions & 3 deletions lib/simpler/router/route.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Simpler
class Router
class Route

attr_reader :controller, :action
attr_reader :method, :path, :controller, :action

def initialize(method, path, controller, action)
@method = method
Expand All @@ -11,10 +11,29 @@ def initialize(method, path, controller, action)
@action = action
end

def match?(method, path)
@method == method && path.match(@path)
def match?(method, path) # Проверяем совпадение маршрута
return false unless @method == method

if @path.include?(':id')
pattern = @path.gsub(':id', '(\d+)')
path.match?(/^#{pattern}$/)
else
@path == path
end
end

def extract_params(path) # Извлекаем параметры из маршрута
return {} unless @path.include?(':id')

pattern = @path.gsub(':id', '(\d+)')
match = path.match(/^#{pattern}$/)

if match
{ id: match[1] }
else
{}
end
end
end
end
end
Loading