3º. 2º cuatrimestre. Itinerario de Tecnologías de la Información. Grado en Ingeniería Informática. Curso 2019/2020
Web services are frequently just Web APIs that can be accessed over a network, such as the Internet, and executed on a remote system hosting the requested services.
Example:
REST is an acronym that stands for Representational State Transfer. Representational state transfer (REST) is a software architectural style that defines a set of constraints to be used for creating Web services.
When an API is RESTful, it is HTTP-based and its resources are identified by their URLs.
Requesting or making a change to a resource comes down to issuing an HTTP request using the particular method that matches your intent.
For example, the HTTP GET method retrieves a resource, and HTTP PUT sends a resource to be saved.
In the context of REST APIs, when making multiple identical requests has the same effect as making a single request – then that REST API is called idempotent.
An idempotent HTTP method is an HTTP method that can be called many times without different outcomes. It would not matter if the method is called only once, or ten times over. The result should be the same. It essentially means that the result of a successfully performed request is independent of the number of times it is executed.
When you design REST APIs, you must realize that API consumers can make mistakes. They can write client code in such a way that there can be duplicate requests as well. These duplicate requests may be unintentional as well as intentional some time (e.g. due to timeout or network issues). You have to design fault-tolerant APIs in such a way that duplicate requests do not leave the system unstable.
See also What are idempotent and/or safe methods? del libro restcookbook.com/
Safe methods are methods that can be cached, prefetched without any repercussions to the resource.
HTTP POST
Generally – not necessarily – POST
APIs are used to create a new resource on server. So when you invoke the same POST request N
times, you will have N
new resources on the server. So, POST
is not idempotent.
HTTP PUT
Generally – not necessarily – PUT
APIs are used to update the resource state. If you invoke a PUT
API N
times, the very first request will update the resource; then rest N-1
requests will just overwrite the same resource state again and again – effectively not changing anything. Hence, PUT
is idempotent.
Recuerda la definición de idempotente:
when making multiple identical requests has the same effect as making a single request
Si el request es idéntico el fichero no cambiará en los subsiguientes requests
HTTP DELETE
When you invoke N
similar DELETE
requests, first request will delete the resource and response will be 200
(OK) or 204
(No Content
). Other N-1
requests will return 404
(Not Found
).
Clearly, the response is different from first request, but there is no change of state for any resource on server side because original resource is already deleted. So, DELETE
is idempotent.
Please keep in mind if some systems may have DELETE
APIs like this:
DELETE /item/last
In the above case, calling operation N times will delete N resources – hence DELETE
is not idempotent in this case. In this case, a good suggestion might be to change above API to POST
– because POST
is not idempotent.
POST /item/last
Now, this is closer to HTTP spec – hence more REST compliant.
Resumen de los Distintos Métodos
HTTP Method | Idempotent | Safe |
---|---|---|
OPTIONS | yes | yes |
GET | yes | yes |
HEAD | yes | yes |
PUT | yes | no |
POST | no | no |
DELETE | yes | no |
PATCH | no | no |
[~/.../chapter20-nodejs/juanIrache-20_3_public_space(master)]$ pwd -P
/Users/casiano/local/src/javascript/eloquent-javascript-3/juanIrache-solutions/20_3_public_space
Quote from the EJS book hints for this exercise:
You can use the function that implements the
DELETE
method as a blueprint for theMKCOL
method. When no file is found, try to create a directory withmkdir
. When a directory exists at that path, you can return a204
response so that directory creation requests are idempotent. If a nondirectory file exists here, return an error code. Code400
(bad request
) would be appropriate.
const {mkdir, stat} = require("fs").promises;
methods.MKCOL = async function(request) {
let path = urlPath(request.url);
let stats;
try {
stats = await stat(path);
} catch (error) {
if (error.code != "ENOENT") throw error;
await mkdir(path);
return {status: 204}; // NO CONTENT
}
if (stats.isDirectory()) return {status: 204};
else return {status: 400, body: "Not a directory"};
};
204:
The server has successfully fulfilled the request and that there is no additional content to send in the response payload body.
A 204 response is terminated by the first empty line after the header fields because it cannot contain a message body.
A 204 response is cacheable by default
Un experimento: si añadimos un body
a una respuesta 204
en el código de la práctica:
[~/.../chapter20-nodejs/juanIrache-20_3_public_space(master)]$ git diff -U12 server.js
diff --git a/20_3_public_space/server.js b/20_3_public_space/server.js
index 79d3f5d..3fa658a 100644
--- a/20_3_public_space/server.js
+++ b/20_3_public_space/server.js
@@ -83,25 +83,25 @@ const { createWriteStream } = require('fs');
function pipeStream(from, to) {
return new Promise((resolve, reject) => {
from.on('error', reject);
to.on('error', reject);
to.on('finish', resolve);
from.pipe(to);
});
}
methods.PUT = async function(request) {
let path = urlPath(request.url);
await pipeStream(request, createWriteStream(path));
- return { status: 204 };
+ return { status: 204, body: path };
};
… Y hacemos un request con PUT
, observamos que el status 204
hace que no llegue ningún cuerpo al cliente:
Server | Client |
---|---|
The server returns a 204 | No body received |
If now we change the code to return a status 200
:
[~/.../chapter20-nodejs/juanIrache-20_3_public_space(master)]$ git diff server.js
diff --git a/20_3_public_space/server.js b/20_3_public_space/server.js
index 79d3f5d..66fa8b9 100644
--- a/20_3_public_space/server.js
+++ b/20_3_public_space/server.js
@@ -92,7 +92,7 @@ function pipeStream(from, to) {
methods.PUT = async function(request) {
let path = urlPath(request.url);
await pipeStream(request, createWriteStream(path));
- return { status: 204 };
+ return { status: 200, body: path };
};
And execute the same request, we get the body (the path
to the modified file):
$ curl -X PUT -d "hello world!" localhost:8000/tutu.txt
/Users/casiano/local/src/javascript/eloquent-javascript-3/juanIrache-solutions/20_3_public_space/tutu.txt
API testing requires an application to interact with API. Rest API can be tested with tools like:
Example Using Insomnia
Example Using Postman
Example Using curl
~/.../chapter20-node-js-crguezl/the-http-module(master)]$ curl -d "param1=value1¶m2=value2" -H "Content-Type: application/x-www-form-urlencoded" -X POST http://10.150.22.51:8000/data
<h1>Hello!</h1>
<p>You asked for
<code>
/data
<!-- The url is the full URL without the server, protocol or port. -->
</code>
using the POST method
</p>
<p>Your headers:</p>
<pre>
{
host: '10.150.22.51:8000',
'user-agent': 'curl/7.54.0',
accept: '*/*',
'content-type': 'application/x-www-form-urlencoded',
'content-length': '27'
}
</pre>