JavaScript: Более безопасное чтение и запись URL
Возможно вы неосознанно пишите URL-адреса небезопасным способом
Можете найти баг в этом коде?
const url = `https://builder.io/api/v2/content
?model=${model}&locale=${locale}?query.text=${text}`
const res = await fetch(url)
Их как минимум три…
Мы разберём их ниже:
Распространённая проблема 1 — неправильные символы-разделители
Это, безусловно ошибка новичка, но её так легко пропустить, что я поймал ей в собственном коде даже после 10 лет JS разработки.
url = url + '?foo=bar'
Но подождите, в исходном URL-адресе мог быть параметр запроса. Ok, должно быть так:
url = url + '&foo=bar'
Но подождите, если в исходном URL-адресе не было параметров запроса, то теперь это неправильно…
Распространённая проблема 2 — забыли закодировать
model
и locale
, скорее всего не нужно кодировать, так как они безопасны для URL-адресов, но я не перестаю думать, что text
может быть любым текстом, включая пробелы и специальные символы, которые вызовут проблемы.
Та что, возможно, мы перестараемся и будем действовать более осторожно:
const url = `https://builder.io/api/v2/content
?model=${
encodeURIComponent(model)
}&locale=${
encodeURIComponent(locale)
}&query.text=${
encodeURIComponent(text)
}`
Но всё кажется немного… уродливее.
Распространённая проблема 3 — случайные пробелы
Чтобы разбить этот длинный URL-адрес на несколько строк, мы случайно включили в URL-адрес символ новой строки и лишние пробелы, из-за чего запрос по этому адресу не будет работать должным образом.
Теперь мы можем правильно разбить строку, но она становится более захламлённой и трудной для восприятия:
const url = `https://builder.io/api/v2/content`
+ `?model=${
encodeURIComponent(model)
}&locale=${
encodeURIComponent(locale)
}&query.text=${
encodeURIComponent(text)
}`
Это сильно сложно для правильного построения одного URL-адреса. И будем ли мы помнить об этом в следующий раз, тем более, что приближается дедлайн, и нам нужно скорее выпустить или исправить эту новую функцию как можно быстрее?
Должен быть лучший способ…
URL
конструктор
Более чистым и безопасным решением вышеуказанных проблем — использование URL
конструктора.
const url = new URL('https://builder.io/api/v2/content')
url.searchParams.set('model', model)
url.searchParams.set('locale', locale)
url.searchParams.set('text', text)
const res = await fetch(url.toString())
Он решает несколько проблем:
- Будут всегда правильно использоваться разделительные символы (
?
для первого параметра и&
после него). - Все параметры автоматически кодируются.
- Нет риска появления дополнительных пробельных символов при разрыве на несколько строк длинных URL-адресов.
Изменение URL-адресов
Это также полезно в случаях, когда мы изменяем URL-адрес, текущее состояние которого не известно.
Например, вместо этой проблемы:
url += (url.includes('?') ? '&' : '?') + 'foo=bar'
Мы можем сделать так:
// Полагая, что `url` это URL-адрес
url.searchParams.set('foo', 'bar')
// Или если URL это строка
const structuredUrl = new URL(url)
structuredUrl.searchParams.set('foo', 'bar')
url = structuredUrl.toString()
Точно также можно переписать другие части URL:
const url = new URL('https://builder.io')
url.pathname = '/blog' // Обновляем путь
url.hash = '#featured' // Обновляем хэш
url.host = 'www.builder.io' // Обновляем хост
url.toString() // https://www.builder.io/blog#featured
Чтение параметров URL
Теперь старая проблема Я просто хочу прочитать параметр запроса из текущего URL без библиотеки
решена.
const pageParam = new URL(location.href).searchParams.get('page')
Или обновите текущий URL-адрес с помощью:
const url = new URL(location.href)
const currentPage = Number(url.searchParams.get('page'))
url.searchParams.set('page', String(currentPage + 1))
location.href = url.toString()
Но это не ограничивается только браузером. Его также можно использовать в Node.js.
const http = require('node:http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `https://${req.headers.host}`)
// Чтение пути, запроса и т.д. ...
});
А также Deno:
import { serve } from "https://deno.land/std/http/mod.ts";
async function reqHandler(req: Request) {
const url = new URL(req.url)
// Чтение пути, запроса и т.д. ...
return new Response();
}
serve(reqHandler, { port: 8000 });
Свойства URL, которые нужно знать
Экземпляры URL
поддерживают все привычные свойства, из которых вы можете читать или писать.
const url = new URL('https://builder.io/blog?page=1');
url.protocol // https:
url.host // builder.io
url.pathname // /blog
url.search // ?page=1
url.href // https://builder.io/blog?page=1
url.origin // https://builder.io
url.searchParams.get('page') // 1
Методы URLSearchParams
, которые нужно знать
Объект URLSearchParam
, доступный в экземпляре URL
как url.searchParams
, поддерживает ряд удобных методов:
searchParams.has(name)
Проверяет содержит ли параметр поиска заданное имя
url.searchParams.has('page') // true
searchParams.get(name)
Получить значение заданного параметра
url.searchParams.get('page') // '1'
searchParams.getAll(name)
Получить все значения заданного параметра. Это удобно, если разрешено использование нескольких значений с одним и тем же именем, например &page=1&page=2
.
url.searchParams.getAll('page') // ['1']
searchParams.set(name, value)
Задать значение параметра
url.searchParams.set('page', '1')
searchParams.append(name, value)
Добавить параметр. Полезно, если вы поддерживаете использование нескольких значений одним параметром, например &page=1&page=2
.
url.searchParams.append('page', '2')
searchParams.delete(name)
Полностью удалить параметр из URL
url.searchParams.delete('page')
Поддержка браузерами и средами исполнения
new URL()
поддерживается всеми современными браузерами, а так же Node.js и Deno!