Node.js tips record some problems encountered in the work or “Nodejs technology stack” communication group, sometimes behind a small problem can also extend a lot of new knowledge points, the process of solving the problem and summarizing itself is a process of growth, here to share the growth with you.

This question was raised by a student in the exchange group when a student encountered a “cross-device link not permitted” as it happened in her previous job. I wanted to help others in need.

1: Start the Node.js service

Start a Node.js service and specify the route /upload/image. Upon receiving the Request, the uploadImageHandler method is called, passing in the Request object.

const http = require('http');
const formidable = require('formidable');
const fs = require('fs');
const fsPromises = fs.promises;
const path = require('path');
const PORT = process.env.PORT || 3000;
const server = http.createServer(async (req, res) => {
  if (req.url === '/upload/image' &&  req.method.toLocaleLowerCase() === 'post') {
    uploadImageHandler(req, res);
  } else {
  	res.setHeader('statusCode'.404);
  	res.end('Not found! ')}}); server.listen(PORT,() = > {
  console.log(`server is listening at ${server.address().port}`);
});
Copy the code

Two: processing image objects

Formidable is an NPM module for processing uploaded files, images and other data, and form.parse is a callback to Promise for easy processing.

Tips: Join method of path module is used to join paths, which will join multiple path parameters we passed in. Because symbols used by different systems such as Linux and Windows are different, this method will transform itself according to the system.

const uploadImageHandler = async (req, res) => {
  const form = new formidable.IncomingForm({ multiples: true });		
  form.encoding = 'utf-8';		
  form.maxFieldsSize = 1024 * 5;		
  form.keepExtensions = true;

  try {
    const { file } = await new Promise((resolve, reject) = > {		
      form.parse(req, (err, fields, file) = > {		
        if (err) {		
          return reject(err);		
        }

         return resolve({ fields, file });		
      });		
    });
    const { name: filename, path: sourcePath } = file.img;
    const destPath = path.join(__dirname, filename);
    console.log(`sourcePath: ${sourcePath}. destPath: ${destPath}`);
    await mv(sourcePath, destPath);
    console.log(`File ${filename} write success.`);
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ code: 'SUCCESS'.message: `Upload success.`}));
  } catch (err) {
    console.error(`Move file failed with message: ${err.message}`);
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ code: 'ERROR'.message: `${err.message}`})); }}Copy the code

Three: realize mv method

Fs. rename Renames a file

An easy way to rename an uploaded image to a local destination path is to use the FS module’s rename(sourcePath, destPath) method, which asynchronously renames the sourcePath file, as shown below:

const mv = async (sourcePath, destPath) => {
	return fsPromises.rename(sourcePath, destPath);
};
Copy the code

cross-device link not permitted

If fs.rename() is permitted, cross-device link not permitted

**EXDEV **oldpath and newpath are not on the same mounted filesystem.  (Linux permits a filesystem to be mounted at multiple points, but rename() does not work across different mount points, even if the same filesystem is mounted on both.)

OldPath and newPath are not on the same mounted file system. (Linux allows a file system to be mounted to multiple points, but Rename () cannot work across different mount points, even if the same file system is mounted on two.)

This problem also occurs on Windows. For details, see Errorco. De /win32/winer…

winerror.h 0x80070011 #define ERROR_NOT_SAME_DEVICE The system cannot move the file to a different disk The system cannot move files to different disk drives.

A friend asked this question on the “Nodejs Stack Communication Group” before, and this is replayed on Windows, because the default directory for uploading files using formidable is the default operating system directory os.tmpdir(), which corresponds to drive C on my computer. If fs.rename() is used to rename disk F, the following error occurs:

C:\Users\ADMINI~1\AppData\Local\Temp\upload_3cc33e9403930347b89ea47e4045b940 F:\study\test\202366
[Error: EXDEV: cross-device link not permitted, rename 'C:\Users\ADMINI~1\AppData\Local\Temp\upload_3cc33e9403930347b89ea47e4045b940' -> 'F:\study\test\202366'] {
  errno: -4037.code: 'EXDEV'.syscall: 'rename'.path: 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\upload_3cc33e9403930347b89ea47e4045b940'.dest: 'F:\\study\\test\\202366'
}
Copy the code

Set the source path and destination path to the same disk partition

Set the temporary path of the file upload middleware to the disk partition where the file is finally written. For example, we saved the image in drive F during the Windows test, so set the uploadDir attribute of the form object formidable to drive F, as shown below:

const form = new formidable.IncomingForm({ multiples: true });		
form.uploadDir = 'F:\\'
form.parse(req, (err, fields, file) = >{... });Copy the code

This approach has some limitations. What if you write to a different disk space?

You can see this way down here.

Read – write – Delete temporary files

One possible way is to read the temporary file and write it to a new location, and finally delete the temporary file. So the following code creates a readable stream and a writable stream object, uses pipe to pipe data to the new location, and finally calls the UNlink method of the FS module to delete the temporary file.

const mv = async (sourcePath, destPath) => {
  try {
    await fsPromises.rename(sourcePath, destPath);
  } catch (error) {
    if (error.code === 'EXDEV') {
      const readStream = fs.createReadStream(sourcePath);		
      const writeStream = fs.createWriteStream(destPath);
      return new Promise((resolve, reject) = > {
        readStream.pipe(writeStream);
        readStream.on('end', onClose);
        readStream.on('error', onError);
        async function onClose() {
          await fsPromises.unlink(sourcePath);
          resolve();
        }
        function onError(err) {
          console.error(`File write failed with message: ${err.message}`);		
          writeStream.close();
          reject(err)
        }
      })
    }

    throwerror; }}Copy the code

4: test

Method 1: Call from a terminal

curl --location --request POST 'localhost:3000/upload/image' \
--form 'img=@/Users/Downloads/ Mayjun.jpeg '
Copy the code

Method 2: POSTMAN call

Reference

  • Github.com/andrewrk/no…
  • Stackoverflow.com/questions/4…
  • Nodejs.org/api/fs.html…