Este ejemplo recibe los datos JSON generados por un servicio.
Puedes probar el ejemplo en https://replit.com/@GilbertoPachec5/srvresultado?v=1. Hazle fork al proyecto y córrelo.
invocaServicio
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
invocaServicio
, 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.
ejecutaServicio
Para que el desarrollo de servicios del lado del servidor se realice de
manera sencilla, uniforme y no repetir código, se introduce la función
ejecutaServicio
, 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 la función con el código del servicio.
A lo largo de esta lección y las siguientes, se profundiza en el uso y estructura de esta clase.
JsonResponse
de JavaScript y PHPCuando un servicio que usa HTTP termina su ejecución, devuelve un conjunto de datos bien definidos por los estándares de internet, al cual llamaremos response o respuesta.
Entre los elementos de una response estám el código de estado, el texto de estado, los encabezados y el cuerpo de la respuesta.
En estos ejemplos, el cuerpo de la respuesta siempre usa el formato JSON.
Para poder manejar adecuadamente esta información, en JavaSvript y en PHP se han creado clases para manejar los datos necesarios.
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
.
A lo largo de esta lección y las siguientes, se profundiza en el uso y estructura de estas clases.
ProblemDetails
de JavaScript y PHPSe acuerdo a los estándares de internet, 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
.
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, 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 invocaServicio(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
const respuesta =
await invocaServicio(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
Ejecuta invocaServicio y
envía request (solicitud).
return [
"nombre" => "pp",
"mensaje" => "Hola."
];
Despierta y recibe request.
const respuesta =
await invocaServicio(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
Hace wait esperando response.
return [
"nombre" => "pp",
"mensaje" => "Hola."
];
Procesa la request y
genera response
(respuesta).
const respuesta =
await invocaServicio(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
Despierta y recibe response.
return [
"nombre" => "pp",
"mensaje" => "Hola."
];
Devuelve response y se duerme.
const respuesta =
await invocaServicio(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
const respuesta =
await invocaServicio(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
Nombre: pp
Mensaje: Hola.
const respuesta =
await invocaServicio(
"srv/devuelve.php")
const body = respuesta.body
alert(`Nombre: ${body.nombre}
Mensaje: ${body.mensaje}`)
Revisa el proyecto en Replit con la URL https://replit.com/@GilbertoPachec5/srvjson?v=1. Hazle fork al proyecto y córrelo. En el ambiente de desarrollo tienes la opción de descargar el proyecto en un zip.
Crea un proyecto PHP Web Server en Replit y edita o sube los archivos de este proyecto.
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 un resultado</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>Servicio que devuelve un resultado</h1> |
16 | |
17 | <p><button onclick="resultado()">Resultado</button></p> |
18 | |
19 | <script type="module"> |
20 | |
21 | import { invocaServicio } from "./lib/js/invocaServicio.js" |
22 | import { muestraError } from "./lib/js/muestraError.js" |
23 | |
24 | async function resultado() { |
25 | try { |
26 | const respuesta = |
27 | await invocaServicio( |
28 | "srv/devuelve.php") |
29 | const body = respuesta.body |
30 | alert(`Nombre: ${body.nombre} |
31 | Mensaje: ${body.mensaje}`) |
32 | } catch (error) { |
33 | muestraError(error) |
34 | } |
35 | } |
36 | // Permite que los eventos de html usen la función. |
37 | window["resultado"] = resultado |
38 | |
39 | </script> |
40 | |
41 | </body> |
42 | |
43 | </html> |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/../lib/php/ejecutaServicio.php"; |
4 | |
5 | ejecutaServicio(function () { |
6 | return [ |
7 | "nombre" => "pp", |
8 | "mensaje" => "Hola." |
9 | ]; |
10 | }); |
11 |
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 valor devuelto no puede representarse como JSON</title> |
10 | |
11 | </head> |
12 | |
13 | <body> |
14 | |
15 | <h1>El valor devuelto no puede representarse como JSON</h1> |
16 | |
17 | <p> |
18 | Debido a un error interno del servidor, la respuesta generada, no se puede |
19 | recuperar. |
20 | </p> |
21 | |
22 | </body> |
23 | |
24 | </html> |
1 | import { |
2 | JsonResponse, JsonResponse_Created, JsonResponse_NoContent, JsonResponse_OK |
3 | } from "./JsonResponse.js" |
4 | import { |
5 | ProblemDetails, ProblemDetails_InternalServerError |
6 | } from "./ProblemDetails.js" |
7 | |
8 | /** |
9 | * Espera a que la promesa de un fetch termine. Si |
10 | * hay error, lanza una excepción. Si no hay error, |
11 | * interpreta la respuesta del servidor como JSON y |
12 | * la convierte en una literal de objeto. |
13 | * @param { string | Promise<Response> } servicio |
14 | */ |
15 | export async function invocaServicio(servicio) { |
16 | let f = servicio |
17 | if (typeof servicio === "string") { |
18 | f = fetch(servicio, { |
19 | headers: { "Accept": "application/json, application/problem+json" } |
20 | }) |
21 | } else if (!(f instanceof Promise)) { |
22 | throw new Error("Servicio de tipo incorrecto.") |
23 | } |
24 | const respuesta = await f |
25 | if (respuesta.ok) { |
26 | if (respuesta.status === JsonResponse_NoContent) { |
27 | return new JsonResponse(JsonResponse_NoContent) |
28 | } |
29 | const texto = await respuesta.text() |
30 | try { |
31 | const body = JSON.parse(texto) |
32 | if (respuesta.status === JsonResponse_Created) { |
33 | const location = respuesta.headers.get("location") |
34 | return new JsonResponse(JsonResponse_Created, body, |
35 | location === null ? undefined : location) |
36 | } else { |
37 | return new JsonResponse(JsonResponse_OK, body) |
38 | } |
39 | } catch (error) { |
40 | // El contenido no es JSON. Probablemente sea texto. |
41 | throw new ProblemDetails(ProblemDetails_InternalServerError, |
42 | "Problema interno en el servidor.", texto) |
43 | } |
44 | } else { |
45 | const texto = await respuesta.text() |
46 | try { |
47 | const { type, title, detail } = JSON.parse(texto) |
48 | throw new ProblemDetails(respuesta.status, |
49 | typeof title === "string" ? title : "", |
50 | typeof detail === "string" ? detail : undefined, |
51 | typeof type === "string" ? type : undefined) |
52 | } catch (error) { |
53 | if (error instanceof ProblemDetails) { |
54 | throw error |
55 | } else { |
56 | // El contenido no es JSON. Probablemente sea texto. |
57 | throw new ProblemDetails(respuesta.status, respuesta.statusText, texto) |
58 | } |
59 | } |
60 | } |
61 | } |
62 | |
63 | // Permite que los eventos de html usen la función. |
64 | window["invocaServicio"] = invocaServicio |
1 | export const JsonResponse_OK = 200 |
2 | export const JsonResponse_Created = 201 |
3 | export const JsonResponse_NoContent = 204 |
4 | |
5 | export class JsonResponse { |
6 | |
7 | /** |
8 | * @param {number} status |
9 | * @param {any} [body] |
10 | * @param {string} [location] |
11 | */ |
12 | constructor(status, body, location) { |
13 | /** @readonly */ |
14 | this.status = status |
15 | /** @readonly */ |
16 | this.body = body |
17 | /** @readonly */ |
18 | this.location = location |
19 | } |
20 | |
21 | } |
1 | import { ProblemDetails } from "./ProblemDetails.js" |
2 | |
3 | /** |
4 | * Muestra un error en la consola y en un cuadro de |
5 | * alerta el mensaje de una excepción. |
6 | * @param { ProblemDetails | Error | null } error descripción del error. |
7 | */ |
8 | export function muestraError(error) { |
9 | if (error === null) { |
10 | console.log("Error") |
11 | alert("Error") |
12 | } else if (error instanceof ProblemDetails) { |
13 | let mensaje = error.title |
14 | if (error.detail) { |
15 | mensaje += `\n\n${error.detail}` |
16 | } |
17 | mensaje += `\n\nCódigo: ${error.status}` |
18 | if (error.type) { |
19 | mensaje += ` ${error.type}` |
20 | } |
21 | console.error(mensaje) |
22 | console.error(error) |
23 | alert(mensaje) |
24 | } else { |
25 | console.error(error) |
26 | alert(error.message) |
27 | } |
28 | } |
29 | |
30 | // Permite que los eventos de html usen la función. |
31 | window["muestraError"] = muestraError |
1 | export const ProblemDetails_BadRequest = 400 |
2 | export const ProblemDetails_NotFound = 404 |
3 | export const ProblemDetails_InternalServerError = 500 |
4 | |
5 | export class ProblemDetails extends Error { |
6 | |
7 | /** |
8 | * @param {number} status |
9 | * @param {string} title |
10 | * @param {string} [detail] |
11 | * @param {string} [type] |
12 | */ |
13 | constructor(status, title, detail, type) { |
14 | super(title) |
15 | /** @readonly */ |
16 | this.status = status |
17 | /** @readonly */ |
18 | this.type = type |
19 | /** @readonly */ |
20 | this.title = title |
21 | /** @readonly */ |
22 | this.detail = detail |
23 | } |
24 | |
25 | } |
1 | <?php |
2 | |
3 | require_once __DIR__ . "/JsonResponse.php"; |
4 | require_once __DIR__ . "/ProblemDetails.php"; |
5 | |
6 | /** |
7 | * Ejecuta una funcion que implementa un servicio. |
8 | */ |
9 | function ejecutaServicio($servicio) |
10 | { |
11 | try { |
12 | $resultado = $servicio(); |
13 | if (!($resultado instanceof JsonResponse)) { |
14 | $resultado = JsonResponse::ok($resultado); |
15 | } |
16 | procesa_json_response($resultado); |
17 | } catch (ProblemDetails $details) { |
18 | procesa_problem_details($details); |
19 | } catch (Throwable $throwable) { |
20 | procesa_problem_details(new ProblemDetails( |
21 | status: ProblemDetails::InternalServerError, |
22 | type: "/error/errorinterno.html", |
23 | title: "Error interno del servidor.", |
24 | detail: $throwable->getMessage() |
25 | )); |
26 | } |
27 | } |
28 | |
29 | function procesa_json_response(JsonResponse $response) |
30 | { |
31 | $json = ""; |
32 | $body = $response->body; |
33 | if ($response->status !== JsonResponse_NoContent) { |
34 | $json = json_encode($body); |
35 | if ($json === false) { |
36 | no_puede_generar_json(); |
37 | return; |
38 | } |
39 | } |
40 | http_response_code($response->status); |
41 | if ($response->location !== null) { |
42 | header("Location: {$response->location}"); |
43 | } |
44 | if ($response->status !== JsonResponse_NoContent) { |
45 | header("Content-Type: application/json"); |
46 | echo $json; |
47 | } |
48 | } |
49 | |
50 | function procesa_problem_details(ProblemDetails $details) |
51 | { |
52 | $body = ["title" => $details->title]; |
53 | if ($details->type !== null) { |
54 | $body["type"] = $details->type; |
55 | } |
56 | if ($details->detail !== null) { |
57 | $body["detail"] = $details->detail; |
58 | } |
59 | $json = json_encode($body); |
60 | if ($json === false) { |
61 | no_puede_generar_json(); |
62 | } else { |
63 | http_response_code($details->status); |
64 | header("Content-Type: application/problem+json"); |
65 | echo $json; |
66 | } |
67 | } |
68 | |
69 | function no_puede_generar_json() |
70 | { |
71 | http_response_code(ProblemDetails::InternalServerError); |
72 | header("Content-Type: application/problem+json"); |
73 | echo '{"type":"/error/nojson.html"' |
74 | . ',"title":"El valor devuelto no puede representarse como JSON."}'; |
75 | } |
76 |
1 | <?php |
2 | |
3 | const JsonResponse_OK = 200; |
4 | const JsonResponse_Created = 201; |
5 | const JsonResponse_NoContent = 204; |
6 | |
7 | class JsonResponse |
8 | { |
9 | |
10 | public int $status; |
11 | public $body; |
12 | public ?string $location; |
13 | |
14 | public function __construct( |
15 | int $status = JsonResponse_OK, |
16 | $body = null, |
17 | ?string $location = null |
18 | ) { |
19 | $this->status = $status; |
20 | $this->body = $body; |
21 | $this->location = $location; |
22 | } |
23 | |
24 | public static function ok($body) |
25 | { |
26 | return new JsonResponse(body: $body); |
27 | } |
28 | |
29 | public static function created(string $location, $body) |
30 | { |
31 | return new JsonResponse(JsonResponse_Created, $body, $location); |
32 | } |
33 | |
34 | public static function noContent() |
35 | { |
36 | return new JsonResponse(JsonResponse_NoContent, null); |
37 | } |
38 | } |
39 |
1 | <?php |
2 | |
3 | class ProblemDetails extends Exception |
4 | { |
5 | |
6 | public const BadRequest = 400; |
7 | public const NotFound = 404; |
8 | public const InternalServerError = 500; |
9 | |
10 | public int $status; |
11 | public string $title; |
12 | public ?string $type; |
13 | public ?string $detail; |
14 | |
15 | public function __construct( |
16 | int $status, |
17 | string $title, |
18 | ?string $type = null, |
19 | ?string $detail = null, |
20 | Throwable $previous = null |
21 | ) { |
22 | parent::__construct($title, $status, $previous); |
23 | $this->status = $status; |
24 | $this->type = $type; |
25 | $this->title = $title; |
26 | $this->detail = $detail; |
27 | } |
28 | } |
29 |
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": "ES6", |
7 | "moduleResolution": "classic", |
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.