How to Properly Download a PDF File Through the Wordpress API from Backend Server

2.1k views Asked by At

I built a custom Wordpress plugin. The plugin frontend is a Facebook React JS application that is embedded into a Wordpress page via a shortcode. It uses the Wordpress REST API exclusively to send and retrieve JSON between the client browser and Wordpress. Wordpress is acting as the middleman in this scenario. The REST routes handling the client requests just call a backend REST API and return the responses to the client from the other server.

I've just added a new feature to the app that allows the user to download a PDF file that's dynamically generated by a different backend server (not Wordpress). My plan was to just add another route on the Wordpress REST API that would call the backend server and return the PDF file generated by that other server. Wordpress would basically just proxy the request to the backend server and return the response back to the client browser.

However, when I try to do that the response body is a string instead of the raw byte array returned from the backend server. I can make the request to the backend server directly and it properly returns the PDF file (e.g. http://default-internal-backend-server.example.com/api/v1/quote/asdf123/pdf).

What's the proper way to have a Wordpress Plugin handle a REST request (e.g. https://example.com/wp-json/my-plugin/v1/quote/asdf123/pdf) and return the raw, untouched, response from an upstream server?

For example, I was doing this:

On the UI of the app I have a simple anchor tag like this:

<a href="http://example.com/wp-json/my-plugin/quote/asdf123/pdf" download="quote.pdf">
   <span>Download PDF</span>
</a>

The above URL is registered in the REST Routes of the plugin as so:

class My_Plugin_REST_Controller extends WP_REST_Controller {
    // ****

    register_rest_route( 'my-plugin', '/quote/(?P<id>\w+)/pdf',
        array(
            array(
                'methods' => WP_REST_Server::READABLE,
                'callback' => array( $this, 'get_quote_pdf' ),
                    'args' => array()
            )
        )
    );

    public function get_quote_pdf( $request ) {
        $api_service = API_Service::get_instance();
        $response = $api_service->getQuotePdf( $request );
        $http_response = new WP_REST_Response( $response, 200 );
        $http_response->header('Content-Type', 'application/pdf');
        return $http_response;
    }

    // ****
}

class API_Service {
    public function getQuotePdf( $request ) {
        $curl = curl_init();

        $default_options = array(
            'url' => 'http://default-internal-backend-server.example.com'
        );

        $api_options = get_option( 'my_plugin_api', $default_options );

        $api_url = $api_options['url'] . '/api/v1/quote/' . $request['id'] . '/pdf'

        curl_setopt_array($curl, array(
            CURLOPT_URL             => $api_url,
            CURLOPT_RETURNTRANSFER  => true,
            CURLOPT_ENCODING        => '',
            CURLOPT_MAXREDIRS       => 10,
            CURLOPT_TIMEOUT         => 30,
            CURLOPT_HTTP_VERSION    => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST   => "GET",
            CURLOPT_HTTPHEADER      => array(
                "Accept: application/pdf",
                "Cache-Control: no-cache"
            )
        ));

        $response = curl_exec( $curl );
        $err = curl_error( $curl );

        curl_close( $curl );

        if ( $err ) {
            return null;
        } else {
            return $response;
        }
    }
}

Wordpress is properly handling the request from the client JavaScript application, the backend server is being called properly, the backend server is returning the PDF file.

However, with the above example, Wordpress is returning a string representation of the PDF instead of the raw bytes returned by the external server. I had to fight with the WP_Rest_Response to get it to set the Content-Type to appplication/pdf as well. If I set the Content-Length header it would change the Content-Type to application/json for no apparent reason.

How can I return the raw bytes instead of a string version of the response?

My next step is to just remove having Wordpress involved with this at all and link to a different URL in the browser (e.g. https://my.example.com/quote/asdf123/pdf) that uses a reverse proxy to get to the internal server directly.

I did read that PHP doesn't have a native data type of byte[] which could be the limiting factor.

0

There are 0 answers