WP_REST_Response para descargar un archivo

5 minutos de lectura

¿Es posible devolver un documento (un PDF generado, un CSV) usando el WP_REST_Response en WordPress?

Hasta ahora he estado registrando un punto final personalizado usando register_rest_resource pero si trato de devolver un archivo (por ejemplo, usando PHP fpassthru($f) o readfile($f) Recibo el error “Encabezados ya enviados”.

En otras palabras: ¿cómo devolvería un archivo usando las API REST de WordPress?

¡Cualquier ayuda es apreciada!

Gracias

  • “Volver a quién? La API devuelve JSON. Entonces, si su pregunta es básicamente, “¿puedo poner datos binarios en JSON?”, entonces la respuesta sería sí. Si debe hacer eso o bajo qué circunstancias podría tener sentido, sería una pregunta diferente.

    – CBroe

    13 de junio de 2017 a las 14:20

  • Acepto que la API debe devolver JSON al destinatario de JS. Pero, ¿qué sucede si mi API toma (por ejemplo) una identificación de un pedido como entrada y debe devolver el PDF de la factura de ese pedido?

    – Diego Vigano

    13 de junio de 2017 a las 14:40

  • Entonces lo consideraría un defecto de diseño: p Los activos binarios grandes no deberían pasarse a través de una API de este tipo en primer lugar. Su API debe devolver un URL para el PDF, que el cliente puede usar para descargarlo.

    – CBroe

    13 de junio de 2017 a las 14:42

  • Sí, pero esto requeriría dos llamadas: la primera para generar y guardar el PDF en el disco del servidor y la segunda para descargar el PDF almacenado en el disco.

    – Diego Vigano

    13 de junio de 2017 a las 16:18

  • Las direcciones URL no tienen que apuntar a datos o archivos “estáticos”.

    – CBroe

    13 de junio de 2017 a las 16:49

Por defecto, todas las respuestas REST se pasan json_encode() para devolver una cadena JSON. Sin embargo, el servidor REST proporciona el enlace WP rest_pre_serve_request que podemos usar para devolver datos binarios en su lugar.

Ejemplo de código:

<?php
/**
 * Serves an image via the REST endpoint.
 *
 * By default, every REST response is passed through json_encode(), as the
 * typical REST response contains JSON data.
 *
 * This method hooks into the REST server to return a binary image.
 *
 * @param string $path Absolute path to the image to serve.
 * @param string $type The image mime type [png|jpg|gif]. Default is 'png'.
 *
 * @return WP_REST_Response The REST response object to serve an image.
 */
function my_serve_image( $path, $type="png" ) {
    $response = new WP_REST_Response;

    if ( file_exists( $path ) ) {
        // Image exists, prepare a binary-data response.
        $response->set_data( file_get_contents( $path ) );
        $response->set_headers( [
            'Content-Type'   => "image/$type",
            'Content-Length' => filesize( $path ),
        ] );

        // HERE → This filter will return our binary image!
        add_filter( 'rest_pre_serve_request', 'my_do_serve_image', 0, 2 );
    } else {
        // Return a simple "not-found" JSON response.
        $response->set_data( 'not-found' );
        $response->set_status( 404 );
    }

    return $response;
}

/**
 * Action handler that is used by `serve_image()` to serve a binary image
 * instead of a JSON string.
 *
 * @return bool Returns true, if the image was served; this will skip the
 *              default REST response logic.
 */
function my_do_serve_image( $served, $result ) {
    $is_image   = false;
    $image_data = null;

    // Check the "Content-Type" header to confirm that we really want to return
    // binary image data.
    foreach ( $result->get_headers() as $header => $value ) {
        if ( 'content-type' === strtolower( $header ) ) {
            $is_image   = 0 === strpos( $value, 'image/' );
            $image_data = $result->get_data();
            break;
        }
    }

    // Output the binary data and tell the REST server to not send any other
    // details (via "return true").
    if ( $is_image && is_string( $image_data ) ) {
        echo $image_data;

        return true;
    }

    return $served;
}

Ejemplo de uso:

<?php
// Register the REST endpoint.
register_rest_route( 'my_sample/v1', 'image', [
    'method' => 'GET',
    'callback' => 'my_rest_get_image'
] );

// Return the image data using our function above.
function my_rest_get_image() {
    return my_serve_image( 'path/to/image.jpeg', 'jpg' );
}

(Necesito esto pronto para formular una respuesta que tal vez esté incompleta)

Comprobando con WP Media nos ponemos en marcha .../?rest_route=/wp/v2/media/ID a JSON API respuesta con enlaces para los archivos multimedia solicitados.

Siguiendo a lo largo, es decir, para la imagen uno de los source _url contiene .../wp-content/uploads/2021/06/Screenshot-2021-06-18-at-10.25.05-150x150.png.

Después de los comentarios (no transmita binarios sino enlaces), agregue el archivo a la colección de WP Media o el punto final personalizado podría responder con una respuesta similar que vincule al archivo generado Y almacenado.

Luego, cualquier cliente compatible con API JSON puede hacer lo que se necesita. En este caso, genere un enlace de descarga.

No se puede utilizar WP_REST_Response para hacer esto. Sin embargo, es posible devolver algo más con el resto de la API.

Si está absolutamente seguro de que tiene la completo respuesta lista (incluyendo encabezados, como Content-Disposition para descargas), simplemente puede exit; después de generar la respuesta final. Tenga en cuenta que esto omite por completo cualquier gancho que se haya llamado después, así que utilícelo con precaución.

un ejemplo con .csv

$filename="example-file.csv";
header("Access-Control-Expose-Headers: Content-Disposition", false);
header('Content-type: text/csv');
header("Content-Disposition: attachment; filename=\"$filename\"");

// output starts here, do not add headers from this point on.
$csv_file = fopen('php://output', 'w');

$csv_header = array(
    'column-1',
    'column-2',
    'column-3',
);

fputcsv($csv_file, $csv_header);

$data = array(
    array('a1', 'b1', 'c1'),
    array('a2', 'b2', 'c2'),
    array('a3', 'b3', 'c3'),
);

foreach ($data as $csv_data_entry) {
    fputcsv($csv_file, $csv_data_entry);
}

fclose($csv_file);

// With a non-file request, you would usually return the result.
// In this case, this would cause the "Headers already sent" errors, so an exit is required.
exit;

¿Ha sido útil esta solución?