Cómo enviar requests con la cookie de sesión mediante Guzzle y Symfony
Gestionar las cookies en las peticiones enviadas mediante la librería Guzzle

Hoy he preparado este artículo corto en el que os hablo de cómo podéis usar Guzzle
para enviar requests (en vez de recurrir al por siempre famoso curl
) pero que os recomiendo que leáis hasta el final (no, de verdad, no es autobombo) ya que el caso de uso que cuento os lo podéis encontrar en algún otro proyecto y así os ahorraréis las 4 horas que estuve yo pegándome hasta dar con la explicación y la solución.
¡Espero que os sirva!
Cómo enviar requests con Guzzle
Lo primero de todo será aprender a enviar requests con Guzzle. Si aún no conocéis esta librería veréis que es muy sencillo usarla (bastante más curl
) y que cuenta con algunas características muy interesantes como una especie de Promises
al más puro estilo Javascript y la posibilidad de configurar middlewares
para evitar tener que repetir determinados pasos en cada request que ejecutemos (por ejemplo, convertir en json
la respuesta. Os animo a leer su documentación porque está bastante bien explicado, aunque es probable que prepare un artículo profundizando más en estos aspectos:
Veamos ahora el código para enviar una petición GET
mediante esta librería:
// Create a client with a base URI$client = new GuzzleHttp\Client(['base_uri' => 'https://foo.com/api/']);// Send a request to https://foo.com/api/test
$response = $client->request('GET', 'test');
Fácil y sencillo. Ahora, si queréis obtener el cuerpo de la respuesta, basta con:
$body = (string)$response->getBody();
y ya podréis manipularla como necesitéis.
La clase GuzzleApiRequest
Con el fin de encapsular las llamadas empleando esta librería, he creado la siguiente clase:
- Como veréis, la clase extiende la interfaz
ApiRequestInterface
que he creado en mi proyecto con el fin de poder cambiar a otra librería si fuese necesario (ya sabéis, open-closed principle). Esta interfaz es tan sencilla como:
- En la linea 22 se encuentra el constructor al cual le estoy pasando la url del backend así como el
path
de la API para crear el cliente de Guzzle con el parámetrobase_uri
apuntando a esa url. - Por otra parte, el método
get
replica el código de antes añadiendo la posibilidad de establecer losqueryParams
de nuestra request y convirtiendo en un json la respuesta obtenida de nuestra API. - Además, capturo la excepción
RequestException
de Guzzle para transformarla en una excepción propia (también llamadaRequestException
) de modo que las clases que empleen la interfazApiRequestInterface
trabajen con las mismas excepciones. - Y finalmente, también he implementado un método
post
para enviar ese tipo de peticiones. En él, compruebo si el array defiles
está vacío para enviar una petición normal o unamultipart
que incluya estos archivos. En la documentación podéis investigar un poco más sobre este tema:
Y con esto ya tendríamos nuestra clase funcionando para enviar peticiones post
y get
.
Enviando la cookie de sesión
Ahora suponed que necesitáis enviar la cookie de sesión con la petición, de modo que la API pueda autenticar la petición y recuperar el usuario que la realizó. El proceso es bastante sencillo (aunque esta vez de he decir que la documentación no fue todo lo clara que debería) y basta con modificar el constructor de la forma que véis a continuación:
Lo que he hecho es añadir dos nuevos parámetros:
RequestStack
de modo que podamos recuperar larequest
actual y con ella las cookies.cookieDomain
para que en el objetojar
que pasaremos al cliente de Guzzle establezcamos el dominio al que pertenecen dichas cookies, algo muy útil en el caso de estemos trabajando con dominios y subdominios.
Finalmente, creo un objeto de la clase CookieJar
con las cookies y su dominio y lo paso al constructor del cliente en el parámetro cookies
. Con esto, las peticiones que enviemos empleando ese cliente ya irán con las cookies que le hayamos pasado.
Pero… y viene el problema…
Nota. Al constructor del cliente de Guzzle también podéis pasarle cookies => true
de modo que creará él solo un JAR donde irá añadiendo las cookies que obtenga en cada petición:
Ah, que quieres abrir dos sesiones a la vez desde el mismo hilo…
Ahora suponed que tenéis la siguiente arquitectura:
- Un backend montado en PHP (en este caso Symfony) que actúa como API
- Un front montado en PHP (en este caso Symfony también) que hace peticiones a la API anterior mediante la clase que acabamos de escribir.
- Un sistema compartido de sesión para el backend y el front (lo que aquí comento me ha pasado con las sesiones guardadas en el sistema de archivos pero creo que el problema será el mismo trabajando con
Memcached
,Redis
o cualquier otro sistema, lo comprobaré la semana que viene) - Apache como webserver (no lo he probado con NGINX).
- Al acceder a la URL
/front-test
del front, enviais con la clase anterior una petición a la url del backend/backend-test
.
Igual os parece algo rebuscada pero creo que en proyectos de tamaño medio no es tan difícil toparse con algo así.
El caso es que, en el momento en que intentéis mandar una petición con las cookies desde el front, el back se quedará colgado. El motivo es el siguiente:
- Apache abre una sesión para el front cuando se accede a
/front-test
- Ahora, en la URL
front-est
del front creamos la petición Guzzle hacia la URLbackend-test
con la cookie de session que obtenemos mediante larequest
. - Apache intenta abrir una segunda sesión para esa cookie pero no puede ya que está bloqueada por la primera.
Tras mucho investigar (pues no di a la primera con la secuencia de hechos que aquí os cuento) la causa del problema y un poquito de ayuda de Stackoverflow, la solución al problema es la siguiente:
- Añadir el servicio
Session
de Symfony al constructor de nuestra claseGuzzleApiRequest
- Cerrar la sesión empleando ese servicio justo antes de enviar la petición:
$this->session->save();
- Reabrirla una vez que la petición se ha completado:
$this->session->start();
Con ello evitaremos intentar abrir dos sesiones simultáneas y todo funcionará como esperamos.
Por ejemplo, el método get
quedaría del siguiente modo:
Puede parecer algo ortodoxo pero he sido incapaz de dar con una solución mejor y por todo lo que he leído, no parece haber otra forma. No obstante, si se os ocurre otra forma de resolver este problema me encantaría leerla.
Gracias a…
Y para terminar os dejo las dos respuestas de stackoverflow que me orientaron hacia la dirección correcta:
¿Quieres recibir más artículos como este?
Suscríbete a nuestra newsletter: