美文网首页
React + Node 上传图片踩过的坑 (1)

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

作者: SunnyEver0 | 来源:发表于2018-07-28 15:09 被阅读21次

背景

最近在做一个上传图片的需求,涉及到了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

  • 检测request header
    其实后面一想是很好解释的,就是JSON parse出错了,但当时被以前的做过的产品冲晕了头脑,因为以前一报这个错,一般是由于传的auth token出错,所以一股脑儿地去寻找header是不是传错了。对header进行了校验,发现没毛病。于是发现问题没这么简单,开始一步一步的跟踪了。正想跟踪的时候,当天宣布停电。。只能明天继续跟踪了。
  • 检测url地址
    首先url抓取了一下,为“http://localhost:4000/api/upload?”,咦,这个api路径怎么跟我之前在App端调试的不相同呢,这个不就是一个原始url地址吗,上传到这里肯定会出错的,于是打印了之前在browser端fetch 的 url的地址,结果发现,都是这一个套路,都是/browser url/+/api/+/route/。没问题的,老铁,,那哪里出错了呢?只有继续跟。
  • 检测地址转换是否出错
    由于之前对于site端还不太熟悉,不太清楚browser端怎么和node端交互的,proxy怎样做转发的。只有继续看代码。深层次的看了一遍代码,并寻求了Jay的帮助后,对site端browser和node之前的交互心中大致有了一个方向。大致流程为:
    site这边请求的url会在proxy端截取后转换为真正的url请求,并重新转给node端进行fetch
    那在转给node之前,url的地址是否转换正确呢?跟踪了一下,竟然发现请求并没有进我们的action方法中...就意味着根本没有进入我们的proxy被截取,那请求去哪儿???黑人问号
    where is req?

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

  • content-type browser && node
    由于沿用了之前老版的processHeader函数,这里面有这样一段代码:
   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)

相关文章

网友评论

      本文标题:React + Node 上传图片踩过的坑 (1)

      本文链接:https://www.haomeiwen.com/subject/pyznmftx.html