背景
最近在做一个上传图片的需求,涉及到了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的上传文件之路。
踩坑之路
1.browser
和node
的交互
没想到一开始就报错了,一段报错完全摸不着头脑
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-type
和 accept
都设置为了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)
网友评论