Cómo enviar archivos grandes a través de WebServices

Horacio, un ex-alumno, me escribió esta consulta:

Hola profesor, tanto tiempo, sabe que tengo una pregunta , sabe que tengo que hacer un web service en php y queria saber que me conviene si soap o rest , son datos de gran tamaño , la idea es hacer una aplicación que permite cargar archivos xlsx(importar archivo) a una base de datos mysql (destino) y de ahí que lo consuma otro sistema web . Son archivos de gran tamaño.según su conocimiento que me recomienda?Aguardo respuesta.Saludos y gracias.

El problema en que se encuentra Horacio radica en que los webservices, tanto REST como SOAP, están basados en el protocolo HTTP.

Este protocolo se presta muy bien para el intercambio de texto, pero cuando se trata de datos, ya no resulta tan conveniente. Mucho menos si se trata del intercambio de muchos datos.

¿Cuál es el problema? Pues que para que los datos se envíen a través de HTTP, primero deben ser transformados a texto, lo cual agrega una sobrecarga de procesamiento y, a la vez, hace que la cantidad total de información a enviar sea mucho mayor.

¿Por qué sucede esto?

Transferencia binaria versus transferencia textual

La raíz del problema es la representación interna de los datos en la computadora.

Un ejemplo muy simple: el número 1234567890 puede ser almacenado entero o como la secuencia de caracteres 1 2 3 4 5 6 7 8 9 0.

En el primero de los casos, nos alcanzarían 31 bits para almacenarlo (230 = 1073741824 y 231=2147483648), mientras que en el segundo necesitaríamos 80 (Asumiendo 8 bits por cada caracter).

Esto como para poner un poco en contexto por qué la transmisión binaria es más eficiente que la textual.

Si multiplicamos esta diferencia de 49 bits por un gran conjunto de información a enviar pronto nos chocamos contra el límite establecido por el servidor web… seguramente lo que le ocurrió a Horacio.

¿Cuál podría ser la solución?

Cambiar la configuración del servidor web

En algunos escenarios, especialmente si se tiene la capacidad y posibilidad de alterar la configuración del servidor web, se podría relajar un poco el criterio para permitir operaciones HTTP más pesadas.

Esta solución no es la óptima principalmente por dos motivos:

  1. No soluciona el problema de la eficiencia
  2. Siempre podrá aparecer un archivo lo suficientemente grande como para superar los nuevos límites y se estaría nuevamente en la situación inicial

¿REST o SOAP?

Respecto de la pregunta de si conviene más usar REST o SOAP diría que da lo mismo.

El problema es usar HTTP para realizar el envío y, tanto REST como SOAP depende de él.

La solución pasa por desligar el envío del archivo respecto del aviso a la contraparte.

Más en concreto, se trata de dividir el problema en tres partes:

  1. Dejar el archivo disponible para que la contraparte lo acceda
  2. Informar a la contraparte dónde está alojado tal archivo para que pueda accederlo
  3. Desde el receptor del mensaje realizar la descarga

Sólo el paso 2 debería depender de un servicio web.

Los pasos 1 y 3 pueden realizarse usando diversas opciones de almacenamiento de archivos binarios. Lo importante es que luego pueda realizarse la descarga sin usar HTTP.

Un ejemplo usando FTP

En esta sección se puede ver un ejemplo de solución usando un servidor FTP.

client.php

<?php

if (!($remoteServer = ftp_connect(getenv('FTP_HOST')))) {
    die('Not connected :(' . PHP_EOL);
}
echo 'Connected!' . PHP_EOL;
if (!ftp_login($remoteServer, getenv('FTP_USER'), getenv('FTP_PWD'))) {
    die('Wrong credentials' . PHP_EOL);
}
echo 'Login succesful!' . PHP_EOL;
$remoteFullPath = getenv('REMOTE_BASE_PATH') . '/' . basename($argv[1]);
if (!ftp_put($remoteServer, $remoteFullPath, $argv[1], FTP_BINARY)) {
    die('Upload failed');
}

echo 'File ' . $argv[1] . ' uploaded!'.PHP_EOL;

ftp_close($remoteServer);

$ch = curl_init(getenv('SERVER_URL') . '/upload');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => [
        'path' => $remoteFullPath
    ],
    CURLOPT_RETURNTRANSFER => true,
]);

$response = curl_exec($ch);
if (200 !== curl_getinfo($ch, CURLINFO_RESPONSE_CODE)) {
    die('Webservice call failed: ' . curl_error($ch));
}
curl_close($ch);
echo 'Message sent!' . PHP_EOL;

server.php:

<?php

error_log('Request received!' . PHP_EOL);
if ('post' === strtolower($_SERVER['REQUEST_METHOD'])) {
    $remotePath = $_POST['path'];
    error_log('Downloading ' . $remotePath . PHP_EOL);
    if (!($remoteServer = ftp_connect(getenv('FTP_HOST')))) {
        http_response_code(500);
        trigger_error('Not connected :(' . PHP_EOL);
        die;
    }
    trigger_error('Connected!' . PHP_EOL);
    if (!ftp_login($remoteServer, getenv('FTP_USER'), getenv('FTP_PWD'))) {
        http_response_code(500);
        trigger_error('Wrong credentials' . PHP_EOL);
        die;
    }
    trigger_error('Login succesful!' . PHP_EOL);
    if (!ftp_get($remoteServer, __DIR__ . '/' . basename($remotePath), $remotePath)) {
        trigger_error('Download of file '.$remotePath.' failed' . PHP_EOL);
        http_response_code(500);
        die;
    }
    trigger_error('Sucessuflly downloaded '.$remotePath.PHP_EOL);
}

Notas:

  1. Para hacer más genérica la solución todos los datos específicos están guardardos en variables de entorno.
  2. En este caso el cliente se bloquea hasta que el servidor realice la descarga. En un caso más realista esto no debería ser así. El servidor debería guardar los datos de la descarga y emitir un acuse de recibo para que el cliente pueda continuar y eventualmente realizar la descarga.
mchojrin

Por mchojrin

Ayudo a desarrolladores PHP a acceder mercados y clientes más sofisticados y exigentes

¿Te quedó alguna duda? Publica aca tu pregunta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.