Todas las partes de una URL y qué puede contener cada una
Una URL es una sola cadena que dice qué protocolo hablar, con qué máquina hablarlo y qué pedir. Leerla correctamente es sobre todo cuestión de saber dónde termina cada parte, porque los delimitadores son caracteres sueltos y gana el primero que aparece.
https://alex:s3cret@www.example.co.uk:8443/docs/intro?lang=en&page=2#notes
│ │ │ │ │ │ └─ fragment
│ │ │ │ │ └──────────────── query
│ │ │ │ └──────────────────────────── path
│ │ │ └──────────────────────────────── port
│ │ └────────────────────────────────────────────────── host
│ └────────────────────────────────────────────────────────────── userinfo
└────────────────────────────────────────────────────────────────────── scheme
Las partes
| Parte | Delimitada por | Para qué sirve | Llega al servidor |
|---|---|---|---|
| Esquema | termina en el primer : | Qué protocolo y qué reglas se aplican al resto | Determina la conexión |
| Userinfo | tras //, termina en @ | Credenciales, obsoletas desde hace mucho para http y https | Solo si el cliente decide enviarlas |
| Host | termina en :, /, ? o # | Un nombre de dominio o dirección IP que resolver y a la que conectarse | Sí, en la cabecera Host |
| Puerto | tras :, termina en /, ? o # | El puerto TCP, 80 por defecto para http y 443 para https | Se usa para la conexión |
| Ruta | empieza en /, termina en ? o # | Qué recurso de ese host | Sí, en la línea de petición |
| Consulta | empieza en ?, termina en # | Parámetros para ese recurso | Sí, en la línea de petición |
| Fragmento | empieza en #, llega hasta el final | Una posición dentro del recurso | No |
El host y el puerto juntos forman la autoridad. El fragmento es la excepción: se elimina antes de construir la petición, así que un servidor nunca lo ve. Los navegadores lo usan para anclas y para el enrutado en el cliente, y algunos flujos de autenticación devuelven tokens en un fragmento a propósito, para que esos tokens no aparezcan nunca en un registro del servidor. Aun así queda en el historial del navegador, así que no es privado, solo no se envía.
Caracteres reservados y no reservados
La codificación porcentual escribe un byte como % más dos dígitos hexadecimales, usando los bytes UTF-8 del carácter. é se convierte en %C3%A9, dos bytes y dos escapes.
Cuatro caracteres son no reservados junto a las letras y los dígitos, y no hace falta codificarlos en ninguna parte: - . _ ~. Todo lo demás o es reservado, es decir, cumple una función estructural en algún punto de una URL, o tiene que codificarse. El conjunto reservado es : / ? # [ ] @ y ! $ & ' ( ) * + , ; =.
La palabra «reservado» no significa «prohibido siempre». Significa que el carácter es un delimitador en alguna parte, y que solo hay que codificarlo en las partes donde se leería como tal.
| Carácter | En un segmento de ruta | En un valor de consulta |
|---|---|---|
| Espacio | %20, siempre | %20 o + |
/ | %2F, o parte el segmento | Válido tal cual |
? | %3F, o la ruta acaba ahí | Válido tal cual |
# | %23, siempre | %23, siempre |
& y = | Válidos tal cual | %26 y %3D, o el par se parte |
+ | Válido, y significa un signo más | %2B, o puede decodificarse como un espacio |
Esa es la regla práctica: codifica el carácter que terminaría la parte en la que estás. Un espacio en una ruta tiene que ser %20, porque un espacio sin codificar termina la URL en la mayoría de analizadores, mientras que un espacio en un valor de consulta puede ser %20 o el antiguo +. Un codificador de URL es la forma fiable de hacerlo, porque codificar una URL entera y codificar un solo componente son operaciones distintas y casi siempre lo que hace falta es la segunda.
Las cadenas de consulta son una convención
La especificación solo dice que la consulta es todo lo que hay entre ? y #, tomado del conjunto de caracteres permitidos. La forma key=value&key=value viene de la codificación de formularios HTML, no del estándar de URI. Nada impide que un servidor interprete ?a:1;b:2 como le convenga.
Como la forma es una convención, las claves repetidas no tienen un significado definido y cada plataforma eligió su propia respuesta:
Comportamiento con ?id=1&id=2 | Dónde |
|---|---|
| Ambos valores, como lista | parse_qs de Python, querystring de Node, Express |
| Solo el primer valor | Query().Get de Go, getParameter de Java |
| Solo el último valor | PHP, Rails |
Unidos en 1,2 | Colecciones de petición de ASP.NET |
Los arrays heredan el mismo problema. ids=1&ids=2, ids[]=1&ids[]=2, ids[0]=1&ids[1]=2 e ids=1,2 están todos muy extendidos, y solo el servidor decide cuál entiende. La notación con corchetes hay que codificarla para que sea estrictamente válida (ids%5B%5D=1), aunque la mayoría de servidores aceptan los corchetes sin codificar. Elige la forma que documente el lado receptor y mantenla.
El signo más merece un aviso aparte. En datos application/x-www-form-urlencoded, es decir, un cuerpo de petición codificado como formulario, + significa un espacio. En una ruta de URL significa un más literal. En una cadena de consulta depende del analizador, y la mayoría de frameworks web aplican ahí la decodificación de formulario, así que + se convierte en un espacio sin avisar. Cualquier valor que pueda contener legítimamente un más, como la salida estándar de Base64 o un número de teléfono, tiene que enviarse como %2B.
Referencias relativas
Una referencia relativa se resuelve contra una URL base sustituyendo todo lo que hay después de la última / de la base. Esa última barra es toda la regla, y es la razón por la que una barra final importa.
| Base | Referencia | Resultado |
|---|---|---|
https://ex.com/docs/intro | guide | https://ex.com/docs/guide |
https://ex.com/docs/intro/ | guide | https://ex.com/docs/intro/guide |
https://ex.com/docs/intro | /guide | https://ex.com/guide |
https://ex.com/docs/intro/ | ../guide | https://ex.com/docs/guide |
https://ex.com/docs/intro?a=1 | ?b=2 | https://ex.com/docs/intro?b=2 |
https://ex.com/docs/intro?a=1 | #top | https://ex.com/docs/intro?a=1#top |
https://ex.com/docs/intro | //cdn.ex.com/x.js | https://cdn.ex.com/x.js |
La última fila es una referencia relativa al esquema: dos barras iniciales conservan el esquema de la base y sustituyen la autoridad. Una referencia que empieza por ? conserva la ruta y descarta la consulta anterior, y una que empieza por # conserva las dos.
Cuándo dos URL son el mismo recurso
El esquema y el host no distinguen mayúsculas de minúsculas, así que HTTPS://Example.COM y https://example.com son una misma dirección. Todo lo que va después del host sí las distingue en lo que respecta al estándar, aunque un servidor concreto decida ignorarlas.
Estos pares son equivalentes:
https://example.com:443/ayhttps://example.com/a, porque el puerto es el que se usa por defectohttps://example.comyhttps://example.com/, porque una ruta vacía significa la raíz/a/%7Eusery/a/~user, porque~es no reservado y codificarlo no cambia nada
Estos pares no lo son:
/docsy/docs/, que son recursos distintos, aunque la mayoría de servidores redirigen uno al otro/p?a=1&b=2y/p?b=2&a=1, ya que el orden de los parámetros forma parte de la cadena/index.htmly/, salvo que el servidor diga lo contrario
Las cachés y las CDN indexan por los bytes exactos, así que un parámetro de seguimiento añadido o una consulta reordenada divide un objeto en caché en dos y reduce a la mitad la tasa de aciertos. Los buscadores tratan las variantes como páginas duplicadas salvo que un enlace canónico apunte a una forma elegida. La solución es escoger una sola forma, redirigir el resto a ella con un 301 y eliminar los parámetros que no cambian la respuesta.
Longitud, registros y privacidad
En la especificación no existe ningún límite de longitud, pero existen límites en todos los demás sitios. Los valores por defecto habituales de los servidores limitan la línea de petición completa a unos 8 KB, algunos proxies y dispositivos a 4 KB, y los clientes antiguos a unos 2 KB. Todo lo que tenga que sobrevivir a clientes de correo, códigos QR y acortadores de enlaces está más seguro por debajo de unos 2000 caracteres.
El argumento más fuerte para mantener las URL cortas es que una cadena de consulta no es privada. Se escribe en los registros de acceso, se pasa a terceros en la cabecera Referer, se guarda en el historial del navegador, se almacena con los marcadores y se copia cada vez que alguien comparte el enlace. Por eso los tokens de sesión, los códigos de restablecimiento de contraseña, las claves de API y los datos personales no pintan nada ahí. Los datos grandes o sensibles van en el cuerpo de la petición, que no tiene un techo de tamaño práctico y no se registra por defecto.
Leer un enlace antes de hacer clic
La autoridad empieza después de :// y termina en la primerísima /, ? o #. Lee ese tramo y luego lee sus dos o tres últimas etiquetas. Ese es el host real, y nada que esté a su izquierda lo cambia.
https://www.paypal.com@198.51.100.7/secure/login
^^^^^^^^^^^^^^ ^^^^^^^^^^^^
userinfo real host
Disfraces habituales que conviene reconocer:
- El texto que va antes de una
@es userinfo, nunca el host, y puede ser el nombre de cualquier marca. paypal.com.secure-login.examplees un subdominio desecure-login.example. Las etiquetas se leen de derecha a izquierda.https://short.example/https://www.bank.com/loginmete una URL entera y convincente en la ruta.- Un host que empieza por
xn--es Punycode, la forma ASCII de un nombre de dominio internacionalizado que produce IDNA.münchen.deviaja por la red comoxn--mnchen-3ya.de, lo cual es legítimo, pero el mismo mecanismo permite homógrafos: laаcirílica (U+0430) es visualmente idéntica a laalatina, y un dominio que la use se codifica como algo parecido axn--pple-43d.com. Los navegadores muestran la forma Punycode cuando una etiqueta mezcla alfabetos, aunque las reglas varían y no son ninguna garantía. - Los delimitadores codificados, como
%2Fo%40, dentro de un host son un intento deliberado de confundir a un analizador.
Pegar el enlace en un analizador de URL lo resuelve en un solo paso, porque un analizador real aplica la misma regla de «gana el primer delimitador» que aplicará el navegador y muestra el host por separado. Para comprobar muchos enlaces a la vez, un probador de regex es una forma rápida de confirmar cuáles tienen de verdad la autoridad que esperas.