Node Express + firebase-admin 服务器 - POST 中的 firebase 数据库参考在 GET 中调用代码 - 为什么?
Node Express + firebase-admin server - firebase database reference in POST calling code in GET - why?
我花了很多时间调试这个问题,我得出的结论是它与我在 POST 和 express 服务器的 GET 路由中使用的 firebase 数据库引用有关。以下是完整的代码。我的前端调用的第一个路由是 router.get('/get-images', function(req, res) ...
请注意,在回调函数中我首先设置了引用 firebaseApp.database().ref(/user_profile_images/${userId})
。此代码从头到尾运行良好。
但是后来我调用了 POST 路由 router.post('/add-images', upload.single('userImage'), function(req, res) ...
,这就是事情开始变得非常奇怪的地方 - 一旦代码执行到了使用 Firebase 引用的地步 firebaseApp.database().ref(
user_profile_images/${userId}).push()...
似乎在这一点上调用了之前在 GET 路由中首次实现的回调函数,这就是我完全不知道的地方到底是怎么回事。
以下是我的完整服务器代码,然后是示例输出,其中我设置了 console.logs 到 'map' 函数中的每个步骤 - 注意当行从 ADD IMAGES 部分更改时的输出5 到 GET IMAGES 第 1 部分 - 这是来自 POST 的执行代码似乎在 GET 路由
中调用参考回调中的代码的要点
ADD IMAGES part 4
ADD IMAGES part 5
GET IMAGES part 1
GET IMAGES part 2
完整服务器代码(减去有效 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...');
这是我代码中每个 console.log() 的命令行输出:
//////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
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)
重要说明:如果我从未在我的应用程序的生命周期中调用 GET 路由,那么 POST 路由会正常执行直至完成....
问题是这个位:
firebaseApp.database().ref(`user_profile_images/${userId}`).on(
'value',
您正在为图像的更改注册一个侦听器,但从未删除它。最初将使用当前值调用侦听器,但随后每次值更改时都会调用它。您的 POST 处理程序更改了值以触发事件。
要解决此问题,请使用 once
而不是 on
。
我花了很多时间调试这个问题,我得出的结论是它与我在 POST 和 express 服务器的 GET 路由中使用的 firebase 数据库引用有关。以下是完整的代码。我的前端调用的第一个路由是 router.get('/get-images', function(req, res) ...
请注意,在回调函数中我首先设置了引用 firebaseApp.database().ref(/user_profile_images/${userId})
。此代码从头到尾运行良好。
但是后来我调用了 POST 路由 router.post('/add-images', upload.single('userImage'), function(req, res) ...
,这就是事情开始变得非常奇怪的地方 - 一旦代码执行到了使用 Firebase 引用的地步 firebaseApp.database().ref(
user_profile_images/${userId}).push()...
似乎在这一点上调用了之前在 GET 路由中首次实现的回调函数,这就是我完全不知道的地方到底是怎么回事。
以下是我的完整服务器代码,然后是示例输出,其中我设置了 console.logs 到 'map' 函数中的每个步骤 - 注意当行从 ADD IMAGES 部分更改时的输出5 到 GET IMAGES 第 1 部分 - 这是来自 POST 的执行代码似乎在 GET 路由
中调用参考回调中的代码的要点ADD IMAGES part 4
ADD IMAGES part 5
GET IMAGES part 1
GET IMAGES part 2
完整服务器代码(减去有效 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...');
这是我代码中每个 console.log() 的命令行输出:
//////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
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)
重要说明:如果我从未在我的应用程序的生命周期中调用 GET 路由,那么 POST 路由会正常执行直至完成....
问题是这个位:
firebaseApp.database().ref(`user_profile_images/${userId}`).on(
'value',
您正在为图像的更改注册一个侦听器,但从未删除它。最初将使用当前值调用侦听器,但随后每次值更改时都会调用它。您的 POST 处理程序更改了值以触发事件。
要解决此问题,请使用 once
而不是 on
。