При разработке картографических приложений на базе Mapbox GL JS часто возникает необходимость реагировать на события, происходящие вне браузера. Например:
Для решения подобных задач используются Webhooks — механизм автоматической доставки HTTP-запросов между системами при наступлении определённого события.
Mapbox GL JS сам по себе является клиентской библиотекой и не предоставляет встроенной системы вебхуков. Однако вебхуки широко используются совместно с Mapbox-приложениями через серверную инфраструктуру.
Типичная схема выглядит следующим образом:
Источник данных
│
▼
Webhook
│
▼
Backend API
│
▼
База данных
│
▼
Mapbox GL JS
После получения вебхука сервер обновляет данные, а клиентское приложение отображает изменения на карте.
Webhook представляет собой HTTP-запрос, автоматически отправляемый одной системой в другую после возникновения определённого события.
В отличие от регулярного опроса сервера (Polling), вебхуки работают по модели уведомлений.
Клиент → Сервер
Есть изменения?
Клиент → Сервер
Есть изменения?
Клиент → Сервер
Есть изменения?
Событие произошло
│
▼
Сервер автоматически отправляет запрос
│
▼
Получатель обновляет данные
Такой подход:
Для Mapbox GL JS вебхуки обычно выступают связующим звеном между источником данных и интерфейсом карты.
Пример процесса:
С точки зрения Mapbox GL JS изменения становятся видимыми через обновление источников данных.
Рассмотрим распространённую архитектуру.
+----------------+
| External System|
+----------------+
|
Webhook
|
▼
+----------------+
| Backend Server |
+----------------+
|
Save GeoJSON
|
▼
+----------------+
| Database |
+----------------+
|
REST API
|
▼
+----------------+
| Mapbox GL JS |
+----------------+
Mapbox GL JS не принимает вебхуки напрямую.
Причины:
Поэтому вебхуки всегда принимаются серверной частью приложения.
Наиболее распространённый вариант — использование Express.
npm install express
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
console.log('Получен webhook');
console.log(req.body);
res.status(200).send('OK');
});
app.listen(3000, () => {
console.log('Server started');
});
После запуска сервер начинает принимать POST-запросы по адресу:
http://localhost:3000/webhook
Типичный JSON может выглядеть следующим образом:
{
"event": "vehicle.updated",
"vehicleId": 42,
"latitude": 51.1694,
"longitude": 71.4491,
"timestamp": "2025-04-15T10:25:00Z"
}
После получения такого события можно обновить координаты объекта в базе данных.
Предположим, сервер сохраняет последние координаты транспорта.
Клиентское приложение запрашивает данные через API.
async function loadVehicles() {
const response = await fetch('/api/vehicles');
const geojson = await response.json();
map.getSource('vehicles').setData(geojson);
}
setInterval(loadVehicles, 5000);
Теперь:
Часто применяется более эффективная схема.
Webhook
│
▼
Backend
│
▼
WebSocket
│
▼
Mapbox GL JS
После получения вебхука сервер отправляет сообщение через WebSocket всем подключённым клиентам.
io.emit('vehicle-update', data);
socket.on('vehicle-update', geojson => {
map.getSource('vehicles').setData(geojson);
});
Преимущество такого подхода — обновления появляются практически мгновенно.
Одним из самых распространённых сценариев является отслеживание транспорта.
Webhook:
{
"vehicleId": 105,
"lat": 43.2389,
"lng": 76.8897
}
Обработчик:
app.post('/webhook', async (req, res) => {
const { vehicleId, lat, lng } = req.body;
await db.updateVehicle(vehicleId, lat, lng);
res.sendStatus(200);
});
После обновления БД новые координаты становятся доступны карте.
Многие приложения используют GeoJSON как основной источник пространственных данных.
Webhook может инициировать обновление файла.
app.post('/webhook', async (req, res) => {
await generateGeoJSON();
res.sendStatus(200);
});
Генерация:
async function generateGeoJSON() {
const features = await getFeatures();
const geojson = {
type: 'FeatureCollection',
features
};
await fs.writeFile(
'data.geojson',
JSON.stringify(geojson)
);
}
Mapbox GL JS затем загружает обновлённый источник.
Первоначальное подключение источника:
map.addSource('cities', {
type: 'geojson',
data: '/data.geojson'
});
Последующее обновление:
async function refreshData() {
const response =
await fetch('/data.geojson');
const data =
await response.json();
map.getSource('cities')
.setData(data);
}
Никогда не следует доверять входящим запросам без проверки.
Большинство поставщиков вебхуков используют подписи.
Пример заголовка:
X-Signature: abc123456789
Проверка:
const crypto = require('crypto');
function verifySignature(
payload,
signature,
secret
) {
const hash =
crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return hash === signature;
}
Использование:
app.post('/webhook', (req, res) => {
const signature =
req.headers['x-signature'];
if (!verifySignature(
JSON.stringify(req.body),
signature,
process.env.SECRET
)) {
return res.sendStatus(401);
}
res.sendStatus(200);
});
Многие сервисы могут повторно отправлять webhook.
Например:
{
"eventId": "evt_123456",
"event": "route.updated"
}
Перед обработкой необходимо проверять уникальность идентификатора события.
const exists =
await db.hasEvent(eventId);
if (exists) {
return;
}
После обработки:
await db.saveEvent(eventId);
Такой подход называется Idempotency.
Если поступает большое количество уведомлений, непосредственная обработка может привести к перегрузке сервера.
Используется очередь сообщений.
Webhook
│
▼
Message Queue
│
▼
Worker
│
▼
Database
Популярные решения:
Пример добавления задания:
await queue.add({
event: req.body
});
Предположим, поступает информация о новой зоне обслуживания.
Webhook:
{
"event": "zone.created",
"zoneId": 12
}
После получения:
await rebuildZonesGeoJSON();
Клиент обновляет слой:
map.getSource('zones')
.setData(newData);
Обновление происходит без перезагрузки страницы.
В задачах мониторинга вебхуки часто применяются для передачи событий:
Пример:
{
"event": "geofence.exit",
"assetId": 87,
"geofenceId": 15
}
После получения сервер может:
В картографических системах вебхуки часто поступают от:
Пример интеграции:
IoT Device
│
▼
Cloud Platform
│
▼
Webhook
│
▼
Backend
│
▼
Mapbox GL JS
Такая архитектура позволяет визуализировать изменения практически в режиме реального времени.
Для диагностики рекомендуется сохранять все входящие события.
Пример:
app.post('/webhook', async (req, res) => {
await db.saveLog({
payload: req.body,
createdAt: new Date()
});
res.sendStatus(200);
});
Логи помогают:
Надёжный обработчик вебхуков всегда содержит механизм обработки исключений.
app.post('/webhook', async (req, res) => {
try {
await processWebhook(req.body);
res.sendStatus(200);
} catch (error) {
console.error(error);
res.sendStatus(500);
}
});
Большинство поставщиков вебхуков автоматически повторяют отправку при получении кодов:
500
502
503
504
Поэтому важно корректно различать успешную и неуспешную обработку.
При развитии API структура событий может изменяться.
Пример:
{
"version": "2.0",
"event": "asset.updated"
}
Обработчик:
switch(req.body.version) {
case '1.0':
processV1(req.body);
break;
case '2.0':
processV2(req.body);
break;
}
Версионирование позволяет обновлять систему без нарушения совместимости.
Сервер должен отвечать максимально быстро
res.sendStatus(200);
Тяжёлая обработка переносится в фоновые задачи.
Необходимо проверять подписи запросов
Любой внешний запрос считается недоверенным до прохождения проверки.
Следует использовать идемпотентную обработку
Повторная доставка не должна приводить к дублированию данных.
Желательно применять очереди сообщений
Особенно при обработке большого потока пространственных событий.
Необходимо вести журнал событий
Это значительно упрощает поддержку и отладку картографических систем.
Обновление карты должно быть отделено от обработки Webhook
Webhook изменяет данные на сервере, а Mapbox GL JS отвечает только за визуализацию обновлённого состояния системы.