Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Express routing: route is ignored

I'm working on a NodeJS app that possess multiple routes and subroutes, and use Express to manage them. One of the feature of my app is to display lists of classes, one list of classes per version of a software. For this feature, I have three subroutes in the route 'classes':

var express = require('express'),
router = express.Router();

var fs = require('fs');
var path = require('path');

router.get('/', function(req, res){
    // default route, redirect to the list of classes of the last version of the software
    // classesGetLastVersion(cb) reads a JSON file and launch callback with last version number
    classesGetLastVersion(function(version) { 
    res.writeHead(301, {
        Location: (req.socket.encrypted ? 'https://' : 'http://') + req.headers.host + '/classes/' + version
    });
    res.end();
});

router.get('/:version', function(req, res){
    // checks the given version in argument, display the list of classes corresponding 
    // to the version (if it exists; else: 404 error page)

    // retrieve the version number specified
    var version = req.params.version;

    // NOTE: we only serve static HTML pages, so here I directly check if the 
    // corresponding file exists
    fs.exists('public/html/classes_' + version + '.html', function(exists){
    if(exists){
        var options = {
            root: path.join(__dirname, __publicRootPath)
        };

        // file exists, serve it
        res.status(200);
        res.set({'Content-type':'text/html'});
        res.sendFile('./html/classes_' + version + '.html', options);
    } else {
        // file doesn't exists, so we'll check if the req.param.version argument corresponds
        // to a class name, in every version of the software

        /** the file 'data/classes.json' has the following architecture:
         * {
         *      "first_version_number": ["className1", "className2", ..., "classNameN"],
         *      "second_version_number" : ["className1", "className2", ..., "classNameN"],
         *      ...
         *      "nth_version_number": ["className1", "className2", ..., "classNameN"]
         * }
         **/
        fs.readFile('data/classes.json', function(err, data){
            if (err) throw err;

            // for clarification purpose
            var className = version;

            var lastVersion,
                jsonData = JSON.parse(data);

            for(var versionName in jsonData){
                console.log('Searching class in version ' + versionName + '...');

                if(jsonData[versionName].lastIndexOf(className) != -1){
                    console.log('Found it! In v' + versionName);
                    lastVersion = versionName;
                } else {
                    console.log('Class is not here :-(');
                }
            }

            if(lastVersion){
                // redirect to the correct class page
                res.writeHead(301, {
                    Location: (req.socket.encrypted ? 'https://' : 'http://') + req.headers.host + '/classes/' + lastVersion + '/' + className
                });
                res.end();
            } else {
                // render 404 - Page not found
                logger.error('404 error - Page not found: public/html/classes_' + version + '.html');
                res.render('errorpages/404.jade', {});
            }
        });
    }
});

router.get('/:version/:name', function(req, res){
    // check the given version AND name of the class, and display the page corresponding
    // to the specified class, if it exists; else: 404 error page

    var version         = req.params.version;
    var className       = req.params.className;
    className = className
        .replace('<', '_').replace('>', '_')
        .replace('%3CT%3E', '_T_')
        .replace('&lt;T$gt;', '_T_');

    console.log('/:version/:className');

    var fileName = path.join('./public/html/class_' + version, className) + '.html';
    fs.exists(fileName, function(exists){
        if(!exists){
            // 404 class not found
            // render 404 - Class not found
            logger.error('404 error - File not found: '  + fileName);
            res.render('errorpages/404_class_not_found.jade', {classname:className});
        } else {
            fileName = path.join('./html/class_' + version, className) + '.html';

            var options = {
                root: path.join(__dirname, __publicRootPath)
            };

            res.status(200);
            res.set({'Content-type':'text/html'});
            res.sendFile(fileName, options);
        }
    });
});

module.exports = router;

So, in principle, nothing's tricky and everything works perfectly, until I tried to implement a new feature: if the user tries to enter the name of a class without specifying its version, I want the second route to check in a JSON file if the class exists in one of the version of the software, and display the page corresponding to the class in the last version found.

But the thing is, for a unknown reason, when I try to access /classes/nameOfAClass, it doesn't evaluate the second route, unless I enter complete bullshit for the name of the class. If I give a name of a correct class, it goes immediately in the third route (even if I only give it only ONE parameter), and gives the last version number of the software for the :version parameter, and tries to resolve /classes/lastVersion/nameOfTheClass.

Do you have any idea why it just ignores the second route with only one parameter and goes directly to the third route, giving automatically a valid version number?

EDITED -> more code now

To help you help me, here's some additional infos about the app: At the root of my projet, I have the file server.js, which declares:

var app = require('./app');

Inside app.js file, I have:

var express  = require('express');
var app      = express();

app.use(compress());

// Serve static files (css, js, images)
app.use(express.static('public'));
app.set('view engine', 'jade');
app.set('views', './views');
app.set('view cache', true);

//require all routes, index.js is called by default
require('./scripts/router')(app);

module.exports = app;

Before you ask yourself "why the heck did he do that": that architecture (server file that require app file that itself declares the express app) is needed on the platform I'll have to deploy my app on. Let's continue further in the architecture.

You surely have notices the line require('./scripts/router')(app);. In this router folder, I have a single file called "index.js" and a folder named "routes"; this folder contains all my subroutes. The index.js file is as follow:

module.exports = function (app) {
    // require several subroutes as follow
    app.use('/classes', require('./routes/classes'));
    [...other subroutes...]

    // ERRORS
    // Handle 404
    app.use(function (error, req) {
        req.status(404);
        req.render('errorpages/404.jade', {});
    });

    // Handle 500
    app.use(function (error, req, res, next) {
        res.status(500);
        res.render('errorpages/500.jade', {});
    });
};

So, in resume:

myProjectRooT
|_ server.js
|_ app.js
|_ scripts/
    |_ router/
        |_ index.js
        |_ routes/
            |_ classes.js
            |_ otherRoute.js
            |_ etc...

Hope that's helping to understand the problem :-)

NEW INFO

HEY! Did you think the problem was strange? Well it got even stranger! As user kanzelm suggested, I console.logged everything (meaning: at the beginning of each route, I do console.log('nameOfTheRoute');), and some results were totally unexpected:

  1. localhost:3000/classes: logs /:version and directly goes to localhost:/classes/lastVersionNumber; that's really unexpected
  2. localhost:3000/classes/aValidVersionNumber: logs /:version, and goes to the correct version page; that's normal
  3. localhost:3000/classes/aNotValidVersionNumber: logs /:version, and looks for a class with the name of the not-valid version number, fails, then redirect to the 404 class not found page; that's normal
  4. localhost:3000/classes/aNotValidClassName: log /:version, looks for a class with this name, fails, and redirect to the 404 class not found page; that's normal
  5. localhost:3000/classes/aValidVersionNumber/aValidClassName: logs /:version/:className and goes to the correct class page; that's normal
  6. localhost:3000/classes/aValidVersionNumber/aNotValidClassName: logs /:version/:className and goes to the 404 class not found page; that's normal

So, here I have two major problems I can't even understand: first, the root route is totally ignored and is never catched, even when I try to go to locahost:3000/classes; it seems that the url is auto-completed with the last valid version number. In theory, that's what I want (look at the code of the very first route), but no console.log from the route, neither from the method classesGetLastVersion, are shown. Secondly, the fact that the route /:version is catched only when a valid version number is given (which is totally fine) or when a NON-VALID version number/class name is given (which is totally not ok at all) is driving me mad.

Any idea?

like image 976
TheFrenchieCake Avatar asked Nov 10 '22 09:11

TheFrenchieCake


1 Answers

Sooo, the problem was... THE CACHE.

I created, as a colleague advised me, dummy files to simulate a new version, and a new set of classes to be searched on the app. Then I tried to go to localhost:3000/classes, and at my big surprise, I was still going into the same version number than before, meaning "2.1" even if I created a false "2.2" version. Emptying the cache with Ctrl+F5 wasn't doing anything, though. So I checked the "disable cache when developer tools box is open" option in the developer tools page of my browser, and then everything went perfectly fine. Every route were catched correctly and were doing their respective job, as expected.

The solution to bypass the cache has been given to me by user Dan Pantry: put Cache-Control: 0 into the header of the express response of the different routes.

like image 54
TheFrenchieCake Avatar answered Nov 14 '22 21:11

TheFrenchieCake