Как правильно кодировать параметры запроса в REST API
Параметры запроса в REST API должны быть закодированы через percent-encoding согласно RFC 3986, чтобы безопасно передавать специальные символы, такие как &, =, пробелы и Unicode. Используйте encodeURIComponent() в JavaScript, urllib.parse.quote() в Python или URLEncoder.encode() в Java.
Зачем параметрам запроса нужно кодирование
Строки запроса в REST API используют структурированный формат, где пары «ключ-значение» разделяются символом &, а ключи отделяются от значений символом =. Если значение параметра содержит любой из этих символов-разделителей или другие зарезервированные символы, такие как #, + или пробелы, структура строки запроса нарушается, и сервер получает некорректные данные.
Рассмотрим такой пример: вы хотите найти «salt & pepper» через API. Без кодирования URL /search?q=salt & pepper сообщает серверу, что есть два параметра: q=salt и pepper (без значения). При правильном кодировании /search?q=salt%20%26%20pepper корректно передаёт единственный параметр q со значением «salt & pepper».
Помимо корректности, правильное кодирование также предотвращает уязвимости безопасности. Незакодированный пользовательский ввод в URL может привести к атакам через инъекции, отравлению кеша и другим эксплойтам. Всегда кодируйте значения параметров, прежде чем включать их в URL.
Кодирование в разных языках
Каждый крупный язык программирования предоставляет встроенные функции для URL-кодирования. Вот как правильно кодировать параметры запроса в самых популярных языках.
// JavaScript
// Используйте encodeURIComponent() для отдельных значений параметров
const query = 'price >= 100 & category = books';
const url = 'https://api.example.com/search?q=' + encodeURIComponent(query);
// "https://api.example.com/search?q=price%20%3E%3D%20100%20%26%20category%20%3D%20books"
// Либо используйте URLSearchParams для нескольких параметров
const params = new URLSearchParams({
q: 'price >= 100 & category = books',
page: '1',
sort: 'price'
});
const url2 = 'https://api.example.com/search?' + params.toString();
# Python
from urllib.parse import urlencode, quote
# Использование urlencode для нескольких параметров
params = {
'q': 'price >= 100 & category = books',
'page': 1,
'sort': 'price'
}
query_string = urlencode(params)
url = f'https://api.example.com/search?{query_string}'
# Использование quote для одного значения
value = quote('price >= 100 & category = books', safe='')
url = f'https://api.example.com/search?q={value}'
// Java
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
String query = "price >= 100 & category = books";
String encoded = URLEncoder.encode(query, StandardCharsets.UTF_8);
// "price+%3E%3D+100+%26+category+%3D+books"
// Обратите внимание: URLEncoder использует + для пробелов (form encoding)
// Для кодирования по RFC 3986 замените + на %20
String rfc3986 = encoded.replace("+", "%20");
String url = "https://api.example.com/search?q=" + rfc3986;
// C#
using System.Net;
using System.Web;
string query = "price >= 100 & category = books";
// Uri.EscapeDataString следует RFC 3986
string encoded = Uri.EscapeDataString(query);
// "price%20%3E%3D%20100%20%26%20category%20%3D%20books"
// HttpUtility.UrlEncode использует form encoding (+ для пробелов)
string formEncoded = HttpUtility.UrlEncode(query);
// "price+%3e%3d+100+%26+category+%3d+books"
// Go
package main
import (
"fmt"
"net/url"
)
func main() {
// url.QueryEscape кодирует для строк запроса (+ для пробелов)
encoded := url.QueryEscape("price >= 100 & category = books")
// "price+%3E%3D+100+%26+category+%3D+books"
// url.PathEscape кодирует для сегментов пути (%20 для пробелов)
pathEncoded := url.PathEscape("my file name.pdf")
// "my%20file%20name.pdf"
// Использование url.Values для построения строк запроса
params := url.Values{}
params.Set("q", "price >= 100 & category = books")
params.Set("page", "1")
queryString := params.Encode()
fmt.Println(queryString)
}
Кодирование массивов и вложенных объектов
REST API часто должны принимать массивы или вложенные объекты в качестве параметров запроса. Единого стандарта для кодирования таких сложных типов не существует, поэтому разные API используют разные соглашения.
// Повторяющиеся ключи (наиболее широко поддерживаемый вариант)
// GET /api/items?tag=javascript&tag=python&tag=go
const params = new URLSearchParams();
['javascript', 'python', 'go'].forEach(tag => params.append('tag', tag));
// Скобочная нотация (распространена в PHP, Rails, Express с qs)
// GET /api/items?tags[]=javascript&tags[]=python&tags[]=go
// Требует ручного кодирования:
const tags = ['javascript', 'python', 'go'];
const qs = tags.map(t => 'tags[]=' + encodeURIComponent(t)).join('&');
// Через запятую (просто, но ограниченно)
// GET /api/items?tags=javascript,python,go
const tagList = encodeURIComponent('javascript,python,go');
// Вложенные объекты (скобочная нотация)
// GET /api/search?filter[status]=active&filter[min_price]=10
// В Python:
params = {
'filter[status]': 'active',
'filter[min_price]': '10'
}
Всегда сверяйтесь с документацией вашего API на предмет ожидаемого формата. При проектировании нового API подход с повторяющимися ключами наиболее переносим между языками программирования и HTTP-библиотеками.
Рекомендации по декодированию на стороне сервера
При обработке входящих запросов на стороне сервера большинство веб-фреймворков автоматически декодируют параметры запроса за вас. Тем не менее есть важные моменты, которые следует учитывать для надёжной обработки на стороне сервера.
- Большинство фреймворков (Express, Django, Spring, ASP.NET) декодируют параметры запроса автоматически. Не декодируйте их вручную повторно, иначе вы получите двойное декодирование.
- Проверяйте декодированные значения на соответствие ожидаемым типам и диапазонам, прежде чем использовать их.
- Устанавливайте ограничения на максимальную длину URL на уровне веб-сервера (обычно 2048 или 8192 символов).
- Учитывайте случай, когда
+может означать либо пробел, либо буквальный знак плюс — в зависимости от типа контента. - Логируйте исходный (закодированный) URL для отладки, поскольку декодированные значения в логах могут вводить в заблуждение.
// Express.js — параметры декодируются автоматически
app.get('/search', (req, res) => {
const query = req.query.q; // Уже декодировано
// НЕ делайте так: decodeURIComponent(req.query.q)
// Проверьте ввод
if (typeof query !== 'string' || query.length > 200) {
return res.status(400).json({ error: 'Invalid query parameter' });
}
// Безопасно используйте декодированное значение
const results = search(query);
res.json(results);
});
Вопросы безопасности
Правильное кодирование параметров запроса — ключевая часть безопасности API. Вот важнейшие практики безопасности, которых следует придерживаться.
- Никогда не доверяйте кодированию на стороне клиента. Всегда проверяйте и очищайте декодированные параметры на сервере, даже если клиент закодировал их правильно.
- Остерегайтесь атак с двойным кодированием. Злоумышленник может закодировать вредоносные символы дважды, чтобы обойти входные фильтры, которые декодируют только один раз. Убедитесь, что ваши фильтры безопасности видят полностью декодированные значения.
- Предотвращайте обход каталогов (path traversal). Закодированные последовательности вроде
%2e%2e%2f(../) в параметрах пути могут использоваться для доступа к файлам за пределами предусмотренного каталога. Декодируйте и проверяйте пути перед операциями с файлами. - Используйте параметризованные запросы. Даже после URL-декодирования никогда не подставляйте пользовательский ввод напрямую в SQL- или NoSQL-запросы. Используйте параметризованные запросы или ORM.
- Ограничивайте длину URL. Чрезмерно длинные строки запроса могут вызвать отказ в обслуживании. Настройте веб-сервер так, чтобы он отклонял URL, превышающие разумную длину.
- Кодируйте и вывод. Когда вы отражаете значения параметров запроса обратно в HTML-ответах, применяйте HTML-кодирование сущностей, чтобы предотвратить атаки межсайтового скриптинга (XSS).