美文网首页
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