Аутентификация JWT с использованием Vue приводит к фатальной ошибке: кучи JavaScript не хватает памяти - PullRequest
0 голосов
/ 10 января 2019

Это довольно сложная проблема, которая включает в себя МНОЖЕСТВО кусочков, поэтому я отправил полный исходный код на GitHub для всех, кто хочет помочь.

Стек

Хорошо, поэтому я пытаюсь настроить аутентификацию с помощью клиентского приложения Nuxt.js (Vue Framework) и серверного API Rails. Вот мой стек проектов:

  1. Бэкэнд API Rails
  2. Devise & Devise-JWT рубиновые камни для обслуживания JWT.
  3. База данных Postgres.
  4. Внешнее приложение Nuxt.js.
  5. @ nuxtjs / axios пакет для подключения приложения Nuxt с Rails API.
  6. @ nuxtjs / auth пакет для обработки логики аутентификации на стороне клиента в Nuxt.
  7. прокси Nginx для маршрутизации запросов к соответствующему сервису (Rails API или Nuxt App)
  8. Docker и docker-compose для соединения всех четырех сервисов (Rails, Nginx, Nuxt, Postgres)

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

Проблема

Приложение Vue прекрасно работает, пока я не попытаюсь войти как существующий пользователь. Как только я это сделаю, произойдет что-то интересное.

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

После небольшой загрузки сайта я получаю эту ошибку:

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

Наряду с этой ошибкой я получаю JS Stack Trace, однако трассировка стека не всегда одинакова. Вот три, которые я получил до сих пор ...

JS Stacktrace 1:

view_1   | <--- JS stacktrace --->
view_1   | 
view_1   | ==== JS stack trace =========================================
view_1   | 
view_1   |     0: ExitFrame [pc: 0x26fd9624fb5d]
view_1   | Security context: 0x140a9a59d921 <JSObject>
view_1   |     1: on [0x1c685bd61259] [_stream_readable.js:~825] [pc=0x26fd96bd1601](this=0x27476ca40f19 <Socket map = 0x2513dd2aaca1>,0x1c685bd160d9 <String[5]: close>,0x27476ca41619 <JSBoundFunction (BoundTargetFunction 0x118981190661)>)
view_1   |     2: connectionListenerInternal(aka connectionListenerInternal) [0x118981190561] [_http_server.js:~325] [pc=0x26fd9706ea91](this=...
view_1   | 
view_1   | FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

JS Stacktrace 2:

view_1   | <--- JS stacktrace --->
view_1   | 
view_1   | ==== JS stack trace =========================================
view_1   | 
view_1   |     0: ExitFrame [pc: 0x3595a694fb5d]
view_1   | Security context: 0x016ed8d9d921 <JSObject>
view_1   |     1: parse(aka parse) [0x34dc366765a9] [/app/node_modules/cookie/index.js:~49] [pc=0x3595a77ee3a0](this=0x297ef7d825b1 <undefined>,0x15d707f39c31 <String[265]: auth._token.local=Bearer%20eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwic2NwIjoidXNlciIsImF1ZCI6bnVsbCwiaWF0IjoxNTQ3MDczMjQyLCJleHAiOjE1NDcwNzY4NDIsImp0aSI6ImUwMWRlNjFkLTRkYzItNDRlMi1iZDY1LWQwYmRmZTcwNGNhZS...
view_1   | 
view_1   | FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

JS Stacktrace 3:

view_1   | <--- JS stacktrace --->
view_1   | 
view_1   | ==== JS stack trace =========================================
view_1   | 
view_1   |     0: ExitFrame [pc: 0x2bda0b2cfb5d]
view_1   | Security context: 0x1e6ec149d921 <JSObject>
view_1   |     1: setContext(aka setContext) [0x3ef9c763dc89] [server-bundle.js:~2584] [pc=0x2bda0bbaa2d1](this=0x0a10aa8025b1 <undefined>,0x28d2a207a4c1 <Object map = 0x10d1f0bf3091>,0x28d2a207a5b1 <Object map = 0x10d1f0bf2cd1>)
view_1   |     2: createApp(aka createApp) [0x27c44ed85199] [server-bundle.js:~1745] [pc=0x2bda0bb9d782](this=0x0a10aa8025b1 <undefined>,0x2bee19762ce9 ...
view_1   | 
view_1   | FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

Моя теория

Я думаю, что проблема связана с тем, как @ nuxtjs / axios или @ nuxtjs / auth работает с прокси Nginx.

Причина, по которой я верю, заключается в том, что когда я использую ту же самую настройку для Vue и Rails, но соединяю их вместе напрямую через axios без прокси Nginx между ними, все работает отлично.

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

Более конкретно, я думаю, что проблема может быть связана с тем, как @ nuxtjs / axios или @ nuxtjs / auth работает с прокси-сервером Nginx при использовании файлов cookie.

Я думаю, что это может иметь место, потому что приложение зависает только тогда, когда установлены куки JWT и хранилище сеансов. Если я удаляю файлы cookie в браузере Safari и обновляю страницу, веб-сайт Vue снова работает нормально.

Предполагая, что моя теория верна, вот соответствующий код:

Конфигурация Nginx:

upstream view {
  server view:3000;
}

upstream api {
  server api:8080;
}

server {
  listen 80;

  location / {
    proxy_pass http://view;
  }

  # FOR DEVELOPMENT ONLY
  location /sockjs-node {
    proxy_pass http://view;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
  }

  location /api {
    rewrite /api/(.*) /$1 break;
    proxy_pass http://api;
  }
}

Nuxt Auth & Axios Config:

axios: {
  proxy: true,
  prefix: "/api"
},
auth: {
  strategies: {
    local: {
      endpoints: {
        login: { url: '/auth/users/sign_in' },
        logout: { url: '/auth/users/sign_out', method: 'delete' },
        user: { url: '/auth/users/current' }
      }
    }
  }
},

Рельсы Маршруты:

Rails.application.routes.draw do
  resources :atoms

  scope '/auth', defaults: {format: :json} do
    devise_for :users, controllers: {sessions: 'sessions'}
    devise_scope :user do
      get 'users/current', to: 'sessions#show'
    end
  end
end

Rails SessionController:

class SessionsController < Devise::SessionsController
  def create
    super { @token = current_token }
  end

  def show
  end

  private

  def current_token
    request.env['warden-jwt_auth.token'] if current_user
  end
end

Rails Session Views:

# app/views/devise/sessions/create.json.jbuilder
json.token @token

# app/views/devise/sessions/show.json.jbuilder
if user_signed_in?
  json.user do
    json.(current_user, :id, :email)
  end
end

Страница входа Nuxt (использует Vuetify):

<template>
  <v-layout>
    <v-flex>
      <v-card v-if="$auth.loggedIn">
        <v-alert type="error" :value="error">{{error}}</v-alert>
        <v-card-text>
          Logged in as {{$auth.user.email}}
        </v-card-text>
        <v-card-actions>
          <v-btn @click="logout">Log out</v-btn>
        </v-card-actions>
      </v-card>
      <v-card v-else>
        <v-alert type="error" :value="error">{{error}}</v-alert>
        <v-card-text>
          <v-form>
            <v-text-field v-model="email" label="Email" />
            <v-text-field v-model="password" label="Password" type="password" />
          </v-form>
          <v-card-actions>
            <v-btn @click="login">Log in</v-btn>
          </v-card-actions>
        </v-card-text>
      </v-card>
    </v-flex>
  </v-layout>
</template>

<script>
  export default {
    data () {
      return {
        email: '',
        password: '',
        error: null
      }
    },
    methods: {
      login () {
        this.$auth.login({
          data: {
            user: {
              email: this.email,
              password: this.password
            }
          }
        }).catch(e => {this.error = e + ''})
      },
      logout () {
        this.$auth.logout().catch(e => {this.error = e + ''})
      }
    }
  }

</script>

Докер Состав:

version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./.tmp/db:/var/lib/postgresql/data
    ports:
      - "5432"
  proxy:
    build: proxy
    restart: always
    ports:
      - '5000:80'
  api:
    build: api
    environment:
      - JWT_SECRET=${JWT_SECRET}
    volumes:
      - ./api:/app
    depends_on:
      - db
  view:
    build: view
    volumes:
      - /app/node_modules
      - ./view:/app

Посмотреть код

Так как это очень сложный проект и состоит из множества частей, я ожидаю, что кому-то будет довольно сложно помочь только с данным кодом. По этой причине я установил репозиторий GitHub со всем кодом в его текущем состоянии. Вы можете просмотреть его здесь .

Чтобы запустить репозиторий, просто клонируйте проект

$ git clone https://github.com/JoshHadik/stack_nuxt_authentication_question.git

Переместиться в каталог

$ cd stack_nuxt_authentication_question/ 

И запустите docker-compose up --build (при условии, что на вашем компьютере есть docker и docker-compose.

$ docker-compose up --build

Настройка базы данных в новой вкладке терминала

$ docker-compose run api rails db:create db:migrate db:seed

После этого вы сможете получить доступ к приложению Vue @ localhost: 5000. Кроме того, база данных должна содержать пользователя со следующими учетными данными: электронная почта: test@example.com пароль: пароль

Маршрут входа в систему по адресу localhost: 5000 / login

Нет прокси

Я упоминал выше, что приложение работает, когда API и представление подключены напрямую без промежуточного прокси. чтобы продемонстрировать, как это работает, я создал дополнительную ветку в проекте под названием no-proxy. Если вы хотите проверить это, просто откройте новый каталог (не тот, к которому вы ранее клонировали) и запустите

$ git clone -b no-proxy https://github.com/JoshHadik/stack_nuxt_authentication_question.git

перейти в каталог

$ cd stack_nuxt_authentication_question/

затем снова запустите docker-compose

$ docker-compose up --build

и настройте базу данных в новой вкладке терминала:

$ docker-compose run api rails db:create db:migrate db:seed

Вы можете просмотреть приложение Nuxt для ветки без прокси-сервера @ localhost: 3000 и маршрут входа в систему @ localhost: 3000 / login. Вы должны иметь возможность войти с теми же учетными данными, что и выше.

Спасибо

Заранее спасибо всем и каждому, кто пытается помочь мне с этим!

...