前言

一般地,文件上传有几种方式,第一种就是同步文件上传,可以使用传统的表单提交的方式,第二种就是异步文件上传的方式,如果要使用Ajax提交的话,对于一般地表单可以直接使用序列化的方式直接提交,但是对于文件格式只能使用H5的APIFormData进行文件上传,但是对于一些旧的浏览器来说,并不支持H5,那么如何在旧的浏览器中实现文件的异步上传呢?这就是我们今天要说的一种解决方案,使用iframe伪装异步文件上传。

原理

对于表单元素form,它具有一些如下的常见属性:

  • name规定表单的名字
  • enctype规定表单的编码方式
  • method规定表单的发送数据方法
  • action规定发送的URL地址
  • target规定在何处打开action URL

然后我们在和表单的同一页面下,增加一个隐藏的iframe元素,让表单的target指向这个隐藏的iframe,跳转后的页面还是当前页面,就像没有刷新一样,然后后台返回的数据当前页面是iframe,只需要获取iframeparent.document即可获取原先的页面,就可以很方便地写入后台返回的信息了。

实现

前端页面iframe.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>iframe 文件上传</title>
</head>
<body>
    <h2 id='info'></h2>
    <form action='/uploadFile' method='POST' target='iframe' enctype='multipart/form-data'>
        <!-- 通过tartget设置表单提交后跳转的地方-->
        <input type='file' name='pic' />
        <input type='submit' value='提交' />
    </form>
    <iframe id="iframe" name='iframe' width='0px' height='0px' frameborder='0'>
    </iframe>
    <!-- 将iframe的高宽,边框都设置为0,相当于隐藏了-->
    <script type="text/javascript">
    	var iframe = document.getElementById("iframe");
    	var info = document.getElementById("info");
        //Firefox/Opera/Safari中是iframe onload事件
        //在IE下,是iframe onreadystatechange事件 
    	iframe.onload = iframe.onreadystatechange = function(){
    		if (this.readyState && this.readyState != "complete") return;
    		else {
                 //获取iframe里面的内容
                 var responseText = iframe.contentDocument.body.textContent;
                 //上传完成后的处理
                 if(responseText!= ""){
                     info.innerHTML = responseText
                 }
             }
    	}
    </script>
</body>
</html>

后端Nodejs版本:

var http = require('http'),
    fs = require("fs"),
    formidable = require('formidable'),
    port = 8080

http.createServer(function(req, res) {
  res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
  if (req.url == '/uploadFile') {              //上传
    var form = new formidable.IncomingForm(),
        files = [],
        fields = [];

    form.uploadDir = 'upload/';

    form
      .on('field', function(field, value) {
        fields.push([field, value]);
      })
      .on('file', function(field, file) {
        files.push([field, file]);
        fs.renameSync(file.path, 'upload/'+file.name);
      })
      .on('end', function() {
        res.writeHead(200, {'content-type': 'text/plain'});
        res.end("上传成功");
      });
    form.parse(req, function(err, fields, files) {
        err && console.log('formidabel error : ' + err); 
    });  
  }else if(req.url == '/iframe.html'){   //发送html页面
    fs.readFile('iframe.html', function (err, data) {
      if (err) {
         console.log(err);
         // HTTP 状态码: 404 : NOT FOUND
         // Content Type: text/plain
         res.writeHead(404, {'Content-Type': 'text/html'});
      }else{             
         // HTTP 状态码: 200 : OK
         // Content Type: text/plain
         res.writeHead(200, {'Content-Type': 'text/html'});    
         
         // 响应文件内容
         res.write(data.toString());        
      }
      //  发送响应数据
      res.end();
   });   
  } else {
    res.writeHead(404, {'content-type': 'text/plain'});
    res.end('404');
  }
}).listen(port);
console.log('listening on http://localhost:'+port+'/');

跨域上传

上面的做法还是仅限于同域的情况,下面我们一起来学习下跨域情况下,该如何使用iframe来上传文件。

这时候我们还需要一个前端同域的中转页面,依然是前台提交请求后跳转到隐藏的iframe,但是这次会带有一个参数,告诉后台需要重定向的地址,也就是我们的中转页面,后台重定向这个中转页面后,还会把上传后的消息通过一个参数附加在URL中。重定向的中转页面会在iframe中显示,然后在中转页面中通过parent.document获取上传页面的document元素,就可以实现跨越上传了:

跨域上传的iframeCors.html基本代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>iframe 文件上传</title>
</head>
<body>
    <h2 id='info'></h2>
    <form action='http://127.0.0.1:8081/uploadFile?cb=http://127.0.0.1:8080/proxy.html' method='POST' target='iframe' enctype='multipart/form-data'>
        <!-- 通过tartget设置表单提交后跳转的地方-->
        <input type='file' name='pic' />
        <input type='submit' value='提交' />
    </form>
    <iframe id="iframe" name='iframe' width='0px' height='0px' frameborder='0'>
    </iframe>
    <!-- 将iframe的高宽,边框都设置为0,相当于隐藏了-->
</body>
</html>

中转页面proxy.html基本代码:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>peoxy</title>
</head>
<body>
	proxy
	<script type="text/javascript">
		var mes = window.location.href.split("mes=")[1];
		parent.document.getElementById("info").innerHTML = mes
	</script>>
</body>
</html>

后台代码:

var http = require('http'),
    fs = require("fs"),
    formidable = require('formidable'),
    port = 8081
http.createServer(function(req, res) {
  if (req.url.indexOf('/uploadFile')>=0) {              //上传
    var form = new formidable.IncomingForm(),
        files = [],
        fields = [];

    form.uploadDir = 'upload/';
    form
      .on('field', function(field, value) {
        fields.push([field, value]);
      })
      .on('file', function(field, file) {
        files.push([field, file]);
        fs.renameSync(file.path, 'upload/'+file.name);
      })
      .on('end', function() {
        var proxy = req.url.split("cb=")[1]+"?mes=上传成功"
        res.writeHead(301, {'Location': proxy});
        res.end();
      });
    form.parse(req, function(err, fields, files) {
        err && console.log('formidabel error : ' + err); 
    });  
  }else {
    res.writeHead(404, {'content-type': 'text/plain'});
    res.end('404');
  }
}).listen(port);
console.log('listening on http://localhost:'+port+'/');