USER
User: //const { gpt, gptweb } = require("./API/gpt");
//const { dalle } = require("./API/dalle");
const { gpt4o } = require("c:/PrivateAPI/API/gpt4o");
//const { gpt3 } = require("./API/gpt3.5");
//const { prompts } = require("./API/gpt-prompts");
const { blackbox } = require("c:/PrivateAPI/API/blackbox");
const { qwen } = require("c:/PrivateAPI/API/qwen");
const { gemini } = require("c:/PrivateAPI/API/gemini");
const WebSocket = require('ws');
const Bottleneck = require('bottleneck');
const { stats, scheduleDailyReset, resetStatistics } = require('./stats'); // Импортируем модуль статистики
const fs = require('fs');
const path = require('path');
const Ajv = require('ajv');
const axios = require('axios');
const https = require('https');
const redis = require('redis');
const { promisify } = require('util');
const express = require('express');
const cors = require('cors'); // Импортируем cors
// Обработка необработанных исключений (помогут выявить обшибки, которые не обрабатываются блоками try/catch)
process.on('uncaughtException', (err) => {
console.error('Необработанное исключение:', err);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Необработанное отклонение промиса:', reason);
});
// Текущее количество подключенных клиентов
let currentConnectedClients = 0;
// Текущее количество обрабатываемых запросов
let currentGPT4oProcessingRequests = 0;
let currentGeminiProcessingRequests = 0;
let currentQwenProcessingRequests = 0;
let currentBlackboxProcessingRequests = 0;
// Объект для хранения количества подключений по IP
const ipConnectionCount = {};
// Максимальное количество подключений с одного IP
const MAX_CONNECTIONS_PER_IP = 100;
// Создание и настройка клиента Redis
const redisClient = redis.createClient();
// Подключаем клиента Redis
redisClient.connect().catch(err => {
console.error('Ошибка подключения к Redis:', err);
});
redisClient.on('error', (err) => {
console.error('Ошибка Redis:', err);
});
// Промис-обертка для команды get
const getAsync = async (key) => {
if (!redisClient.isOpen) { // Проверяем, открыт ли клиент
console.error('Клиент Redis не подключён, невозможно выполнить get:', key);
return null; // Возвращаем null, если клиент не подключен
}
try {
return await redisClient.get(key);
} catch (error) {
console.error('Ошибка при получении из Redis:', error);
throw error; //throw error чтобы выше поймали
}
};
// SSL сертификаты
const serverOptions = {
key: fs.readFileSync('C:\\OSPanel\\userdata\\config\\cert_files\\gpt.loc-key.pem'), // Приватный ключ
cert: fs.readFileSync('C:\\OSPanel\\userdata\\config\\cert_files\\gpt.loc.pem'), // Сертификат
};
// HTTPS сервер
const httpsServer = https.createServer(serverOptions);
// Создайте WebSocket-сервер, используя существующий HTTPS сервер
const wss = new WebSocket.Server({ server: httpsServer });
// Запустите сервер
httpsServer.listen(3000, () => {
console.log('HTTPS сервер и WebSocket сервер запущены на порту 3000');
});
// Создаем экземпляр Ajv (проверка входящей переписки на соответствие схеме)
const ajv = new Ajv();
// Объект для хранения действий и состояния каждого клиента
const clientActions = {};
// Объект для хранения ограничителей по каждому клиенту
const limiters = {};
// Домен
const allowedOrigin = 'https://gpt.loc';
wss.on('connection', (ws, req) => {
//console.log('Новое соединение:', req.socket.remoteAddress);
const urlParams = new URLSearchParams(req.url.split('?')[1]);
const userId = urlParams.get('token'); // Получаем UUID из URL
const userIP = req.socket.remoteAddress;
//console.log("IP-адрес - ", userIP, "Время подключения - ", new Date().toISOString());
const userHeaders = req.headers;
// Проверяем Origin
if (!checkOrigin(userHeaders.origin, ws)) {
return; // Если отклонено, выходим из обработчика
}
// Если количество подключений с этого IP превышает лимит, закрываем соединение
if (ipConnectionCount[userIP] >= MAX_CONNECTIONS_PER_IP) {
console.log(`Превышен лимит подключений для IP: ${userIP}, отказано в соединении.`);
stats.totalIPBlocks++;
ws.close(); // Закрываем соединение
return; // Выходим из обработчика
}
// Увеличиваем счетчик подключений
currentConnectedClients++;
// Обновляем максимальное количество клиентов, если текущее количество больше
if (currentConnectedClients > stats.maxConnectedClients) {
stats.maxConnectedClients = currentConnectedClients;
console.log(`Максимальное количество подключенных клиентов: ${stats.maxConnectedClients}`);
}
// Увеличиваем счетчик подключений для этого IP
ipConnectionCount[userIP] = (ipConnectionCount[userIP] || 0) + 1;
// Увеличиваем общий счетчик подключений
stats.totalConnections++;
console.log('Client connected with user ID:', userId, ' and IP:', userIP);
// Инициализируем состояние клиента, если его еще нет
if (!clientActions[userId]) {
clientActions[userId] = {
actions: [], // Массив для хранения действий
connected: true, // Статус подключения
lastRequestTime: new Date(), // Добавляем время последнего запроса
ws: ws,
processing: false,
delay: 1000, // Задержка по умолчанию, например, 1000 мс
responseTimes: [] // массив для хранения временных меток (начало и конец 3-х ответов)
};
}
else {
// обновляем ws при переподключении
clientActions[userId].connected = true;
clientActions[userId].ws = ws;
// вы можете обновить другие параметры здесь
}
if (!limiters[userId]) {
// Создаем экземпляр Bottleneck для каждого клиента (ограничение на количество запросов по Id)
limiters[userId] = new Bottleneck({
maxConcurrent: 1, // Максимальное количество одновременных задач
minTime: 1000, // Минимальное время между запросами в миллисекундах
highWater: 0 // Не разрешать в очереди больше 0 запросов (игнорировать)
});
}
ws.on('message', (message) => {console.log(clientActions[userId].processing);
// Обновляем время последнего запроса
clientActions[userId].lastRequestTime = new Date();
// Проверяем, является ли сообщение экземпляром Buffer
if (Buffer.isBuffer(message))
message = message.toString();
try {
// Преобразуем строку JSON обратно в объект
message = JSON.parse(message);
//Получаем историю чата из запроса
let messages = message.messages;
let model = message.model;
let rewrite = message.rewrite;
// Выполняем валидацию сообщений
messages = validateMessages(messages);
// Проверяем, остались ли сообщения после валидации
if (messages.length === 0) {
const response = { code: 2, message: "Нет валидных сообщений для обработки." };
sendMessage(ws, response);
return;
}
// Урезаем последнее сообщение до 2000 символов, если оно от клиента
if (messages[messages.length - 1].role === 'user') {
messages[messages.length - 1].content = messages[messages.length - 1].content.substring(0, 2000000);
}
// Увеличиваем общий счетчик запросов
stats.totalRequests++;
// Проверяем не обрабатывается ли в данный момент запрос клиента
if(clientActions[userId].processing){
const responseTimes = clientActions[userId].responseTimes;
let lastEndTime;
// Проверяем, существует ли time end последнего ответа
if (responseTimes.length > 0 && responseTimes[responseTimes.length - 1].end) {
lastEndTime = responseTimes[responseTimes.length - 1].end;
} else {
lastEndTime = new Date(); // Присваиваем текущее время, если нет предыдущего завершения
}
const currentTime = new Date();
const timeSinceLastEnd = currentTime - lastEndTime;
// Если с момента последнего ответа прошло больше 40 секунд, сбрасываем флаг
if (timeSinceLastEnd > 40000) {
clientActions[userId].processing = false;
}
else{
// Увеличиваем счетчик ошибок
stats.totalProcessingErrors++;
console.log('Обрабатывается предыдущий запрос.');
const response = { code: 2, message: "Сервер занят. Пожалуйста, попробуйте позже." };
// Отправка ответа клиенту
setTimeout(() => {
sendMessage(ws, response);
}, 5 * 1000);
return;
}
}
// Задержка перед отправкой запроса
const responseTimes = clientActions[userId].responseTimes; // Получаем массив responseTimes
// Проверяем, прошло ли больше 15 секунд с момента окончания последнего ответа
if (responseTimes.length === 3) {
const lastEndTime = responseTimes[responseTimes.length - 1].end;
const currentTime = new Date();
// Вычисляем разницу во времени
const timeSinceLastEnd = currentTime - lastEndTime; // Время в миллисекундах
if (timeSinceLastEnd > 20000) { // Если прошло больше 15 секунд
clientActions[userId].delay = 1000; // Устанавливаем delay на 1 мс
}
}
console.log('Задержка - ', clientActions[userId].delay)
// Используем Bottleneck для управления количеством запросов
limiters[userId].schedule(() => {
clientActions[userId].processing = true;
// Отправка запроса на сервер чата с задержкой
setTimeout(() => {
call(messages, model, ws, userId, rewrite);
}, clientActions[userId].delay);
}).catch(error => {
stats.totalIPBlocks++;
//console.error('Rate limit exceeded, message ignored:', error);
//const response = { code: 2, message: "Превышен лимит запросов. Пожалуйста, попробуйте позже." };
// Отправка ответа клиенту
//sendMessage(ws, response);
});
} catch (error) {
stats.totalWsMessErrors++;
console.error('Error parsing JSON:', error);
const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз."};
console.log(response);
// Отправка ответа клиенту
sendMessage(ws, response);
}
});
ws.on('close', () => {
try {
// Проверка, существует ли userId в clientActions
if (clientActions[userId]) {
clientActions[userId].connected = false; // Обновление статуса подключения
//delete clientActions[userId]; // Удаление состояния клиента
}
// Проверка, существует ли userId в limiters
if (limiters[userId]) {
//delete limiters[userId]; // Удаление ограничителя для данного клиента
}
console.log('Client ',userId, ' disconnected');
// Уменьшаем счетчик подключений для этого IP
if (ipConnectionCount[userIP] !== undefined) {
ipConnectionCount[userIP]--;
// Если счетчик равен 0, удаляем запись из объекта
if (ipConnectionCount[userIP] === 0) {
delete ipConnectionCount[userIP];
}
}
// Уменьшаем счетчик при отключении клиента
currentConnectedClients = Math.max(currentConnectedClients - 1, 0);
} catch (error) {
console.error('Error handling client disconnect:', error);
}
});
});
// Запускаем планировщик для статистики
scheduleDailyReset();
// Таймер для проверки клиентов каждые 3 минут
setInterval(() => {
const currentTime = new Date();
for (const userId in clientActions) {
const client = clientActions[userId];
const lastRequestTime = client.lastRequestTime;
// Проверяем, прошло ли более 30 минут с последнего сообщения
const longTimeInactive = lastRequestTime && (currentTime - lastRequestTime) > 30 * 60 * 1000;
// Проверка на отключение и прошло ли более минуты с последнего сообщения
const userDisconnectedAndInactive = !client.connected && (currentTime - lastRequestTime) > 3 * 60 * 1000;
// Проверяем, прошло ли более 30 минут с последнего сообщения
if (longTimeInactive || userDisconnectedAndInactive) {
const response = { code: 3, message: "" };
// Отправляем сообщение клиенту
if (client.ws && client.ws.readyState === WebSocket.OPEN) {
sendMessage(client.ws, response);
// Закрываем соединение
client.ws.close();
}
// Удаляем записи о клиенте, если нет активности более 30 минут
delete clientActions[userId];
delete limiters[userId];
}
}
console.log('Количество активных клиентов - ', Object.keys(clientActions).length);
}, 3 * 60 * 1000); // Проверяем каждые 10 минут
// Очистка старых соединений раз в минуту (по желанию)
/*setInterval(() => {
Object.keys(clientActions).forEach(userId => {
if (!clientActions[userId].connected) {
delete clientActions[userId]; // Удаляем пользователя из памяти, если он отключен
delete limiters[userId];
}
});
console.log('Количество активных клиентов - ', Object.keys(clientActions).length);
}, 10 * 60 * 1000);*/ // Очистка каждые 10 минут
// Код выводит текущие метрики использования памяти приложения
setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`Текущие метрики использования памяти:
RSS: ${memoryUsage.rss / (1024 * 1024)} MB,
Heap Total: ${memoryUsage.heapTotal / (1024 * 1024)} MB,
Heap Used: ${memoryUsage.heapUsed / (1024 * 1024)} MB,
External: ${memoryUsage.external / (1024 * 1024)} MB`);
}, 60 * 60 * 1000); // Вывод каждый 60 секунд
// Проверяет с какого домена пришел запрос
function checkOrigin(origin, socket) {
if (origin !== allowedOrigin) {
console.log('Недопустимый домен. Соединение разорвано: ', origin);
socket.close(); // Закрываем соединение, если домен не разрешен
return false; // Возвращаем false для индикатора отклонения
}
return true; // Возвращаем true, если домен разрешен
}
// Отправляет ответ клиенту
function sendMessage(ws, message) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(message));
} else {
console.warn(`Соединение закрыто. Сообщение не отправлено.`);
}
}
// Удаление из массива сообщений не соответствующих схеме
function validateMessages(messages) {
// Определяем схему для сообщений
const schema = {
type: 'object',
properties: {
role: { type: 'string' },
content: { type: 'string' }
},
required: ['role', 'content'],
additionalProperties: false
};
// Фильтруем массив, оставляя только корректные сообщения
return messages.filter(message => {
// Проверяем, соответствует ли сообщение схеме
if (ajv.validate(schema, message)) {
// Обрезаем содержимое, если оно длиннее 2000 символов
if (message.content.length > 2000000) {
message.content = message.content.substring(0, 2000000);
}
return true; // Сообщение валидно, оставляем в массиве
}
return false; // Сообщение не валидно, исключаем из массива
});
}
// Слуйчайное число
function getRandomInRange(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// Обновляем временные метки в responseTimes
function updateResponseTimes(userId, startTime, endTime) {
try {
const responseTime = { start: startTime, end: endTime };
// Получаем массив responseTimes
const responseTimes = clientActions[userId].responseTimes;
// Добавляем новую временную метку
responseTimes.push(responseTime);
// Удаляем старые временные метки, если их больше трех
if (responseTimes.length > 3) {
responseTimes.shift(); // Удаляем старую временную метку
}
// Обновляем задержку на основе средней продолжительности ответов
updateClientDelay(userId);
console.log(clientActions[userId].responseTimes);
} catch (error) {
console.error('Ошибка при обновлении временных меток:', error);
// Можно добавить дополнительные действия в случае ошибки, например отправить уведомление
}
}
// Проверяем длительность последних трех ответов и устанавливаем новое значение delay
function updateClientDelay(userId) {
try {
const responseTimes = clientActions[userId].responseTimes;
if (responseTimes.length < 3) {
clientActions[userId].delay = 1000; // Устанавливаем delay на 1000 мс
return; // Если еще нет ответов, ничего не делаем
}
// Проверяем, все ли три ответа дольше 15 секунд
const allResponsesLongerThan15s = responseTimes.every(time => {
const duration = time.end - time.start; // Вычисляем продолжительность
return duration > 15000; // Проверяем, больше ли 15 секунд
});
// Устанавливаем delay в зависимости от проверки
if (allResponsesLongerThan15s) {
clientActions[userId].delay = 20000; // Устанавливаем delay на 20000 мс
} else {
clientActions[userId].delay = 1000; // Устанавливаем delay на 1000 мс
}
} catch (error) {
console.error('Ошибка при обновлении задержки клиента:', error);
// Можно добавить дополнительные действия в случае ошибки, например установить задержку по умолчанию
clientActions[userId].delay = 1000; // Пример установки по умолчанию
}
}
// Вызов модели
async function call(messages, model, ws, userId, rewrite){
if (model == 'gpt-4o')
return callGPT4o(messages, ws, userId, rewrite);
else if (model == 'gemini-pro')
return callGemini(messages, ws, userId, rewrite);
else if (model == 'qwen')
return callQwen(messages, ws, userId, rewrite);
else if (model == 'blackbox')
return callBlackbox(messages, ws, userId, rewrite);
else
return callGPT4o(messages, ws, userId, rewrite);
}
//============== ФУНКЦИИ API МОДЕЛЕЙ ===============================================
//==================================================================================
//==================================================================================
//==================================================================================
// GPT4o
async function callGPT4o(messages, ws, userId, rewrite) {
const lastMessageContent = messages[messages.length - 1].content; // Используем только последнее сообщение
const cacheKey = `gpt4o:${userId}:${lastMessageContent}`; // Создаем ключ кэша на основе сообщения, модели и userId
// Записываем время начала ответа
const startTime = new Date();
// Пробуем получить ответ из кэша
let cachedResponse;
try {//throw new Error("Намеренная ошибка для тестирования catch");
cachedResponse = await getAsync(cacheKey);
} catch (error) {
console.error('Ошибка при получении из Redis:', error);
// Переходим к запросу по API
}
if (cachedResponse && !rewrite) {
console.log('Отправка ответа из кэша для GPT4o');
// Имитируем задержку перед отправкой ответа из кэша (например, 1 с)
setTimeout(() => {
sendMessage(ws, JSON.parse(cachedResponse));
// Записываем время окончания ответа
const endTime = new Date();
updateResponseTimes(userId, startTime, endTime);
clientActions[userId].processing = false;
}, 2 * 1000); // 2 секунды задержки
return;
}
// Увеличиваем счетчик запросов находящихся в обработке
currentGPT4oProcessingRequests++;
// Обновляем максимальное количество обрабатываемых запросов, если текущее больше
if (currentGPT4oProcessingRequests > stats.maxGPT4oProcessingRequests) {
stats.maxGPT4oProcessingRequests = currentGPT4oProcessingRequests;
console.log(`Новое максимальное количество одновременно обрабатываемых запросов к GPT4o: ${stats.maxGPT4oProcessingRequests}`);
}
try {//throw new Error("Намеренная ошибка для тестирования catch");
console.log('Запрос клиента к GPT4o: ', messages[messages.length - 1]);
let fullResponse = "";
await new Promise((resolve, reject) => {
gpt4o({ messages, markdown: false }, (result) => {
if (!result.finish) {
fullResponse = result.message || "";
} else {
if(fullResponse == '')
stats.totalGPT4oLimitStops++;
let responseMessage = fullResponse || 'Попробуйте написать позже или выберите другую модель.';
console.log('Ответ GPT4o:', { code: 1, message: responseMessage });
try {//throw new Error("Намеренная ошибка для тестирования catch");
// Изменение здесь: используем метод set для установки TTL (в секундах)
redisClient.set(cacheKey, JSON.stringify({ code: 1, message: responseMessage }), { EX: 10 * 60 });
} catch (error) {
console.error('Ошибка при сохранении в Redis:', error);
}
resolve({ code: 1, message: responseMessage });
stats.totalGPT4oResponse++;
sendMessage(ws, { code: 1, message: responseMessage });
// Записываем время окончания ответа
const endTime = new Date();
updateResponseTimes(userId, startTime, endTime);
}
});
});
} catch (error) {
stats.totalGPT4oMessErrors++;
console.error('Error:', error);
sendMessage(ws, { code: 2, message: "Произошла ошибка. Попробуйте еще раз." });
}finally {
// Уменьшаем счетчик независимо от результата
currentGPT4oProcessingRequests--;
clientActions[userId].processing = false; // Сбрасываем флаг обработки в false
}
}
// Gemini
async function callGemini(messages, ws, userId, rewrite) {
const lastMessageContent = messages[messages.length - 1].content; // Используем только последнее сообщение
const cacheKey = `gemini:${userId}:${lastMessageContent}`; // Создаем ключ кэшана основе сообщения, модели и userId
// Записываем время начала ответа
const startTime = new Date();
// Попробуйте получить ответ из кэша
let cachedResponse;
try {
cachedResponse = await getAsync(cacheKey);
} catch (error) {
console.error('Ошибка при получении из Redis:', error);
// Переходим к запросу по API
}
if (cachedResponse && !rewrite) {
console.log('Отправка ответа из кэша для Gemini');
// Имитируем задержку перед отправкой ответа из кэша (например, 1 с)
setTimeout(() => {
sendMessage(ws, JSON.parse(cachedResponse));
// Записываем время окончания ответа
const endTime = new Date();
updateResponseTimes(userId, startTime, endTime);
clientActions[userId].processing = false;
}, 1 * 1500); // 2 секунды задержки
return;
}
// Увеличиваем счетчик запросов находящихся в обработке
currentGeminiProcessingRequests++;
// Обновляем максимальное количество обрабатываемых запросов, если текущее больше
if (currentGeminiProcessingRequests > stats.maxGeminiProcessingRequests) {
stats.maxGeminiProcessingRequests = currentGeminiProcessingRequests;
console.log(`Новое максимальное количество одновременно обрабатываемых запросов к Gemini: ${stats.maxGeminiProcessingRequests}`);
}
try {//throw new Error("Намеренная ошибка для тестирования catch");
console.log('Запрос клиента к Gemini: ', messages[messages.length - 1]);
let fullResponse = "";
await new Promise((resolve, reject) => {
gemini({ messages }, (result) => {
if (!result.finish) {
fullResponse = result.message || "";
} else {
if(fullResponse == '')
stats.totalGeminiLimitStops++;
let responseMessage = fullResponse || 'Попробуйте написать позже или выберите другую модель.';
console.log('Ответ Gemini:', { code: 1, message: responseMessage });
try {
// Изменение здесь: используем метод set для установки TTL
redisClient.set(cacheKey, JSON.stringify({ code: 1, message: responseMessage }), { EX: 10 * 60 });
} catch (error) {
console.error('Ошибка при сохранении в Redis:', error);
}
resolve({ code: 1, message: responseMessage });
stats.totalGeminiResponse++;
sendMessage(ws, { code: 1, message: responseMessage });
// Записываем время окончания ответа
const endTime = new Date();
updateResponseTimes(userId, startTime, endTime);
}
});
});
} catch (error) {
stats.totalGeminiMessErrors++;
console.error('Error:', error);
sendMessage(ws, { code: 2, message: "Произошла ошибка. Попробуйте еще раз." });
}finally {
// Уменьшаем счетчик независимо от результата
currentGeminiProcessingRequests--;
clientActions[userId].processing = false;
}
}
// Qwen
async function callQwen(messages, ws, userId, rewrite) {
const lastMessageContent = messages[messages.length - 1].content; // Используем только последнее сообщение
const cacheKey = `qwen:${userId}:${lastMessageContent}`; // Создаем ключ кэшана основе сообщения, модели и userId
// Записываем время начала ответа
const startTime = new Date();
// Попробуйте получить ответ из кэша
let cachedResponse;
try {
cachedResponse = await getAsync(cacheKey);
} catch (error) {
console.error('Ошибка при получении из Redis:', error);
// Переходим к запросу по API
}
if (cachedResponse && !rewrite) {
console.log('Отправка ответа из кэша для Qwen');
// Имитируем задержку перед отправкой ответа из кэша (например, 1 с)
setTimeout(() => {
sendMessage(ws, JSON.parse(cachedResponse));
// Записываем время окончания ответа
const endTime = new Date();
updateResponseTimes(userId, startTime, endTime);
clientActions[userId].processing = false;
}, 1 * 1500); // 2 секунды задержки
return;
}
// Увеличиваем счетчик запросов находящихся в обработке
currentQwenProcessingRequests++;
// Обновляем максимальное количество обрабатываемых запросов, если текущее больше
if (currentQwenProcessingRequests > stats.maxQwenProcessingRequests) {
stats.maxQwenProcessingRequests = currentQwenProcessingRequests;
console.log(`Новое максимальное количество одновременно обрабатываемых запросов к Qwen: ${stats.maxQwenProcessingRequests}`);
}
try {//throw new Error("Намеренная ошибка для тестирования catch");
console.log('Запрос клиента к Qwen: ', messages[messages.length - 1]);
let fullResponse = "";
await new Promise((resolve, reject) => {
qwen({ messages, markdown: false }, (result) => {
if (!result.finish) {
fullResponse = result.message || "";
} else {
if(fullResponse == '')
stats.totalQwenLimitStops++;
let responseMessage = fullResponse || 'Попробуйте написать позже или выберите другую модель.';
console.log('Ответ Qwen:', { code: 1, message: responseMessage });
try {
// Изменение здесь: используем метод set для установки TTL
redisClient.set(cacheKey, JSON.stringify({ code: 1, message: responseMessage }), { EX: 10 * 60 });
} catch (error) {
console.error('Ошибка при сохранении в Redis:', error);
}
resolve({ code: 1, message: responseMessage });
stats.totalQwenResponse++;
sendMessage(ws, { code: 1, message: responseMessage });
// Записываем время окончания ответа
const endTime = new Date();
updateResponseTimes(userId, startTime, endTime);
}
});
});
} catch (error) {
stats.totalQwenMessErrors++;
console.error('Error:', error);
sendMessage(ws, { code: 2, message: "Произошла ошибка. Попробуйте еще раз." });
}finally {
// Уменьшаем счетчик независимо от результата
currentQwenProcessingRequests--;
clientActions[userId].processing = false;
}
}
// Blackbox
async function callBlackbox(messages, ws, userId, rewrite) {
const lastMessageContent = messages[messages.length - 1].content; // Используем только последнее сообщение
const cacheKey = `qwen:${userId}:${lastMessageContent}`; // Создаем ключ кэшана основе сообщения, модели и userId
// Записываем время начала ответа
const startTime = new Date();
// Попробуйте получить ответ из кэша
let cachedResponse;
try {
cachedResponse = await getAsync(cacheKey);
} catch (error) {
console.error('Ошибка при получении из Redis:', error);
// Переходим к запросу по API
}
if (cachedResponse) {
console.log('Отправка ответа из кэша для Qwen');
// Имитируем задержку перед отправкой ответа из кэша (например, 1 с)
setTimeout(() => {
sendMessage(ws, JSON.parse(cachedResponse));
// Записываем время окончания ответа
const endTime = new Date();
updateResponseTimes(userId, startTime, endTime);
clientActions[userId].processing = false;
}, 1 * 1500); // 2 секунды задержки
return;
}
// Увеличиваем счетчик запросов находящихся в обработке
currentBlackboxProcessingRequests++;
// Обновляем максимальное количество обрабатываемых запросов, если текущее больше
if (currentBlackboxProcessingRequests > stats.maxBlackboxProcessingRequests) {
stats.maxBlackboxProcessingRequests = currentBlackboxProcessingRequests;
console.log(`Новое максимальное количество одновременно обрабатываемых запросов к Blackbox: ${stats.maxBlackboxProcessingRequests}`);
}
try {//throw new Error("Намеренная ошибка для тестирования catch");
console.log('Запрос клиента к Blackbox: ', messages[messages.length - 1]);
let fullResponse = "";
await new Promise((resolve, reject) => {
blackbox({ messages, markdown: false, websearch: false, llama: false }, (result) => {
if (!result.finish) {
fullResponse = result.message || "";
} else {
if(fullResponse == '')
stats.totalBlackboxLimitStops++;
let responseMessage = fullResponse || 'Попробуйте написать позже или выберите другую модель.';
console.log('Ответ Blackbox:', { code: 1, message: responseMessage });
try {
// Изменение здесь: используем метод set для установки TTL
redisClient.set(cacheKey, JSON.stringify({ code: 1, message: responseMessage }), { EX: 10 * 60 });
} catch (error) {
console.error('Ошибка при сохранении в Redis:', error);
}
resolve({ code: 1, message: responseMessage });
stats.totalBlackboxResponse++;
sendMessage(ws, { code: 1, message: responseMessage });
// Записываем время окончания ответа
const endTime = new Date();
updateResponseTimes(userId, startTime, endTime);
}
});
});
} catch (error) {
stats.totalBlackboxMessErrors++;
console.error('Error:', error);
sendMessage(ws, { code: 2, message: "Произошла ошибка. Попробуйте еще раз." });
}finally {
// Уменьшаем счетчик независимо от результата
currentBlackboxProcessingRequests--;
clientActions[userId].processing = false;
}
}
//============== ВЫЗОВ API МОДЕЛЕЙ (КОНЕЦ) ======================
// ======== КОД ДЛЯ ПОЛУЧЕНИЯ ДОПОЛНИТЕЛЬНЫХ ЗАПРОСОВ ОТ ДРУГИХ СЕРВЕРОВ ПУТЕМ БАЛЛАНСИРОВКИ =======
// ...
//const app = express();
//const PORT = 3001;
// Увеличиваем лимит на 200kb (лимит на размер данных, которые можно получать через express (по умолчанию 100Кб))
//app.use(bodyParser.json({ limit: '200kb' })); //(200Кб ~ 200000 символов)
//app.use(bodyParser.urlencoded({ limit: '200kb', extended: true }));
// -------- Обработка сообщений обратной связи --------
const app = express();
const PORT = 3002;
// Настройки CORS
const corsOptions = {
origin: 'https://gpt.loc', // Разрешаем только этот домен
methods: ['GET', 'POST'], // Разрешаем только GET и POST запросы
credentials: true, // Разрешаем использование куки (если нужно)
};
// Использование cors с опциями
app.use(cors(corsOptions));
// Middleware для парсинга JSON
app.use(express.json());
const TELEGRAM_BOT_TOKEN = '<TRUFFLEHOG_REDACTED_TELEGRAMBOTTOKEN>'; // Ваш токен бота
const TELEGRAM_CHAT_ID = '-1002303094611'; // ID вашей группы или чата
// Роут для обработки POST-запроса от клиента
app.post('/feedback', async (req, res) => {
// Получаем IP-адрес отправителя
const ipAddress = req.ip; // Это IP-адрес клиента
let { feedback, email } = req.body; // Извлечение feedback и email из тела запроса
// Очищаем входящие данные
feedback = sanitizeInput(feedback);
email = sanitizeInput(email);
// Проверяем, что оба поля присутствуют
if (!feedback || !email) {
return res.status(400).send({ message: 'Feedback и Email обязательны.' });
}
// Проверяем корректность email
if (!isValidEmail(email)) {
return res.status(400).send({ message: 'Некорректный Email.' });
}
// Здесь можно добавить логическую обработку, например, сохранение в базе данных
//console.log("Полученные данные:");
//console.log("Отзыв:", feedback);
//console.log("Email:", email);
//console.log("IP:", ipAddress);
// Формируем сообщение для отправки в Telegram
const message = `Получено сообщение от ${email}:\n${feedback}`;
// Отправка сообщения в Telegram
axios.post(`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`, {
chat_id: TELEGRAM_CHAT_ID,
text: message,
})
.then(response => {//throw new Error("Намеренная ошибка для тестирования catch");
console.log('Сообщение от пользователя отправлено в Telegram.');
res.status(200).json({ message: 'Ok', feedback: feedback, email: email });
})
.catch(error => {
console.error('Ошибка при отправке сообщения в Telegram:', error);
res.status(500).send({ message: 'Ошибка при отправке сообщения в Telegram.' });
});
});
// Запуск сервера с HTTPS
https.createServer(serverOptions, app).listen(PORT, () => {
console.log(`Сервер запущен на https://localhost:${PORT}`);
});
// Функция для очистки входящих данных
function sanitizeInput(input) {
return input
.replace(/<\/?[^>]+(>|$)/g, "") // Убираем HTML-теги
.replace(/</g, "<") // Заменяем HTML-коды
.replace(/>/g, ">")
.trim(); // Убираем лишние пробелы
}
// Функция для проверки email
function isValidEmail(email) {
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; // Регулярное выражение для проверки email
return emailPattern.test(email);
}
// -------- END Обработка сообщений обратной связи --------
Этот файл лежит в папке sitename.ru\chat\API\ как должен быть настроен контекст server на nginx
Assistant: настроить контекст сервера для вашего Node.js приложения в Nginx, вам нужно будет создать конфигурацию Nginx, которая будет проксировать запросы к вашему приложению. Предположим, что ваше приложение запущено на локальном сервере (localhost) на порту 3000. Убедитесь, что у вас установлен Nginx и он доступен.
Вот пример конфигурации Nginx для вашего Node.js приложения, расположенного в папке `sitename.ru/chat/API/`:
1. **Создайте новый файл конфигурации Nginx** для вашего сайта:
Обычно файлы конфигурации находятся в директории `/etc/nginx/sites-available/`. Вы можете создать новый файл, например, `sitename.ru.conf`:
```bash
sudo nano /etc/nginx/sites-available/sitename.ru.conf
```
2. **Добавьте следующую конфигурацию** в файл:
```nginx
server {
listen 80;
server_name sitename.ru www.sitename.ru;
location / {
proxy_pass http://localhost:3000; # Ваш Node.js сервер
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# Настройки для HTTPS
listen 443 ssl;
ssl_certificate /path/to/your/certificate.crt; # Путь к вашему SSL сертификату
ssl_certificate_key /path/to/your/private.key; # Путь к вашему приватному ключу
location / {
proxy_pass http://localhost:3000; # Ваш Node.js сервер
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
```
Замените `/path/to/your/certificate.crt` и `/path/to/your/private.key` на фактические пути к вашему SSL сертификату и ключу.
3. **Создайте символическую ссылку** файла конфигурации в `sites-enabled` для активации:
```bash
sudo ln -s /etc/nginx/sites-available/sitename.ru.conf /etc/nginx/sites-enabled/
```
4. **Проверьте конфигурацию на ошибки**:
```bash
sudo nginx -t
```
5. **Перезапустите Nginx**, чтобы применить изменения:
```bash
sudo systemctl restart nginx
```
Теперь Nginx будет проксировать запросы с `http://sitename.ru` и `https://sitename.ru` на ваш Node.js сервер, работающий на порту 3000.
### Для HTTPS:
Если вы еще не настроили сертификаты, вы можете использовать Let's Encrypt для создания бесплатного SSL-сертификата. Убедитесь, что Nginx открыт на порту 443 для HTTPS.
### Примечания:
- Убедитесь, что у вашего Node.js приложения правильные настройки CORS, если ваш frontend и backend находятся на разных доменах или если вы используете разные порты.
- Если вы хотите работать с поддоменами или различными маршрутами, вам нужно будет соответствующим образом настроить дополнительные `location` блоки в конфигурации Nginx.
- Запускайте Node.js приложение через процесс-менеджер, например, `pm2`, чтобы оно автоматически перезапускалось в случае сбоя.
User: Зачем два раза блок
location / {
proxy_pass http://localhost:3000; # Ваш Node.js сервер
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
Assistant: вашем примере конфигурации Nginx блок `location /` повторяется дважды — один для порта 80 и другой для порта 443. Это делается для разделения настроек, так как два порта обслуживают разные протоколы (HTTP и HTTPS).
Тем не менее, повторение одного и того же блока (с идентичными настройками) может быть избыточным. Вместо этого, вы можете просто определить его один раз, убрав различные `server` блоки, однако вам придется дублировать директивы `proxy_pass` в каждом из этих блоков. Вот как это можно сделать более аккуратно, объединив `location` блоки:
```nginx
server {
listen 80;
server_name sitename.ru www.sitename.ru;
# Перенаправление HTTP на HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name sitename.ru www.sitename.ru;
ssl_certificate /path/to/your/certificate.crt; # Путь к вашему SSL сертификату
ssl_certificate_key /path/to/your/private.key; # Путь к вашему приватному ключу
location / {
proxy_pass http://localhost:3000; # Ваш Node.js сервер
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
```
Таким образом, при обращении к вашему сайту по HTTP (порт 80) автоматически перенаправляется на HTTPS (порт 443). Теперь блок `location /` определён только для блока HTTPS, но также нужно изменить конфигурацию так, чтобы ненужные дублирования не возникали.
### Объяснение:
1. **HTTP блок (порт 80)**: Этот блок обрабатывает запросы на HTTP и перенаправляет их на HTTPS. Это важно для безопасности, так как HTTP соединения могут быть перехвачены.
2. **HTTPS блок (порт 443)**: Этот блок обрабатывает маршруты к вашему Node.js приложению и использует SSL/TLS для защищённой передачи данных.
Эта конфигурация является стандартной практикой в Nginx для обеспечения безопасного веб-сервиса.
User: теперь все запросы будут перенаправляться на http://localhost:3000, т.е. на node.js, даже index.html, который по сути является статичной html страницей?
Assistant: , если вы используете один и тот же блок `location /` для обработки всех запросов, включая статические файлы (например, `index.html`), все запросы будут перенаправляться на ваш Node.js сервер, запущенный на `http://localhost:3000`. Это может быть неэффективно, если вы хотите обслуживать статические файлы непосредственно через Nginx, так как обычно Nginx настроен для обработки статического контента более эффективно и быстро.
Чтобы обеспечить корректное обслуживание статических файлов, вы можете изменить конфигурацию Nginx так, чтобы запросы к статическим файлам (например, HTML, CSS, JS и изображениям) обрабатывались локально, а все остальные запросы (например, API) проксировались на Node.js. Вот пример, как это можно сделать:
```nginx
server {
listen 80;
server_name sitename.ru www.sitename.ru;
# Перенаправление HTTP на HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name sitename.ru www.sitename.ru;
ssl_certificate /path/to/your/certificate.crt; # Путь к вашему SSL сертификату
ssl_certificate_key /path/to/your/private.key; # Путь к вашему приватному ключу
# Определите каталог с вашими статическими файлами
root /path/to/your/static/files; # Замените на реальный путь к диретории с вашими статическими файлами
location / {
try_files $uri $uri/ @nodejs; # Пробуем найти файл, если не найден, передаём на прокси
}
location @nodejs {
proxy_pass http://localhost:3000; # Ваш Node.js сервер
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
```
### Объяснение изменений:
1. **`root /path/to/your/static/files;`**: Укажите путь к папке, где находятся ваши статические файлы (например, `index.html`, CSS и JS файлы).
2. **`try_files $uri $uri/ @nodejs;`**: Nginx сначала проверяет, существует ли запрашиваемый файл. Если запрашиваемый файл найден, Nginx отправляет его клиенту. Если файл не найден, запрос передаётся в блок `@nodejs`, который проксирует его на ваш Node.js сервер.
3. **Обработка запросов**: Теперь Nginx будет сначала пытаться обслуживать статические файлы, что повышает производительность и снижает нагрузку на Node.js, который будет обрабатывать только динамические запросы.
Эта конфигурация позволяет плавно сочетать статический и динамический контент, улучшая производительность вашего веб-приложения.
User: try_files $uri $uri/ @nodejs;: Nginx сначала проверяет, существует ли запрашиваемый файл. Если запрашиваемый файл найден, Nginx отправляет его клиенту. Если файл не найден, запрос передаётся в блок @nodejs, который проксирует его на ваш Node.js сервер.
Зачем? Если пользователь запрашивает несуществующую страницу надо перенаправлять его на кастомную страницу 404
Assistant: абсолютно правы! Если пользователь запрашивает несуществующую страницу, гораздо лучше перенаправить его на кастомную страницу 404, чем просто проксировать запрос на сервер Node.js. Это обеспечивает лучшую пользовательскую опытность и позволяет вам показать удобное сообщение об ошибке.
Чтобы реализовать обработку ошибки 404 в Nginx, вы можете скорректировать конфигурацию следующим образом:
```nginx
server {
listen 80;
server_name sitename.ru www.sitename.ru;
# Перенаправление HTTP на HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name sitename.ru www.sitename.ru;
ssl_certificate /path/to/your/certificate.crt; # Путь к вашему SSL сертификату
ssl_certificate_key /path/to/your/private.key; # Путь к вашему приватному ключу
# Определите каталог с вашими статическими файлами
root /path/to/your/static/files; # Замените на реальный путь к диретории с вашими статическими файлами
location / {
try_files $uri $uri/ =404; # Если файл не найден, возвращаем 404
}
location @nodejs {
proxy_pass http://localhost:3000; # Ваш Node.js сервер
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
error_page 404 /404.html; # Указываем кастомную страницу 404
location = /404.html {
internal; # Указываем, что эта страница доступна только из Nginx, а не непосредственно через клиент
}
}
```
### Объяснение изменений:
1. **`try_files $uri $uri/ =404;`**: Если запрашиваемый файл существует, он будет возвращен клиенту. Если файл не найден, Nginx вернет код ошибки 404 вместо перенаправления на Node.js сервер.
2. **`error_page 404 /404.html;`**: Указывает Nginx, что использовать страницу `/404.html` для отображения ошибки 404. Вам нужно создать этот файл и поместить его в корневую директорию, указанную в `root`.
3. **`location = /404.html { internal; }`**: Эта настройка указывает, что страница 404 доступна только из внутренних перенаправлений Nginx, и не может быть запрошена непосредственно пользователем.
Теперь, если пользователь запрашивает несуществующую страницу, он увидит кастомизированную страницу 404, а не просто перенаправления на ваш Node.js сервер. Это позволит вам предоставить более удобный и целенаправленный интерфейс в случае ошибок.
User: location @nodejs
что это означает? Код в моем первом сообщении это файл call.js в папке sitename.ru\chat\API\, который обрабатывает вебсокет подключения и запросы от страницы index.html из папки sitename.ru\chat. Значит ли это что location @nodejs будет срабатывать, когда пользователь открывает этот index.html или отправляет сообщения в чат через него, т.е. когда в итоге выполняется call.js?
Assistant: контексте вашей конфигурации Nginx, блок `location @nodejs` — это специальная метка, которую вы определяете в своей конфигурации, и она используется для обработки запросов, которые не смогли совпасть с другими определёнными локальными маршрутами, такими как статические файлы. Давайте разберём, как это работает на практике.
### Что означает `location @nodejs`?
- **`location @nodejs`**: Этот блок определяет кастомный маршрут, который используется для обработки запросов, которые не соответствуют ни одному из ранее определённых маршрутов.
### Как это взаимодействует с вашим приложением:
1. **Запрос статических файлов**: Когда пользователь открывает `index.html` или делает запросы на статические файлы (например, CSS, JS), Nginx сначала проверяет, существует ли данный файл на диске, используя `try_files $uri $uri/`. Если файл существует, Nginx отправляет его клиенту напрямую.
2. **Обработка входящих запросов**: Если пользователь отправляет сообщения в чат, эти запросы могут быть отправлены на определённый API-эндпоинт (например, `/api/message`) или какое-то другое URL, который не является статическим файлом. Если запрос не найден, то Nginx применяется логика из блока `@nodejs`, что ведёт к проксированию запроса на ваш Node.js сервер.
3. **Когда используется `@nodejs`**: Например, если запрос идёт на какой-то маршрут, который не обслуживается статическими файлами (например, `/api/chat`), и Nginx не находит соответствующий файл, он передаст этот запрос в блок `@nodejs`, который затем проксирует его на localhost:3000 (ваш сервер Node.js).
```nginx
location / {
try_files $uri $uri/ @nodejs; # Если файл не найден, передаем на @nodejs
}
location @nodejs {
proxy_pass http://localhost:3000; # Прокси на Node.js сервер
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
```
### Как это касается вашего `call.js`:
- **`call.js`** будет срабатывать, когда ваш Node.js сервер получает запросы от клиента (например, сообщений в чате). Это происходит через WebSocket или HTTP запрос, которые вы настроили в вашем «call.js» файл.
- Таким образом, если пользователь отправляет сообщение через интерфейс chat, этот запрос может быть обработан Node.js и вызовет соответствующую логику в `call.js`.
### Итог:
- **Запросы на статические файлы**, такие как `index.html`, будут обработаны Nginx и возвращены клиенту.
- **Запросы на API или динамичные маршруты**, которые не соответствуют статическим файлам, будут автоматически перенаправлены в блок `@nodejs`, который устраивает проксирование к вашему серверу Node.js, где находится вся логика в `call.js`.
### Если вы не хотите отправлять все запросы на Node.js:
Вы можете дополнительно настроить конфигурацию Nginx, чтобы исключить динамические вызовы на определённые маршруты или чтобы конкретные эндпоинты не обрабатывались через Node.js, если это не нужно. Убедитесь, что у вас правильная маршрутизация на Nginx так, чтобы избежать ненужной нагрузки на сервер.
User: пользователь может непосредственно открыть (через строку браузера) либо site.ru либо site.ru/chat. В обоих случаях открывается соответствующий index.html. В случае с site.ru/chat когда открывается его index.html срабатывает js скрипт, который сразу пытается установить веб сокет соединение с сервером
function connect() {
const url = `wss://gpt.loc:3000?token=${userId}`; // URL WebSocket сервера
ws = new WebSocket(url);
...
на сервере за это отвечает как раз call.js
// HTTPS сервер
const httpsServer = https.createServer(serverOptions);
// Создайте WebSocket-сервер, используя существующий HTTPS сервер
const wss = new WebSocket.Server({ server: httpsServer });
// Запустите сервер
httpsServer.listen(3000, () => {
console.log('HTTPS сервер и WebSocket сервер запущены на порту 3000');
});
...
wss.on('connection', (ws, req) => {
//console.log('Новое соединение:', req.socket.remoteAddress);
const urlParams = new URLSearchParams(req.url.split('?')[1]);
const userId = urlParams.get('token'); // Получаем UUID из URL
const userIP = req.socket.remoteAddress;
//console.log("IP-адрес - ", userIP, "Время подключения - ", new Date().toISOString());
const userHeaders = req.headers;
тот же js файл на стороне клиента отправляет запросы клиента в чат (через тот же вебсокет), которые также обрабатываются файлом call.js.
Как при такой логике написать контекст server на Nginx?
Assistant: аша конфигурация предполагает, что у вас есть веб-приложение на клиентской стороне, которое открывает статические файлы `index.html` для разных маршрутов (например, `/` и `/chat`), и использует WebSocket для связи с сервером через Node.js. При этом ваши WebSocket-соединения и не только нужно корректно обрабатывать в конфигурации Nginx.
#### Конфигурация Nginx
Для реализации этой логики, вам нужно настроить Nginx так, чтобы обслуживать статические файлы и одновременно проксировать HTTP и WebSocket запросы к серверу Node.js. Вот пример, который подходит для вашего случая:
```nginx
server {
listen 80;
server_name gpt.loc www.gpt.loc;
# Перенаправление HTTP на HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name gpt.loc www.gpt.loc;
ssl_certificate /path/to/your/certificate.crt; # Путь к вашему SSL сертификату
ssl_certificate_key /path/to/your/private.key; # Путь к вашему приватному ключу
# Определите каталог с вашими статическими файлами
root /path/to/your/static/files; # Путь к вашему каталогу с index.html и другими статическими файлами
# Обработка запросов на статические файлы
location / {
try_files $uri $uri/ =404; # Если файл не найден, вернуть 404
}
# Проксирование WebSocket соединений
location /chat {
proxy_pass http://localhost:3000; # Проксируем WebSocket запросы к Node.js серверу
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; # Принимаем обновления соединения для WebSocket
proxy_set_header Connection "upgrade"; # Устанавливаем соединение как обновляемое
proxy_set_header Host $host; # Отправляем оригинальный заголовок хоста
proxy_cache_bypass $http_upgrade; # Пропускаем кэш для WebSocket
}
# Проксирование остального API
location /api {
proxy_pass http://localhost:3000; # Проксируем остальные API запросы к Node.js серверу
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# Кастомная страница 404
error_page 404 /404.html;
location = /404.html {
internal; # Указываем, что страница доступна только внутренним образом
}
}
```
### Объяснение конфигурации:
1. **HTTP на HTTPS**: Запросы на порт 80 (HTTP) перенаправляются на порт 443 (HTTPS).
2. **Статические файлы**: Блок `location /` проверяет, существует ли запрашиваемый статический файл (например, `index.html`). Если файл не найден, возвращает ошибку 404. Путь к корневой папке вашего проекта задается с помощью `root`.
3. **WebSocket и API**:
- **`location /chat`**: Этот блок проксирует WebSocket соединения, когда клиент отправляет сообщения через WebSocket. Это позволит вашему клиентскому скрипту устанавливать соединение с `wss://gpt.loc/chat?token=${userId}`.
- **`location /api`**: Если у вас есть другие API, которые также обрабатываются через Node.js, они будут отправлены в этот блок. Например, если у вас есть маршруты, начинающиеся с `/api`, они будут проксированы к серверу Node.js.
4. **Кастомная страница 404**: Если запрашиваемый файл не найден, будет возвращена статическая страница 404, которую вы настройте в вашей предопределенной директории.
### Почему это работает:
- **Статические ресурсы** (например, HTML, CSS, JavaScript) обрабатываются непосредственно Nginx, что позволяет избежать ненужной нагрузки на Node.js.
- **Запросы WebSocket** и другие динамические запросы (например, к вашему API) обрабатываются через ваш Node.js сервер, который имеет логику обработки.
Теперь, если пользователь открывает `site.ru` или `site.ru/chat`, все статические файлы будут предоставлены напрямую Nginx, а запросы WebSocket будут правильно проксированы.
User: для чего на моем сайте
# Проксирование остального API
location /api {
proxy_pass http://localhost:3000; # Проксируем остальные API запросы к Node.js серверу
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
Assistant: