Montag, 4. März 2013

Play Framework und Nginx: Durchreichen von Medien

Der Verbund von Play! und einem Reverse Proxy wie Nginx funktioniert problemlos für HTML-Dateien. Sobald aber andere Dateien wie Bilder oder XML-Dokumente zum Client durchgereicht werden sollen, werden sie von Nginx blockiert. Der Grund dafür ist, dass Nginx nicht mit gechunkten Antworten auskommt. In diesem Fix wurde das Problem bei Play! behoben, allerdings nur für Dateien im Assets-Verzeichnis (CSS, Logo, etc). Wie können nun aber Dateien, die nicht in den Assets liegen, erfolgreich geladen werden?
Als Beispiel nehmen wir die Sitemap, ein zentraler Baustein vom SEO, welcher auf jeder Homepage verfügbar sein sollte. Da die Sitemap oft wechselt, ist es sinnlos, diese statisch in Play! einzubinden (zusammen mit den CSS oder Javascripts im Assets-Vezeichnis). Die Sitemap ist als XML-Datei irgendwo auf dem Server gespeichert und soll beim Aufruf von beispielsweise http://mydomain.com/sitemap.xml angezeigt werden. Nachdem in Play! eine Route und der dazugehörige Controller erstellt wurde, können wir die Methode in der Controller-Klasse implementieren.
Route
 GET          /sitemap.xml                    controllers.SEO.getSitemap()  
Controller: SEO.java
 public static Result getSitemap() {  
      final File file = new File("/location/of/sitemap.xml");  
      if (!file.canRead()) {  
           return notFound();  
      }  
        
      return ok(file);  
 }  

Das File wird vom Dateisystem gelesen und an den Client weitergeleitet. Ohne Reverse Proxy funktioniert der obige Code tadellos. Ein Reverse Proxy vor dem Server erfordert aber ein paar zusätzliche Zeilen:
Controller: SEO.java
 public static Result getSitemap() {  
      final File file = new File("/location/of/sitemap.xml");  
      if (!file.canRead()) {  
           return notFound();  
      }  
   
      FileInputStream stream;
      ByteArrayOutputStream bos;
      try {  
           // convert the file to a byte-array  
           stream = new FileInputStream(file);  
           bos = new ByteArrayOutputStream();  
           byte[] buffer = new byte[1024];  
           for (int readNum; (readNum = stream.read(buffer)) != -1;) {  
                bos.write(buffer, 0, readNum);  
           } 
   
           // get the mime type. For a XML-document it is "application/xml"  
           String mimeType = MimeTypes.forFileName(file.getName()).get();  
           return ok(bos.toByteArray()).as(mimeType);  
      } catch (IOException e) {  
           // return an error to indicate the exception  
           return internalServerError();  
      } finally {
           if(stream != null) { stream.close(); }
      }
 }  

Die XML-Datei muss als Byte-Array durch Nginx durchgeschleust werden, damit sie nicht blockiert wird. Die selbe Prozedur kann bei Bildern oder anderen Dateitypen angewendet werden. Wichtig dabei ist, dass der Mime-Type entsprechend angepasst wird.