Ich entdecke immer mal wieder kleine Helferlein, die mir bis dato noch nicht untergekommen waren. In der heutigen Episode: alias_method_chain

Die Methode wurde mit Revision 4276 in Rails eingeführt und dient dazu eine oft benutze Kombination von alias Aufrufen zu vereinfachen.

1
2
3
4
5
6
7
8
9
10
11

  def example
  end

  def example_with_put
    puts 'ping'
    example_without_put
  end

  alias_method :example_without_put, :example
  alias_method :example, :example_with_put
Was passiert hier?

Wir erstellen ein Alias meiner alten Methode example und leiten alle Aufrufe an example_with_put weiter. Diese Methode wiederum tut was Sinnvolles und ruf dann meine originale example Methode via _example_without_put auf. Eigentlich nichts besonderes..

Da dieser Trick recht oft verwendet wird, erhielt Rails mit der Rev 4276 die Methode alias_method_chain, die dieses “Doppel-Aliasing” übernimmt. Ich kann die beiden alias_method Aufrufe auch folgendermaßen erstellen:

1
2

  alias_method_chain :example, :put

Mehr Information finden sich im Code von Rails.

Rails kann alles vieles. Und, zu den vielen Sachen die es kann, gehört auch das Ausliefern von Dateien. Diese Problematik erledigt das Framework in der Regel zwar ganz gut, aber sobald die Größe der Dateien zunimmt kann es mitunter zu merkwürdigem Fehlverhalten und Downloadfehlern kommen.

Eine Ursache dafür ist Rubys Gargabe Collector. Da Rails die Dateien, bevor es sie ausliefert, in den Speicher lädt, greift er ganz gerne mal dazwischen und sorgt für Unruhe im Speicherwald. Es scheint mir allerdings generell recht fragwürdig 100 Mb in den Speicher zu laden. Es muss also eine andere Lösung her.

Im Normalfall besteht ein Railsstack aus mehreren Mongrel Instanzen und einem lokalen Proxy der die Anfragen auf diese Instanzen verteilt. Sind mehrere App Server im Einsatz wird noch ein weiterer Proxy/Loadbalancer benötigt, der die Requests auf die einzelnen Server verteilt. Es bietet sich also an, die Dateien direkt durch den jeweiligen lokalen Proxy (i.R. Nginx, Lighty, Pound oder Apache) ausliefern zu lassen.

Um das zu erreichen, lassen wir Rails anstatt der eigentlichen Datei einen modifizierten, internen Header in die Antwort zum Client einsetzen. Diese wird vom Proxy erkannt und veranlasst ihn dann eine Datei aus einem definierten Ort zu verschicken. Dieser interne Header wird in der Regel vom Proxy aus den Headern entfernt, bevor die Antwort an den externen Client weitergereicht wird.

Am Beispiel Nginx könnte der Code dafür so aussehen:

1
2
3
4
5
6
7
8
9
10

case RAILS_ENV
when 'production'
  response.headers['X-Accel-Redirect'] = "/protected/" + File.basename(file.path)
  response.headers['Content-Disposition'] = "attachment; filename=file.tar"
  render :nothing => true
else
  # Directly send in development env
  send_data(file.read, :filename => File.basename(file.path))
end

 

Die Konfiguration des Nginx ist recht einfach:

1
2
3
4
5

location /protected/ {
  internal;
  alias /path/to/file/directory/;
}

 

In ‘Multi-Server’ Umgebungen sollte man darauf achten, dass Downloads nicht durch den First-Proxy geleitet werden, sondern direkt auf den jeweiligen App-Server. So lässt es sich vermeiden, dass der Loadbalancer die längeren Anfragen/Antworten als Timeouts deutet und die Verbindung kappt.