Настройте Vue.js чтобы разрешить обновление браузера (серверная часть django, сервер nginx)

#django #vue.js #nginx

#django #vue.js #nginx

Вопрос:

У меня есть производственная настройка следующим образом;

  • Серверная часть Django REST Framework
  • Интерфейс Vuejs
  • Контейнер Docker, который создает Vuejs для производства и копирует в папки Django Docker container / static / и / template /
  • обратный прокси-сервер nginx для обработки входящих запросов

Все работает нормально, когда я перехожу на домашнюю страницу (на домашней странице нет вызовов серверного API), а затем перемещаюсь по SPA с помощью панели навигации.

У меня начинаются проблемы, когда я пытаюсь перейти непосредственно на страницу в SPA. Серверные запросы, которые должны запускаться при «создании» в Vuejs, не запускаются.

Я видел, как некоторые люди предполагают, что это связано с тем, что маршрутизатор Vue находится в режиме истории, который я хотел бы сохранить.

Основное предлагаемое решение — добавить try_files $uri $uri/ /index.html; в конфигурацию nginx в качестве универсального. Однако, поскольку я просто проксирую все запросы в Django для обработки начальной стадии маршрутизации, и у меня уже есть все уловки в моем urls.py file ( re_path(r"^.*/$", TemplateView.as_view(template_name="index.html"), name="frontend") ) тогда, я думаю, я с этим справился.

Почему запросы API (которые запускаются при создании страницы Vuejs) будут работать при навигации с помощью маршрутизатора, но не при переходе непосредственно на страницу по URL?

конфигурация nginx

 server {
    listen 80;
    location / {
        proxy_pass http://csgs:8000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        # try_files $uri $uri/ /index.html;
    }

    location /static/ {
        alias /home/app/web/static/;
    }
}
  

django urls.py узоры

 urlpatterns = [
    path("admin/", admin.site.urls),
    path("api-auth/", include("rest_framework.urls")),
    path("api/", include("api.urls")),
    path("", TemplateView.as_view(template_name="index.html"), name="home"),
    re_path(
        r"^.*/$", TemplateView.as_view(template_name="index.html"), name="frontend"
    ),
]
  

Тело страницы SPA index:

 <body>
  <noscript>
    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
      Please enable it to continue.</strong>
  </noscript>
  <div id="app"></div>
  <!-- built files will be auto injected -->
</body>
  

Обновить:
Я ввел инструкции отладки и теперь могу сравнивать отправленный запрос и полученный ответ в каждом сценарии.

Навигация по маршрутизатору Vue:

 url = api/booking/ground-stations/ user.service.js:11:16
request = {
  "params": null,
  "headers": {
    "Authorization": "Token 164d5d7bc0fc0be90168739958c0c8640ed52f60"
  }
}
response = {
  "data": [
    {}
  ],
  "status": 200,
  "statusText": "OK",
  "headers": {
    "allow": "GET, HEAD, OPTIONS",
    "connection": "keep-alive",
    "content-length": "82",
    "content-type": "application/json",
    "date": "Sat, 10 Oct 2020 21:49:58 GMT",
    "referrer-policy": "same-origin",
    "server": "nginx/1.17.10",
    "vary": "Accept",
    "x-content-type-options": "nosniff",
    "x-frame-options": "DENY"
  },
  "config": {
    "url": "api/booking/ground-stations/",
    "method": "get",
    "headers": {
      "Accept": "application/json, text/plain, */*",
      "Authorization": "Token 164d5d7bc0fc0be90168739958c0c8640ed52f60"
    },
    "params": null,
    "transformRequest": [
      null
    ],
    "transformResponse": [
      null
    ],
    "timeout": 0,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "maxBodyLength": -1
  },
  "request": {}
}
  

Обновление браузера:

 url = api/booking/ground-stations/ user.service.js:11:16
request = {
  "params": null,
  "headers": {
    "Authorization": "Token 164d5d7bc0fc0be90168739958c0c8640ed52f60"
  }
}
response = {
  "data": "<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/static/favicon.ico><title>CS: GS</title><link rel=stylesheet href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css><link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;800amp;display=swap" rel=stylesheet><link href=/static/css/app.d8cde755.css rel=preload as=style><link href=/static/css/chunk-vendors.93ac251e.css rel=preload as=style><link href=/static/js/app.7e344e2e.js rel=preload as=script><link href=/static/js/chunk-vendors.945fac67.js rel=preload as=script><link href=/static/css/chunk-vendors.93ac251e.css rel=stylesheet><link href=/static/css/app.d8cde755.css rel=stylesheet></head><body><noscript><strong>We're sorry but frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/static/js/chunk-vendors.945fac67.js></script><script src=/static/js/app.7e344e2e.js></script></body></html>",
  "status": 200,
  "statusText": "OK",
  "headers": {
    "connection": "keep-alive",
    "content-length": "1315",
    "content-type": "text/html; charset=utf-8",
    "date": "Sat, 10 Oct 2020 21:51:25 GMT",
    "referrer-policy": "same-origin",
    "server": "nginx/1.17.10",
    "x-content-type-options": "nosniff",
    "x-frame-options": "DENY"
  },
  "config": {
    "url": "api/booking/ground-stations/",
    "method": "get",
    "headers": {
      "Accept": "application/json, text/plain, */*",
      "Authorization": "Token 164d5d7bc0fc0be90168739958c0c8640ed52f60"
    },
    "params": null,
    "transformRequest": [
      null
    ],
    "transformResponse": [
      null
    ],
    "timeout": 0,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "maxBodyLength": -1
  },
  "request": {...}
}
  

Итак, теперь у меня возникает вопрос: почему обновление страницы приводит к ответу html на мои запросы внутреннего API, когда URL-адрес и запрос остаются неизменными как для маршрутизатора vuejs, так и для навигации в браузере firefox?

ОБНОВЛЕНИЕ 2: итак, мы провели еще несколько исследований. Сначала я удалил оператор catch all в URL-адресах Django и заменил его явными инструкциями;

 path("", TemplateView.as_view(template_name="index.html"), name="home"),
    path("ground-stations/", TemplateView.as_view(template_name="index.html"), name="gs"),
  

Теперь я должен видеть, когда происходят какие-либо 404s.

Теперь, когда я отправляю запрос, я вижу, что при навигации с использованием Vuejs API axios добавляет конечные точки к базовому URL «http://127.0.0.1:7100 /», что является правильным. Однако, если я обновлю страницу, базовый URL-адрес теперь станет «http://127.0.0.1/ground-stations /», и к этому добавляются конечные точки, что неверно.

Почему это происходит и как мне это исправить?

Ответ №1:

Похоже, попытка обрабатывать внешние и внутренние маршруты через django — не лучший способ сделать это.

Я нашел эту ссылку полезной: https://medium.com/@zhzhang.4390/vue-in-production-how-to-call-or-proxy-apis-f8045b5a7d16

В принципе, я мог бы использовать некоторые функции в Vue.js для прокси-вызовов API к серверной части, что я и делаю для развертывания разработки Docker. Однако мне это показалось не очень элегантным.

Вместо этого я выбрал его второй вариант разделения серверной части и интерфейса на разные контейнеры и позволил nginx выполнять маршрутизацию.

Это дало дополнительное преимущество, полностью отделив мою серверную часть Django от Vue.js передний конец. Теперь Django не нужно знать о каких-либо интерфейсных маршрутах или URL-адресах и не нужно собирать статические файлы из внешнего каталога dist.

urls.py

 urlpatterns = [
    path("admin/", admin.site.urls),
    path("api-auth/", include("rest_framework.urls")),
    path("api/", include("api.urls")),
]
  

интерфейс Dockerfile

 FROM node:lts-alpine as build-stage

WORKDIR /app
COPY frontend/package*.json ./
RUN npm install
COPY frontend/ .
RUN npm run build

FROM nginx:stable-alpine
COPY --from=build-stage /app/dist /home/frontend
RUN rm /etc/nginx/conf.d/default.conf
COPY deploy/prod/nginx.conf /etc/nginx/conf.d
  

nginx.conf

 server {
    listen 80;
    location / {
      root /home/frontend;
      index index.html;
      try_files $uri $uri/ /index.html;
    }
    location /api/ {
        proxy_pass http://csgs:8000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }
    location /admin {
        proxy_pass http://csgs:8000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }
    location /static/ {
        root /home/frontend;
        try_files $uri $uri/ @csgs_static;
    } 
    location @csmoc_static {
        root /home/csgs;
    } 
}
  

И я использовал файл Docker Compose для монтирования статической папки из моего внутреннего контейнера Docker в /home/csgs.

Это гораздо больше похоже на настройку, подходящую для производства, чем у меня было раньше, и теперь я могу обновить страницу, и все работает так, как ожидалось.