Nathan
Nathan

Reputation: 7709

How to return binary data from custom wordpress rest api endpoint

I am writing a custom endpoint for a REST api in wordpress, following the guide here: https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/

I am able to write a endpoint that returns json data. But how can I write an endpoint that returns binary data (pdf, png, and similar)?

My restpoint function returns a WP_REST_Response (or WP_Error in case of error). But I do not see what I should return if I want to responde with binary data.

Upvotes: 1

Views: 1322

Answers (3)

Scuzzy
Scuzzy

Reputation: 12332

The problem is wp_json_encode is always applied by WP_REST_Server::serve_request().

If you create a filter on rest_pre_serve_request and return true, you can echo the body/payload prior to WordPress doing it. This may be useful if you still want WordPress to handle setting the headers, e.g. setting your Content-Type header:

add_filter('rest_pre_serve_request', function() use ($payload) {
    ...
    // this allows delaying the echo until headers are sent
    readfile($path); 
    return true;
});
return new WP_REST_Response(headers: [
    'Content-type' => 'application/pdf',
    ...
]);

There is also rest_pre_echo_response in which you can return null to also skip, but the code is applying additional modifiers to the result and is ultimately redundant if you're echoing inside this hook.

Upvotes: 0

Lucio Crusca
Lucio Crusca

Reputation: 1517

Late to the party, but I feel the accepted answer does not really answer the question, and Google found this question when I searched for the same solution, so here is how I eventually solved the same problem (i.e. avoiding to use WP_REST_Response and killing the PHP script before WP tried to send anything else other than my binary data).

function download(WP_REST_Request $request) {
  $dir = $request->get_param("dir");

  // The following is for security, but my implementation is out 
  // of scope for this answer. You should either skip this line if 
  // you trust your client, or implement it the way you need it.
  $dir = sanitize_path($dir);

  $file = $request->get_param("file");

  // See above...
  $file = sanitize_path($file);

  $sandbox = "/some/path/with/shared/files";
  
  // full path to the file
  $path = $sandbox.$dir.$file;

  $name = basename($path);

  // get the file mime type 
  $finfo = finfo_open(FILEINFO_MIME_TYPE);
  $mime_type = finfo_file($finfo, $path);

  // tell the browser what it's about to receive
  header("Content-Disposition: attachment; filename=$name;");
  header("Content-Type: $mime_type");
  header("Content-Description: File Transfer");
  header("Content-Transfer-Encoding: binary");
  header('Content-Length: ' . filesize($path));
  header("Cache-Control: no-cache private");

  // stream the file without loading it into RAM completely
  $fp = fopen($path, 'rb');
  fpassthru($fp);

  // kill WP
  exit;
}

Upvotes: 3

Alex Knopp
Alex Knopp

Reputation: 935

I would look at something called DOMPDF. In short, it streams any HTML DOM straight to the browser. We use it to generate live copies of invoices straight from the woo admin, generate brochures based on $wp_query results etc. Anything that can be rendered by a browser can be streamed via DOMPDF.

Upvotes: -1

Related Questions