1

I have two Symfony apps (APIs) talking to each other via HTTP requests/responses using cURL PHP function. This works fine when they get small JSON responses, but the problem comes when getting and serving files. API1 (exposed to the Internet) needs to serve a file that is only accessible by API2(private, connected to API1 via VPN).

If I encode the content of the file in the first API and then pass it in the response body there is no problem, I can reconvert the file back to a stream and serve in the first API as a BinaryFileResponse. The problem comes with big files (>30MB), where the response body is huge and symfony's is not able to allocate that much memory.

Is there a way to forward or redirect a BinaryFileResponse from one API to the other, so the middle layer is invisible for the client?

These are the two pieces of code in each application:

Public API:

/**
 *
 * @Get("/api/login/getfile")
 */
public function testGetFilePrivate(Request $request)
{
    $url = 'private/variantset/9/getfile';
    $url = $this->container->getParameter('api2_url').$url;

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT_MS, 300000); //Set timeout in ms
    $response = curl_exec($ch);
    curl_close($ch);
    $data = json_decode($response, TRUE);
    $fileContent = base64_decode($data['filedata']);
    $response = new Response($fileContent);

    $disposition = $response->headers->makeDisposition(
        ResponseHeaderBag::DISPOSITION_ATTACHMENT,
        $data['filename']
    );

    $response->headers->set('Content-Disposition', $disposition);
    return $response;
}

Private API:

/**
* @Get("/api/private/variantset/{id}/getfile")
*/
public function getVariantsetDataFileById($id)
{
    $variantset = $this->getVariantsetById($id);

    if(!$variantset){
        $response = array("getdata"=>"ko","error"=>"Variantset does not exists");        
        return new JsonResponse($response);
    }

    if($variantset->getCreated()){

        $auth_variants_dir = $this->container->getParameter('auth_variants_path');
        $file_path = $auth_variants_dir . '/' . $variantset->getDatafile() . '.gpg';

        $data = [
        "getdata"=>"ok",
        "filename" => $variantset->getDatafile() . '.gpg',
        "filedata" => base64_encode(file_get_contents($file_path))
        ];

        $response = new Response();
        $response->setContent($this->container->get('serializer')->serialize($data, 'json'));
        $response->headers->set('Content-Type', 'application/json');
    }else{
        $response = new JsonResponse(array("getdata"=>"ko","error"=>"Variantset does not have a file yet")); 
    }

    return $response;
}
MarcSitges
  • 282
  • 3
  • 11

1 Answers1

1

Finally found the solution by combining the answers in Streaming a large file using PHP and Manipulate a string that is 30 million characters long

Instead of using cURL PHP function, the HTTP stream wrapper is used to catch API2 response. This wrapperThe output is then parsed by using Symfony's StreamedResponse Class:

    $response = new StreamedResponse(function() use($url) {
        $handle = fopen($url, 'r');
        while (!feof($handle)) {
            $buffer = fread($handle, 1024);
            echo $buffer;
            flush();
        }
        fclose($handle);
    });

    $response->headers->set('Content-Type', 'application/octet-stream');
    return $response;

I am still struggling on how to the content-type from the initial request, I will edit the response if I finally manage to get it properly. Any suggestions are welcomed.

Community
  • 1
  • 1
MarcSitges
  • 282
  • 3
  • 11