Google Maps JavaScript API в браузере изначально рассчитан на прямое взаимодействие клиента с серверами картографической платформы. Однако экосистема Google Maps включает не только клиентский рендеринг карт, но и множество HTTP-сервисов: Geocoding API, Places API, Distance Matrix API, Directions API. Именно эти REST-интерфейсы часто становятся причиной внедрения промежуточного слоя — прокси-сервера.
Прокси в контексте работы с API Google Maps представляет собой серверный компонент, через который проходят все запросы к внешним сервисам. Он принимает запросы от фронтенда, выполняет их от своего имени и возвращает результат обратно, при этом контролируя доступ, форматирование, кеширование и безопасность.
Прямые вызовы API из браузера сопровождаются рядом технических и архитектурных ограничений:
1. Утечка API-ключа API-ключ, встроенный в клиентский код, становится доступным любому пользователю через инструменты разработчика. Даже при ограничениях по HTTP-referer ключ может быть использован в обход сценария, например через серверные запросы.
2. Ограничения CORS Не все Google Maps API эндпоинты поддерживают кросс-доменные запросы из браузера без ограничений. Это особенно актуально для legacy REST API и кастомных интеграций.
3. Контроль квот и злоупотреблений Без промежуточного слоя невозможно ограничить частоту запросов от конкретного пользователя, что приводит к быстрому исчерпанию квоты API.
4. Отсутствие бизнес-логики Клиентская сторона не должна содержать сложную логику обработки геоданных, агрегации результатов или трансформации ответов.
Прокси становится частью серверного слоя между интерфейсом пользователя и внешними API.
Типовая схема выглядит следующим образом:
Такой подход позволяет централизовать:
Наиболее распространённый вариант — использование Node.js с фреймворком Express.
import express fr om 'express';
import fetch from 'node-fetch';
const app = express();
const PORT = 3000;
const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY;
app.get('/api/geocode', async (req, res) => {
const address = req.query.address;
if (!address) {
return res.status(400).json({ error: 'Address is required' });
}
const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${GOOGLE_API_KEY}`;
try {
const response = await fetch(url);
const data = await response.json();
res.json(data);
} catch (error) {
res.status(500).json({ error: 'Proxy request failed' });
}
});
app.listen(PORT, () => {
console.log(`Proxy server running on port ${PORT}`);
});
В данной архитектуре API-ключ полностью скрыт от клиента, а взаимодействие с Google API происходит исключительно на серверной стороне.
Часто Google Maps API возвращает избыточные или нестабильные структуры данных. Прокси может выступать как слой трансформации.
Пример упрощения ответа Geocoding API:
app.get('/api/geocode', async (req, res) => {
const address = req.query.address;
const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${GOOGLE_API_KEY}`;
const response = await fetch(url);
const data = await response.json();
const result = data.results?.[0];
const simplified = result ? {
formattedAddress: result.formatted_address,
location: result.geometry.location,
placeId: result.place_id
} : null;
res.json(simplified);
});
Такой подход снижает нагрузку на клиент и стандартизирует формат данных.
Geocoding и Places API часто возвращают одинаковые результаты для повторяющихся запросов. Введение кеширования позволяет существенно сократить количество обращений к Google API.
Наиболее распространённое решение — Redis.
import Redis from 'ioredis';
const redis = new Redis();
app.get('/api/geocode', async (req, res) => {
const address = req.query.address;
const cacheKey = `geocode:${address}`;
const cached = await redis.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}
const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${GOOGLE_API_KEY}`;
const response = await fetch(url);
const data = await response.json();
await redis.set(cacheKey, JSON.stringify(data), 'EX', 86400);
res.json(data);
});
Ключевые эффекты кеширования:
Прокси позволяет внедрить контроль нагрузки на уровне пользователей или IP-адресов.
Пример с использованием express-rate-lim it:
import rateLimit fr om 'express-rate-lim it';
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 30,
message: { error: 'Too many requests' }
});
app.use('/api/', limiter);
Это предотвращает:
Вместо классического сервера прокси может быть реализован через serverless-инфраструктуру: Google Cloud Functions, AWS Lambda или Vercel Functions.
Пример Cloud Function:
const fetch = require('node-fetch');
exports.geocodeProxy = async (req, res) => {
const address = req.query.address;
const key = process.env.GOOGLE_API_KEY;
const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${key}`;
const response = await fetch(url);
const data = await response.json();
res.status(200).send(data);
};
Особенности serverless-подхода:
Places API часто возвращает большое количество полей, которые не всегда необходимы клиенту. Прокси может фильтровать результат.
app.get('/api/places', async (req, res) => {
const query = req.query.query;
const url = `https://maps.googleapis.com/maps/api/place/textsearch/json?query=${encodeURIComponent(query)}&key=${GOOGLE_API_KEY}`;
const response = await fetch(url);
const data = await response.json();
const simplified = data.results.map(place => ({
name: place.name,
rating: place.rating,
location: place.geometry.location,
placeId: place.place_id
}));
res.json(simplified);
});
Такой слой уменьшает объём передаваемых данных и повышает предсказуемость клиентского API.
Прокси позволяет реализовать многоуровневую защиту:
1. Сокрытие ключа API-ключ хранится только на сервере, в переменных окружения.
2. Ограничение доменов Даже при компрометации прокси можно ограничить доступ к нему по IP или токенам.
3. Авторизация пользователей Добавляется проверка JWT или session-based авторизации перед проксированием запроса.
function authMiddleware(req, res, next) {
const token = req.headers.authorization;
if (!token || token !== 'valid-token') {
return res.status(403).json({ error: 'Forbidden' });
}
next();
}
app.use('/api/', authMiddleware);
Прокси становится точкой наблюдения за всеми обращениями к Google Maps API.
Возможные метрики:
Пример простого логирования:
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});
Для production-систем обычно используются специализированные системы наблюдения: Prometheus, Grafana, ELK Stack.
Google Maps API может возвращать различные типы ошибок:
Прокси позволяет унифицировать обработку:
function handleGoogleError(status) {
const map = {
'OVER_QUERY_LIMIT': 429,
'REQUEST_DENIED': 403,
'INVALID_REQUEST': 400
};
return map[status] || 500;
}
Несмотря на то, что сам JavaScript API используется на клиенте для отображения карты, прокси часто дополняет его функциональность:
Так формируется гибридная архитектура: