Você está na página 1de 23

>>

API
Application Programming Interface
Es la abstracción de una implementación
Existen muchos tipos:
Sistema operativo
Entornos de programación
Servicios web

Arquitectura REST
REST (REpresentational State Transfer) es un tipo de arquitectura de
desarrollo web que se apoya totalmente en el estándar HTTP. REST nos
permite crear servicios compatibles con cualquier dispositivo o cliente
HTTP, por lo que es mucho más sencillo que antiguas alternativas, como
SOAP y XML-RPC.

REST se definió en el 2000 por Roy Fielding, coautor de la especificación


HTTP.

Cliente-Servidor

Interfaz de comunicación que abstrae para el cliente el funcionamiento


interno del servidor.

Sin estado

Ninguna información de contexto del cliente se almacena en el servidor


(sesiones, cookies…). Cada petición del cliente contiene toda la
información necesaria para que el servidor sea capaz de preparar la
respuesta.

Cacheable

El cliente puede querer cachear las respuestas, por lo que estas deben
ser cacheables. Es decir, debe incluirse en los headers de las
respuestas la cabecera Last-Modified y soportar la recepción de la
cabecera If-Unmodified-Since en las peticiones GET del cliente.
Interfaz uniforme

Identificación de recursos
Manipulación de recursos mediante su representación
Mensajes autodescriptivos
Hipermedia como motor de estado

Sistema de capas

El servidor debe seguir una arquitectura de capas, en la que cada capa


conoce únicamente la capa inmediatamente inferior. De esta forma, la
capa superior (interfaz de interacción) abstrae el comportamiento
interno. El cliente no debe conocer nada sobre la implementación del
servidor.

Código en demanda

Es el único requisito opcional. Se provee al cliente con código capaz


de manejar la respuesta del servidor. El mejor ejemplo de código en
demanda son los scripts de JavaScript que contienen las páginas web.

Para que un servicio sea considerado RESTful debe cumplir una serie de
requisitos:

1. Uso correcto de URIs


2. Uso correcto de HTTP
3. Implementación de Hypermedia

Uso correcto de URIs


Las URL (Uniform Resource Locator) son un tipo de URI (Uniform Resource
Identifier) que permiten identificar de forma única un recurso. Tienen la
forma:

<protocolo>://<hostname>[:puerto]/<ruta_del_recurso>.

Existen una serie de reglas a la hora de nombrar recursos:

El nombre de un recurso no debe implicar una acción. Las acciones ya


se especifican mediante los verbos de HTTP.
Deben ser únicas. No debemos tener más de una URI para identificar
un mismo recurso.
Deben ser independientes del formato. /people/432.pdf no sería
correcto.
Deben mantener una jerarquía lógica, de lo más amplio a lo más
concreto.
Incorrecto: /facturas/998/clientes/232
Correcto: /clientes/232/facturas/998
Los filtrados de información de un recurso no se hacen en la URI, sino
utilizando parámetros HTTP:
Incorrecto: /facturas/orden/desc/fecha-
desde/2007/pagina/2
Correcto: /facturas?fecha-
desde=2007&orden=DESC&pagina=2

Uso correcto de HTTP


Cualquier desarrollador web debe conocer bien la especificación HTTP. Los
siguientes RFC (Request For Comments) contienen la especificación
completa:

RFC 7230 - Message syntax and routing


(https://tools.ietf.org/html/rfc7230)
RFC 7231 - Semantics and Content (https://tools.ietf.org/html/rfc7231)
RFC 7232 - Conditional requests (https://tools.ietf.org/html/rfc7232)
RFC 7233 - Range requests (https://tools.ietf.org/html/rfc7233)
RFC 7234 - Caching (https://tools.ietf.org/html/rfc7234)
RFC 7235 - Authentication (https://tools.ietf.org/html/rfc7235)
RFC 7236 - Authentication Scheme Registrations
(https://tools.ietf.org/html/rfc7236)
RFC 7237 - Method Registrations (https://tools.ietf.org/html/rfc7237)

Aunque podemos resumirla en 3 aspectos clave:

Métodos HTTP
Códigos de estado
Formatos de contenido

Métodos HTTP
HTTP pone a nuestra disposición 5 métodos o "verbos":
GET: Para leer un recurso
HEAD: Para leer metainformación de un recurso
POST: Para crear un recurso
PUT: Para editar un recurso
DELETE: Para eliminar un recurso
PATCH: Para editar partes de un recurso

Códigos de estado
Para que un servicio pueda ser considerado RESTful es vital que haga un
uso correcto de los códigos de estado
(http://es.wikipedia.org/wiki/Anexo%3aC%C3%B3digos_de_estado_HTTP)
que ofrece HTTP. Existe un código preestablecido para cada posible
situación a la que nuestro servicio tenga que enfrentarse. Estos códigos se
agrupan de la siguiente forma (sólo se enumeran algunos):

1XX: Respuestas informativas. Se utilizan cuando un cliente quiere


saber si su petición será aceptada o rechazada por los headers antes
de enviar al servidor todo el cuerpo de la misma (si es de gran
tamaño).

100: La petición será aceptada (continue)


101: Conmutando protocolos
102: Procesando

2XX: Peticiones correctas. La petición fue recibida correctamente,


entendida y aceptada.

200: OK
201: Elemento creado
202: Petición aceptada, pero aún se está procesando. Podría no
completarse si se produce algún error
203: Información no autoritativa. La respuesta ha recibido alguna
transformación por parte de un proxy
204: Respuesta vacía
205: El servidor solicita al cliente que recargue la página
206: Contenido parcial. Utilizado para dividir una descarga o
interrumpirlas y reanudarlas
207: Estado múltiple. El cuerpo es un XML con subcódigos de
estado, dependiendo de las sub-peticiones hechas
3XX: Redirecciones. El cliente tiene que realizar una acción adicional.
No está permitido hacerlo más de 5 veces seguidas: bucle infinito de
redirección.

300: Se ofrecen distintas posibilidades al cliente. Ejemplo:


distintos formatos de vídeo.
301: Movido permanentemente.
302: Movido temporalmente. Es el ejemplo más popular de
redirección, así como un ejemplo de contradicción del
estándar.
303: See other. La respuesta a la petición se encuentra haciendo
GET a otra URI. Muchas veces se utiliza 302 como si fuera 303.
304: Indica que el recurso no ha sido modificado desde la última
vez que se pidió. Ahorra ancho de banda.
307: Redirección temporal. Como 303 pero debe soportar
cualquier método (no solo GET).

4XX: Errores del cliente. La solicitud es incorrecta o no puede


procesarse.

400: Solicitud incorrecta.


401: No autorizado para acceder al recurso en este momento (o
la autorización ha fallado).
403: No autorizado para acceder al recurso. La autorización se
ha realizado correctamente pero el acceso es denegado.
404: Recurso no encontrado.
405: Método (GET, POST...) no permitido para la URI utilizada.
406: Los formatos aceptados por el cliente (header Accept) no
están disponibles.
410: Indica que el recurso ya no está disponible ni lo estará
(pero lo ha estado en el pasado). En ocasiones se utiliza
erróneamente 404 en lugar de 410.
423: Recurso bloqueado.

5XX: Errores del servidor. El servidor falló al completar una solicitud


aparentemente válida.

500: Error interno, genérico, ajeno a la naturaleza del propio


servidor web.
501: Funcionalidad no implementada.
503: Servicio no disponible en este momento.
505: Versión de HTTP no soportada.
509: Límite de ancho de banda excedido (no es un código
oficial, pero es ampliamente utilizado).

De esta forma conseguimos:

Ofrecer una interfaz estándar


Cualquier cliente puede hacer uso de nuestra API sin tener que hacer
un esfuerzo adicional para integrarse con nosotros
No tenemos que preocuparnos de mantener nuestros propios
mensajes de estado

Error común:

1. >> PUT /people/123


2.
3. Status Code 200
4. Content:
5. {
6. success: false,
7. code: 734,
8. error: "datos insuficientes"
9. }

Formatos de contenido
Para indicar los tipos de formato aceptados, el cliente debe especificar el
header Accept. La API RESTful debe devolver el resultado en el primer
formato disponible, o un error 406 si ninguno de los formatos aceptados
está disponible.

1. GET /people/1234
2. Accept: application/epub+zip, application/pdf, application/json

En la respuesta se debe indicar el header Content-Type para indicar el


formato del cuerpo.

1. Status Code 200


2. Content-Type: application/pdf
3. ...

Existen muchos tipos de contenido: application/pdf, audio/mp4,


image/gif, multipart/form-data, text/plain, etc. Además se pueden
utilizar tipos personalizados para la implementación hypermedia.
Implementación Hypermedia
La idea de Hypermedia es sencilla: conectar unos recursos con otros
mediante enlaces. Por ejemplo:

1. <Pedido>
2. <id>1234</id>
3. <status>Pending</status>
4. <links>
5. <link rel="factura">
6. http://example.com/api/pedido/1234/factura
7. </link>
8. </links>
9. </pedido>

El cliente debe entender la información, de forma que sepa separar el


recurso en sí con la información para enlazarla con otros recursos. Para ello
se utilizan los headers Accept y Content-Type. Es una forma de que el
cliente y el servicio sepan que están utilizando el mismo idioma. Este idioma
debe especificarse en algún documento que el cliente de la API debe
conocer.

HAL (Hypertext Application Language) (https://tools.ietf.org/html/draft-kelly-


json-hal-06) es un ejemplo de especificación de representación de recursos
utilizando JSON. El tipo de contenido es application/hal+json:

1. Request:
2. GET /pedido/1234
3. Accept: application/hal+json, text/xml
4.
5. Response:
6. Status Code 200
7. Content-Type: application/hal+json
8. Content:
9. {
10. id: 1234,
11. status: 'Pending',
12. _links: {
13. factura: {
14. href: 'http://example.com/api/pedido/1234/factura',
15. type: 'application/pdf'
16. }
17. }
18. }
¿Para qué sirve Hypermedia? Para automatizar la utilización de APIs sin
que haya interacción humana. También para no tener que conocer la
ubicación exacta de todos los recursos.

Versionado de una API REST


Las APIs cambian rápido
Los clientes se adaptan despacio
Debemos dar compatibilidad hacia atrás

Versionado de la API

Indicando la versión en la URL

Se trata de la opción más aconsejada, aunque tiene el


inconveniente de que un cliente que quiera actualizarse tiene que
cambiar todas las URLs. Ejemplo: Twitter.

https://api.twitter.com/1.1/statuses/user_timeline.json

Indicando la versión como parámetro de la URL

Tiene el mismo inconveniente de tener que cambiar las URLs


para actualizarse. Ejemplo: YouTube.

http://gdata.youtube.com/feeds/api/videos?
q=surfing&caption&v=2

Indicando la versión en el header Accept-Version

Tiene la ventaja de que las URLs no cambian.

Nuestro servidor puede, por ejemplo, separar con un proxy las peticiones de
cada versión a una máquina o endpoint diferente.

APIs REST con Node.js


Node.js es una plataforma de ejecución de JavaScript basada en eventos
que sigue un modelo de I/O no bloqueante. Un servidor HTTP básico tiene
el siguiente aspecto:
1. var http = require('http');
2. http.createServer(function (req, res) {
3. res.writeHead(200, {'Content-Type': 'text/plain'});
4. res.end('Hello World\n');
5. }).listen(1337, '127.0.0.1');

Es una plataforma especialmente adecuada para la construcción de APIs


REST por algunos motivos:

JavaScript es perfecto para APIs JSON. El serializado/deserializado


es nativo.
Normalmente las APIs ofrecen peticiones pequeñas y de poca
necesidad computacional, el caso de uso perfecto para Node.js, ya
que durante un proceso intenso de cómputo el bucle de eventos no se
procesa (cuando se ejecuta en un único proceso).
NodeJS también funciona muy bien con long polling
(http://es.wikipedia.org/wiki/Tecnolog%C3%ADa_Push#Long_polling),
ya que no necesita crear un hilo para cada petición, como por
ejemplo hacen Python o Ruby. Se pueden hacer APIs que exploten
esta característica, si algunas peticiones requieren un procesamiento
largo y el cliente quiere enterarse del progreso.

Una API REST sencilla, para la gestión básica de un listado de usuarios,


podría implementarse en NodeJS utilizando símplemente el módulo http.
Supongamos la siguiente lista de usuarios:

1. var people = [
2. {dni: 111, name: 'Pepe', age: 30},
3. {dni: 222, name: 'Jose', age: 20},
4. {dni: 333, name: 'Carlos', age: 25}
5. ];

Utilizando el módulo http, sobre el servidor que hemos visto antes,


podemos implementar unas operaciones básicas:

GET /people
GET /people/<dni>
POST /people/<dni>
PUT /people/<dni>
DELETE /people/<dni>

El código sería como sigue:


1. var http = require('http');
2.
3. http.createServer(function (req, res) {
4.
5. if (req.method === 'GET') {
6.
7. if (req.url === '/people') {
8. res.writeHead(200, {'Content-Type': 'application/jso
n'});
9. res.end(JSON.stringify(people));
10. return;
11. }
12.
13. if (req.url.indexOf('/people/') === 0) {
14. var person = people.filter(function (p) {
15. return (p.dni == req.url.replace('/people/', '')
);
16. })[0];
17. if (!person) {
18. res.writeHead(404);
19. res.end();
20. return;
21. }
22. res.writeHead(200, {'Content-Type': 'application/jso
n'});
23. res.end(JSON.stringify(person));
24. return;
25. }
26.
27. } else if (req.method === 'POST') {
28.
29. if (req.url.indexOf('/people/') === 0) {
30. (function () {
31. var body = '';
32. req.on('data', function (data) {
33. body += String(data);
34. });
35. req.on('end', function () {
36. people.push(JSON.parse(body));
37. res.writeHead(201); res.end();
38. });
39. }());
40. }
41.
42. } else if (req.method === 'PUT') {
43.
44. if (req.url.indexOf('/people/') === 0) {
45. (function () {
46. var body = '';
47. req.on('data', function (data) {
48. body += String(data);
49. });
50. req.on('end', function () {
51. body = JSON.parse(body);
52. people = people.map(function (p) {
53. if (p.dni == req.url.replace('/people/',
'')) {
54. return body;
55. }
56. return p;
57. });
58. res.writeHead(200); res.end();
59. });
60. }());
61. }
62.
63. } else if (req.method === 'DELETE') {
64.
65. if (req.url.indexOf('/people/') === 0) {
66. people = people.filter(function (p) {
67. return (p.dni != req.url.replace('/people/', '')
);
68. });
69. res.writeHead(200); res.end();
70. }
71. }
72.
73. }).listen(3000);

El código no es muy complejo ni extenso. No obstante, existen algunos


módulos especialmente diseñados para la creación de aplicaciones y
servicios web.

Módulos de Node.js para construir


APIs REST
A continuación se presentan algunos de los más populares frameworks de
apoyo al desarrollo de APIs REST para Node.js.

Express (http://expressjs.com/)
Inspirado en Sinatra (http://es.wikipedia.org/wiki/Sinatra_%28software%29),
un popular framework de aplicaciones web para Ruby. Del mismo modo que
Sinatra, en lugar de seguir el popular patrón MVC se centra en permitir la
creación de aplicaciones web con el mínimo esfuerzo.

El pequeño servicio REST listado anteriormente, con Express se simplificaría


de forma considerable:
1. var http = require('http');
2. var express = require('express');
3. var bodyParser = require('body-parser');
4.
5. var app = express();
6. app.use(bodyParser.json());
7.
8. var people = [
9. {dni: 111, name: 'Pepe', age: 30},
10. {dni: 222, name: 'Jose', age: 20},
11. {dni: 333, name: 'Carlos', age: 25}
12. ];
13.
14. app.get('/people', function (req, res) {
15. res.send(people);
16. });
17.
18. app.get('/people/:dni', function (req, res) {
19. var person = people.filter(function (p) {
20. return p.dni == req.params.dni;
21. })[0];
22. if (!person) {
23. return res.send(404);
24. }
25. res.send(person);
26. });
27.
28. app.post('/people/:dni', function (req, res) {
29. people.push(req.body);
30. res.send(201);
31. });
32.
33. app.put('/people/:dni', function (req, res) {
34. people = people.map(function (p) {
35. if (p.dni == req.params.dni) {
36. return req.body;
37. }
38. return p;
39. });
40. res.send(200);
41. });
42.
43. app.delete('/people/:dni', function (req, res) {
44. people = people.filter(function (p) {
45. return p.dni != req.params.dni;
46. });
47. res.send(200);
48. });
49.
50. var server = http.createServer(app);
51. server.listen(3000);
Nótese la simplificación en cuanto al manejo de parámetros en la URI y las
respuestas al cliente.

Restify (http://mcavage.me/node-restify/)
A diferencia de Express, que está diseñado para construir aplicaciones web
y añade soporte para renderizado de vistas, Restify es un framework
diseñado para crear servicios REST, exclusivamente. De esta forma es más
ligero que Express. El código escrito para Restify es prácticamente
idéntico al escrito para Express, pero Restify ofrece algunas facilidades
muy útiles para nuestras APIs:

Rutas versionadas, por medio del header Accept-Version:

1. var restify = require('restify');


2. var server = restify.createServer();
3.
4. var PATH = '/hello/:name';
5. server.get({path: PATH, version: '1.1.3'}, function (req, r
es) {
6. res.send('hello: ' + req.params.name);
7. });
8. server.get({path: PATH, version: '2.0.0'}, function (req, r
es) {
9. res.send({hello: req.params.name});
10. });
11.
12. server.listen(8080);

Authorization: autenticación básica con login y password o mediante


HTTP Signature (https://github.com/joyent/node-http-signature).

Gzip response: comprime las respuestas.

Peticiones condicionales con eTag: para el cacheo de recursos.

Soporte JSONP: para paliar las limitaciones del cross-origin.

Throttling: límites de peticiones en tiempo, por usuario, IP, ruta, etc.

Koa (http://koajs.com/)
Se trata de una evolución de Express, basada en el uso global de
generadores (https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Statements/function%2a). Al contrario
que Express, no incorpora ningún middleware de serie. Esto quiere decir
que los objetos request y response originales del módulo http de
Node.js no se alteran, sino que se trabaja con un objeto auxiliar por petición:
el "contexto":

1. app.use(function *(){
2. this; // is the Context
3. this.request; // is a koa Request
4. this.response; // is a koa Response
5. this.req; // original http request
6. this.res; // original http response
7. });

El pequeño servicio REST de ejemplo con el que estamos trabajando podría


escribirse así con Koa:

1. var koa = require('koa');


2. var route = require('koa-route');
3. var parseBody = require('co-body');
4.
5. var app = koa();
6.
7. var people = [
8. {dni: 111, name: 'Pepe', age: 30},
9. {dni: 222, name: 'Jose', age: 20},
10. {dni: 333, name: 'Carlos', age: 25}
11. ];
12.
13. app.use(function *(next) {
14. if (['POST', 'PUT'].indexOf(this.method) === -1) {
15. return yield next;
16. }
17. this.body = yield parseBody(this);
18. yield next;
19. });
20.
21. app.use(route.get('/people', function *() {
22. this.body = people;
23. }));
24.
25. app.use(route.get('/people/:dni', function *(dni) {
26. var person = people.find(function (p) {
27. return p.dni == dni;
28. });
29. if (!person) {
30. return this.throw(404)
31. }
32. this.body = person;
33. }));
34.
35. app.use(route.post('/people/:dni', function *(dni) {
36. people.push(this.body);
37. }));
38.
39. app.use(route.put('/people/:dni', function *(dni) {
40. var newPerson = this.body;
41. people = people.map(function (p) {
42. if (p.dni == dni) {
43. return newPerson;
44. }
45. return p;
46. });
47. }));
48.
49. app.use(route.delete('/people/:dni', function *(dni) {
50. var person = people.find(function (p) {
51. return p.dni == dni;
52. });
53. if (!person) {
54. return this.throw(404)
55. }
56. people = people.filter(function (p) {
57. return p.dni != dni;
58. });
59. }));
60.
61. app.listen(3000);

Sails (http://sailsjs.org/)
Se trata de un framework MVC modular que utiliza Express por debajo para
gestionar la comunicación HTTP. Automatiza la creación de APIs de forma
importante:

1. $ sails new api_sails


2. $ cd api_sails/
3. $ sails generate api user
4.
5. $ curl -v -X POST 127.0.0.1:1337/user -H "Content-Type: aplicati
on/json" --data '{"dni": 1234, "name": "Jose", "age": 15}'
6.
7. * Hostname was NOT found in DNS cache
8. * Trying 127.0.0.1...
9. * Connected to 127.0.0.1 (127.0.0.1) port 1337 (#0)
10. > POST /user HTTP/1.1
11. > User-Agent: curl/7.37.1
12. > Host: 127.0.0.1:1337
13. > Accept: * / *
14. > Content-Type: application/json
15. > Content-Length: 40
16. >
17. * upload completely sent off: 40 out of 40 bytes
18. < HTTP/1.1 200 OK
19. < X-Powered-By: Sails <sailsjs.org>
20. < Access-Control-Allow-Origin:
21. < Access-Control-Allow-Credentials:
22. < Access-Control-Allow-Methods:
23. < Access-Control-Allow-Headers:
24. < Content-Type: application/json; charset=utf-8
25. < Content-Length: 145
26. < Set-Cookie: sails.sid=s%3AG7EbZ70cNKC23T2hnHLdcAGc.AXOtUdV0zM3
msk%2B0%2FfDOUX3Ip2WkOeyqoa2Ez8AlYUU; Path=/; HttpOnly
27. < Date: Tue, 30 Sep 2014 00:52:31 GMT
28. < Connection: keep-alive
29. <
30. {
31. "dni": 1234,
32. "name": "Jose",
33. "age": 15,
34. "createdAt": "2014-09-30T00:52:31.200Z",
35. "updatedAt": "2014-09-30T00:52:31.200Z",
36. "id": 4
37. }
38.
39. $ curl -v -X POST 127.0.0.1:1337/user -H "Content-Type: aplicati
on/json" --data '{"dni": 1235, "name": "Jose", "age": 25}'
40.
41. * Hostname was NOT found in DNS cache
42. * Trying 127.0.0.1...
43. * Connected to 127.0.0.1 (127.0.0.1) port 1337 (#0)
44. > POST /user HTTP/1.1
45. > User-Agent: curl/7.37.1
46. > Host: 127.0.0.1:1337
47. > Accept: * / *
48. > Content-Type: application/json
49. > Content-Length: 40
50. >
51. * upload completely sent off: 40 out of 40 bytes
52. < HTTP/1.1 200 OK
53. < X-Powered-By: Sails <sailsjs.org>
54. < Access-Control-Allow-Origin:
55. < Access-Control-Allow-Credentials:
56. < Access-Control-Allow-Methods:
57. < Access-Control-Allow-Headers:
58. < Content-Type: application/json; charset=utf-8
59. < Content-Length: 145
60. < Set-Cookie: sails.sid=s%3Az6-qkcaxuiDo_wgg_VloezIl.vqSaGAz8GJI
l%2B8N%2BuRAhjyjz%2F1gqaxGQdsRiri%2BMUpM; Path=/; HttpOnly
61. < Date: Tue, 30 Sep 2014 00:53:31 GMT
62. < Connection: keep-alive
63. <
64. {
65. "dni": 1235,
66. "name": "Jose",
67. "age": 25,
68. "createdAt": "2014-09-30T00:53:31.660Z",
69. "updatedAt": "2014-09-30T00:53:31.660Z",
70. "id": 5
71. }
72.
73. $ curl -v -XGET 127.0.0.1:1337/user
74.
75. * Hostname was NOT found in DNS cache
76. * Trying 127.0.0.1...
77. * Connected to 127.0.0.1 (127.0.0.1) port 1337 (#0)
78. > GET /user HTTP/1.1
79. > User-Agent: curl/7.37.1
80. > Host: 127.0.0.1:1337
81. > Accept: * / *
82. >
83. < HTTP/1.1 200 OK
84. < X-Powered-By: Sails <sailsjs.org>
85. < Access-Control-Allow-Origin:
86. < Access-Control-Allow-Credentials:
87. < Access-Control-Allow-Methods:
88. < Access-Control-Allow-Headers:
89. < Content-Type: application/json; charset=utf-8
90. < Content-Length: 491
91. < Set-Cookie: sails.sid=s%3AqQiPi2H5UCelBVNzMIJqNHgj.FsAcdbrBsJH
RIwyRNlYvEFH4INnjhWpznfjknBX4b3k; Path=/; HttpOnly
92. < Date: Tue, 30 Sep 2014 00:53:51 GMT
93. < Connection: keep-alive
94. <
95. [
96. {
97. "dni": 1235,
98. "name": "Jose",
99. "age": 25,
100. "createdAt": "2014-09-30T00:52:22.760Z",
101. "updatedAt": "2014-09-30T00:52:22.761Z",
102. "id": 3
103. },
104. {
105. "dni": 1234,
106. "name": "Jose",
107. "age": 15,
108. "createdAt": "2014-09-30T00:52:31.200Z",
109. "updatedAt": "2014-09-30T00:52:31.200Z",
110. "id": 4
111. }
112. ]
Como puede observarse, la respuesta de creación de un recurso con Sails
es un buen ejemplo de API que desobedece las directrices REST respecto a
los códigos de estado de HTTP: en lugar de devolver un 201 (recurso
creado) devuelve un 200 genérico de OK. No obstante, estas respuestas
pueden modificarse con posterioridad.

Sails genera una serie de archivos categorizados en 5 directorios:

Responses: funciones mnemotécnicas para las respuestas HTTP de


nuestra API.
Controllers: funciones encargadas de atender las peticiones.
Models: el modelo de datos.
Services: utilidades al alcance de diferentes partes de la aplicación.
Policies: autorización y control de acceso.

Hapi.js (http://hapijs.com/)
Sigue un enfoque parecido al de Express, centrándose en facilitar la
construcción de servicios web en lugar de preocuparse por la
infraestructura de la aplicación. Tiene una forma propia de especificar rutas
y manejadores:

1. var hapi = require('hapi');


2. var server = new hapi.Server(3000);
3.
4. server.route({method: 'GET', path: '/people', handler: function
(req, res) {
5. res(people);
6. }});
7.
8. server.route({method: 'GET', path: '/people/{dni}', handler: fun
ction (req, res) {
9. var person = people.filter(function (p) {
10. return p.dni == req.params.dni;
11. })[0];
12. if (!person) {
13. return res(404);
14. }
15. res(person);
16. }});
17.
18. ...
19. server.start();
Seguridad
A la hora de desarrollar una API hay que tener en cuenta el control de
acceso. En base a ello podemos tener tres tipos de APIs:

API pública: Abierta a todo el mundo. No necesita ningún tipo de


control de acceso.
API privada: API protegida y de uso limitado a una serie de usuarios.
API mixta: Algunos recursos son privados y otros públicos, o algunas
acciones están permitidas y otras no sobre un mismo recurso para
diferentes usuarios.

Para controlar el acceso a un recurso es necesario identificar al usuario.


Para ello existen varios mecanismos de autenticación:

Basic authentication

La más conocida y soportada por todo tipo de frameworks. Consiste


en el envío de un login y password en la cabecera Authorization,
codificados como username:password en Base 64. Por ejemplo:

1. HEADERS: {
2. "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
3. ...
4. }

Para considerarlo un método seguro debe utilizarse en combinación


con SSL, ya que transmitir credenciales en texto plano no es buena
idea en otro caso.

API key como parámetro de la URI o como header

Consiste en añadir un paso adicional al control de acceso. En una


primera fase el cliente envía los credenciales de usuario al servidor
para obtener un token. A partir de ese momento el resto de peticiones
incluyen dicho token de acceso. El servidor, por tanto, debe conocer la
información de permisos asociada a cada token.

1. "http://esto_es_mi_api.com/people?auth=292j293j98fkl2jl1kxc
v0"
Un posible problema es el robo de dicho token de acceso. Se pueden
realizar algunas mejoras para evitarlo:

Cambiar el token asociado al usuario cada vez que se realice una


petición. El servidor puede enviar uno nuevo en la respuesta y
desechar el token previo para evitar su futuro uso. De esta forma
los tokens son de un sólo uso.
No limitarse al token para controlar el acceso, sino comprobar
también el User Agent, la IP u otra información del cliente.

HMAC (Hash-based Message Authentication Code)

Existe una clave secreta compartida entre cliente y servidor, y una


clave privada que el cliente no tiene que compartir. Es utilizad por AWS
y Azure, por ejemplo.

OAuth 2.0:

Se trata de la mejor alternativa. Es un método por token, pero la


generación del mismo tiene lugar durante un proceso complejo.

Probando nuestra API


Cuando desarrollamos APIs REST podemos realizar pruebas sencillas de
funcionamiento, para analizar los headers y cuerpos de las respuestas de
nuestro servicio, utilizando algo tan sencillo como CURL
(http://es.wikipedia.org/wiki/CURL).

1. #!/bin/bash
2.
3. curl -X GET -v 127.0.0.1:3000/people
4.
5. curl -X GET -v 127.0.0.1:3000/people/111
6.
7. curl -X POST -v -H "Content-Type: application/json" \
8. --data '{"dni": 444, "name": "Enrique", "age": 33}' 127.0.0.
1:3000/people/444
9.
10. curl -X PUT -v -H "Content-Type: application/json" \
11. --data '{"dni": 444, "name": "Jose", "age": 33}' 127.0.0.1:3
000/people/444
12.
13. curl -X DELETE -v 127.0.0.1:3000/people/111
Por ejemplo, la petición GET sobre el recurso /people/333 daría como
resultado:

1. $ curl -X GET -v 127.0.0.1:3000/people/333


2.
3. * Hostname was NOT found in DNS cache
4. * Trying 127.0.0.1...
5. * Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0)
6. > GET /people/333 HTTP/1.1
7. > User-Agent: curl/7.37.1
8. > Host: 127.0.0.1:3000
9. > Accept: * / *
10. >
11. < HTTP/1.1 200 OK
12. < X-Powered-By: koa
13. < Content-Type: application/json; charset=utf-8
14. < Content-Length: 36
15. < Date: Tue, 30 Sep 2014 00:18:00 GMT
16. < Connection: keep-alive
17. <
18. * Connection #0 to host 127.0.0.1 left intact
19.
20. {"dni":333,"name":"Carlos","age":25}

Adicionalmente podemos hacer pruebas de carga sobre nuestro servidor


para analizar su comportamiento en situaciones extremas. Por ejemplo,
utilizando loadtest (https://www.npmjs.org/package/loadtest). En el
siguiente ejemplo sometemos nuestra API a una prueba de carga,
simulando 20 clientes que realizan 1000 peticiones GET /people por
segundo.
1. $ loadtest -c 20 --rps 1000 http://127.0.0.1:3000/people
2. [Sep 30 2014 13:52:33] INFO Requests: 0, requests per second: 0,
mean latency: 0 ms
3. [Sep 30 2014 13:52:38] INFO Requests: 4517, requests per second:
903, mean latency: 0 ms
4. [Sep 30 2014 13:52:43] INFO Requests: 9516, requests per second:
1000, mean latency: 0 ms
5. [Sep 30 2014 13:52:48] INFO Requests: 14520, requests per second
: 1001, mean latency: 80 ms
6. [Sep 30 2014 13:52:53] INFO Requests: 17432, requests per second
: 576, mean latency: 60 ms
7. [Sep 30 2014 13:52:58] INFO Requests: 21972, requests per second
: 919, mean latency: 390 ms
8. [Sep 30 2014 13:52:58] INFO Errors: 3467, accumulated errors: 34
67, 15.8% of total requests
9. [Sep 30 2014 13:53:03] INFO Requests: 26972, requests per second
: 1000, mean latency: 0 ms
10. [Sep 30 2014 13:53:03] INFO Errors: 5000, accumulated errors: 84
67, 31.4% of total requests

También es posible ver las peticiones por segundo que aguanta para un
determinado número de clientes simultáneos:

1. $ loadtest -c 100 http://127.0.0.1:3000/people


2. [Sep 30 2014 14:10:07] INFO Requests: 0, requests per second: 0,
mean latency: 0 ms
3. [Sep 30 2014 14:10:12] INFO Requests: 1872, requests per second:
374, mean latency: 80 ms
4. [Sep 30 2014 14:10:17] INFO Requests: 1872, requests per second:
0, mean latency: 0 ms
5. [Sep 30 2014 14:10:22] INFO Requests: 2965, requests per second:
216, mean latency: 580 ms
6. [Sep 30 2014 14:10:27] INFO Requests: 10552, requests per second
: 1539, mean latency: 160 ms
7. [Sep 30 2014 14:10:32] INFO Requests: 13303, requests per second
: 550, mean latency: 180 ms

Conclusiones
Un servicio o API tiene que cumplir 3 requisitos fundamentales para
ser considerado REST:
1. Uso correcto de URIs.
2. Uso correcto de HTTP.
Métodos HTTP
Códigos de estado
Formatos de contenido
3. Implementación de Hypermedia.
La mayoría de APIs que dicen ser REST en realidad no lo son.
Podemos probar cualquier API con CURL.
El rendimiento es importante, debemos probar nuestras APIs en
situaciones extremas.
Node.js es una buena plataforma para la construcción de APIs REST.
Existen diversos enfoques y módulos para construir APIs con Node.js.

Desarrollo de APIs RESTful con Node.js. - Sergio García Mondaray (sgmonda@gmail.com |


@sgmonda)

Você também pode gostar