Está en la página 1de 15

Tecnicatura Universitaria en Desarrollo de Software

Programación 2
Módulo 3: Backend
Tema 3: Routing
3.3. Routing
3.3.1. Introducción

3.3.2. Comportamiento de redireccionamiento

3.3.3. Rutas dinámicas y reglas de variables

3.3.4. Métodos HTTP

3.3.5. Comunicación entre cliente y servidor

3.3.5.1. Hacer una petición (request)

3.3.5.2. Enviar una respuesta (response)


3.3.1. Introducción
Las aplicaciones web hacen uso significativo de las URL’s (Uniform Resource Locators) para suministrar
recursos a sus clientes. Como ya hemos visto previamente, en el ámbito del desarrollo de aplicaciones
web cuando hablamos de recursos nos referimos a la información proporcionada a los clientes.
Pudiendo ir desde representaciones de datos a través de texto o números, hasta archivos multimedia
como imágenes o vídeos, entre otros tipos de datos.

El proceso para determinar que recurso debe ser proporcionado a un cliente se denomina routing.
Esencialmente, el routing se encarga de dirigir las peticiones de los clientes a las funciones o
controladores adecuados, que se encargarán de proporcionar la respuesta correspondiente del recurso
solicitado.

En Flask, crearemos una conexión entre una URL y una función que retorne la respuesta a una petición
específica. Es decir, cuando se realice una petición a nuestra aplicación web, la respuesta devuelta será
aquella que hayamos definido en la función asociada a esa URL. Para ello haremos uso del decorador
@route para enlazar una función a una URL. Anteriormente hemos visto una de estas definiciones, pero
ahora profundizaremos en el tema.

@app.route('/')
def hello_world():

return 'Hola Mundo!'

Si recordamos al realizar una petición a la URL http://127.0.0.1:5000/ recibimos como


respuesta el mensaje “Hola Mundo!” en nuestro navegador. Esto se debe a que la función
hello_world está asociada a la URL / mediante el decorador @route y por lo tanto será la función
que se ejecute cuando se realice una petición a dicha URL.

Observación: la ruta / es conocida como ruta raíz, y es la ruta que se emplea por defecto
cuando se accede a una aplicación web. Es decir, que si no se especifica una ruta en particular,
se accederá a la ruta raíz.

De manera similar podríamos definir nuevas rutas para los endpoints que necesitemos y enlazarlos con
alguna función en particular. Por ejemplo, si deseamos que al visitar la URL
http://127.0.0.1:5000/academia nos muestre el mensaje “Bienvenido a Academia!”
deberemos definir una función que retorne dicho mensaje y asociarla a la ruta /academia mediante
el decorador @route.

@app.route('/academia')

def bienvenida():

return 'Bienvenido a Academia!'

Y así sucesivamente, podemos definir tantas rutas como necesitemos para nuestra aplicación web.

Es importante hacer algunas aclaraciones antes de continuar:

1. Sino especificamos un método HTTP en el decorador @route este se establece por defecto
como GET. Posteriormente veremos cómo manejar diferentes métodos HTTP en una misma
ruta.
2. No pueden existir dos funciones asociadas con un mismo endpoint bajo el mismo método
HTTP. Es decir, que en nuestro ejemplo no podríamos tener otra función asociada con la
ruta /academia, ni tampoco con /hola bajo el método GET. En caso de que esto suceda,
Flask devolverá un error. Por ejemplo, si definimos la siguiente función:

@app.route('/academia')
def bienvenida():

return 'Bienvenido a Academia!'

@app.route('/academia')
def bienvenida2():

return 'Bienvenido a Academia 2!'

Al intentar acceder a la URL http://127.0.0.1:5000/academia se lanzará un error.

3. Es posible manejar múltiples rutas con una sola función. En otras palabras podemos tener
varias rutas para un mismo endpoint, para lo cual apilaremos los decoradores de rutas sobre
la función a la cual queremos asociarlos. Por ejemplo, si deseamos que al visitar la URL
http://127.0.0.1:5000/academia o http://127.0.0.1:5000/home nos
muestre el mensaje “Bienvenido a Academia!” deberemos definir una función que retorne
dicho mensaje y asociarla a las rutas /academia y /home mediante el decorador @route.

@app.route('/academia')
@app.route('/home')
def bienvenida():

return 'Bienvenido a Academia!'

4. En Flask, el tipo de respuesta que retornan estas funciones tiene formato HTML por defecto.
Aunque podemos devolver otro tipo de respuesta de ser necesario, como JSON, XML, etc.
Veremos cómo hacerlo más adelante.

3.3.2. Comportamiento de redireccionamiento


Para hablar de este tema veamos primero la definición las siguientes rutas:

@app.route('/help/')
def help():

return 'Soporte de la aplicación'

@app.route('/about')
def about():

return 'Información acerca de la aplicación'


Podemos ver que los endpoints difieren en una barra diagonal (/) al final. Esta pequeña diferencia
provocará comportamientos distintos en nuestra aplicación Flask.

Endpoint /help/:
En este caso, tienes una ruta definida como /help/. Si un usuario intenta acceder a la ruta sin la barra
diagonal (/help), Flask realizará una redirección automática (un redireccionamiento 301 Moved
Permanently) a la ruta canónica que has definido (/help/). Esto ayuda a garantizar que los usuarios
siempre sean redirigidos a la versión canónica de la URL, lo que puede ser útil para la consistencia de las
rutas y para fines de SEO (Search Engine Optimization).

Una ruta canónica se refiere a la URL preferida y estandarizada de una página web, utilizada
para mantener la consistencia y redirigir a los usuarios a una única versión de esa URL. Un
ejemplo de una ruta canónica sería asegurarse de que tanto example.com/page como
example.com/page/ redirijan a la misma URL canónica example.com/page/.

Endpoint /about:

Para la ruta definida como /about. Si un usuario intenta acceder a la ruta incluyendo una barra diagonal
al final (/about/), Flask no realizará una redirección y, en cambio, devolverá un error 404 Not Found.
Esto se debe a que la ruta definida no coincide exactamente con la URL solicitada, lo que puede indicar
que la URL es incorrecta o que no existe una ruta correspondiente.

En definitiva, este mecanismo de URLs únicas permite establecer un comportamiento específico para el
redireccionamiento y el manejo de rutas canónicas en una aplicación Flask. Estas convenciones pueden
ser utilizadas para dirigir a los usuarios a las versiones correctas y consistentes de las URLs. Sin embargo,
es importante tener en cuenta que estas convenciones no son obligatorias y que pueden ser modificadas
según las necesidades de la aplicación.

3.3.3. Rutas dinámicas y reglas de variables


Hasta el momento hemos visto como crear rutas estáticas, lo cual puede llegar a ser limitante en muchas
ocasiones. Para ejemplificar esto supongamos la siguiente problemática:

Tenemos una aplicación que cuenta con los datos de cada usuario registrado en la misma, y
deseamos dar un mensaje de bienvenida personalizado a cada uno de ellos. Por ejemplo, si el
usuario se llama “carlitos” podríamos brindar la respuesta “Bienvenido, carlitos”.

Si usaremos rutas estáticas, podríamos definir una ruta como:

@app.route('/perfil')
def perfil():

return 'Bienvenido carlitos!'

Pero esta ruta sólo funciona para carlitos. De esta manera, tendríamos que definir una nueva ruta
por cada usuario registrado, lo cual es inviable.

Como solución a esto, deberemos definir rutas dinámicas siguiendo las reglas establecidas en Flask.

Podemos agregar secciones de variables en un endpoint, marcando las secciones con el nombre de una
variable entre corchetes angulares.
Ejemplo de sintaxis:
@app.route('/<nombre_de_variable>').

Luego la función asociada al endpoint recibe una variable como un argumento clave-valor.

Retomando el ejemplo previamente propuesto, podemos reformular la función perfil para que reciba
como parámetro una variable incluida en nuestro endpoint. En este caso la nombramos <username>:

@app.route('/perfil/<username>')
def perfil(username):

"""Mensaje de bienvenida a un usuario"""

return f'Bienvenido {username}!'

Con esta nueva definición podemos tener un mensaje de bienvenida personalizado para cada usuario.
Por supuesto, este ejemplo es muy simple; pero en la realidad esta ruta podría desembocar en una
consulta a una base de datos, la cual extraiga información de este usuario.

Como vemos será de suma utilidad poder definir rutas dinámicas.

Opcionalmente, podemos usar un convertidor para especificar el tipo del argumento como
<convertidor:nombre_de_variable>.

Modificando el ejemplo anterior, la función perfil puede recibir como parámetro una variable de tipo
entero en lugar de una cadena. Este entero representará un dato que permita identificar a nuestro
usuario, por ejemplo un ID interno con el cual se registró en nuestra aplicación, como
<int:user_id>:

@app.route('/perfil/<int:user_id>')
def perfil(user_id):

"""Mensaje de bienvenida a un usuario"""

return f'Bienvenido {user_id}!'

Los tipos que podemos indicar como convertidor son:

Tipo Descripción
string Sino especificamos un tipo de convertidor este se establece por defecto. Acepta
cualquier cadena sin el carácter '/'
int Acepta enteros positivos
float Acepta valores de punto flotante positivos
path Similar a string, pero pueden incluir al carácter '/'
uuid Acepta cadenas UUID

3.3.4. Métodos http


Como hemos mencionado en anteriormente, una aplicación web necesitará usar diferentes métodos
HTTP para acceder a ciertas URLs. Por lo tanto, será fundamental familiarizarnos con métodos HTTP con
los que trabajaremos en Flask.
Por defecto, una ruta responde a una petición GET. Es decir, hasta el momento todas las rutas que hemos
definido sólo funcionarán para peticiones de este tipo.

Para indicar cuales métodos HTTP manejará un endpoint debemos usar el parámetro methods en el
decorador route().

from flask import request

@app.route('/login', methods=['GET', 'POST'])

def login():

if request.method == 'POST':

return logearse()

else:

return mostrar_formulario()

En el ejemplo anterior el endpoint /login admite los métodos HTTP GET y POST. Además, podemos
notar que dependiendo del método con el cual se realizó la petición se ejecutará una función u otra.
Intentará iniciar la sesión del usuario si el método empleado en la petición fue POST, y en caso contrario,
devolverá un formulario donde ingresar sus datos.

Hemos de mencionar que Flask permite separar estas tareas empleando diferentes decoradores en lugar
de agregar route(). Cada decorador se relaciona con un método HTTP, como get(),post(),
put(), etc.

Reformulemos el ejemplo anterior empleando estos decoradores:

@app.post('/login')
def login_post():

return logearse()

@app.get('/login')
def login_get():

return mostrar_formulario()

3.3.5. Comunicación entre cliente y servidor


Como sabemos, en una aplicación web el cliente envía peticiones para recibir o modificar recursos, y el
servidor envía respuestas a esas peticiones. Veremos a continuación las maneras estándar para hacer
peticiones y enviar respuestas.

3.3.5.1. Hacer una petición (request)


Una aplicación web que cuente con una arquitectura REST requiere que un cliente haga una petición al
servidor para recibir o modificar datos en el servidor. Por lo cual es crucial reaccionar a las peticiones
que el cliente envía al servidor. En Flask, podemos acceder a las peticiones mediante el objeto global
request.

El objeto request es utilizado de forma predeterminada en Flask. Este nos permite acceder a los datos
transmitidos en las peticiones del cliente y así poder responder a las mismas.

Este objeto puede ser usado para acceder a los datos de un formulario mediante el atributo form; a los
parámetros enviados en la petición, a la cabecera de la petición mediante el atributo headers, o como
vimos en ejemplos anteriores, al método que ha sido empleado en la petición mediante el atributo
method; entre otros atributos y métodos. En particular nos gustaría mencionar como acceder a los
parámetros de la petición.

Vale la pena señalar que existen varias formas de proporcionar datos mediante parámetros en un
endpoint. Lo que nos lleva a establecer la siguiente clasificación para ellos.

Parámetros de ruta (Path Parameters)

Un parámetro de ruta vive dentro de un endpoint, para ayudar a reducir el alcance de la llamada a un
recurso individual. Es decir, que permite a una ruta indicar de manera específica sobre que recurso se
quiere operar. Por ejemplo, anteriormente definimos el endpoint /perfil/<int:user_id>, el cual
mostraba un mensaje de bienvenida a un usuario en concreto (identificado mediante un ID único). En
este caso la operación a realizar sería mostrar un mensaje, y al emplear el parámetro de ruta
<int:user_id> reducimos su alcance a un usuario en concreto.

Esto es bueno porque evita tener que construir un cuerpo solo para proporcionar algo tan simple como
un identificador de recursos, puesto que no es requerido ningún otro dato adicional.

Como hemos visto anteriormente, en Flask podemos acceder a este parámetro de ruta como si de una
variable se tratase y de dicha manera podemos acceder a ella en la definición de la función asociada a
ese endpoint.

Parámetros de consulta (Query Parameters)

Estos parámetros son empleados para modificar el alcance de una petición, y así reflejar un subconjunto
de recursos. En otras palabras, al emplear query params buscamos que una petición se realice sobre
ciertos recursos en particular, brindando así una finalidad más específica a una ruta definida. Además, a
veces son necesarios varios datos para poder completar la tarea definida para endpoint. Por lo cual
podemos indicarlos empleando este tipo de parámetro, en lugar de incluirlos como parte del endpoint
en si mismo, como hacíamos con los parámetros de ruta.

Construir una consulta es sencillo. Para comenzar, se agrega un signo de interrogación (?) al final del
endpoint para indicar que, a continuación, se indicarán los parámetros de consulta. A continuación, se
proporciona el nombre y el valor del parámetro, empleando el formato nombre=valor. Los
parámetros adicionales se separan con un carácter ampersand (&).

Esto le dice al endpoint que filtre los resultados y solo devuelva los que coincidan con uno o más de los
valores de la consulta. Es decir, que aplique la función definida en esa ruta para los datos suministrados
en la consulta.

Por ejemplo, supongamos que deseamos conocer todos los usuarios de la aplicación, para ello podríamos
definir una ruta para el endpoint /users, el cual nos devuelva un listado con todos estos usuarios. Sin
embargo, si deseáramos obtener solo una parte de esta lista, como los usuarios que se registraron hace
menos de un mes este endpoint ya no nos serviría. En lugar de definir un nuevo endpoint para este caso
particular podríamos solicitar más datos en la petición que permitan satisfacer ambas tareas. Este es un
caso de uso para los query params.

Por supuesto, ahora nos queda ver como acceder a estos parámetros en Flask. Antes hemos mencionado
que para acceder a los datos transmitidos en una petición haremos uso del objeto global request
incluido en Flask. Este cuenta con un atributo llamado args, el cual es un diccionario con todos los query
params que se enviaron en la petición HTTP. Aunque existe otra manera de acceder a ellos que Flask
recomienda usar en su lugar, mediante del método get. Veamos ambar implementaciones, sin olvidar
que en los próximos ejemplos emplearemos el método get del atributo args.

Sintaxis accediendo por clave a los valores del atributo args:


dato_buscado = request.args['clave']

Sintaxis usando el método get del atributo args:


dato_buscado = request.args.get('clave', default = '')

El método get devuelve el valor indicado en default si no existen datos relacionados a la clave
ingresada.

Volviendo al ejemplo que planteamos, si queremos una lista de usuarios registrados el último mes
podríamos tener un query parameter llamado register_date y obtener el listado de aquellos que
sean menores a esa fecha.

Siendo ese el caso, podemos definir esta ruta de la siguiente manera.

from flask import request

@app.route('/users')
def get_users():

register_date = request.args.get('register_date','')

if register_date != '':

return get_recents_users(filter = register_date)

else:

return get_recents_users()

En este caso suponemos que la consulta para los usuarios es una tarea que realizará la función
get_recents_users. La cual devolverá los usuarios cuya fecha de registro en nuestra aplicación sea
menor a la fecha suministrada en el parámetro de consulta (query parameter) register_date, pero
en caso de no incluirse este parámetro se obtendrán todos los usuarios registrados sin tener en cuenta
la fecha.

De manera similar, podríamos solicitar otros parámetros de consulta para establecer una tarea más
específica.
Cuerpo de la solicitud (Body Paramaters)

Una petición puede incluir también parámetros de cuerpo debido a que, a veces, la API necesitará que
los datos requeridos en la petición tengan un formato en particular. En esta materia trabajaremos con el
formato JSON, pero podría ser cualquier otro si así lo deseáramos, como XML, YAML, entre otros.

Por lo general, las peticiones que requieren de este tipo de parámetro realizarán un cambio en los datos
del recurso al que se busca acceder. Por ejemplo, si necesitamos registrar un usuario necesitaríamos
enviar los datos del mismo, y en casos de este tipo lo recomendable sería enviar todos esos datos en una
única estructura, un objeto JSON para nosotros.

De esa manera podríamos definir el siguiente objeto JSON para representar al usuario que buscamos
registrar.

{
"username": "Kramer",

"firstname": "Michel",

"lastname": "Richards",

"email": "kramer@gmail.com"

Al enviar la petición correspondiente podemos incluir este objeto JSON como el cuerpo de la misma,
luego al recibir esta petición se empleará este objeto para registrar al usuario en cuestión.

En Flask, si el cuerpo de una petición tiene el formato JSON, podemos acceder a él mediante el atributo
json del objeto global request.
Sintaxis:
body_params = request.json

Este atributo es un diccionario, del cual podemos acceder a sus elementos mediante las diferentes claves
suministradas en el objeto JSON. Con esto podemos definir una ruta para el endpoint
/register_user que solucione la problemática planteada.

@app.route('/register_user')
def register_user():

body_params = request.json

register(

username = body_params.get('username'),

firstname = body_params.get('firstname'),

lastname = body_params.get('lastname'),

password = body_params.get('password')

)
return "Usuario registrado"

Una vez más estamos omitiendo la implementación del registro como tal, debido a que puede darse
sobre una base de datos u otro sistema (tema que veremos posteriormente). Sin embargo, este ejemplo
es más que suficiente para dejar en claro como acceder a los parámetros de cuerpo de una petición.

En conclusión, dependiendo de la finalidad de la petición HTTP, podremos incluir ciertos datos en esa
petición de diferentes maneras:

5. Si necesitamos un recurso o tarea específica lo recomendable será emplear parámetros de


ruta (path parameters).

6. Si en su lugar requerimos filtrar una colección de recursos u operaciones podremos emplear


parámetros de consulta (query parameters), accesibles desde nuestra API en Flask mediante
el atributo args del objeto global request.

7. Por último, siempre que necesitemos organizar los datos suministrados a la API mediante
estructuras complejas, como objetos JSON, recomendaremos emplear parámetros de
cuerpo en el que incluiremos estas estructuras. En Flask, estos serán accesibles mediante
atributo json del objeto global request.

Observación: el objeto global request cuenta con distintos atributos y métodos para acceder
al cuerpo de la petición, pero es recomendable usar hacerlo de la manera descripta
anteriormente.

3.3.5.2. Enviar una respuesta (response)


Cuando el servidor envía una respuesta a un cliente, el servidor debe incluir un content-type en la
cabecera de la respuesta. Este campo le indica al cliente el tipo de datos que están siendo enviados en
el cuerpo de la respuesta. El content-type que el servidor devuelve en la respuesta debería ser uno
de las opciones que el cliente especificó en el campo de tipos aceptados de la petición.

En Flask, el valor retornado por una función que incluya el decorador route es automáticamente
convertido a un objeto de la clase Response.

Por ejemplo, si el valor retornado es una cadena, esta se convierte en objeto response donde el cuerpo
de esta response será la cadena mencionada. Además, este objeto reponse tendrá un código de estado
200 OK y un text/html como MIME type.

Por otro lado, si retornamos un diccionario o una lista, la función jsonify() incluida en Flask será
invocada para producir una respuesta.

En general, podemos establecer los siguientes casos para la conversión de valores retornados a objetos
response.

1. Si el valor de retorno es un objeto de la clase Response este es devuelve directamente, sin


la necesidad de un paso intermedio.

2. Si este valor es una cadena, entonces un objeto response es creado con los datos y
parámetros por defecto

3. Si el valor de retorno es un iterador o generador que devuelve cadenas o bytes, entonces


será tratado como un steaming response (no profundizaremos en este caso).

4. Si el valor es un diccionario o una lista, la función jsonify() incluida en Flask será


invocada para producir una respuesta. Dicha función devolverá el objeto response
buscábamos.

5. Si una tupla es retornada, los elementos en la tupla pueden contener información adicional.
Para esto las tuplas deben tener algunas de las siguientes formas:

– (response, status)
– (response, headers)
– (response, status, headers)
El valor del elemento status de la tupla pasará a ser el código de estado de la respuesta,
y de manera similar, el elemento headers será asignado al a la cabecera de la respuesta
(este último puede ser una lista o un diccionario).

6. Si no se produce ninguno de los casos anteriores, Flask asumirá que el valor de retorno es
una aplicación WSGI y la convertirá en un objeto response (tampoco abarcaremos este caso
en la materia).

Veamos algunos ejemplos de response.

En este caso, la response tiene como cuerpo la cadena 'Bienvenido a Academia!', el código de
estado 200 e indicamos únicamente que la cabecera 'Content-Type' debe ser 'text/html;
charset=utf-8'.

@app.route('/home')
def home():
return ('Bienvenido a Academia!', 200, {'Content-Type':'text/html;
charset=utf-8'})

Hemos de aclarar que esta cabecera es la que Flask establece por defecto. Por lo cual podríamos omitirla
y tener el mismo resultado con una tupla de dos elementos.

@app.route('/home')
def home():

return ('Bienvenido a Academia!', 200)

Por supuesto, podríamos aclarar que el cuerpo de la respuesta debe ser interpretado como JSON en
lugar de HTML, o cualquier otro tipo de transferencia de datos como XML. Veamos este primer caso:

@app.route('/home')
def home():

return ({'message':'Bienvenido a Academia!'}, 200, {'Content-Type'


:'application/json'})

Si probamos este endpoint veremos que la respuesta devuelta por nuestra aplicación tiene un nuevo
formato. Si planeamos devolver JSON Flask recomienda encarecidamente que hagamos uso de
diccionarios para establecer el cuerpo de las respuestas que devolvamos, o bien que serialicemos objetos
mediante la función jsonify. Si hacemos uso de esta estructura de datos no será necesario indicar en
la tupla la cabecera 'Content-Type', ya que se asignará de manera automática.

@app.route('/home')
def home():

return ({'message':'Bienvenido a Academia!'}, 200)

Por supuesto existen muchas otras maneras de responder a una petición, pero esta materia queremos
centrarnos en crear una API que permita devolver JSON. Por lo cual recomendamos prestar especial
atención a este último ejemplo.

Por último, si bien Flask se encarga de hacer estas conversiones de manera automática, siempre
podemos emplear la función make_response() para establecer explícitamente como será la
respuesta que devolveremos a una petición HTTP.
Bibliografía
• Flask - Quickstart
• Flask - Request Object
• Flask - Resonse Object
• Flask - HTTP Methods
• Flask - Adding HTTP Method Overrides

También podría gustarte