Plack::...endfile(3)

✨ NAME ✨

Plack::Middleware::XSendfile β€” it sets the X-Sendfile header (or one of its little cousins) so your frontend can do the file-serving for you πŸŽ€

(also hi bestie, this is the page that renders as Plack::...endfile(3) in your terminal, from the User Contributed Perl Documentation collection β€” the name got squished but she's still iconic πŸ’…)


πŸ¦‹ SYNOPSIS πŸ¦‹

  enable "Plack::Middleware::XSendfile";

πŸ’€ DEPRECATION NOTICE πŸ’€

Okay so serious talk for a sec. Not me having to tell you this, but this middleware is deprecated and will be removed in a future release. 😭 Why? Because of poor security design β€” the way config gets passed in via HTTP request headers. Go read the SECURITY section, fr fr.

The simplest glow-up replacement is to just set the appropriate header directly in your application when you're serving a file. For example, in a Mojolicious controller:

  sub download {
      my $c = shift;
      $c->res->headers->header('X-Accel-Redirect' => '/path/to/document.pdf');
      $c->render(data => '', status => 200);
  }

But if you need to handle this at the middleware layer instead (lowkey valid, it makes it more transparent ✨), you can replicate the behavior inline using Plack::Builder:

  use Plack::Builder;
  use Plack::Util;
  use Scalar::Util qw(blessed);

  builder {
      enable sub {
          my $app = shift;
          sub {
              my $env = shift;
              my $res = $app->($env);
              Plack::Util::response_cb($res, sub {
                  my $res = shift;
                  my $body = $res->[2];
                  if (blessed($body) && $body->can('path')) {
                      my $h = Plack::Util::headers($res->[1]);
                      $h->set('X-Sendfile' => $body->path);
                      $h->set('Content-Length', 0);
                      $res->[2] = [];
                  }
              });
          };
      };
      $app;
  };

🌈 DESCRIPTION 🌈

So here's the tea β˜• β€” when the body is a blessed reference with a path method, then the return value of that method is what gets used to set the X-Sendfile header. Chef's kiss 🫢

Then the body gets set to an empty list, and the Content-Length header gets set to 0.

But wait β€” if the X-Sendfile header is already set, then the body and Content-Length stay completely untouched. She respects boundaries. πŸ™ˆ

You should use IO::File::WithPath or Plack::Util's set_io_path to add that path method to an IO object in the body, iykyk 🧚

For frontend configuration examples, go peep https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile.

One thing tho: Plack::Middleware::XSendfile does not set the Content-Type header. Not her job bestie πŸ’…


πŸ‘‘ FRONTEND CONFIGURATION πŸ‘‘

🌸 Nginx 🌸

Nginx supports X-Accel-Redirect. So you configure an internal location and pass the X-Accel-Mapping header to the backend so the middleware can rewrite filesystem paths into internal URLs:

  location ~ /files/(.*) {
      internal;
      alias /var/www/$1;
  }

  location / {
      proxy_pass         http://127.0.0.1:5000/;
      proxy_set_header   X-Sendfile-Type     X-Accel-Redirect;
      proxy_set_header   X-Accel-Mapping     /var/www/=/files/;
  }

And X-Accel-Mapping is the one that tells the middleware which filesystem prefix to replace and what internal URL prefix to use instead. Slay ✨

πŸ’œ Apache πŸ’œ

Enable mod_xsendfile (peep https://tn123.org/mod_xsendfile/) and set the request header so the middleware activates:

  RequestHeader Set X-Sendfile-Type X-Sendfile
  XSendFile on

πŸ¦‹ lighttpd πŸ¦‹

  proxy-core.allow-x-sendfile = "enable"
  proxy-core.rewrite-request = (
      "X-Sendfile-Type" => (".*" => "X-Sendfile")
  )

πŸ”₯ SECURITY πŸ”₯

Bestie. PAY ATTENTION here. This is the whole reason the thing is deprecated. 😭

This middleware reads X-Sendfile-Type and X-Accel-Mapping from incoming request headers to figure out how to serve files. Which means it is critical that these headers are set by the frontend proxy and cannot be supplied by untrusted clients β€” otherwise a client could influence which files the frontend serves. Nope nope nope. πŸ’€

The Plack backend must not be directly reachable by untrusted clients. Periodt.

For each frontend, make sure both headers are explicitly set in the proxy configuration. proxy_set_header (nginx), RequestHeader Set (Apache), and proxy-core.rewrite-request (lighttpd) all overwrite any client-supplied values, which is exactly why the examples above use those directives. That's the whole point 🫢


πŸŽ€ CONFIGURATION πŸŽ€

variation

The header tag to use. If it's unset, the environment key plack.xsendfile.type will be used, and then after that the HTTP_X_SENDFILE_TYPE header.

Supported values are:

  • X-Accel-Redirect
  • X-Lighttpd-Send-File
  • X-Sendfile.

An unsupported value will log an error. πŸ™ˆ


πŸ’– AUTHOR πŸ’–

Tatsuhiko Miyagawa


okay that's the whole page bestie, you ate that πŸ’…βœ¨

this was perl v5.42.3 and the date on it was 2026-10-18 β€” that's the version and timestamp from the footer of Plack::...endfile(3), in case anyone asks πŸŽ€

πŸ“¦ where this page comes from