@GuirecCorbel
Décrire le chemin fait par une requête de sa réception par l'application jusqu'au retour de la réponse
Interface pour développer des applications web en Ruby
Intercepte les requêtes HTTP et retourne un résultat
class ParisRb
def call(env)
[200, {}, ["Bonjour ParisRb"]]
end
end
run ParisRb.new
➜ parisrb rackup
Thin web server (v1.6.3 codename Protein Powder)
Maximum connections set to 1024
Listening on localhost:9292, CTRL+C to stop
➜ parisrb curl http://localhost:9292/
Bonjour ParisRb%
{"SERVER_SOFTWARE"=>"thin 1.6.3 codename Protein Powder",
"SERVER_NAME"=>"localhost",
"rack.input"=>#<Rack::Lint::InputWrapper:0x007f0c3fa46918 @input=#<StringIO:0x007f0c3fb9a2b0>>,
"rack.version"=>[1, 0],
"rack.errors"=>#<Rack::Lint::ErrorWrapper:0x007f0c3fa46710 @error=#<IO:<STDERR>>>,
"rack.multithread"=>false,
"rack.multiprocess"=>false,
"rack.run_once"=>false,
"REQUEST_METHOD"=>"GET",
"REQUEST_PATH"=>"/",
"PATH_INFO"=>"/",
"REQUEST_URI"=>"/",
"HTTP_VERSION"=>"HTTP/1.1",
"HTTP_USER_AGENT"=>"curl/7.35.0",
"HTTP_HOST"=>"localhost:9292",
"HTTP_ACCEPT"=>"*/*",
"GATEWAY_INTERFACE"=>"CGI/1.2",
"SERVER_PORT"=>"9292",
"QUERY_STRING"=>"",
"SERVER_PROTOCOL"=>"HTTP/1.1",
"rack.url_scheme"=>"http",
"SCRIPT_NAME"=>"",
"REMOTE_ADDR"=>"127.0.0.1",
"async.callback"=>#<Method: Thin::Connection#post_process>,
"async.close"=>#<EventMachine::DefaultDeferrable:0x007f0c3fad4ad8>,
"rack.tempfiles"=>[]}
class ParisRb
def call(env)
if env["REQUEST_PATH"] == "/bonsoir"
[200, {}, ["Bonsoir ParisRb"]]
elsif env["REQUEST_PATH"] == "/bonjour"
[200, {}, ["Bonjour ParisRb"]]
else
[404, {}, ["Not Found"]]
end
end
end
run ParisRb.new
➜ parisrb curl http://localhost:9292/bonsoir
Bonsoir ParisRb%
➜ parisrb curl http://localhost:9292/bonjour
Bonjour ParisRb%
➜ parisrb curl http://localhost:9292/salut
Not Found%
#config.ru
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
run Rails.application
➜ parisrb rackup
[2015-06-07 03:08:29] INFO WEBrick 1.3.1
[2015-06-07 03:08:29] INFO ruby 2.1.5 (2014-11-13) [x86_64-linux]
[2015-06-07 03:08:29] INFO WEBrick::HTTPServer#start: pid=7646 port=9292
127.0.0.1 - - [07/Jun/2015:03:08:31 -0400] "GET / HTTP/1.1" 200 - 0.2457
127.0.0.1 - - [07/Jun/2015:03:08:39 -0400] "GET /products HTTP/1.1" 200 - 0.7318
Besoin d'un application simple ?
Utilisez Sinatra plutôt que Rack
require 'benchmark'
class TimeMiddleware
def initialize(app)
@app = app
end
def call(env)
puts Benchmark.measure { @response = @app.call(env) }
@response
end
end
use Rack::Sendfile
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007f2f84717ba8>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use WebConsole::Middleware
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run Parisrb::Application.routes
module Parisrb
class Application < Rails::Application
# ...
end
end
# railties/lib/rails/application.rb
module Rails
class Application < Engine
# ...
end
end
# railties/lib/rails/engine.rb
module Rails
class Engine < Railtie
# ...
def routes
@routes ||= ActionDispatch::Routing::RouteSet.new
@routes.append(&Proc.new) if block_given?
@routes
end
# ...
end
end
Rails.application.routes.draw do
resources :products
end
# actionpack/lib/action_dispatch/routing/route_set.rb
def draw(&block)
clear! unless @disable_clear_and_finalize
eval_block(block)
finalize! unless @disable_clear_and_finalize
nil
end
def eval_block(block)
if block.arity == 1
raise "You are using the old router DSL which has been removed in Rails 3.1. " <<
"Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
end
mapper = Mapper.new(self)
if default_scope
mapper.with_default_scope(default_scope, &block)
else
mapper.instance_exec(&block)
end
end
#actionpack/lib/action_dispatch/routing/mapper.rb
def resource(*resources, &block)
# ...
resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
# ...
set_member_mappings_for_resource
end
self
end
def set_member_mappings_for_resource
member do
get :edit if parent_resource.actions.include?(:edit)
get :show if parent_resource.actions.include?(:show)
if parent_resource.actions.include?(:update)
patch :update
put :update
end
delete :destroy if parent_resource.actions.include?(:destroy)
end
end
Bref :
#resources = [#get, #post, #delete, #patch, #put]
#actionpack/lib/action_dispatch/routing/mapper.rb
def get(*args, &block)
map_method(:get, args, &block)
end
def map_method(method, args, &block)
options = args.extract_options!
options[:via] = method
match(*args, options, &block)
self
end
Bref :
[#get, #post, #delete, #patch, #put] = #match
match 'products', to: 'products#index', via: :get
match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
match 'photos/:id', to: PeoductRackApp, via: :get
# Yes, controller actions are just rack endpoints
match 'photos/:id', to: ProductsController.action(:show), via: :get
def match(path, *rest)
# ... manipule les options
paths.each do |_path|
# ... continue de manipuler les options
decomposed_match(_path, route_options)
end
end
def decomposed_match(path, options) # :nodoc:
if on = options.delete(:on)
send(on) { decomposed_match(path, options) }
else
case @scope.scope_level
when :resources
nested { decomposed_match(path, options) }
when :resource
member { decomposed_match(path, options) }
else
add_route(path, options)
end
end
end
def add_route(action, options) # :nodoc:
# ... manipule les options
mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
# app.class == ActionDispatch::Routing::RouteSet::Dispatcher
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
# actionpack/lib/action_dispatch/journey/routes.rb
# Add a route to the routing table.
def add_route(app, path, conditions, defaults, name = nil)
route = Route.new(name, app, path, conditions, defaults)
route.precedence = routes.length
routes << route
named_routes[name] = route if name && !named_routes[name]
clear_cache!
route
end
require ::File.expand_path('../config/environment', __FILE__)
run ProductsController.action(:index)
module Parisrb
class Application < Rails::Application
# ...
end
end
# railties/lib/rails/application.rb
module Rails
class Application < Engine
# ...
end
end
# railties/lib/rails/engine.rb
module Rails
class Engine < Railtie
# ...
def routes
@routes ||= ActionDispatch::Routing::RouteSet.new
@routes.append(&Proc.new) if block_given?
@routes
end
# ...
end
end
# actionpack/lib/action_dispatch/routing/route_set.rb
def call(env)
req = request_class.new(env)
req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
@router.serve(req)
end
# actionpack/lib/action_dispatch/journey/router.rb
def serve
find_routes(req).each do |match, parameters, route|
# ... manipule la requête
status, headers, body = route.app.serve(req)
# ... manipule la requête
return [status, headers, body]
end
return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
end
# actionpack/lib/action_dispatch/journey/router.rb
def find_routes req
routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
r.path.match(req.path_info)
}
# ... renseigne les informations sur le chemin et les paramêtres
end
# actionpack/lib/action_dispatch/journey/routes.rb
module ActionDispatch
module Journey # :nodoc:
# The Routing table. Contains all routes for a system. Routes can be
# added to the table by calling Routes#add_route.
class Routes # :nodoc:
include Enumerable
# ...
end
end
end
# actionpack/lib/action_dispatch/journey/router.rb
def serve
find_routes(req).each do |match, parameters, route|
# ... manipule la requête
status, headers, body = route.app.serve(req)
# ... manipule la requête
return [status, headers, body]
end
return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
end
module ActionDispatch
module Routing
class RouteSet #:nodoc:
class Dispatcher < Routing::Endpoint #:nodoc:
def serve(req)
req.check_path_parameters!
params = req.path_parameters
prepare_params!(params)
# Just raise undefined constant errors if a controller was specified as default.
unless controller = controller(params, @defaults.key?(:controller))
return [404, {'X-Cascade' => 'pass'}, []]
end
dispatch(controller, params[:action], req.env)
end
def dispatch(controller, action, env)
controller.action(action).call(env)
end
end
end
end
end
#app/controller/products_controller.rb
class ProductsController < ApplicationController
def index
@products = Product.all
end
end
#app/views/products/index.html.erb
<p id="notice"><%= notice %></p>
<h1>Listing Products</h1>
<table>
<thead>
<tr>
<th>Title</th>
<th>Price</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @products.each do |product| %>
<tr>
<td><%= product.title %></td>
<td><%= product.price %></td>
<td><%= link_to 'Show', product %></td>
<td><%= link_to 'Edit', edit_product_path(product) %></td>
<td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Product', new_product_path %>
#app/controller/products_controller.rb
class ProductsController < ApplicationController
def index
@products = Product.all
render
end
end
#actionpack/lib/action_controller/metal/implicit_render.rb
def send_action(method, *args)
ret = super
default_render unless performed?
ret
end
def default_render(*args)
render(*args)
end
#app/controller/products_controller.rb
class ProductsController < ApplicationController
def index
@products = Product.all
render
render
end
end
#actionpack/lib/action_controller/metal/rendering.rb
def render(*args) #:nodoc:
raise ::AbstractController::DoubleRenderError if self.response_body
super
end
#actionpack/lib/abstract_controller/rendering.rb
def render(*args, &block)
options = _normalize_render(*args, &block)
# => {:prefixes=>["products", "application"], :template=>"index",
:layout=>#<Proc:0x007fca6ca8dcc8@/home/dougui/workspace/rails/rails/actionview/lib/action_view/layouts.rb:386>
self.response_body = render_to_body(options)
# Cherche le bon système de template selon l'extension du fichier et
# exécute celui par défaut si rien n'est trouvé
_process_format(rendered_format, options) if rendered_format
self.response_body
end
@message = 'Bonjour ParisRb'
ERB.new(File.new("app/views/products/index.html.erb").read).result
# => "Bonjour ParisRb\n"
#app/views/products/index.html.erb
<%= @message %>
ERB (Embedded Ruby) : Système de template
@message = 'Bonjour ParisRb'
ERB.new("<%= @message %>").result
# => "Bonjour ParisRb\n"
#app/controllers/products_controller.rb
def index
@message = 'Bonjour ParisRb'
end
#app/views/products/index.html.erb
<%= self.class == controller.class %> # => false
<%= @message %> # => 'Bonjour ParisRb'
?????
def view_context_class
@view_context_class ||= begin
supports_path = supports_path?
routes = respond_to?(:_routes) && _routes
helpers = respond_to?(:_helpers) && _helpers
Class.new(ActionView::Base) do
if routes
include routes.url_helpers(supports_path)
include routes.mounted_helpers
end
if helpers
include helpers
end
end
end
end
#actionview/lib/action_view/base.rb
def assign(new_assigns) # :nodoc:
@_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
end