[Catalyst-commits] r14535 - trunk/examples/CatalystAdvent/root/2014

jnapiorkowski at dev.catalyst.perl.org jnapiorkowski at dev.catalyst.perl.org
Mon Dec 1 21:02:18 GMT 2014


Author: jnapiorkowski
Date: 2014-12-01 21:02:18 +0000 (Mon, 01 Dec 2014)
New Revision: 14535

Modified:
   trunk/examples/CatalystAdvent/root/2014/5.pod
Log:
finished 5

Modified: trunk/examples/CatalystAdvent/root/2014/5.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2014/5.pod	2014-12-01 18:56:01 UTC (rev 14534)
+++ trunk/examples/CatalystAdvent/root/2014/5.pod	2014-12-01 21:02:18 UTC (rev 14535)
@@ -5,7 +5,7 @@
 We changed L<Catalyst> so that if you return a filehandle for the body
 response we now pass the filehandle down to the underlying Plack server.
 This enables one to take better advantage of the server's specific powers
-and to tkae advantage of middleware in the related middleware ecosystem.
+and to take advantage of middleware in the related middleware ecosystem.
 
 =head1 Introduction
 
@@ -23,12 +23,107 @@
       $c->response->body($fh);
     }
 
-However in the past 
+However in the past the way this worked 'under the hood' was that during
+body finalization if we noticed the body was a filehandle we we read lines
+from it and then use the PSGI writer object to stream the data.  This was a
+nice and compatible approach (works the same no matter what the underlying
+plack server was) but it meant that you could not take advantage of any of the
+server abilities.  It also generally meant that you're file serving would have
+to be blocking.  And since its all happening at the Perl level it was probably
+not very fast.
+
+Now if during body finalization we notice that the body response is a filehandle
+like object (specifically if its a glob or an object that does the getline method)
+we pass that filehandle directly to the PSGI response.  That means instead of
+handling this at the Catalyst level we can take advantage of service specific features.
+
+=head1 Example
+
+In this example we will use L<Plack::Middleware::XSendfile> so that a compatible
+web server can directly service static files in an optimized manner.  You might want
+to do this if the static file needs to be behind some sort of authentication or 
+authorization but you don't want to sacrifice using a server optimized static file
+server.
+
+Here's a Controller:
+
+    package MyApp::Controller::Static;
+
+    use base 'Catalyst::Controller';
+    use IO::File::WithPath;
+
+
+    sub file :GET Path('') Args {
+      my ($self, $c, @args) = @_;
+      my $path = $c->path_to('static', @args);
+      my $fh = IO::File::WithPath->new($path);
+      $c->response->body($fh);
+    }
+
+    1;
+
+Please note this is for example use only.  You will need to do a bit more work to make
+sure you are not opening a security hole here...
+
+So now when you issue "GET /static/path/to/file.txt" we will create a filesystem path
+like "$APPROOT/static/path/to/file.txt" and then open a filehandle on it.  However by
+using L<IO::File::WithPath> we get a filehandle object with an additional method called
+C<path>.  This method returns the real path to the file that the filehandle is openned
+on.
+
+We now need to add the correct middleware to the Catalyst middleware stack.
+
+    package MyApp;
+
+    use Catalyst;
+
+    __PACKAGE__->setup_middleware('XSendfile');
+    __PACKAGE__->setup;
+
+Now when serving a response if that bit of middleware notices the filehandle response and
+it finds the ->path method AND the plack server running supports one of the more common
+'XSENDFILE' like approaches (Apache, Nginx support variations of this), it will strip out
+the actual body and add some HTTP headers so that the webserver you are using knows to
+serve the file directly using its highly optimized static file serving setup.  Here's an
+example nginx configuration (this assumes you are running your Catalyst application in
+fastcgi under nginx.)
+
+    server {
+        server_name example.com;
+        root /my/app/root;
+        location /private/repo/ {
+            internal;
+            alias /my/app/repo/;
+        }
+        location /private/staging/ {
+            internal;
+            alias /my/app/staging/;
+        }
+        location @proxy {
+            include /etc/nginx/fastcgi_params;
+            fastcgi_param SCRIPT_NAME '';
+            fastcgi_param PATH_INFO   $fastcgi_script_name;
+            fastcgi_param HTTP_X_SENDFILE_TYPE X-Accel-Redirect;
+            fastcgi_param HTTP_X_ACCEL_MAPPING /my/app=/private;
+            fastcgi_pass  unix:/my/app/run/app.sock;
+       }
+
+Nginx needs the additional X-Accel-Mapping header to be set in the webserver configuration,
+so the middleware will replace the absolute path of the IO object with the internal nginx
+path. This is also useful to prevent a buggy app to serve random files from the filesystem,
+as it's an internal redirect.
+
+In the example above, passing filehandles with a local path matching /my/app/staging or
+/my/app/repo will be served by nginx. Passing paths with other locations will lead to an
+internal server error.
+
+Setting the body to a filehandle without the path method bypasses the middleware completely.
 =head1 Discussion
 
 =head1 More Information
 
 L<Plack::Middleware::XSendfile>, L<IO::File::WithPath>
+L<https://metacpan.org/pod/Catalyst::Response#res-body-text-fh-iohandle_object>
 
 =head1 Author
 




More information about the Catalyst-commits mailing list