Файл index.html NodeJS Express Host затем направляется к определенному маршруту Spa с параметрами

У меня есть приложение, которое использует 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 clientThis 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 и другие функции. Пожалуйста, дайте мне знать, если потребуется дополнительная информация, и спасибо за помощь.

🤔 А знаете ли вы, что...
Node.js активно используется для создания серверных приложений на стороне бэкенда в стеке MEAN (MongoDB, Express.js, Angular, Node.js).


1
72
3

Ответы:

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.


Решено

Да, перенаправление и отправку файла нельзя использовать вместе. Итак, вы можете отправить файл, а затем продолжить работу с 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
            }
        }));
    }
}

Как известно, цикл запрос-ответ может иметь только один запрос и один ответ. Поэтому 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' }