In this tutorial, we discuss how to upload binary files using FormData and XMLHttpRequest, and parsing multipart/form-data requests server side using Multer. This method is one of several other possible methods to upload binary data.
In our code, once a user choses an image in AddRecipe, an UPLOAD_REQUEST action will be dispatched, and handled by AddRecipePageSagas.js as follows:
function* uploadRequest(action) {
console.log('uploadRequest=', action);
const channel = yield call(_createUploadFileChannel, '/api/upload/file', action.payload);
const result = yield take(channel);
if (result.success)
action.payload.imageURL = result.response.url.replace(/\\/g,"/");
yield put({type: action.type_success, payload: {...action}});
if (result.err)
yield put({type: action.type_failure, payload: {file: action.payload, err: result.err}});
}
The saga calls _createUploadFileChannel function which returns a channel created specifically for the received event, then it will retrieve the response from it once it done sending the data and receives a response.
If the result is successful, it will dispatch a sucess action, otherwise a failure action.
function _createUploadFileChannel(endpoint, file) {
return eventChannel(emitter => {
const xhr = new XMLHttpRequest();
xhr.responseType = "json";
const onFailure = (e) => {
emitter({err: new Error('Upload failed')});
emitter(END);
};
xhr.upload.addEventListener("error", onFailure);
xhr.upload.addEventListener("abort", onFailure);
xhr.onreadystatechange = () => {
const {readyState, status} = xhr;
if (readyState === 4) {
if (status === 200) {
emitter({success: true, response: xhr.response});
emitter(END);
} else {
onFailure(null);
}
}
};
xhr.open("POST", endpoint, true);
let formData = new FormData();
formData.append('filename', file.filename);
formData.append('buffer', file.data);
xhr.send(formData);
return () => {
xhr.upload.removeEventListener("error", onFailure);
xhr.upload.removeEventListener("abort", onFailure);
xhr.onreadystatechange = null;
xhr.abort();
};
},
buffers.sliding(2));
}
This helper function returns an arrow function as a result of its execusion. Its main purpose is to create a configured instance following the received parameters as follows:
function* AddRecipePageSagas() {
yield takeEvery(uploadConstants.UPLOAD_REQUEST, uploadRequest);
}
let Recipe = require(MODEL_PATH + 'Recipe');
let upload = require('multer')();
let base64Img = require('base64-img');
let generateSafeId = require('generate-safe-id');
module.exports = (app) => {
.
.
.
app.post('/api/upload/file', upload.any(), function (req, res, next) {
console.log("app.post('/api/upload/file')");
let baseURL = 'http://132.72.46.150:8080/';
let filename = generateSafeId();
let relativePath = "server/public/";
let absfilepath = base64Img.imgSync(req.body.buffer, relativePath + 'images/', filename);
let correctPath = absfilepath.substr(relativePath.length, absfilepath.length);
//let img_url = new URL(correctPath, baseURL);
let img_url = baseURL + correctPath;
res.json({url: img_url});
res.end();
});
.
.
.
}
The flow at server is as follows:
This tutorial provided the basic details of the following elements:
Created: August 5th, 2018 Updated: August 5th, 2018