Envío y recepción de un archivo desde JavaScript con la API Fetch a un servidor con PHP
Introducción
Esta práctica trata sobre enviar un archivo con la API Fetch desde JavaScript en un host X (ejemplo.host-1.com) a otro host Z (ejemplo.host-2.com) donde será recibido y procesado con PHP para dar una respuesta JSON.
Requisitos
- 1 host X configurado para archivos .html y .js
- 1 host Z configurado para archivos .php
Paso 1: Configuraciones en el archivo php.ini
- file_uploads = on
Verificar que los valores de las siguientes variables estén establecidas de un modo que puedan cumplir con a las necesidades de cada quien
- upload_max_filesize
- post_max_size
- memory_limit
- max_execution_time
- max_input_time
Paso 2: Permisos y propietarios del directorio
Verificar que los script de PHP puedan realmente tener permisos para escribir en el directorio donde se pretende subir el archivo, así que hay que verificar con cuidado esto, que dependerá del juego de configuraciones establecidas entre el sistema de directorios, Apache y PHP. Por ejemplo, normalmente quien ejecuta los archivos de PHP es el usuario www-data:www-data y sí el directorio donde se pretende subir es de user-x:user-x entonces dependiendo de los permisos 755, 775 se podrá realizar sin problemas o no. En caso de tener problemas de permisos se pude probar momentáneamente con 777 sin embargo esto es malo para la seguridad del sistema, por tanto hay que procurar evitarlo siempre.
Paso 3: Crear el código que enviará el archivo
a.- Creamos un archivo .html común y corriente
<!DOCTYPE html>
<html lang="es_MX">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Envío de Archivo con Fetch</title>
</head>
<body>
</body>
</html>
b.- Ponemos un input y un botón
<!DOCTYPE html>
<html lang="es_MX">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Envío de Archivo con Fetch</title>
</head>
<body>
<h1>Campo y botón para enviar archivo</h1>
<input type="file" />
<button type="button">Enviar Archivo</button>
</body>
</html>
c.- Creamos la función que enviará el archivo
<!DOCTYPE html>
<html lang="es_MX">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Envío de Archivo con Fetch</title>
</head>
<body>
<h1>Campo y botón para enviar archivo</h1>
<input type="file" />
<button type="button">Enviar Archivo</button>
<script>
function enviarArchivo(){
const datosFormulario = new FormData();
const campoArchivo = document.querySelector('input[type="file"]');
datosFormulario.append("avatar", campoArchivo.files[0]);
var respuesta_clonada;
fetch("http://ejemplo.host-2.com/archivos/recepcion-archivo-1.php", {
method: "POST",
body: datosFormulario,
})
.then((respuesta) => {
//Para obtener el estado de la respuesta
console.info(respuesta.status);
//Devuelve verdadero si la respuesta es 2xx
console.info(respuesta.ok);
//Para obtener los encabezados
console.info(respuesta.headers);
//Obtiene el tipo de petición
console.info(respuesta.type);
//Obtiene la URL de quien emitió la respuesta
console.info(respuesta.url);
//Obtenemos el cuerpo de la respuesta solo sí el contenido es un objeto JSON
respuesta.json()
.then((json) => {
console.log('Tuviste éxito');
console.log(json);
})
.catch(error_json => {
console.warn('Ocuarrio un error con el formato JSON');
console.warn(error_json);
})
})
.catch(error => {
console.error('Ocuarrio un error');
console.error(error);
})
}
</script>
</body>
</html>
d.- Hacemos que el botón llame a la función que enviará el archivo
<!DOCTYPE html>
<html lang="es_MX">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Envío de Archivo con Fetch</title>
</head>
<body>
<h1>Campo y botón para enviar archivo</h1>
<input type="file" />
<button type="button" onclick="enviarArchivo()">Enviar Archivo</button>
<script>
function enviarArchivo(){
const datosFormulario = new FormData();
const campoArchivo = document.querySelector('input[type="file"]');
datosFormulario.append("avatar", campoArchivo.files[0]);
var respuesta_clonada;
fetch("http://php81.programadorvagabundo.com/archivos/recepcion-archivo-1.php", {
method: "POST",
body: datosFormulario,
})
.then((respuesta) => {
//Para obtener el estado de la respuesta
console.info(respuesta.status);
//Devuelve verdadero si la respuesta es 2xx
console.info(respuesta.ok);
//Para obtener los encabezados
console.info(respuesta.headers);
//Obtiene el tipo de petición
console.info(respuesta.type);
//Obtiene la URL de quien emitió la respuesta
console.info(respuesta.url);
//Obtenemos el cuerpo de la respuesta solo sí el contenido es un objeto JSON
respuesta.json()
.then((json) => {
console.log('Tuviste éxito');
console.log(json);
})
.catch(error_json => {
console.warn('Ocuarrio un error con el formato JSON');
console.warn(error_json);
})
})
.catch(error => {
console.error('Ocuarrio un error');
console.error(error);
})
}
</script>
</body>
</html>
Paso 4: Crear el código que recibirá y procesara el archivo
Creamos un archivo .php que será el encargado de recibir y procesar el archivo enviado desde la API Fetch del archivo .html
<?php
//Si quieres permitir todos los orígenes *** header('Access-Control-Allow-Origin: *');
//Acceso sólo para un origen
header('Access-Control-Allow-Origin: http://ejemplo.host-1.com');
// Encabezado de contenido de acuerdo a lo que se va a devolver
header("Content-type: application/json; charset=utf-8");
$directorio_subidas = 'subidas/';
//Obtiene el nombre del archivo del nombre del archivo subido por el cliente
$nombre_archivo = basename($_FILES['avatar']['name']);
$url_archivo_subido = $directorio_subidas . $nombre_archivo;
if (move_uploaded_file($_FILES['avatar']['tmp_name'], $url_archivo_subido)) {
//Para devolver un estado de respuesta específico (201)
http_response_code(201);
//Crea un objeto JSON
echo json_encode([
'mensaje' => 'Se subió correctamente el archivo',
'url' => $url_archivo_subido
]);
} else {
//Para devolver un estado de respuesta específico (500)
http_response_code(500);
//Crea un objeto JSON
echo json_encode(['No se pudo subir el archivo']);
}
Resumen
Felicidades lo lograste 🙂
Otras Consideraciones Importantes
- Solo podemos elegir un método de lectura corporal, por ejemplo, si ya tenemos la respuesta con respuesta.text(), entonces response.json() no funcionará, ya que el contenido del cuerpo ya ha sido procesado, por esto, si se quiere seguir ocupando la respuesta se tendría que clonar antes con respuesta.clone() y almacenar el valor en una variable.
- En esta prueba el modo predefinido sobre CORS para fetch fue ‘cors’, así que esto quiere decir que debe estar correctamente configurado el host a donde se enviaron los datos para que pueda dar una respuesta apropiada. De lo contrario nos podra dar errores como respuesta. Si te da errores el host a donde enviaste los datos puedes ocupar el modo ‘no-cors’ que se establece dentro de las opciones de fetch() pero en consecuencia solo podras recibir una respuesta vacía o seá la variable response de fetch API estará vacía
Bibliografía
JS-PHP
https://www.w3schools.com/php/php_file_upload.asp
https://phpenthusiast.com/blog/javascript-fetch-api-tutorial
https://www.espai.es/blog/2019/09/como-enviar-y-recibir-datos-con-la-api-fetch/
FormData
https://developer.mozilla.org/es/docs/Web/API/FormData
https://developer.mozilla.org/en-US/docs/Web/API/FormData/append
Fetch
https://developer.mozilla.org/en-US/docs/Web/API/Response/headers
https://developer.mozilla.org/en-US/docs/Web/API/Response
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
https://developer.mozilla.org/en-US/docs/Web/API/fetch
https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
https://developer.mozilla.org/en-US/docs/Web/API/Request
https://developer.mozilla.org/en-US/docs/Web/API/Request/formData
https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
https://processwire.com/talk/topic/27260-input-post-vs-js-fetch/
https://processwire.com/talk/topic/22516-when-using-js-fetch-it-seems-config-ajax-is-ignored/
PHP-Archivos
https://image.intervention.io/v2/introduction/configuration
https://www.hostinger.com/tutorials/what-is-php-ini/
https://www.php.net/manual/es/reserved.variables.files
https://www.php.net/manual/es/features.file-upload.post-method.php
PHP-Network
https://www.php.net/manual/es/function.http-response-code.php