简体   繁体   中英

Node Express + firebase-admin server - firebase database reference in POST calling code in GET - why?

I have spent a lot of time debugging this issue and I have come to the conclusion that it is related to the firebase database reference I'm using in my POST and GET routes of my express server. Following is the complete code. The very first route that is called by my frontend is router.get('/get-images', function(req, res) ... Notice that in the callback function I first set up reference firebaseApp.database().ref(/user_profile_images/${userId}) . This code works fine from beginning to end.

However later on I call the POST route router.post('/add-images', upload.single('userImage'), function(req, res) ... and this is where things start getting really odd - once the code execution gets to the point where the Firebase reference is used firebaseApp.database().ref( user_profile_images/${userId} ).push()... it seems at this point the callback function first implemented in the GET route at some point before, is getting called, and this is where I'm totally ignorant as to what is going on.

Following is my complete server code followed by sample out put where I have setup console.logs to 'map' each step in the function - pay attention to the output when the lines change from ADD IMAGES part 5 to GET IMAGES part 1 - this is the point that execution code from the POST seems to call code in the reference callback in the GET route

ADD IMAGES part 4
ADD IMAGES part 5
GET IMAGES part 1
GET IMAGES part 2

Complete server code (minus valid keys/db names/etc):

let express = require('express');
let cors = require('cors');
let bodyParser = require('body-parser');
const uuidv4 = require('uuid/v4');
let base64Img = require('base64-img');
var firebaseAdmin = require("firebase-admin");
let cloudinary = require('cloudinary');
let multer = require('multer');
let UserProfileImage = require('./utils/UserProfileImage');
let _ = require('lodash');


/**
 *  ========================
 * firebase set up
 *
 * @type       {Function}
 */
var firebaseServiceAccount = require('./somekeyfile.json');

let firebaseApp = firebaseAdmin.initializeApp({
  credential: firebaseAdmin.credential.cert(firebaseServiceAccount),
  databaseURL: 'https://somedb.firebaseio.com/'
});
// ========================


/**
 * ========================
 * couldinary configuration
 */
cloudinary.config({ 
  cloud_name: 'somecloudname', 
  api_key: 'somekey', 
  api_secret: 'somesecret' 
});

const CLOUDINARY_UPLOAD_PRESET = 'veeezmct';
let port = process.env.PORT || 8080;
// ========================



// ROUTES FOR OUR API
// ===================================



// get an instance of the express Router
let router = express.Router();


// test route to make sure everything is working
// (accessed at POST http://localhost:8080/image-controller-api/upload)
let upload = multer({
    dest: 'uploads/',
    limits: {
        fileSize: 5 * 1024 * 1024 // no larger than 5mb, you can change as needed.
    }
});
router.post('/add-images', upload.single('userImage'), function(req, res) {

// console.log("\n########################\n########################\n########################\n");
console.log('ADD IMAGES');

// make sure there is a user id before continuing
if (req.body.userId === undefined || req.file === undefined) {
    return res.json({message: 'User ID and image file required to proceed'});
}

console.log('ADD IMAGES part 2');

// get the FB userId fromt he request body
const userId = req.body.userId;
const position = req.body.position;
const isPrimary = req.body.isPrimary;
let file = req.file;

console.log('ADD IMAGES part 3');

let uploadType = 'authenticated';
cloudinary.v2.uploader.upload(
    req.file.path,
    {
        upload_preset: CLOUDINARY_UPLOAD_PRESET,
        type: uploadType,
        sign_url: true,
        folder: userId
    },
    function(error, result) {

        console.log('ADD IMAGES part 4');

        // need to save the image url in the firebase
        // user record here - create a new user profile image
        //let ref = firebaseApp.database().ref(`user_profile_images/${userId}`);
        let userProfileImage = new UserProfileImage();
        userProfileImage.resourceId = result.public_id;
        userProfileImage.userId = userId;
        userProfileImage.url = result.secure_url;
        userProfileImage.position = position;
        userProfileImage.isPrimary = isPrimary;
        userProfileImage.isPrivate = "false";

        console.log('ADD IMAGES part 5');


        // use 'child' and 'set' combination to save data
        // in your own generated key
        firebaseApp.database().ref(`user_profile_images/${userId}`)
            .push().update(userProfileImage).then(function(ref1) {
                console.log('ADD IMAGES part 6');

                base64Img.requestBase64(
                    result.secure_url,
                    function(err, messageRes, body) {
                        console.log('ADD IMAGES part 7');

                        if (!err) {
                            console.log('SUCCESS ENCODING IMAGE');
                            return res.json({imageData: body});
                        } else {
                            console.log('ERROR ENCODING IMAGE');
                            return res.json({error: err});
                        }

                    }
                );


        }, function(error) {
            console.log('ERROR AT END', error);
        });

    }
);

});


/**
 * get user profile images by user ID
 **/
router.get('/get-images', function(req, res) {

console.log("\n///////////////////////////////////////\n///////////////////////////////////////\n///////////////////////////////////////\n///////////////////////////////////////\n");

console.log('GET IMAGES');

let userId = req.query.userId;
firebaseApp.database().ref(`user_profile_images/${userId}`).on(
        'value',
        snapshot => {

            console.log('GET IMAGES part 1');

            // if we have valid objects to iterate through
            if (snapshot.val()) {
                console.log('GET IMAGES part 2');

                // iterate through the objects to get each image url
                // and base64 representation

                // get the userProfileImages object list
                let userProfileImages = snapshot.val();
                // flag to determine how many requests have been completed
                let completeRequests = 0;
                // the total number of request to make which is the total
                // number of userProfileImages to process
                let numberOfRequestsToMake = Object.keys(userProfileImages).length;

                // iterate through each of the user profile images
                console.log('GET IMAGES part 3');
                _.map(userProfileImages, (userProfileImage, key) => {

                    console.log('GET IMAGES LOOP ', completeRequests);
                    completeRequests++;

                    if (completeRequests === numberOfRequestsToMake) {
                        console.log('GET IMAGES part 4');
                        console.log('# of requests complete, return to client');
                        return res.json({ userProfileImages: userProfileImages });
                    }

                }); // end _.map

            } else {
                console.log('ok sending default empty array');
                return res.json({userProfileImages: {}});
            }

        },
        errorObject => {
            console.log('base64Img.requestBase64 result error');
            return res.json({error: error});
        }
    );

});


// set up the server
let app = express();

app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());


// REGISTER OUR ROUTES
// all of our routes will be prefixed with /image-controller-api
app.use('/image-controller-api', router);


// START THE SERVER
app.listen(port);
console.log('Listening...');

Here is the command line output from each of the console.log() in my code:

//////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////

GET IMAGES
GET IMAGES part 1
GET IMAGES part 2
GET IMAGES part 3
GET IMAGES LOOP  0
GET IMAGES LOOP  1
GET IMAGES LOOP  2
GET IMAGES LOOP  3
GET IMAGES LOOP  4
GET IMAGES part 4
# of requests complete, return to client
ADD IMAGES
ADD IMAGES part 2
ADD IMAGES part 3
ADD IMAGES part 4
ADD IMAGES part 5
GET IMAGES part 1
GET IMAGES part 2
GET IMAGES part 3
GET IMAGES LOOP  0
GET IMAGES LOOP  1
GET IMAGES LOOP  2
GET IMAGES LOOP  3
GET IMAGES LOOP  4
GET IMAGES LOOP  5
GET IMAGES part 4
# of requests complete, return to client
FIREBASE WARNING: Exception was thrown by user callback. Error: Can't set headers after they are sent.
at ServerResponse.setHeader (_http_outgoing.js:371:11)

Important note: IF I never call the GET route in the lifecyle of my app, then the POST route executes fine to completion....

The problem is this bit:

firebaseApp.database().ref(`user_profile_images/${userId}`).on(
        'value',

You're registering a listener for changes to the images but never removing it. The listener will initially be called with the current value but it will then be subsequently called every time the value changes. Your POST handler changes the value so that triggers the event.

To fix this problem use once instead of on .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM