Saltar al contenido principal

Obtener datos de internet

Obtener datos a través de internet usando el paquete http.

La mayoría de las aplicaciones requieren algún tipo de comunicación o recuperación de datos de internet. Muchas aplicaciones lo hacen a través de solicitudes HTTP, que se envían desde un cliente a un servidor para realizar una acción específica para un recurso identificado a través de un URI (Uniform Resource Identifier).

Técnicamente, los datos comunicados a través de HTTP pueden tener cualquier formato, pero usar JSON (JavaScript Object Notation) es una opción popular debido a su legibilidad para los humanos y su naturaleza independiente del lenguaje. El Dart SDK y su ecosistema también cuentan con un amplio soporte para JSON con múltiples opciones para satisfacer de la mejor manera los requisitos de tu aplicación.

En este tutorial, aprenderás más sobre solicitudes HTTP, URIs y JSON. Luego aprenderás cómo usar package:http así como el soporte JSON de Dart en la librería dart:convert para obtener, decodificar y luego usar datos en formato JSON recuperados de un servidor HTTP.

Conceptos de trasfondo

#

Las siguientes secciones proporcionan un trasfondo e información adicionales sobre las tecnologías y conceptos utilizados en el tutorial para facilitar la obtención de datos del servidor. Para saltar directamente al contenido del tutorial, consulta Recuperar las dependencias necesarias.

JSON

#

JSON (JavaScript Object Notation) es un formato de intercambio de datos que se ha vuelto omnipresente en el desarrollo de aplicaciones y la comunicación cliente-servidor. Es ligero pero también fácil de leer y escribir para los humanos debido a que está basado en texto. Con JSON, varios tipos de datos y estructuras de datos simples como listas y mapas se pueden serializar y representar mediante strings.

La mayoría de los lenguajes tienen muchas implementaciones y los analizadores se han vuelto extremadamente rápidos, por lo que no necesitas preocuparte por la interoperabilidad o el rendimiento. Para obtener más información sobre el formato JSON, consulta Introducción a JSON. Para aprender más sobre cómo trabajar con JSON en Dart, consulta la guía Uso de JSON.

Solicitudes HTTP

#

HTTP (Hypertext Transfer Protocol) es un protocolo sin estado diseñado para transmitir documentos, originalmente entre clientes web y servidores web. Interactuaste con el protocolo para cargar esta página, ya que tu navegador utiliza una solicitud HTTP GET para recuperar el contenido de una página de un servidor web. Desde su introducción, el uso del protocolo HTTP y sus diversas versiones se ha expandido también a aplicaciones fuera de la web, básicamente dondequiera que se necesite comunicación de un cliente a un servidor.

Las solicitudes HTTP enviadas desde el cliente para comunicarse con el servidor están compuestas por múltiples componentes. Las librerías HTTP, como package:http, te permiten especificar los siguientes tipos de comunicación:

  • Un método HTTP que define la acción deseada, como GET para recuperar datos o POST para enviar nuevos datos.
  • La ubicación del recurso a través de un URI.
  • La versión de HTTP que se está utilizando.
  • Cabeceras que proporcionan información adicional al servidor.
  • Un cuerpo opcional, para que la solicitud pueda enviar datos al servidor, no solo recuperarlos.

Para aprender más sobre el protocolo HTTP, consulta Una descripción general de HTTP en mdn web docs.

URIs y URLs

#

Para hacer una solicitud HTTP, necesitas proporcionar un URI (Uniform Resource Identifier) al recurso. Un URI es una cadena de caracteres que identifica de manera única un recurso. Una URL (Uniform Resource Locator) es un tipo específico de URI que también proporciona la ubicación del recurso. Las URLs de los recursos en la web contienen tres elementos de información. Para esta página actual, la URL está compuesta por:

  • El esquema utilizado para determinar el protocolo utilizado: https
  • La autoridad o nombre de host del servidor: dart.dev
  • La ruta al recurso: /server/fetch-data.html

También hay otros parámetros opcionales que no son utilizados por la página actual:

  • Parámetros para personalizar comportamiento adicional: ?key1=value1&key2=value2
  • Un ancla, que no se envía al servidor, la cual apunta a una ubicación específica en el recurso: #uris

Para aprender más sobre las URLs, consulta ¿Qué es una URL? en mdn web docs.

Recuperar las dependencias necesarias

#

La librería package:http proporciona una solución multiplataforma para realizar solicitudes HTTP componibles, con un control detallado opcional.

Para agregar una dependencia de package:http, ejecuta el siguiente comando dart pub add desde la raíz de tu repositorio:

dart pub add http

Para usar package:http en tu código, impórtalo y, opcionalmente, especifica un prefijo de librería:

dart
import 'package:http/http.dart' as http;

Para aprender más detalles sobre package:http, consulta su página en el sitio pub.dev y su documentación de la API.

Construir una URL

#

Como se mencionó anteriormente, para hacer una solicitud HTTP, primero necesitas una URL que identifique el recurso solicitado o el endpoint al que se accede.

En Dart, las URLs se representan mediante objetos Uri. Hay muchas formas de construir un Uri, pero debido a su flexibilidad, analizar una cadena con Uri.parse para crear uno es una solución común.

El siguiente snippet muestra dos formas de crear un objeto Uri que apunta a información simulada en formato JSON sobre package:http alojado en este sitio:

dart
// Parse the entire URI, including the scheme
Uri.parse('https://dart.dev/f/packages/http.json');

// Specifically create a URI with the https scheme
Uri.https('dart.dev', '/f/packages/http.json');

Para aprender sobre otras formas de construir e interactuar con URIs, consulta la documentación de URI.

Hacer una solicitud de red

#

Si solo necesitas obtener rápidamente una representación en cadena (string) de un recurso solicitado, puedes usar la función de nivel superior read que se encuentra en package:http que devuelve un Future<String> o lanza una ClientException si la solicitud no fue exitosa. El siguiente ejemplo utiliza read para recuperar la información simulada en formato JSON sobre package:http como un string, y luego la imprime:

dart
void main() async {
  final httpPackageUrl = Uri.https('dart.dev', '/f/packages/http.json');
  final httpPackageInfo = await http.read(httpPackageUrl);
  print(httpPackageInfo);
}

Esto produce la siguiente salida en formato JSON, que también se puede ver en tu navegador en /f/packages/http.json.

json
{
  "name": "http",
  "latestVersion": "1.1.2",
  "description": "A composable, multi-platform, Future-based API for HTTP requests.",
  "publisher": "dart.dev",
  "repository": "https://github.com/dart-lang/http"
}

Ten en cuenta la estructura de los datos (en este caso un mapa), ya que la necesitarás al decodificar el JSON más adelante.

Si necesitas otra información de la respuesta, como el código de estado o las cabeceras, puedes usar en su lugar la función de nivel superior get que devuelve un Future con una Response.

El siguiente snippet utiliza get para obtener la respuesta completa con el fin de salir temprano si la solicitud no fue exitosa, lo cual se indica con un código de estado de 200:

dart
void main() async {
  final httpPackageUrl = Uri.https('dart.dev', '/f/packages/http.json');
  final httpPackageResponse = await http.get(httpPackageUrl);
  if (httpPackageResponse.statusCode != 200) {
    print('Failed to retrieve the http package!');
    return;
  }
  print(httpPackageResponse.body);
}

Hay muchos otros códigos de estado además del 200 y es posible que tu aplicación quiera manejarlos de manera diferente. Para obtener más información sobre lo que significan los diferentes códigos de estado, consulta Códigos de estado de respuesta HTTP en mdn web docs.

Algunas solicitudes al servidor requieren más información, como información de autenticación o de agente de usuario (user-agent); en este caso es posible que debas incluir cabeceras HTTP. Puedes especificar las cabeceras pasando un Map<String, String> de los pares clave-valor como el parámetro opcional nombrado headers:

dart
await http.get(
  Uri.https('dart.dev', '/f/packages/http.json'),
  headers: {'User-Agent': '<product name>/<product-version>'},
);

Hacer múltiples solicitudes

#

Si estás realizando múltiples solicitudes al mismo servidor, puedes en su lugar mantener una conexión persistente a través de un Client, el cual tiene métodos similares a los de nivel superior. Solo asegúrate de limpiar los recursos con el método close cuando hayas terminado:

dart
void main() async {
  final httpPackageUrl = Uri.https('dart.dev', '/f/packages/http.json');
  final client = http.Client();
  try {
    final httpPackageInfo = await client.read(httpPackageUrl);
    print(httpPackageInfo);
  } finally {
    client.close();
  }
}

Para permitir que el cliente vuelva a intentar las solicitudes fallidas, importa package:http/retry.dart y envuelve tu Client creado en un RetryClient:

dart
import 'package:http/http.dart' as http;
import 'package:http/retry.dart';

void main() async {
  final httpPackageUrl = Uri.https('dart.dev', '/f/packages/http.json');
  final client = RetryClient(http.Client());
  try {
    final httpPackageInfo = await client.read(httpPackageUrl);
    print(httpPackageInfo);
  } finally {
    client.close();
  }
}

El RetryClient tiene un comportamiento predeterminado para la cantidad de veces que debe reintentar y cuánto tiempo esperar entre cada solicitud, pero su comportamiento se puede modificar mediante parámetros en los constructores RetryClient() o RetryClient.withDelays().

package:http tiene mucha más funcionalidad y personalización, así que asegúrate de revisar su página en el sitio pub.dev y su documentación de la API.

Decodificar los datos recuperados

#

Aunque ahora has realizado una solicitud de red y has recuperado los datos devueltos como un string, acceder a partes específicas de información desde un string puede ser un desafío.

Dado que los datos ya están en formato JSON, puedes usar la función integrada de Dart json.decode en la librería dart:convert para convertir la cadena de texto cruda en una representación JSON utilizando objetos Dart. En este caso, los datos JSON se representan en una estructura de mapa y, en JSON, las claves del mapa siempre son strings, por lo que puedes convertir el resultado de json.decode en un Map<String, dynamic>:

dart
import 'dart:convert';

import 'package:http/http.dart' as http;

void main() async {
  final httpPackageUrl = Uri.https('dart.dev', '/f/packages/http.json');
  final httpPackageInfo = await http.read(httpPackageUrl);
  final httpPackageJson = json.decode(httpPackageInfo) as Map<String, dynamic>;
  print(httpPackageJson);
}

Crear una clase estructurada para almacenar los datos

#

Para proporcionar al JSON decodificado una mayor estructura, haciendo que sea más fácil trabajar con él, crea una clase que pueda almacenar los datos recuperados usando tipos específicos según el esquema de tus datos.

El siguiente snippet muestra una representación basada en clases que puede almacenar la información del paquete devuelta desde el archivo JSON simulado que solicitaste. Esta estructura asume que todos los campos excepto repository son obligatorios y se proporcionan siempre.

dart
class PackageInfo {
  final String name;
  final String latestVersion;
  final String description;
  final String publisher;
  final Uri? repository;

  PackageInfo({
    required this.name,
    required this.latestVersion,
    required this.description,
    required this.publisher,
    this.repository,
  });
}

Codificar los datos en tu clase

#

Ahora que tienes una clase para almacenar tus datos, necesitas agregar un mecanismo para convertir el JSON decodificado en un objeto PackageInfo.

Convierte el JSON decodificado escribiendo manualmente un método fromJson que coincida con el formato JSON anterior, realizando las conversiones de tipo según sea necesario y manejando el campo opcional repository:

dart
class PackageInfo {
  // ···

  factory PackageInfo.fromJson(Map<String, dynamic> json) {
    final repository = json['repository'] as String?;

    return PackageInfo(
      name: json['name'] as String,
      latestVersion: json['latestVersion'] as String,
      description: json['description'] as String,
      publisher: json['publisher'] as String,
      repository: repository != null ? Uri.tryParse(repository) : null,
    );
  }
}

Un método escrito a mano, como en el ejemplo anterior, suele ser suficiente para estructuras JSON relativamente simples, pero también existen opciones más flexibles. Para aprender más sobre la serialización y deserialización de JSON, incluyendo la generación automática de la lógica de conversión, consulta la guía Uso de JSON.

Convertir la respuesta a un objeto de tu clase estructurada

#

Ahora tienes una clase para almacenar tus datos y una forma de convertir el objeto JSON decodificado en un objeto de ese tipo. A continuación, puedes escribir una función que lo una todo:

  1. Crea tu URI basándote en el nombre de paquete pasado.
  2. Usa http.get para recuperar los datos de ese paquete.
  3. Si la solicitud no tiene éxito, lanza una Exception o preferiblemente tu propia subclase personalizada de Exception.
  4. Si la solicitud tiene éxito, usa json.decode para decodificar el cuerpo de la respuesta en una cadena JSON.
  5. Convierte la cadena JSON decodificada en un objeto PackageInfo usando el constructor de fábrica PackageInfo.fromJson que creaste.
dart
Future<PackageInfo> getPackage(String packageName) async {
  final packageUrl = Uri.https('dart.dev', '/f/packages/$packageName.json');
  final packageResponse = await http.get(packageUrl);

  // If the request didn't succeed, throw an exception
  if (packageResponse.statusCode != 200) {
    throw PackageRetrievalException(
      packageName: packageName,
      statusCode: packageResponse.statusCode,
    );
  }

  final packageJson = json.decode(packageResponse.body) as Map<String, dynamic>;

  return PackageInfo.fromJson(packageJson);
}

class PackageRetrievalException implements Exception {
  final String packageName;
  final int? statusCode;

  PackageRetrievalException({required this.packageName, this.statusCode});
}

Utilizar los datos convertidos

#

Ahora que has recuperado los datos y los has convertido a un formato más accesible, puedes usarlos como quieras. Algunas posibilidades incluyen mostrar la información en una CLI, o mostrarla en una aplicación web o de Flutter.

Aquí tienes un ejemplo completo y ejecutable que solicita, decodifica y luego muestra la información simulada sobre los paquetes http y path:

¿Qué sigue?

#

Ahora que has recuperado, analizado y utilizado datos de internet, considera aprender más sobre Concurrencia en Dart. Si tus datos son grandes y complejos, puedes mover la recuperación y decodificación a otro isolate como un trabajador en segundo plano para evitar que tu interfaz deje de responder.