Archives: marzo 2017

Solucionar problema de Autoridad de Certificación en los binarios .EXE generados con PY2EXE

Últimamente he empezado a utilizar la herramienta py2exe para poder mejorar la distribución de mis desarrollos en Python en sistemas Windows, mediante la generación de un ejecutable .exe autocontenido que evite tener que pedir al usuario que instale Python y las correspondientes librerías.

La herramienta py2exe puede ser instalada de manera sencilla con el comando:

pip install py2exe

Y puedes compilar rápidamente cualquier programa en Python realizando un fichero setup.py simple como este:

from distutils.core import setup
import py2exe

setup(console=['mi_programa.py'])

El cual deberemos ejecutar desde una consola de comandos de la siguiente forma:

python setup.py install

Al ejecutarlo nos resolverá todas las dependencias y dejará la versión autcontenida con el ejecutable mi_programa.exe en la carpeta dist.

Si trabajas con servicios o mínimamente con conexiones seguras haciendo peticiones, seguro que acabas usando la librería requests o puede que la librería con la que trabajes, la use como base para hacer conexiones seguras con SSL. Si intentas usar estas librerías que trabajan con certificados SSL te encontrarás con el siguiente error cuando ejecutas tu binario .exe:

requests.exceptions.SSLError: [Errno 2] No such file or directory

El cual viene dado porque cuando se empaqueta todo, el certificado de la Autoridad de Certificación no se incluye al ser un fichero que no es de Python. A causa de esto, cuando se empaquetan todas la librerías y se llama de manera relativa al certificado de la librería desde nuestro empaquetado con nuestro binario .exe de py2exe, éste no se encuentra porque ninguna parte.

Para solucionarlo es tan sencillo como proporcionar un certificado válido del tipo cacert.pem en la variable de entorno de Python REQUESTS_CA_BUNDLE de nuestro programa. Pero para resolverlo, vamos a hacerlo de manera elegante parcheando dicha variable sólo si es necesario, para poder seguir tirando de los certificados de las propias librería mientras desarrollamos.

Para ello vamos a instalar certifi, una librería que nos facilita una serie de Certificados Raíz que nos van a permitir validar la integridad de certificados, tanto de SSL, como de TSL de los servicios a los que nos conectemos.

pip install py2exe

Ahora vamos a modificar un poco nuestro setup.py:

from distutils.core import setup
import py2exe
import certifi

setup(console=['mi_programa.py'], data_files=[certifi.where()])

Hemos añadido únicamente el import de certifi y en los parámetros de setup hemos añadido el fichero de certificado que nos devuelve certifi gracias al método where. Este fichero se copiará en dist al mismo nivel que nuestro .exe.

Por último añadiremos en mi_programa.py al inicio de nuestro programa el siguiente código:

cacert_path = os.path.join(os.getcwd(), 'cacert.pem')
if os.path.exists(cacert_path):
    os.environ['REQUESTS_CA_BUNDLE'] = cacert_path

El código genera la ruta hasta certificado cacert.pem, usando el directorio de trabajo que será dist. Esa ruta se busca si existe entre las rutas que maneja Python para resolver las librerías y dependencias. Si se ejecuta desde el entorno de desarrollo, encontrará el de la propia librería que lo este usando, sino parcheará añadirá la ruta para que coja certificado que hemos copiado en dist.

De esta forma no sólo se soluciona el problema del certificado de la Autoridad de Certificación, sino que el parche se aplica selectivamente copiando fichero de certificado necesario. Esto es importante porque los certificados pueden cambiar y basta con volver a generar los binarios .exe con py2exe, teniendo las librerías actualizadas y con los certificados en regla, para que el ejecutable creado también los tenga.

Frase Memorable 9


Zygmunt Bauman dijo, “El regalo más importante que puedes hacer a la gente que quieres, es darles el sacrificio de tu tiempo”.


Buenas Prácticas para el Buen Diseño de una API RESTful

Según pasa el tiempo y vas haciendo servicios que tienen que escalarse e integrarse con otros servicios, te vas dando cuenta de lo necesario que es un buen diseño o por lo menos de un diseño mínimamente bien pensando y ejecutado que cumpla una serie de requisitos mínimos. Ésta es la idea que pretendo transmitiros, pero antes os ubico un poco.

En la entrada de Linkero – Creación de APIs RESTful en Python os presenté el framework Linkero que permitía montar APIs RESTful, y se presentaba junto con un ejemplo a forma de iniciación en las mismas.  En este entrada voy presentar una serie de recomendaciones, que pueden considerarse buenas prácticas, y que debéis tener en cuenta para asegurar un buen diseño de vuestras APIs RESTful.

El concepto subyacente de una API RESTful es la de dividir la estructura de la API en recursos lógicos, montados sobre una URL, que nos permitan acceder a información o datos concretos usando métodos HTTP como POST, GET, PUT y DELETE para operar con los recursos. Estos métodos son equiparables a las operaciones CRUD (Create, Read, Update, Delete) de las bases de datos. Aclarado esto, empecemos con las recomendaciones.

 

Usar nombres pero no verbos


Por cuestiones de mejor comprensión y legibilidad de la semántica de la API, usa sólo nombres para toda su estructura.

RecursoPOST
create
GET
read
PUT
update
DELETE
/carsCrea un nuevo cocheDevuelve la lista de cochesActualización en bloque de cochesBorrar todos los coches
/cars/711Método no permitido (405)Devuelve un coche específicoActualiza un coche específicoBorra un coche específico

No usar verbos como:

/getAllCars
/createNewCar
/deleteAllRedCar

La propia semántica que compone la URL, junto con el método  HTTP usado (POST, GET, PUT, DELETE), permite prescindir de verbos como "create", "get", "update" y "delete".

 

Métodos GET y los parámetros de consulta no deben alterar el estado


Usa los métodos POST, PUT o DELETE para cambiar los estados de los recursos, en vez de hacerlo con el método GET o un petición con parámetros de consulta. Para algo tienes 3 métodos que te permiten hacer eso 😉 .

Esto es lo que no se debería hacer:

GET /users/711?activate
GET /users/711/activate

Es mucho mejor hacer esto:

PUT /users/711/activate

o esto:

POST /users/711/activate

Cuál usar dependerá del diseño de la base de datos o la lógica con la que se estructura el almacenamiento de la misma.

 

Usar nombres en plural


No mezcles nombres en singular con nombres en plural. Hazlo simple y mantén los nombres en plural.

En vez de usar:

/car
/user
/product
/setting

Usa el plural:

/cars 
/users 
/products 
/settings

El uso del plural te permitirá posteriormente hacer operaciones del tipo:

/cars/<id> 
/users/<id> 
/products/<id> 
/settings/<name>

 

Usar subrecursos para establecer relaciones


Si un recurso está relacionado con otro, usa subrecursos montados sobre la estructura de la URL.

GET /cars/711/drivers/ Devuelve una lista de conductores para el coche 711
GET /cars/711/drivers/4 Devuelve el conductor #4 para el coche 711

 

Usar cabeceras HTTP para la serialización de formatos


Tanto la parte cliente como la del servidor, necesitan saber en qué formato se están pasando los datos para poder comunicarse. Lo más sencillo es especificarlo en la cebecera HTTP.

Content-Type: Define el formato de la petición.
Accept: Define la lista de formatos aceptados en la respuesta.

 

Usar HATEOAS


El uso de HATEOAS (Hypermedia as the Engine of Application State) es un diseño que nos permite incluir el principio de hipervínculos de manera similar a la navegación web, lo que nos permite una mejor navegación por la API.

En este ejemplo la respuesta en formato JSON nos devuelve junto con la información del equipo con id 5000, una serie de referencias a recursos relacionados, como pueden ser el estadio del equipo o sus jugadores. Normalmente se suele agrupar todas las referencias dentro de una propiedad o atributo llamada "links".

{
    "links": [{
        "rel": "self",
        "href": "http://localhost:8080/soccer/api/teams/5000"
    }, {
        "rel": "stadium",
        "href": "http://localhost:8080/soccer/api/teams/5000/stadium"
    }, {
        "rel": "players",
        "href": "http://localhost:8080/soccer/api/teams/5000/players"
    }],
    "teamId": 5000,
    "name": "Real Madrid C.F.",
    "foundationYear": 1902,
    "rankingPosition": 1
}

Este tipo de diseño está pensado para la longevidad del software y la evolución independiente, ya que frecuentemente en la arquitectura de sistemas y servicios, es a corto plazo lo que más suele fallar.

 

Proveer filtrado, ordenación, selección de campos y paginación para colecciones


 

Filtrado

Utilizar un parámetro de consulta único para todos los campos o un lenguaje de consulta formalizado para filtrar.

GET /cars?color=red Devulve una lista de coches rojos
GET /cars?seats<=2 Devuelve una lista de coches con un máximo de 2 plazas

 

Ordenación

Permitir ordenación ascendente o descendente sobre varios campos

GET /cars?sort=-manufactorer,+model

En el ejemplo se devuelve una lista de coches ordenada con los fabricantes de manera descendiente y los modelos de manera ascendente.

 

Selección de campos

Permitir la selección de unos pocos campos de un recurso si no se precisan todos. Dar al consumidor de la API la posibilidad de qué campos quiere que se devuelvan, reduce el volumen de tráfico en la comunicación y aumenta la velocidad de uso de la API.

GET /cars?fields=manufacturer,model,color

En el ejemplo se pide la lista de coches, pero pidiendo sólo los campos de "fabricante", "modelo" y "color".

 

Paginación

Uso de "offset" para establecer la posición de partida de una colección y "limit" para establecer el número de elementos de la colección a devolver desde el offset. Es un sistema flexible para el que consume la API y bastante extendido entre los sistemas de bases de datos.

GET /cars?offset=10&limit=5

En el ejemplo se devuelve una lista de coches correspondiente a los coches comprendidos de la posición 10 a la 15 de la colección.

Para devolver al consumidor de la API el número total de entradas se puede usar la cabecera HTTP  "X-Total-Count" y para completar la información proporciona, por ejemplo, devolver los enlaces a la página previa y siguiente, entre otros, usando la cabecera HTTP "Link".

Link: <http://localhost/sample/api/v1/cars?offset=15&limit=5>; rel="next",
<http://localhost/sample/api/v1/cars?offset=5&limit=5>; rel="prev",
<http://localhost/sample/api/v1/cars?offset=50&limit=3>; rel="last",
<http://localhost/sample/api/v1/cars?offset=0&limit=5>; rel="first",

Como alternativa es posible usar HATEOAS, como ya he comentado anteriormente.

 

Versionar la API


Es muy recomendable, tirando a obligatorio 😉 , el poner versión de API y no liberar APIs sin versión. Usa un simple número para especificar la versión y no uses un versionado tipo semver del tipo 1.5.

Si se usa la URL para marcar la versión, pon una "v" precediendo el número de versión.

/blog/api/v1

Es la forma más sencilla y eficaz de asegurar la compatibilidad en el versionado de la API. Especificarla como parámetro o en la petición y/o en la respuesta, conlleva el aumento del tráfico y el  coste de  computación, al tener que existir lógica para discriminar las distintas versiones soportadas sobre una misma URL.

 

Manejar errores con código de estado HTTP


Es difícil trabajar con una API que ignora el manejo de errores, por no decir imposible 😉 . La devolución de un código HTTP 500 para cualquier tipo de error o acompañado de una traza de error del código del servidor no es muy útil, además de que puede ponernos en peligro ante una vulnerabilidad que un atacante quisiera explotar.

 

Usar códigos de estado HTTP

El estándar HTTP proporciona más de 70 códigos de estado para describir los valores de retorno. En general no los utilizaremos todos, pero se debe utilizar por lo menos un mñinimo de 10, que suelen ser los más comunes:

CódigoSignificadoExplicación
 200 OK Todo está funcionado
 201 OK Nuevo recurso ha sido creado
 204 OK El recurso ha sido borrado satisfactoriamente
 304 No modificado El cliente puede usar los datos cacheados
 400 Bad Request La petición es inválida o no puede ser servida. El error exacto debería ser explicado en el payload de respuesta
 401 Unauthorized La petición requiere de la autenticación del usuario
 403 Forbidden El servidor entiende la petición pero la rechaza o el acceso no está permitido
 404 Not found No hay un recurso tras la URI
 422 Unprocessable Entity Debe utilizarse si el servidor no puede procesar la entidad. Por ejemplo, faltan campos obligatorios en el payload.
 500 Internal Server Error Los desarrolladores de APIs deben evitar este error. Si se produce un error global, el stracktrace se debe registrar y no devolverlo como respuesta.

 

Usar el payload para los errores

Todas las excepciones deben ser asignadas en un payload de error. A continuación se muestra un ejemplo de cómo podría verse un payload de error en formato JSON:

{
  "errors": [
   {
    "userMessage": "Sorry, the requested resource does not exist",
    "internalMessage": "No car found in the database",
    "code": 34,
    "more info": "http://dev.mascandobits.es/blog/api/v1/errors/12345"
   }
  ]
}

 

Permitir la sustitución del método HTTP


Algunos proxys sólo admiten métodos POST y GET. Para que una API RESTful funcione con estas limitaciones, la API necesita una forma de sustituir el método HTTP.

Una opción es utilizar la cebecera HTTP "X-HTTP-Method-Override" para sobrescribir el método POST y así personalizar la petición POST para que cumpla, por ejemplo, una operación DELETE.

X-HTTP-Method-Override: DELETE

 

Espero que esta concisa guía os permita realizar APIs RESTful con un mejor diseño, o mejorar aquellas que ya tengáis desarrolladas, eso sí generando una nueva versión de la API 😉 .