React + Node 上传图片踩过的坑 (1)

2018-07-28  本文已影响21人  SunnyEver0

背景

最近在做一个上传图片的需求,涉及到了App(React Native)和Site(React + Node SSR)两端。由于本人对App开发较为熟悉,在做项目的时候,也是App开发先行。所以最初在App端简单封装了一下fetch,包装了一下FormData即校验通过,部分代码如下:

  //纯粹的request层
  const request = (url, opts, responseCallback, exceptionCallback) => {
    return new Promise((resolve, reject) => {
      fetch(url, opts)
        .then(async response => {
          typeof responseCallback === 'function' && (await responseCallback(response));
          return response.json();
        })
        .then(res => resolve(res))
        .catch(async err => {
          typeof exceptionCallback === 'function' && (await exceptionCallback(err));
          reject(err);
        });
    });
  };

  async uploadFile(route, filePath, fileName) {
    let fullRoute = getFullRoute(route);

    if (!fileName) fileName = getFileNameWithPath(filePath);
    let formData = new FormData();
    let file = { uri: filePath, type: 'multipart/form-data', name: fileName };
    formData.append('file', file);

    let headers = (await processHeader(route)) || {};
    headers['Content-Type'] = 'multipart/form-data';

    let opts = { headers, method: 'POST', body: formData };

    const responseHandler = response => getResponseCallback(fullRoute, opts, response);
    const exceptionHandler = err => processException(err, { url: fullRoute, opts: opts, params: null });
    return request(fullRoute, opts, responseHandler, exceptionHandler);
  }

以为site端,就这样包装一下,也能直接通过。在自己本地撘的node服务器(express + multer)试了一下,通过了,上传图片完毕。感觉很美好,于是乐观地开始了site的上传文件之路。

本地node服务器上传

踩坑之路

1.browsernode的交互

没想到一开始就报错了,一段报错完全摸不着头脑
SyntaxError: Unexpected token \ in JSON at position

2.route handler报500

router.all('*', handler);
app.all('*', subdomain('*', router));

async function handler(req, res, next) {
  console.log(req.body)
}

在server中的handler进行跟踪,发现传递过来的req.url 报500。这就很懵逼了,由于自己以前对node不太熟悉,捉鬼已经不是我们目前能搞定的事,即使要搞定也需耗费大量的时间,项目即将提测,于是再次寻求Jay的帮助,帮忙捉鬼。到底是哪里出错了。。。在Jay秀了一波操作后,发现了一个很无语的问题,在app处理subdomian中发现了一个错,JSON.prase error

   if (typeof Headers !== 'undefined') {
      this.headers = new Headers();
      this.headers.append('content-type', 'application/json');
      this.headers.append('accept', 'application/json');
      this.headers.append('X-Login-Status', customerTypeToNumber);
      return;
    }
    this.headers['content-type'] = 'application/json';
    this.headers['accept'] = 'application/json';
    Object.assign(this.headers, { 'X-Login-Status': customerTypeToNumber });

卧槽,,,这里默认将content-typeaccept都设置为了application/json。所以在上传时,默认转换为json格式,但formData不干了。。
同样在node中也干了这个事,更有甚,将其配置在了配置文件中,无奈只有重新写一个if else判断一下,假如route
uploadImage则不使用默认配置。

    www: {
      headers: {
        'content-type': 'application/json',
        accept: 'application/json',
        Authorization: 'Bearer sS43Jvbg7ofw1C5U487dduM1zdabGuOFmBJxp6ew'
      }
    },

终于把之前第一个问题报错的原因找到了,以为可以满心欢喜地走下去调通早点下班,结果其它的问题又来了。

3.where is formdata body?

我们将node传递回来的req参数,取出body,使用fetch上传。但后台一直报错:
socket hang up
证明这个body并不是我们想要的formdata,我们需要把formData从这个req中解救出来,在与Jay讨论以后,决定使用之前推荐过的库formidable进行parse。

4.安装formidable库后打包报错

npm install formidable --save 执行命令之后,我们require到以前ajax.js的uploadFile中,但打包一直报错,由于很久没有开发web,在浏览器中使用了require语法,但这个是node或者rn的语法,在浏览器中并没有进行配置,或者引入相关require js的库,造成了require undefined的错误。在向Jay请教以后,在site端开发,特别是这种SSR的模式,需要更加注意node与browser的分离,且ajax需要做得纯粹,就只是一层网络请求,不需要去额外处理参数。

5.node FormData

在node环境下,是没有FormData类的,看了一下node-fetch的语法,可以直接传入file stream到body,但由于后端需要filename参数,直接传入stream还是会报'socket hang up'的错误。于是还是需要包装一层FormData进行上传,看到项目已经有form-data的库,引入使用:

const fs = require('fs');
const path = require('path');
const FormData = require('form-data');
const formidable = require('formidable');

const getFormDataWithReq = req => {
  return new Promise((resolve, reject) => {
    let form = new formidable.IncomingForm();
    form.parse(req, function(err, fields, files) {
      if (err) return reject(err);
      let formData = new FormData();
      // 从path取出 readable stream
      const oldPath = files.file.path;
      let file = fs.createReadStream(oldPath);
      formData.append('file', file);
      formData.append('fileName', files.file.name || DEFAULT_UPLOAD_NAME);
     // let formdata = {
        //file: file,
        //fileName: files.file.name
      //};
      resolve(formdata);

    });
  });
};

但上传之后报错,后端返回数据:

{FileName: null, Result: "InputFileFormatError", MsgList: null}

打印formdata:

FormData {
  _overheadLength: 302,
  _valueLength: 21,
  _valuesToMeasure:
   [ ReadStream {
       _readableState: [Object],
       readable: true,
       domain: null,
       _events: [Object],
       _eventsCount: 3,
       _maxListeners: undefined,
       path: '/var/folders/5m/lkn7wz3x1h152frh4lnv4klr0000gn/T/upload_e0245d26befa61f640994923bddc0b29',
       fd: null,
       flags: 'r',
       mode: 438,
       start: undefined,
       end: undefined,
       autoClose: true,
       pos: undefined,
       bytesRead: 0,
       emit: [Function] } ],
  writable: false,
  readable: true,
  dataSize: 0,
  maxDataSize: 2097152,
  pauseStreams: true,
  _released: false,
  _streams:
   [ '----------------------------019502177798059283796474\r\nContent-Disposition: form-data; name="file"; filename="upload_e0245d26befa61f640994923bddc0b29"\r\nContent-Type: application/octet-stream\r\n\r\n',
     DelayedStream {
       source: [Object],
       dataSize: 0,
       maxDataSize: Infinity,
       pauseStream: true,
       _maxDataSizeExceeded: false,
       _released: false,
       _bufferedEvents: [Array],
       _events: [Object],
       _eventsCount: 1 },
     [Function: bound ],
     '----------------------------019502177798059283796474\r\nContent-Disposition: form-data; name="fileName"\r\n\r\n',
     '确认收货_Site.png',
     [Function: bound ] ],
  _currentStream: null,
  _boundary: '--------------------------019502177798059283796474' 
}

看情况 formdata中 DelayedStream 中的dataSize为0,证明取file stream时传入的path不对?还是这种取文件的方式有问题?目前还在持续跟踪中。。。

期间遭遇了两次宕机,一次停电维护,很多服务挂了。一次登录登不上,又持续了一上午。
但终究原因还是自己对node及服务器SSR渲染这一块儿的不熟悉导致,学习之路永不停息。

2018-8-2更新:

formdata中 DelayedStream 中的dataSize为0,并没有什么影响。问题的原因还是formdata中的content-type boundary不一致导致的。详情见React + Node 上传图片踩过的坑 (2)

上一篇 下一篇

猜你喜欢

热点阅读