Sunday, 7 May 2017

net::ERR_CONNECTION_CLOSED on remote server when there are more than 7 sub-documents in mongo document

I am developing a MEAN project with angular 4.1.0.

On my localhost, everything works without errors. When I deploy to the server however, retrieving a user with more than 8 question-answer pairs causes a net::ERR_CONNECTION_CLOSED error on the xhr request that angular's http module fires.

The digital ocean droplet I am hosting on uses an nginx reverse proxy and uses a letsencrypt SSL certificate. I have tried restarting server, nginx service, node.js etc.

Please point me in possible directions.


The chrome dev tool network tab screenshots

  1. After logging in to an account where there are only 7 question answer pairs After logging in to an account where there are only 7 question answer pairs

  2. Then, after going to mlab.com and manually adding another question answer pair to same account and then refreshing the page (notice the number of questions in now 8) After going to mlab.com and manually adding another question answer pair to same account and then refreshing the page

  3. Finally, after logging in and out of the same account (notice the xhr request to qapairs?jwt=ey... returned a failed status) After logging in and out of the same account

The source files:

Backend

server.js

const express = require('express')
const path = require('path')
const morgan = require('morgan') // logger
const bodyParser = require('body-parser')
require('dotenv').config()

const apiRoutes = require('./routes/api.routes')
const userRoutes = require('./routes/user.routes')

const app = express()
app.set('port', (process.env.PORT || 3001))
app.use(require('helmet')())
app.use(require('compression')({ threshold: 0 }))
app.use('/', express.static(path.join(__dirname, '/../../dist'), { maxAge: 86400000 }))

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))

app.use(morgan('dev'))

const { mongoose } = require('./imports')

if (process.env.NODE_ENV !== 'production') {
  mongoose.connect('mongodb://localhost:27017/quiz')
} else {
  mongoose.connect(`mongodb://${process.env.DB_USER}:${process.env.DB_PASS}@ds127731.mlab.com:27731/splearn`)
}

const db = mongoose.connection

db.on('error', console.error.bind(console, 'connection error:'))
db.once('open', function () {
  console.log('Connected to MongoDB')

  // Routes
  app.use('/api', apiRoutes)
  app.use('/user', userRoutes)

  // all other routes are handled by Angular
  app.get('/*', function (req, res) {
    res.sendFile(path.join(__dirname, '/../../dist/index.html'))
  })

  app.listen(app.get('port'), function () {
    console.log('Angular 2 Full Stack listening on port ' + app.get('port'))
  })
})

module.exports = app


api.routes.js

const express = require('express')
const router = express.Router()
const jwt = require('jsonwebtoken')
const { levenshtein, attemptAnswer } = require('../helpers')
const User = require('../models/user.model')

router.use('/', (req, res, next) => {
  jwt.verify(req.query.jwt, process.env.JWT_SECRET, (err, decoded) => {
    if (err) return res.status(401).json({title: 'Not authenticated', error: 'Not authenticated. Please log in.'})
    req.body.decodedUserID = jwt.decode(req.query.jwt).user
    next()
  })
})

// select all
router.get('/qapairs', function (req, res) {
  User.findById(req.body.decodedUserID)
    .then((foundUser) => {
      res.json(foundUser.qapairs)
    })
    .catch((err) => res.status(500).json({title: 'An error occured', error: err}))
})

/* etc etc etc */

module.exports = router


user.model.js

const { mongoose } = require('../imports')
const questionAnswerPairSchema = require('./questionAnswerPair.schema')
var uniqueValidator = require('mongoose-unique-validator')

const userSchema = mongoose.Schema({
  firstName: String,
  email: {type: String, required: true, unique: true},
  password: { type: String, required: true },
  qapairs: [questionAnswerPairSchema]
})

userSchema.plugin(uniqueValidator)

const User = mongoose.model('User', userSchema)

module.exports = User


questionAnswerPair.schema.js

const { mongoose } = require('../imports')
const { nextAssessmentDate } = require('../helpers')

const questionAnswerPairSchema = mongoose.Schema({
  question: String,
  correctAnswers: [String],
  wrongAnswers: [String],
  explanation: String,
  createdAt: { type: Date, default: new Date() },
  correctAttempts: { type: Number, default: 0 },
  wrongAttempts: { type: Number, default: 0 },
  netCorrectAttempts: { type: Number, default: 0 },
  lastAssessed: Date,
  toBeAssessedNext: Date
})

questionAnswerPairSchema.pre('save', function (next) {
  this.toBeAssessedNext = nextAssessmentDate(this.netCorrectAttempts, this.lastAssessed || this.createdAt)
  next()
})

module.exports = questionAnswerPairSchema

Frontend

qa-pairs.service.ts

The error is being caught here in the getQAPairs function. Being passed to it is a ProgressEvent object with a 'type' property of 'error'.

import { Injectable, EventEmitter } from '@angular/core';
import { Http, Headers } from "@angular/http";
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import QAPair, { IQAPair } from './qa-pair.model'
import { ErrorsService } from "app/errors/errors.service";

@Injectable()
export class QaPairsService {
  public qapairs: QAPair[] = []
  public qapairsToBeAssessed = []
  public qapairsChanged: EventEmitter<any[]> = new EventEmitter()
  private headers = new Headers({'Content-Type': 'application/json'})
  private qapairsUrl = 'api/qapairs'

  constructor(private http: Http, private errorsService: ErrorsService) {}

  getQAPairs () {
    const jwt = localStorage.getItem('jwt') ? `?jwt=${localStorage.getItem('jwt')}` : ''

    return this.http.get(this.qapairsUrl + jwt)
      .map(response => {
        this.qapairs = response.json().map((qapair: IQAPair) => new QAPair(qapair))
        this.qapairsChanged.emit(this.qapairs) // So that the nav bar gets wind of the qapairs, after initializing without being logged in.
        return this.qapairs
      })
      .catch(
        (error: any) => {
          error = error.json()
          this.errorsService.handleError(error)
          return Observable.throw(error)
        }
      )
  }
  /* etc, etc, etc */
}

qa-pairs.component.ts

import { Component, OnDestroy, OnInit } from '@angular/core';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';
import { QaPairsService } from "app/qa-pairs/qa-pairs.service";
import { filterToBeAssessed } from "app/helpers";


@Component({
  selector: 'app-qa-pairs',
  templateUrl: './qa-pairs.component.html',
  styleUrls: ['./qa-pairs.component.scss']
})
export class QaPairsComponent implements OnDestroy, OnInit {
  public ngUnsubscribe: Subject<void> = new Subject<void>();
  public qapairs = []
  public qapairsToBeAssessed = []
  public currentQapair
  public isEditMode: Boolean
  public shouldShowNewQAModal: Boolean = false

  constructor(private qaService: QaPairsService) { }

  ngOnInit() {
    this.qaService.getQAPairs()
      .takeUntil(this.ngUnsubscribe)
      .subscribe((qapairsArr) => {
        this.qapairs = qapairsArr
        this.qapairsToBeAssessed = filterToBeAssessed(qapairsArr)
      })
    this.qaService.qapairsChanged
      .takeUntil(this.ngUnsubscribe)      
      .subscribe((updatedQAPairs) => {
        this.qapairs = updatedQAPairs
        this.qapairsToBeAssessed = filterToBeAssessed(updatedQAPairs)
      })
  }

  /*etc, etc, etc*/

  ngOnDestroy() {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }
}



via T M

No comments:

Post a Comment