Cómo recibir request en PHP con el content-type JSON, acceder a los datos del body de la solicitud y devolver datos evitando restricciones de CORS.
En este artículo te resolvemos varias dudas frecuentes, de programación del lado del servidor, cuando quieras montar un API rudimentaria en PHP y recibir request venidas desde frameworks Javascript que envían datos JSON.
Se trata de un artículo técnico que vamos a englobar dentro del Manual de desarrollo de APIs y que viene a aclarar algunas dudas básicas para desarrolladores de PHP que desean crear su propio sistema REST desde cero. Es un problema habitual en PHP, que surge cuando queremos montar un API rudimentaria, que responda request originadas desde frameworks Javascript como Angular, Polymer, React, Vue, etc.
Si nos ponemos a programar el API en PHP (si la podemos llamar así, porque lo único que buscamos de momento es atender requests por Ajax y devolver respuestas en JSON), podremos encontrarnos ante dos problemas fundamentales que queremos resolver. Son los siguientes:
- PHP no recibe los datos por el mecanismo POST habitual ($_POST)
- En el lado del cliente recibimos un error CORS: No 'Access-Control-Allow-Origin' header is present
Recepción de los datos en PHP con php://input
Habitualmente los frameworks Javascript, cuando quieren enviar datos al servidor por POST, usan un mecanismo diferente que el montado de fábrica en los navegadores cuando se envía un formulario. Esto es porque el framework de turno compone la solicitud HTTP usando cabeceras y cuerpo de la request diferentes de las que se utilizan al enviar un formulario tradicional.
Cuando envías datos POST por un framework generalmente usas una request con el content-type "application/json" y los datos en si viajan en el body de la request, codificados con formato JSON.
Esto provoca que, cuando se intenta acceder a los datos mediante el mecanismo habitual que usamos para los formularios, observamos que el array $_POST está vacío. Aunque la situación no tiene nada de excepcional sí que resulta llamativa, puesto que en el framework Javascript de turno hemos solicitado que se envíen los datos con el method "post" y observamos que esos datos no están llegando a PHP con $_POST.
Realmente la solución es sencilla y se basa en recibir los datos por un mecanismo diferente. En PHP realizaremos la siguiente operación.
$JSONData = file_get_contents("php://input");
$dataObject = json_decode($JSONData);
Con la primera estamos trayendo el body de la request que nos manda el framework. Esto los devuelve una cadena en formato JSON, ya que el framework nos ha mandado datos en crudo, y además la solicitud viene con el content-type "application/json".
En la segunda línea se hace una decodificación del JSON para obtener un objeto nativo de PHP, con el que podremos trabajar para obtener los correspondientes valores.
Por ejemplo si el JSON tuviera propiedades como "nombre" y "edad", podríamos acceder a ellos mediante el objeto PHP:
echo $dataObject->nombre;
echo $dataObject->edad;
Solucionar los problemas de CORS
CORS son las siglas de "Cross-origin resource sharing" y es básicamente una restricción de acceso a recursos que están localizados en otros dominios.
Generalmente, cuando desarrollas una aplicación con tu framework Javascript, usas dominios distintos para lo que es el servidor de los archivos de tu App y el servidor de PHP. Por ejemplo, tienes el servidor de tu app en un dominio como http://127.0.0.1:8081/ o http://localhost:8080. Pero sin embargo el archivo al que dirigimos las request Ajax, en nuestra aplicación PHP, es algo como http://localhost/post.php.
Esta situación, de uso de dominios distintos en producción también es habitual. Tu página o aplicación puede estar en https://example.com y tu servidor de API puede estar en https://api.example.com.
En resumen, por las restricciones de CORS, por motivos relacionados con la seguridad, no se puede acceder a recursos (APIs) desde Javascript que se encuentren en otros dominios. Esto puede ser porque uses un subdominio distinto o que uses un mismo servidor pero atendiendo en puertos distintos.
Para evitar que la barrera de CORS nos afecte, el servidor tiene que emitir unas cabeceras HTTP en la respuesta. Estas cabeceras le dicen al navegador que ciertos recursos sí que van a estar disponibles desde otros dominios distintos del habitual. Así que desde PHP es tan sencillo como escribir unas pocas líneas de código para el envío de esas cabeceras. Así nuestra API estará disponible para el acceso desde otros dominios.
Las cabeceras en sí se consiguen con este código.
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
Como puedes apreciar, son varias cabeceras las que tenemos que configurar. Algunas veces no te harán falta todas, pero no está de más colocarlas así, si es que quieres desactivar las restricciones CORS.
Resumimos ahora las cabeceras implicadas, para que tengas algo más de información.
Access-Control-Allow-Origin
En la primera cabecera estamos diciendo que se permita el acceso a los recursos desde cualquier origen (cualquier dominio, aunque se podría especificar sobre qué dominios en particular, si es que solo queremos permitir el acceso desde determinados dominios).
Esta primera cabecera es la más importante y la que primeramente percibirás que te está faltando. En su ausencia, el navegador te marcará el siguiente error: "Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource."
Access-Control-Allow-Headers
La segunda dice que se acepten diversos content-type, como el "application/json" que usan los frameworks habitualmente.
Esta cabecera también es fundamental y si no la colocas el navegador te lo marcará con el el error: "Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response"
Access-Control-Allow-Methods
La tercera dice que se permitan diversos métodos de conexión que se suelen usar la comunicación con el servicio web, para las distintas operaciones del API. Estos son GET, POST, PUT y DELETE.
Esta tercera cabecera en principio parece que no es necesaria, ya que el servidor permite el acceso con métodos como POST de manera predeterminada, pero nunca está de más indicarlo en tu API.
Definir la respuesta con content-type: application/json
Ya puestos a intentar ser lo más correctos posible, si tu código PHP devuelve al cliente datos en JSON, lo que es común en el desarrollo de un API RESTful, tendrás que agregar esta otra cabecera.
header('content-type: application/json; charset=utf-8');
No colocar esta cabecera en la respuesta del servidor generalmente no te producirá un problema y el framework Javascript podrá entender que los datos son en formato JSON, aunque el content-type que le envíe el servidor sea otro. Sin embargo, podría darse el caso que tu framework se despiste si no la colocas que crea que la respuesta le está llegando en texto plano, no haciendo la decodificacón del código en un objeto Javascript.
En resumen, falle o no el framework al reconocer el formato de la respuesta, lo correcto desde PHP sería informar del tipo de contenido correctamente a cualquier cliente que haga una request contra nuestra API.
Conclusión
Espero que estas indicaciones te resuelvan los problemas más comunes que puedes tener al desarrollar APIs con PHP. Recuerda que si usas un framework PHP puedes evitar algunas de estas complejidades de desarrollo de API, ya que éstos suelen hacer mucho trabajo por su cuenta.
Hace poco tuvimos un evento que explicaba cómo desarrollar API RESTful con Laravel. Te recomendamos ver el vídeo de la clase si te interesa saber más sobre las ventajas de desarrollar un API apoyado por un framework PHP.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...