A partir de esta lección se introducen elementos de software que facilitan el desarrollo de aplicaciones orientadas a servicios.
Este ejemplo recibe los datos JSON generados por un servicio.
Puedes probar el ejemplo en http://srvdevuelve.rf.gd/.
exportaAHtml
El código más moderno de JavaScript se distribuye en módulos.
El código de eventos de las etiquetas de HTML no tienen acceso directo al
contenido de módulos, pero al asignar un elemento del módulo como una
propiedad del objeto window, el elemento de software se vuelve visible
dentro de los eventos. Se puede hacer con una instrucción de la forma
window["propiedad"] = elementoDeSoftware
o de la forma
window.propiedad = elementoDeSoftware
La función exportaAHtml
hace visible una función a los eventos
del código HTML.
Para usarla, debes importar el archivo donde está definida esta función y luego invocarla pasándole la función que se exporta..
A lo largo de esta lección y las siguientes, se profundiza en el uso y estructura de esta función.
consumeJson
El código que se muestra en el ejemplo de servicio se repite en muchos
ejemplos posteriores, pero además debe realizar algunas cuastiones
adicionales. Para simplificar el trabajo, se introduce la función
consumeJson
, que realiza todo lo necesario para controlar la
conxión desde el navegador web hacia el servidor.
Para usarla, debes importar el archivo donde está definida esta función y luego invocarla pasándole la url del servicio, o alternativamente, pasándole el resultado de invocar la función fetch que comunica al servicio.
A lo largo de esta lección y las siguientes, se profundiza en el uso y estructura de esta función.
devuelveJson
Para devolver al cliente los resultados de la ejecución de un servicio, se
usa la función devuelveJson
, escrita en PHP.
Para usarla, debes importar el archivo donde está definida esta función
usando require
o require_once
y luego invocarla
pasándole el resultado generado por el servicio.
A lo largo de esta lección y las siguientes, se profundiza en el uso y estructura de esta clase.
ProblemDetails
Se acuerdo con el RFC 9457 en https://www.rfc-editor.org/info/rfc9457, cuando un servicio falla, debe devolver una estructura JSON conocida como Problem Details que informa los detalles del fallo.
El tipo de fallo se describe en el campo type, del problem details y, adicionalmente, todo tipo de fallo debe tener una pequeña página donde se describe el problema. En estos ejemplos, se usa la carpeta error para almacenar las páginas que decriben los errores.
Para usarla en JavaScript, debes importar el archivo donde está definida esta clase.
Para usarla en PHP, debes importar el archivo donde está definida esta
clase usando require
o require_once
. En este
ejemplo no se usa la versión en PHP; se empezará a usar a partir de la
lección sobre validaciones.
A lo largo de esta lección y las siguientes, se profundiza en el uso y estructura de estas clases.
muestraError
Manejamos los errores en el navegador web y de una manera uniforme con la
función muestraError
.
Cuando se tiene un error normal de JavaScript, se muestra la excepción correspondiente en la consola del navegador y el mensaje de la excepción en un cuadro de alert.
Cuando es un ProblemDetails, cuya estructure se revisa en la lección sobre validaciones, sus propiedades se muestran en la consola del navegador y en un cuadro de alert.
Para usarla, debes importar el archivo donde está definida esta función y colocarla dentro de un catch. Se le pasa como parámetro la excepción atrapada por el catch.
A lo largo de esta lección y las siguientes, se profundiza en el uso y estructura de esta función.
const respuesta =
await consumeJson(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
const respuesta =
await consumeJson(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
Ejecuta invocaServicio y
envía request (solicitud).
devuelveJson([
"nombre" => "pp",
"mensaje" => "Hola."
]);
Despierta y recibe request.
const respuesta =
await consumeJson(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
Hace wait esperando response.
devuelveJson([
"nombre" => "pp",
"mensaje" => "Hola."
]);
Procesa la request y
genera response
(respuesta).
const respuesta =
await consumeJson(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
Despierta y recibe response.
devuelveJson([
"nombre" => "pp",
"mensaje" => "Hola."
]);
Devuelve response y se duerme.
const respuesta =
await consumeJson(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
const respuesta =
await consumeJson(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
Nombre: pp
Mensaje: Hola.
const respuesta =
await consumeJson(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
Prueba el ejemplo en http://srvdevuelve.rf.gd/.
Crea tu proyecto en GitHub:
Crea una cuenta de email, por ejemplo, pepito@google.com
Crea una cuenta de GitHub usando el email anterior y selecciona el nombre de usuario unsando la parte inicial del correo electrónico, por ejemplo pepito.
Crea un repositorio nuevo. En la página principal de GitHub cliquea 📘 New.
En la página Create a new repository introduce los siguientes datos:
Proporciona el nombre de tu repositorio debajo de donde dice Repository name *.
Mantén la selección Public para que otros usuarios puedan ver tu proyecto.
Verifica la casilla Add a README file. En este archivo se muestra información sobre tu proyecto.
Cliquea License: None. y selecciona la licencia que consideres más adecuada para tu proyecto.
Cliquea Create repository.
Importa el proyecto en GitHub:
En la página principal de tu proyecto en GitHub, en la pestaña < > Code, cliquea < > Code y en la sección Branches y copia la dirección que está en HTTPS, debajo de Clone.
En Visual Studio Code, usa el botón de la izquierda para Source Control.
Cliquea el botón Clone Repository.
Pega la url que copiaste anteriormente hasta arriba, donde dice algo como Provide repository URL y presiona la teclea Intro.
Selecciona la carpeta donde se guardará la carpeta del proyecto.
Abre la carpeta del proyecto importado.
Añade el contenido de la carpeta descompactada que contiene el código del ejemplo.
Edita los archivos que desees.
Haz clic derecho en index.html
, selecciona
PHP Server: serve project y se abre el navegador para que puedas
probar localmente el ejemplo.
Para depurar paso a paso haz lo siguiente:
En el navegador, haz clic derecho en la página que deseas depurar y selecciona inspeccionar.
Recarga la página, de preferencia haciendo clic derecho en el ícono de volver a cargar la página y seleccionando vaciar caché y volver a cargar de manera forzada (o algo parecido). Si no aparece un menú emergente, simplemente cliquea volver a cargar la página . Revisa que no aparezca ningún error ni en la pestañas Consola, ni en Red.
Selecciona la pestaña Fuentes (o Sources si tu navegador está en Inglés).
Selecciona el archivo donde vas a empezar a depurar.
Haz clic en el número de la línea donde vas a empezar a depurar.
En Visual Studio Code, abre el archivo de PHP donde vas a empezar a depurar.
Haz clic en Run and Debug .
Si no está configurada la depuración, haz clic en create a launch json file.
Haz clic en la flechita RUN AND DEBUG, al lado de la cual debe decir Listen for Xdebug .
Aparece un cuadro con los controles de depuración
Selecciona otra vez el archivo de PHP y haz clic en el número de la línea donde vas a empezar a depurar.
Regresa al navegador, recarga la página y empieza a usarla.
Si se ejecuta alguna de las líneas de código seleccionadas, aparece resaltada en la pestaña de fuentes. Usa los controles de depuración para avanzar, como se muestra en este video.
Sube el proyecto al hosting que elijas. En algunos casos puedes usar filezilla (https://filezilla-project.org/)
Abre un navegador y prueba el proyecto en tu hosting.
En el hosting InfinityFree, la primera ves que corres la página, puede marcar un mensaje de error, pero al recargar funciona correctamente. Puedes evitar este problema usando un dominio propio.
Para subir el código a GitHub, en la sección de SOURCE CONTROL, en Message introduce un mensaje sobre los cambios que hiciste, por ejemplo index.html corregido, selecciona v y luego Commit & Push.
Haz clic en los triángulos para expandir las carpetas
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Servicio que devuelve JSON</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Servicio que devuelve JSON</h1> |
16 | |
17 | <p><button onclick="resultado()">Resultado</button></p> |
18 | |
19 | <script type="module"> |
20 | |
21 | import { exportaAHtml } from "./lib/js/exportaAHtml.js" |
22 | import { consumeJson } from "./lib/js/consumeJson.js" |
23 | import { muestraError } from "./lib/js/muestraError.js" |
24 | |
25 | async function resultado() { |
26 | try { |
27 | const respuesta = |
28 | await consumeJson( |
29 | "srv/devuelve.php") |
30 | const body = respuesta.body |
31 | alert(`Nombre: ${body.nombre} |
32 | Mensaje: ${body.mensaje}`) |
33 | } catch (error) { |
34 | muestraError(error) |
35 | } |
36 | } |
37 | exportaAHtml(resultado) |
38 | |
39 | </script> |
40 | |
41 | </body> |
42 | |
43 | </html> |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/devuelveJson.php"; |
4 | |
5 | devuelveJson([ |
6 | "nombre" => "pp", |
7 | "mensaje" => "Hola." |
8 | ]); |
9 |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>Error interno del servidor</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Error interno del servidor</h1> |
16 | |
17 | <p>Se presentó de forma inesperada un error interno del servidor.</p> |
18 | |
19 | </body> |
20 | |
21 | </html> |
1 | <!DOCTYPE html> |
2 | <html lang="es"> |
3 | |
4 | <head> |
5 | |
6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width"> |
8 | |
9 | <title>El resultado no puede representarse como JSON</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>El resultado no puede representarse como JSON</h1> |
16 | |
17 | <p> |
18 | Debido a un error interno del servidor, el resultado generado, no se puede |
19 | recuperar. |
20 | </p> |
21 | |
22 | </body> |
23 | |
24 | </html> |
1 | import { exportaAHtml } from "./exportaAHtml.js" |
2 | import { ProblemDetails } from "./ProblemDetails.js" |
3 | |
4 | /** |
5 | * Espera a que la promesa de un fetch termine. Si |
6 | * hay error, lanza una excepción. Si no hay error, |
7 | * interpreta la respuesta del servidor como JSON y |
8 | * la convierte en una literal de objeto. |
9 | * |
10 | * @param { string | Promise<Response> } servicio |
11 | */ |
12 | export async function consumeJson(servicio) { |
13 | |
14 | if (typeof servicio === "string") { |
15 | servicio = fetch(servicio, { |
16 | headers: { "Accept": "application/json, application/problem+json" } |
17 | }) |
18 | } else if (!(servicio instanceof Promise)) { |
19 | throw new Error("Servicio de tipo incorrecto.") |
20 | } |
21 | |
22 | const respuesta = await servicio |
23 | |
24 | const headers = respuesta.headers |
25 | |
26 | if (respuesta.ok) { |
27 | // Aparentemente el servidor tuvo éxito. |
28 | |
29 | if (respuesta.status === 204) { |
30 | // No contiene texto de respuesta. |
31 | |
32 | return { headers, body: {} } |
33 | |
34 | } else { |
35 | |
36 | const texto = await respuesta.text() |
37 | |
38 | try { |
39 | |
40 | return { headers, body: JSON.parse(texto) } |
41 | |
42 | } catch (error) { |
43 | |
44 | // El contenido no es JSON. Probablemente sea texto de un error. |
45 | throw new ProblemDetails(respuesta.status, headers, texto, |
46 | "/error/errorinterno.html") |
47 | |
48 | } |
49 | |
50 | } |
51 | |
52 | } else { |
53 | // Hay un error. |
54 | |
55 | const texto = await respuesta.text() |
56 | |
57 | if (texto === "") { |
58 | |
59 | // No hay texto. Se usa el texto predeterminado. |
60 | throw new ProblemDetails(respuesta.status, headers, respuesta.statusText) |
61 | |
62 | } else { |
63 | // Debiera se un ProblemDetails en JSON. |
64 | |
65 | try { |
66 | |
67 | const { title, type, detail } = JSON.parse(texto) |
68 | |
69 | throw new ProblemDetails(respuesta.status, headers, |
70 | typeof title === "string" ? title : respuesta.statusText, |
71 | typeof type === "string" ? type : undefined, |
72 | typeof detail === "string" ? detail : undefined) |
73 | |
74 | } catch (error) { |
75 | |
76 | if (error instanceof ProblemDetails) { |
77 | // El error si era un ProblemDetails |
78 | |
79 | throw error |
80 | |
81 | } else { |
82 | |
83 | throw new ProblemDetails(respuesta.status, headers, respuesta.statusText, |
84 | undefined, texto) |
85 | |
86 | } |
87 | |
88 | } |
89 | |
90 | } |
91 | |
92 | } |
93 | |
94 | } |
95 | |
96 | exportaAHtml(consumeJson) |
1 | /** |
2 | * Permite que los eventos de html usen la función. |
3 | * @param {function} functionInstance |
4 | */ |
5 | export function exportaAHtml(functionInstance) { |
6 | window[nombreDeFuncionParaHtml(functionInstance)] = functionInstance |
7 | } |
8 | |
9 | /** |
10 | * @param {function} valor |
11 | */ |
12 | export function nombreDeFuncionParaHtml(valor) { |
13 | const names = valor.name.split(/\s+/g) |
14 | return names[names.length - 1] |
15 | } |
1 | import { exportaAHtml } from "./exportaAHtml.js" |
2 | import { ProblemDetails } from "./ProblemDetails.js" |
3 | |
4 | /** |
5 | * Muestra un error en la consola y en un cuadro de |
6 | * alerta el mensaje de una excepción. |
7 | * @param { ProblemDetails | Error | null } error descripción del error. |
8 | */ |
9 | export function muestraError(error) { |
10 | |
11 | if (error === null) { |
12 | |
13 | console.error("Error") |
14 | alert("Error") |
15 | |
16 | } else if (error instanceof ProblemDetails) { |
17 | |
18 | let mensaje = error.title |
19 | if (error.detail) { |
20 | mensaje += `\n\n${error.detail}` |
21 | } |
22 | mensaje += `\n\nCódigo: ${error.status}` |
23 | if (error.type) { |
24 | mensaje += ` ${error.type}` |
25 | } |
26 | |
27 | console.error(mensaje) |
28 | console.error(error) |
29 | console.error("Headers:") |
30 | error.headers.forEach((valor, llave) => console.error(llave, "=", valor)) |
31 | alert(mensaje) |
32 | |
33 | } else { |
34 | |
35 | console.error(error) |
36 | alert(error.message) |
37 | |
38 | } |
39 | |
40 | } |
41 | |
42 | exportaAHtml(muestraError) |
1 | /** |
2 | * Detalle de los errores devueltos por un servicio. |
3 | */ |
4 | export class ProblemDetails extends Error { |
5 | |
6 | /** |
7 | * @param {number} status |
8 | * @param {Headers} headers |
9 | * @param {string} title |
10 | * @param {string} [type] |
11 | * @param {string} [detail] |
12 | */ |
13 | constructor(status, headers, title, type, detail) { |
14 | super(title) |
15 | /** |
16 | * @readonly |
17 | */ |
18 | this.status = status |
19 | /** |
20 | * @readonly |
21 | */ |
22 | this.headers = headers |
23 | /** |
24 | * @readonly |
25 | */ |
26 | this.type = type |
27 | /** |
28 | * @readonly |
29 | */ |
30 | this.detail = detail |
31 | /** |
32 | * @readonly |
33 | */ |
34 | this.title = title |
35 | } |
36 | |
37 | } |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/devuelveResultadoNoJson.php"; |
4 | |
5 | function devuelveJson($resultado) |
6 | { |
7 | |
8 | $json = json_encode($resultado); |
9 | |
10 | if ($json === false) { |
11 | |
12 | devuelveResultadoNoJson(); |
13 | } else { |
14 | |
15 | http_response_code(200); |
16 | header("Content-Type: application/json"); |
17 | echo $json; |
18 | } |
19 | } |
20 |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/INTERNAL_SERVER_ERROR.php"; |
4 | |
5 | function devuelveResultadoNoJson() |
6 | { |
7 | |
8 | http_response_code(INTERNAL_SERVER_ERROR); |
9 | header("Content-Type: application/problem+json"); |
10 | echo '{' . |
11 | '"title": "El resultado no puede representarse como JSON."' . |
12 | '"type": "/error/resultadonojson.html"' . |
13 | '}'; |
14 | } |
15 |
1 | <?php |
2 | |
3 | const INTERNAL_SERVER_ERROR = 500; |
Este archivo ayuda a detectar errores en los archivos del proyecto.
Lo utiliza principalmente Visual Studio Code.
No se explica aquí su estructura, pero puede encontrarse la explicación de todo en la documentación del sitio de Visual Studio Code.
1 | { |
2 | "compilerOptions": { |
3 | "checkJs": true, |
4 | "strictNullChecks": true, |
5 | "target": "ES6", |
6 | "module": "Node16", |
7 | "moduleResolution": "Node16", |
8 | "lib": [ |
9 | "ES2017", |
10 | "WebWorker", |
11 | "DOM" |
12 | ] |
13 | } |
14 | } |
En esta lección se mostró el funcionamiento de un ejemplo recibe los datos JSON generados por un servicio.