У меня есть приложение, которое использует Angular 13 в качестве внешнего интерфейса и nodeJS с Express для внутреннего интерфейса. Бэкэнд-серверы файлов внешнего интерфейса. Я работаю над добавлением поддоменов в свое приложение, и, хотя у меня это в некоторой степени получилось, есть пара проблем, с которыми я столкнулся, и я тоже не могу найти решение.
Я сделал большую часть этого, но проблема, похоже, в том, что когда я отправляю index.html с помощью функции sendFile(), мне удается изменить URL-адрес на URL-адрес Auth/Auth с параметрами, но веб-сайт этого не делает. работаю и получаю следующие ошибки:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
This page isn’t working "domain" redirected you too many times.
Недавно я представил субдомены, и мой server.js:
const swagger = express();
var app = express();
//apiRoutes.use(enforce.HTTPS());
app.use(express.static(path.join(__dirname, '/dist/App'), { dotfiles: 'allow' }));
allRoutes.use(bodyParser.json({limit: '50mb'}));
allRoutes.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
allRoutes.use(cors());
// use JWT auth to secure the api
if (process.env.NODE_ENV !== "production" || process.env.ENV === "dev") {
swagger.use('/', swaggerUi.serve, swaggerUi.setup(swaggerFile, { swaggerOptions: { persistAuthorization: true }}));
app.use('/swagger', swaggerUi.serve, swaggerUi.setup(swaggerFile, { swaggerOptions: { persistAuthorization: true }}));
}
allRoutes.use('/webhooks', require('./backend/webhooks/webhooks.controller'));
allRoutes.use(subdomain('webhooks', require('./backend/webhooks/webhooks.controller')));
allRoutes.use('*', jwt());
// allRoutes.use(subdomain('api', jwt()));
// api routes
allRoutes.use('/api/users', require('./backend/users/users.controller'));
// global error handler
// start server
app.use(subdomain('swagger', swagger));
// app.use(subdomain('webhooks', require('./backend/webhooks/webhooks.controller')));
app.use('/swagger', swagger);
app.get(/^/(?!api).*/,async function(req,res) {
let isNonExcludedUrl = !req.url.includes('swagger') && !req.subdomains.includes('swagger') &&!req.url.includes('webhooks') && !req.subdomains.includes('webhooks');
console.info('subdomain ', req.subdomains);
if (req.subdomains.includes('webhooks')) {
console.info('in webhooks')
} else if (req.subdomains.length > 0 && isNonExcludedUrl) {
console.info('processing website subdomain ');
await website.checkSubdomainForWebsite(req, res)
} else if (isNonExcludedUrl) { //&& !req.url.includes('webhooks')
res.sendFile(path.join(__dirname + '/dist/App/index.html'));
}
});
app.use('/', allRoutes);
allRoutes.use(errorHandler);
app.use(errorHandler);
const port = process.env.PORT ? (process.env.PORT) : 4000;
var server = http.createServer(options, app);
server.listen(port, () => {
console.info("server starting on port : " + port)
});
Функция checkSubdomainForWebsite:
async function checkSubdomainForWebsite(req,res) {
let orgIdent = req.subdomains[0];
if (orgIdent) {
let qrDevice = await getOrCreateWebsiteDevice(orgIdent);
let websiteUrl = `/Auth/Auth`;
res.sendFile(path.join(__dirname+'/dist/DineNGo/index.html'));
res.redirect(url.format({
pathname: websiteUrl, // assuming that index.html lies on root route
query: {
"qr": encodeURIComponent(encrypt(orgIdent)),
"website": true
}
}));
}
}
Ожидаемое поведение: когда вызывается функция checkSubdomainForWebsite(), сразу после вызова res.sendFile() и загрузки index.html я хочу перенаправиться на определенную страницу Angular SPA с параметрами запроса. URL-адрес конкретного компонента Angular, по которому я хочу перейти, — это Auth/Auth.
Изучив это, я обнаружил, что res.sendFile() и res.redirect не следует использовать вместе, и я почти уверен, что именно это вызывает проблему, но я не могу найти альтернативный подход, который бы работал. Как лучше всего разместить файл index.html и направить его к определенному компоненту в интерфейсе Angular через маршруты. Ниже я предоставил свой service.js и другие функции. Пожалуйста, дайте мне знать, если потребуется дополнительная информация, и спасибо за помощь.
Nginx — правильный выбор для обслуживания веб-интерфейса, тогда как node.js обычно используется только для обслуживания серверных API. Nginx превосходно справляется с эффективным обслуживанием статического контента, такого как HTML, CSS, JavaScript и файлов изображений. Nginx может действовать как обратный прокси-сервер, перенаправляя клиентские запросы на внутренние серверы, в вашем случае API-интерфейсы node.js.
#Nginx config
server {
listen 80;
server_name example.com; # your domain name or ip address
location / {
root /path/to/your/angular/dist; # your real dist path
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:4000;
}
location /webhooks {
proxy_pass http://localhost:4000;
}
location /swagger {
proxy_pass http://localhost:4000;
}
}
и ваш Node.js должен слушать 4000.
Я добавил краткую конфигурацию Nginx. Пожалуйста, обратитесь к нему.
Да, перенаправление и отправку файла нельзя использовать вместе. Итак, вы можете отправить файл, а затем продолжить работу с AJAX/Angular.
Вы можете попробовать сначала перенаправить с помощью строки запроса, но включите в строку запроса некоторый флаг, который обработчик будет читать при перенаправлении, и по нему будет известно, что теперь ему нужно отправить файл (или, возможно, в переменную памяти в сочетании с другими проверками), (это делает два запроса и запускает другое промежуточное программное обеспечение и может вызвать слишком много перенаправлений, поэтому было бы лучше обрабатывать это во внешнем интерфейсе), например:
добавить флаг в qs:
"redirected": true
читай каждый раз:
if (req.query.redirected) return res.sendFile(path.join(__dirname+'/dist/DineNGo/index.html'));
код:
async function checkSubdomainForWebsite(req,res) {
let orgIdent = req.subdomains[0];
// add some flag to make sure it's redirect in combination with other checks
if (req.query.redirected) return res.sendFile(path.join(__dirname+'/dist/DineNGo/index.html'));
if (orgIdent) {
let qrDevice = await getOrCreateWebsiteDevice(orgIdent);
let websiteUrl = `/Auth/Auth`;
// redirect first
res.redirect(url.format({
pathname: websiteUrl, // assuming that index.html lies on root route
query: {
"qr": encodeURIComponent(encrypt(orgIdent)),
"website": true,
"redirected": true
}
}));
}
}
Это сработало на удивление хорошо. Просто интересно, есть ли недостатки у этого подхода?
наряду с указанным, возможно, ваш SPA зависит от сервера, поэтому я бы попробовал обработать его на внешнем интерфейсе, например, просто отправить файл по этому маршруту, а затем получить URL-адрес запроса с помощью службы angular http, перенаправить с помощью маршрутизатора, поделитесь им с компонентами и т. д.
Как известно, цикл запрос-ответ может иметь только один запрос и один ответ. Поэтому res.sendFile и последующее res.redirect в одном и том же цикле являются нарушением и выдают сообщение об ошибке.
Ниже приведен обходной путь.
Минимальный код ниже адресует то же самое с помощью JavaScript в браузере. Но здесь есть и проблема. Этот тип кода отключает кнопку возврата. Мне еще предстоит найти решение этой конкретной проблемы - ломается кнопка назад. Когда пользователь нажимает кнопку «Назад», он не переходит на страницу index.html. Если кто-то найдет решение этой проблемы, это может быть одним из способов обхода этого вопроса.
сервер.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res
.cookie('somecookie', 'somevalue')
.sendFile(__dirname + '/' + './index.html');
});
app.get('/route1', (req, res) => {
console.info(req.query);
res.send('route1 navigation automated from the root route');
});
app.listen(3000, console.info('L@3000'));
index.html
<!DOCTYPE html>
<html>
<head>
Navigation automated to another route on load.
</head>
<body>
<br />
<br />
<a href = "./route1">A test route</a>
</body>
<script async>
history.pushState({}, '', window.location.href);
//for testing, assumed there is no other cookies
const cookie = document.cookie;
window.location.href = './route1?' + cookie;
// to avoid unnecessary redirection, this cookie
// needs to be deleted once the redirection is done
// { code to delete the cookie }
</script>
</html>
Результаты теста:
URL : http://localhost:3000
Redirected with the URL : http://localhost:3000/route1?somecookie=somevalue
server console :
{ somecookie: 'somevalue' }
Спасибо за ответ. Можете ли вы привести пример или показать мне, как я могу изменить свой код для реализации такого поведения? На самом деле мне нужно обратное: мне нужен сервер для загрузки angular index.html, но с определенным маршрутом к компоненту. Будет ли Nginx это поддерживать?