Servicios Web REST
Breve introducción a REST
Contenido de la charla• Qué es REST?
o Definición
o Conceptos
o Verbos HTTP
o Códigos de respuesta
o Ejemplos
• APIs CRUDo Operaciones sobre recursos
o Problemas de concurrencia
o Operaciones asíncronas
• Seguridad en REST
• Testing y pruebas
Sobre mi• Programador web PHP
desde hace 4 años
(aprox. 1 con
Symfony2).
• Muy fan de
NodeJS, Angular y
Javascript en general
• Aficionado a la
seguridad informática y
redes.
Qué es REST?Definición y conceptos
Qué es REST? Es una tecnología?
Qué es REST? Es una tecnología? ✗
Es una arquitectura?
Qué es REST? Es una tecnología? ✗
Es una arquitectura? ✗
Es un “estilo arquitectónico”?
Qué es REST? Es una tecnología? ✗
Es una arquitectura? ✗
Es un “estilo arquitectónico”? ✓
Cuando hablamos de REST nos referimos a una forma
de implementar una arquitectura en concreto
(arquitectura WebService) por eso solemos decir que
REST es sólo un estilo de elaborar servicios web.
Qué significa REST?
REpresentational
State
Transfer
¿Qué es REST?• La primera vez que se habló de REST fue en el año
2000 en la tesis doctoral de Roy Fielding, uno de los
principales autores del protocolo HTTP
• Está orientado a transferencias de recursos, no a
llamadas de procedimientos remotos (RPC)
• Hace un uso muy eficiente del protocolo
HTTP, rompe con el esquema de la web que
funciona solo con GET y POST
REST en la actualidad• A día de hoy se conoce por REST a casi cualquier
servicio Web que trabaje con XML y HTTP
• La mayor parte de las APIs existentes son simples
interfaces para interactuar con la base de datos
(CRUD básico)
• REST nos permite modelar totalmente nuestro
negocio sin importar el lenguaje en que este
implementado el servicio o el cliente
Conceptos y reglas• El servicio representará recursos, no acciones
o No se publican verbos como “comprar”
o Se usan recursos como “pedido” o “compra”
• Todos los recursos deben de poseer un UUIDo Universal Unique Identifier
o Todo recurso en REST necesita un UUID para ser identificado dentro del sistema
• La forma en que se represente internamente un recurso debe ser privada
• Todos los recursos deben de poseer un interfaz de operaciones, y todos deben de implementarlas.
Cómo funciona?• Las operaciones se realizan por transferencia de
estadoo El cliente pide una copia del recurso al servicio
o El cliente modifica el recurso
o El cliente transfiere el recurso actualizado al servicio
• Los recursos deben ser multimediao Los recursos deben de poder expresarse en más de un formato
• JSON, XML, CSV…
o En las peticiones podemos y debemos indicar el tipo de formato que admitimos, o una lista con varios, ordenada por prioridades.
• Según la implementación que se consiga, un API se cataloga en uno de los niveles REST
Verbos HTTP• El protocolo HTTP define un conjunto de verbos que nos
permiten realizar todo tipo de operaciones:
o GET (LEER)
o PUT (CREAR O ACTUALIZAR RECURSO COMPLETO)
o POST (CREAR, ACTUALIZAR PARCIALMENTE UN RECURSO)
o PATCH (ACTUALIZAR PARCIALMENTE UN RECURSO)
o DELETE (BORRAR)
o OPTIONS (CONSULTAR OPERACIONES)
• http://www.restapitutorial.com/lessons/httpmethods.html
• Usarlos incorrectamente lleva a desastres como el de Basecamp.o http://www.mail-archive.com/[email protected]/msg04213.html
POST e idempotencia• POST es él verbo “maldito” por REST, al no ser
idempotente.
• La idempotencia asegura que ejecutar n veces
una transferencia obtendrá el mismo resultado.
• Se usa para modelar operaciones como:o Crear recursos
o Modificar parcialmente recursos…
Códigos de Respuesta• El resultado de una transferencia REST viene dado por el código de
estado de la respuesta HTTP, en REST, los más habituales son:
o 200 (OK)
o 201 (Creado)
o 202 (Aceptado)
o 204 (Sin contenido)
o 304 (No modificado)
o 400 (Petición mal formada)
o 401 (No autorizado)
o 403 (Acceso no permitido)
o 404 (No encontrado)
o 409 (Conflicto)
o 412 (Fallo de pre-condición)
o 500 (Problema de Servidor)
• http://www.restapitutorial.com/httpstatuscodes.html
VEAMOS EJEMPLOS!
EjemplosEn nuestra API de pruebas usaremos:
• GETo Obtener recursos o colecciones de recursos
• POSTo Creación de recursos
• PATCHo Actualizaciones parciales
• PUTo Actualizaciones totales de recursos
• DELETEo Eliminar recursos
• OPTIONSo Consultar acciones disponibles
La URL sobre la que trabajaremos será:
http://symfonyvalencia.es/api/usuarios
El servidor puede devolver datos en XML o en JSON
Transferencia OPTIONSConsultar acciones
Cliente ServidorOPTIONS /api/usuarios/B2K1E3
Host: www.symfonyvalencia.es
HTTP/1.1 200 OK
Allow: GET, PUT, POST, OPTIONS, DELETE, PATC
H
Transferencia GET Obtener una Colección
Cliente ServidorGET /api/usuarios HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
Transferencia GET Obtener una Colección
Cliente ServidorGET /api/usuarios HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
[{
“id”: “X1B4Z3”,
“nombre”: “Miguel Ángel”,
“edad”: 27,
“lenguajes”: [“PHP”, “Javascript”]
},
{
“id”: “AB325J”,
“nombre”: “Ángel Miguel”,
“edad”: 26,
“lenguajes”: [“Ruby”, “Java”]
}]
Transferencia GETObtener un registro
Cliente ServidorGET /api/usuarios/X1B4Z3 HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
Transferencia GETObtener un registro
Cliente ServidorGET /api/usuarios/X1B4Z3 HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
Etag: W/2cc7-3f3ba34cc25d
{
“id”: “X1B4Z3”,
“nombre”: “Miguel Ángel”,
“edad”: 27,
“lenguajes”: [“PHP”, “Javascript”]
}
Transferencia POSTCrear un recurso
Cliente ServidorPOST /api/usuarios HTTP/1.1
Host: www.symfonyvalencia.es
Content-Type: application/x-www-
form-urlencoded
Accept: application/json
nombre=Manolo&edad=26&localidad
=Valencia
Transferencia POSTCrear un recurso
Cliente ServidorPOST /api/usuarios HTTP/1.1
Host: www.symfonyvalencia.es
Content-Type: application/x-www-
form-urlencoded
Accept: application/json
nombre=Manolo&edad=26&localidad
=Valencia
HTTP/1.1 201 Created
Content-Type: application/json
Location: http://symfonyvalencia.es/usuarios/B2
K1E3
Etag: W/2cc7-3f3ba34cc25d
{
”id”: “B2K1E3”
}
Transferencia PUTActualizar un recurso
Cliente ServidorPUT /api/usuarios/X1B4Z3
Host: www.symfonyvalencia.es
Content-Type: application/json
Accept: application/json
{
“nombre”: “Miguel Ángel”,
“edad”: 27,
“lenguajes”: [„PHP‟, „Javascript‟],
“localidad”: “Valencia”
}
Transferencia PUTActualizar un recurso
Cliente ServidorPUT /api/usuarios/X1B4Z3
Host: www.symfonyvalencia.es
Content-Type: application/json
Accept: application/json
{
“nombre”: “Miguel Ángel”,
“edad”: 27,
“lenguajes”: [„PHP‟, „Javascript‟],
“localidad”: “Valencia”
}
HTTP/1.1 204 No Content
Content-Type: application/json
Etag: W/2cc7-3f3ba34cc25d
Transferencia DELETEEliminar un recurso
Cliente ServidorDELETE /api/usuarios/B2K1E3
Host: www.symfonyvalencia.es
HTTP/1.1 204 No Content
API’S CRUDLas API‟s CRUD son el tipo de API más extendido en el mundo
REST
APIs CRUD• Son las APIs más habituales
• CRUDo Create (crear)
o Read (leer)
o Update (actualizar)
o Delete (borrar)
• Cubren las operaciones básicas que realizamos habitualmente sobre entidades
• Implementan todos los verbos HTTP
• Son lo contrario a APIs read-only
• Son bastante sencillas de implementar
• En Symfony2 existen bundles que facilitan su implementación, como FOSRESTBundle
APIs CRUD• Si pretendemos ceñirnos absolutamente al
concepto de API REST, no debemos de diseñar
nuestra API como un reflejo de las entidades, ya
que expone nuestro esquema de base de datos.
• Un cambio en una entidad no debería de afectar
al uso del API.
• Se trata de modelar nuestro negocio con
operaciones sobre recursos no de crear un interfaz
para gestionar nuestra base de datos.
Acceso concurrente• Si a nuestro Web Service se conectan
simultáneamente varias personas y modifican el
mismo registro… ¿Puede haber problemas de
inconsistencia?
• El problema de la inconsistencia en REST se
soluciona habitualmente con la inclusión de
cabeceras Etag y If-Match.
• Se utilizan Etags débiles, ya que las fuertes son
demasiado estrictas para nuestro propósito
Ejemplo 1/3
Cliente ServidorGET /api/usuarios/X1B4Z3 HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
Etag: W/689438a788bbc2e
{
“id”: “X1B4Z3”,
“nombre”: “Miguel Ángel”,
“edad”: 27,
“lenguajes”: [“PHP”, “Javascript”]
}
Ejemplo 2/3
Cliente Servidor (Match OK)PUT /api/usuarios/X1B4Z3 HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
If-Match: W/689438a788bbc2e
Content-Type: application/json
{
“id”: “X1B4Z3”,
“nombre”: “Miguel Ángel Sánchez”,
“edad”: 23,
“lenguajes”: [“PHP”, “Javascript”],
“localidad”: “Valencia”
}
HTTP/1.1 204 No Content
Etag: W/91246ef669ab7
Ejemplo 3/3
Cliente Servidor (Match KO)PUT /api/usuarios/X1B4Z3 HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
If-Match: W/689438a788bbc2e
Content-Type: application/json
{
“id”: “X1B4Z3”,
“nombre”: “Miguel Ángel Sánchez”,
“edad”: 23,
“lenguajes”: [“PHP”, “Javascript”],
“localidad”: “Valencia”
}
HTTP/1.1 412 Precondition Failed
Etag: W/91246ef669ab7
Actualizaciones parciales• Otro problema básico en REST es la actualización
parcial del contenido.
• Si usamos PUT, sobrescribimos todo el recurso, ¿y si solo
queremos cambiar un atributo del recurso?
• El método habitual se basa en crear nuevos tipos MIME
de cambio de estado o diff y usar POST.
• La solución moderna pasa por usar el verbo
PATCH, funciona igual que con POST, pero es más
semántico y solo admite tipos MIME que representen
diffs
Actualizaciones parciales• Cuando hablamos de diffs hablamos de crear un
formato o tipo MIME que represente una operación
de actualización parcial sobre un registro.
• Suelen nombrarse como:o application/recurso-diff+formato
• Recurso es el tipo de entidad (usuario, factura…)
• Formato puede ser cualquier formato (json, xml…)
• Ejemplos:o application/usuario-diff+json
o application/usuario-diff+xml
Ejemplo de actualización
PATCH /api/usuarios/X1B4Z3 HTTP/1.1Host: www.symfonyvalencia.es
Accept: application/jsonIf-Match: W/979c576c5be5fa5Content-Type: application/usuario-diff+json
[{“tipo-cambio”: “sustituir”,
“campo”: “edad”,“valor”: “23”
},{
“tipo-cambio”: “anadir”,“campo”: “localidad”,
“valor”: “Valencia”}]
Operaciones de larga duración o asíncronas
Operaciones de larga duración o asíncronas
Cuando las operaciones no son inmediatas, o deben
ser encoladas porque se procesan en bloque a cierta
hora mediante un cron, o necesitan validación de un
servicio externo (pasarelas de pago, Paypal…), el
usuario debe de poder seguir el estado del recurso.
Con REST es posible modelar operaciones asíncronas
o encolables creando un recurso intermedio que
represente el estado del recurso antes de finalizar su
creación.
Ejemplo• Vamos a realizar un pago a una tienda online vía
REST
• El pago debe de ser validado por la entidad
bancaria que implementa la pasarela de pago.
• El proceso puede tardar entre 3 y 5min
• El cliente desea saber cuando ha sido validado el
pago
Ejemplo 1/4
Cliente ServidorPOST /api/pago HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
Content-Type: application/json
{
“metodo”: “visa”,
“numtarjeta”: 1234567812345678,
“cvv”: 123
“caducidad”: “12/17”
“cantidad”: 2500
}
HTTP/1.1 202 Accepted
Location: /api/cola-pago/XBC3719
{
“location”: “/api/cola-pago/XBC3719”,
“estado”: “pendiente”
}
Ejemplo 2/4
Cliente ServidorGET /api/cola-pago/XBC3719
HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{
“estado”: “pendiente”
}
Ejemplo 3/4
Cliente Servidor (Pago OK)GET /api/cola-pago/XBC3719
HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{
“estado”: “ok”,
“id”: “/api/pago/DEB743AC”
}
Ejemplo 3 bis/4
Cliente Servidor (Pago OK)GET /api/pago/DEB743AC HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{
“metodo”: “visa”,
“numtarjeta”: 1234567812345678,
“cvv”: 123
“caducidad”: “12/17”
“cantidad”: 2500
“estado”: “pagado”
}
Ejemplo 4/4
Cliente Servidor (Error de pago)GET /api/cola-pago/XBC3719
HTTP/1.1
Host: www.symfonyvalencia.es
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{
“estado”: “error”,
“error”: “sin fondos
}
Seguridad en RESTNo todas las API‟s son públicas
Seguridad en REST
• En el contexto de un servicio REST no existe el concepto
de Sesión, ya que HTTP es un protocolo Stateless no
orientado a conexión.
• Si nuestra API necesita autenticar a sus usuarios, estos
deben de hacerlo en cada petición
• Esto simplifica un ataque por parte de intrusos
• Veamos algunas normas básicas de seguridad en REST
1. No uses IDs predecibles
1. No uses IDs predecibles• Cualquier intruso que sniffe nuestro tráfico y
detecte una petición del tipo:
GET /api/usuario/1/datos-bancarios
• Se verá tentado de probar con:
GET /api/usuario/2/datos-bancarios
GET /api/usuario/3/datos-bancarios
GET /api/usuario/n/datos-bancarios
• Tampoco debemos usar las claves primarias de
nuestra base de datos, nos exponemos a ataques
SQLi
2. Usa HTTPS
2. Usa HTTPS• Con el protocolo HTTPS ya tenemos ganado la
mitad
• Es un protocolo seguro y el contenido va cifrado, tanto para peticiones como para respuestas.
• Si transmitimos información sensible no será fácilmente reproducible por un atacante que haya usado un sniffer
• Nos da seguridad a la hora de transmitir contraseñas
3. Genera Tokens de seguridad
3. Genera Tokens de seguridad
• Si creamos nuestro propio esquema de
seguridad, generando Tokens que caduquen cada
cierto tiempo, evitaremos transmitir la contraseña
demasiadas veces.
• Después de la primera autenticación, el servidor
nos transmite nuestro Token
• En las próximas peticiones lo incorporaremos en
una cabecera, o una cookie
4. Usa criptografía hardcore
4. Algoritmo HMAC• Nos puede ayudar a construir buenos Tokens
PARTE_PUBLICA = UUID + “:” + NIVEL_SEGURIDAD + ”:” + TIMESTAMP
FIRMA = HMAC(CLAVE_SECRETA, PARTE_PUBLICA)
TOKEN = FIRMA + ”_” + PARTE_PUBLICA
• Desde el servidor basta con descomponer el Tokenen las componentes FIRMA y PARTE_PUBLICA, volver a calcular la firma con la PARTE_PUBLICA recibida y comparar la firma generada con la recibida
4. Algoritmos ajustables
• Algoritmos como BCRYPT o PBKDF aceptan un
parámetro de “complejidad”, que permiten indicar
cuanto tardará en calcularse la contraseña.
• Esto limita los ataques de fuerza bruta, por
ejemplo, con una complejidad de 500ms solo
podrían probarse 2 passwords por segundo.
5. Usa OAuth
5. Usa OAuth
• Una opción alternativa a generar nuestros tokens es
implementar un servidor OAuth que nos permita
acceder a nuestra API
• Existen librerías en casi todos los lenguajes que
implementan OAuth
• Podemos incluso usar nuestros credenciales en
redes sociales para acceder a nuestra API.
Testing y pruebas
Asegurándonos de que todo anda bien
Testing y pruebas
Testing y pruebas
• Como cualquier otro programa, aplicación web… que desarrollemos, las APIs REST se pueden implementar usando TDD.
• Es importante testear siempre que todos los códigos de respuesta devueltos son adecuados, tanto para casos de éxito como para casos de error.
• IMPORTANTE: Muchos frameworks implementan listenersde excepciones y evitan, por ejemplo que una página devuelva un 404, enmascarando el resultado con una respuesta 200.
Testing con xUnit
• Podemos usar tranquilamente nuestro framework
de pruebas xUnit para testear, no solo los códigos
de respuesta, sino toda la lógica de negocio.
• Las prácticas habituales de testing con este tipo de
herramientas son totalmente válidas para el testeo.
Testing con cURL
Testing con cURL• Es una alternativa para realizar nuestros tests (cURL
+ Shell Script)
• Con cURL podemos crear exactamente la
transferencia que buscamos.
• Si se tienen suficientes conocimientos sobre cURL se
pueden crear buenos tests con un tiempo de
ejecución bastante bajo.
Testing con Guzzle
… porque cURL no es para todos los públicos
Testing con Guzzle (PHP)• Guzzle es una librería en PHP que actúa como un
wrapper de cURL, exprimiendo al máximo su potencia.
• Permite crear clientes REST muy potentes y escalables
• Es realmente sencillo de utilizar y es orientado a objetos
• Se integra bien con PHPUnit.
Testing con POSTMAN
Testing con POSTMAN
• POSTMAN es un plugin para Chrome que actúa
como cliente REST
• Nos permite guardar las transferencias
preconfiguradas
• Permite autenticación mediante OAuth de forma
bastante sencilla
• No es una herramienta automática (hay que
ejecutar manualmente cada test y comprobar su
resultado), por lo que no es recomendable testear
sólo con POSTMAN
Documentación con Swagger
Documentación con Swagger
• Swagger es un estándar para definición de APIs
REST y un framework que nos permite visualizar su
representación y también consumir el API.
• Es una herramienta muy útil para publicar nuestra
API y que todo el mundo aprenda a usarla
rápidamente.
• Existe un bundle para Symfony2 que implementa
Swagger: NelmioApiDocBundle
Top Related