Ten przewodnik zakłada praktyczną znajomość założeń frameworka Rack i związanych z nim pojęć takich jak obiekty middleware, mapowanie url (url maps), czy obiekt Rack::Builder.
1 Wprowadzenie do Rack
Rack dostarcza minimalnego, modułowego i adaptowalnego interfejsu do rozwijania aplikacji webowych w Rubym. Poprzez żądania i odpowiedzi HTTP w najprostszej możliwej postaci unifikuje i łączy w sobie API dla serwerów WWW, frameworków webowych i oprogramowania pośredniczącego (tzw. middleware software) w jedną metodę zapytania.
Note: Interfejs serwera webowego umożliwia komunikację (ang. interfacing) aplikacji webowej z serwerem WWW, inną aplikacją lub frameworkiem webowym. Przyjęło się mylne utożsamianie interfejsu z węższym pojęciem interfejsu użytkownika (komunikacja aplikacja-użytkownik). -przypis tłumacza
Wyjaśnienie czym dokładnie jest Rack nie jest zadaniem tego przewodnika. Jeśli nie jest ci on jeszcze znany, sięgnij po któreś z tych źródeł :
- Oficjalna strona projektu
- Introducing Rack
- Ruby on Rack #1 – Hello Rack!
- Ruby on Rack #2 – The Builder
2 Rails on Rack
2.1 Obiekty Rack w Aplikacjach Railsowych
ActionController::Dispatcher.new to podstawowy obiekt aplikacji Rack występujący w aplikacjach Railsowych. Każdy z serwerów WWW zgodnych z Rack powinien posłużyć się tym obiektem, aby obsłużyć aplikację Railsową.
2.2 script/server
script/server stanowi Railsowy odpowiednik skryptu Rack rackup. Jego zadaniem jest stworzenie obiektu Rack::Builder i uruchomienie serwer WWW.
Oto jak script/server tworzy instancję Rack::Builder:
app = Rack::Builder.new {
use Rails::Rack::LogTailer unless options[:detach]
use Rails::Rack::Debugger if options[:debugger]
use ActionDispatch::Static
run ActionController::Dispatcher.new
}.to_app
Obiekty middleware zastosowane w powyższym kodzie przydają się praktycznie tylko w środowisku rozwojowym. Poniższa tabela opisuje ich użycie:
Middleware | Zastosowanie |
---|---|
Rails::Rack::LogTailer | Dołącza zawartość logu do konsoli |
ActionDispatch::Static | Zapisuje statystyki serwera w katalogu RAILS_ROOT/public |
Rails::Rack::Debugger | Uruchamia Debuggera |
2.3 rackup
Aby stosować skrypt rackup zamiast Railsowego script/server, możesz umieścić następujący kod w pliku config.ru w katalogu głównym aplikacji:
# RAILS_ROOT/config.ru
require "config/environment"
use Rails::Rack::LogTailer
use ActionDispatch::Static
run ActionController::Dispatcher.new
Następnie uruchomić serwer:
[lifo@null application]$ rackup config.ru
Aby sprawdzić dostępne opcje rackup wywołaj:
[lifo@null application]$ rackup --help
3 Stos middleware modułu Action Controller
Wiele komponentów modułu Action Controller jest zaimplementowanych jako obiekty middleware Rack. ActionController::Dispatcher używa ActionController::MiddlewareStack, aby łącząc różne wewnętrzne i zewnętrzne obiekty middleware stworzyć kompletną Railsową aplikację Rack.
ActionController::MiddlewareStack stanowi Railsowy odpowiednik obiektu Rack::Builder, ale nieco bardziej elastyczniejszy i dostosowany do wymagań Railsów.
3.1 Podgląd stosu middleware
Aby wyświetlić zawartość stosu middleware, który jest aktualnie w użyciu posłużymy się zadaniem rake:
$ rake middleware
Oto jak mógłby wyglądać rezultat tego zapytania dla świeżo wygenerowanej aplikacji:
use Rack::Lock
use ActionController::Failsafe
use ActionController::Session::CookieStore, , {:secret=>"<secret>", :session_key=>"_<app>_session"}
use Rails::Rack::Metal
use ActionDispatch::RewindableInput
use ActionController::ParamsParser
use Rack::MethodOverride
use Rack::Head
use ActiveRecord::QueryCache
run ActionController::Dispatcher.new
Zadania poszczególnych obiektów middleware objaśnione zostały w sekcji Stos wewnętrznych obiektów Middleware.
3.2 Konfiguracja stosu middleware
Railsy dostarczają nam prostego interfejsu konfiguracyjnego config.middleware dzięki któremu dodamy, usuniemy i zmodyfikujemy obiekty ze stosu poprzez environment.rb lub plik konfiguracji określonego środowiska environments/<environment>.rb.
3.2.1 Dodanie obiektu middleware
Aby dodać obiekt middleware do stosu możesz posłużyć się dowolną z poniższych metod:
- config.middleware.use(new_middleware, args) – Dodaje nowy obiekt middleware na dnie stosu.
- config.middleware.insert_before(existing_middleware, new_middleware, args) – Dodaje nowy obiekt middleware przed wskazanym, istniejącym obiektem middleware.
- config.middleware.insert_after(existing_middleware, new_middleware, args) – Dodaje nowy obiekt middleware za wskazanym, istniejącym obiektem middleware.
Przykład:
# config/environment.rb
# Odłóż Rack::BounceFavicon na dno stosu
config.middleware.use Rack::BounceFavicon
# Dodaj Lifo::Cache za ActiveRecord::QueryCache.
# Przekaż argument { :page_cache => false } do Lifo::Cache.
config.middleware.insert_after ActiveRecord::QueryCache, Lifo::Cache, :page_cache => false
3.2.2 Zamiana obiektów middleware
Możesz podmienić istniejący obiekt ze stosu middleware za pomocą config.middleware.swap.
Przykład:
# config/environment.rb
# Zamień ActionController::Failsafe na Lifo::Failsafe
config.middleware.swap ActionController::Failsafe, Lifo::Failsafe
3.2.3 Stos middleware jest tablicą
Stos middleware zachowuje się jak zwykła tablica. Możesz posłużyć się metodami odpowiednimi dla tablic, aby dodać, zmienić kolejność lub usunąć elementy stosu. Metody przestawione powyżej są po prostu wygodniejsze.
Przykładowo, poniższe polecenie usunie obiekty middleware odpowiadające podanej nazwie klasy:
config.middleware.delete(middleware)
3.3 Stos wewnętrznych obiektów Middleware
Znaczna część funkcjonalności modułu Action Controller opiera sie na stosowaniu obiektów middleware. Poniższa tabela przedstawia ich użycie:
Middleware | Zastosowanie |
---|---|
Rack::Lock | Podnosi flagę env["rack.multithread"] i przełącza aplikację w tryb Mutex. |
ActionController::Failsafe | Zwraca klientowi status HTTP 500 jeśli podczas wysyłania wystąpiły jakieś wyjątki. |
ActiveRecord::QueryCache | Aktywuje cache dla zapytań w module Active Record. |
ActionController::Session::CookieStore | Przełącza na użycie cookie do przechowywania sesji. |
ActionController::Session::MemCacheStore | Przełącza na użycie pamięci memcached do przechowywania sesji. |
ActiveRecord::SessionStore | Użycie bazy danych do przechowywania sesji. |
Rack::MethodOverride | Ustanawia metodę HTTP na podstawie parametru _method lub env["HTTP_X_HTTP_METHOD_OVERRIDE"]. |
Rack::Head | Odrzuca wartość body odpowiedzi, jeśli klient przesłał żądanie HEAD. |
Możesz użyć powyższych obiektów middleware w stworzonym przez siebie stosie Rack.
3.4 Dostosowanie wewnętrznego stosu middleware
Możesz zamienić cały stos middleware przy pomocy ActionController::Dispatcher.middleware=.
Przykład:
Umieść następujący kod w initializers:
# config/initializers/stack.rb
ActionController::Dispatcher.middleware = ActionController::MiddlewareStack.new do |m|
m.use ActionController::Failsafe
m.use ActiveRecord::QueryCache
m.use Rack::Head
end
Podgląd stosu middleware:
$ rake middleware
(in /Users/lifo/Rails/blog)
use ActionController::Failsafe
use ActiveRecord::QueryCache
use Rack::Head
run ActionController::Dispatcher.new
3.5 Stosowanie obiektu Rack Builder
Oto sposób, na to by zamiast Railsowego MiddlewareStack stosować Rack::Builder
Wyczyść istniejący stos middleware
# environment.rb
config.middleware.clear
Umieść plik config.ru w katalogu RAILS_ROOT
# config.ru
use MyOwnStackFromStratch
run ActionController::Dispatcher.new
4 Metalowe aplikacje Railsowe
Metalowe aplikacje Railsowe to minimalne aplikacje Rack, zaprojektowane tak, aby ściśle łączyły się z typowymi aplikacjami Railsowymi. Jako że Metalowe aplikacje Railsowe pomijają cały stos modułu Action Controller, obsługa żądania nie obciąża w ogóle samych Railsów. Może to być istotne w szczególnych przypadkach, kiedy wydajność stosu frameworka Rails sprawia problemy.
Ryan Bates stworzył interesujący screencast, w którym omawia generowanie i stosowanie Metalowych aplikacji Railsowych.
4.1 Generowanie Metalowej aplikacji Railsowej
Railsy dostarczają generatora metal, który umożliwia tworzenie tego typu aplikacji:
$ script/generate metal poller
Zawartość pliku poller.rb wygenerowanego w katalogu app/metal:
# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)
class Poller
def self.call(env)
if env["PATH_INFO"] =~ /^\/poller/
[200, {"Content-Type" => "text/html"}, ["Hello, World!"]]
else
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
end
end
Aplikacje znajdujące się w app/metal w katalogu wtyczek również zostaną odnalezione i dodane do listy.
Celem aplikacji Metalowych jest optymalizacja. Zanim zdecydujesz się na ich stosowanie upewnij się, że pojąłeś konsekwencje stosowania powiązanego działania. Zacznij od przeczytania postu Performance of Rails Metal na weblogu projektu.
4.2 Kolejność wykonania
Wszystkie aplikacje Metalowe uruchamiane są przez obiekt middleware Rails::Rack::Metal, który jest częścią łańcucha ActionController::MiddlewareStack.
Oto podstawowa metoda odpowiedzialna za działanie aplikacji Metalowej:
def call(env)
@metals.keys.each do |app|
result = app.call(env)
return result unless result[0].to_i == 404
end
@app.call(env)
end
Zmienna @metals w powyższym kodzie to posortowana tablica asocjacyjna. Ze względu na domyślne sortowanie alfabetyczne plik aaa.rb znajdzie się przed bbb.rb w łańcuchu.
Można zastąpić domyślne sortowanie w twoim środowisku. Wystarczy dodać następującą linijkę do pliku config/environment.rb
config.metals = ["Bbb", "Aaa"]
Każdy łańcuch w tej tablicy powinien odpowiadać nazwie twojej metalowej klasy. Pamiętaj, że aplikacje Metalowe, które nie znajdą się na tej liście nie zostaną wczytane.
Aplikacje metalowe nie mogą zwracać statusu HTTP 404, ponieważ jest on używany przy wywoływaniu łańcucha Metalowego. Jeśli zajdzie taka konieczność posłuż się standardowym kontrolerem, lub odpowiednio zaadaptowanym obiektem middleware.