文件上传实现
标签(空格分隔): fileupload ajax php java
概述
文件上传是最古老的互联网操作之一。
最早的HTTP POST是不支持文件上传的,给编程开发带来很多问题。但是在1995年,ietf出台了rfc1867,也就是《RFC 1867 -Form-based File Upload in HTML》,用以支持文件上传。所以Content-Type的类型扩充了multipart/form-data用以支持向服务器发送二进制数据。因此发送post请求时候,表单属性enctype共有二个值可选,这个属性管理的是表单的MIME编码:
① application/x-www-form-urlencoded(默认值)
② multipart/form-data
其实form表单在你不写enctype属性时,也默认为其添加了enctype属性值,默认值是enctype=”application/x- www-form-urlencoded”.
传统形式
ajax出现之前,只能通过最传统的form表单,form表单配置enctype并包含input标签。如下:
<form id="upload-form" action="upload.php" method="post" enctype="multipart/form-data" >
<input type="file" id="upload" name="upfile" /> <br />
<input type="submit" value="upload" name="platform"/>
</form>
展现形式如下:
点击【Upload】按钮,即可将文件提交至upload.php.
ajax形式
之前文件已经讨论过,参见[论浏览器的局部刷新][2]
传输分析
通过Chrome的开发人员工具可以查看文件上传的网络传输情况,如下图:
浏览器发送了一个POST
请求,请求头中的Content-Type、Content-Length尤为重要,如下:
Content-Type:multipart/form-data; boundary=—-WebKitFormBoundary2WqHnU60gxoCkr4C
Content-Length: 288
请求体(body部分)如下:
——WebKitFormBoundary2WqHnU60gxoCkr4C
Content-Disposition: form-data; name=”upfile”; filename=””
Content-Type: application/octet-stream——WebKitFormBoundary2WqHnU60gxoCkr4C
Content-Disposition: form-data; name=”platform”upload
——WebKitFormBoundary2WqHnU60gxoCkr4C–
可以看出,content-type的multipart/form-data指示了当前post请求传输的是文件的二进制流,boundary指定了分隔符,body中的各字段直接通过boundary进行分割。在本例中,body中包含upfile、platform两个字段。
服务端收到前端POST
请求后,使用特殊的处理逻辑进行处理,下面我们分别介绍使用php和使用java来处理上传逻辑。
PHP处理
php处理文件上传相对来说比较简单,也是我见过处理起来最为简单的一个语言(接触后端语言有限,暂时作此评价)。只需要通过$_FILES
即可获取指定的上传文件,代码如下:
<html>
<head></head>
<body>
<form id="upload-form" action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" id="upload" name="upload">
<input type="submit" value="Upload">
</form>
<?php
if(is_uploaded_file($_FILES['upload']['tmp_name'])){
$upfile=$_FILES["upload"];
//获取数组里面的值
$name=$upfile["name"];//上传文件的文件名
$type=$upfile["type"];//上传文件的类型
$size=$upfile["size"];//上传文件的大小
$tmp_name=$upfile["tmp_name"];//上传文件的临时存放路径
echo "fileNames -> upload|".$name."|".$size;
echo "<br />"
}
?>
</body></html>
代码分析
- 通过$_FILES获取指定的文件对象;
- 通过系统函数
is_uploaded_file(file)
来判断文件是否是上传文件,确保而已的用户无法欺骗脚本去访问不能访问的文件,例如/etc/passwd;- 直接通过属性name、type、size即可获取文件的属性值。
总结
无需借助任何框架,直接通过系统代码即可实现文件上传的功能,怎一个“赞”字了得。当然,搭建一个完整的服务只靠一个php文件肯定是不行的,具体细节会另外具文讨论。
java处理
springMVC处理
java在servlet3.0之前,处理文件文件上传比较复杂,一般会借助第三方库,下面介绍通过springMVC + FileUpload 来实现文件上传。
package com.shushanfx.uploadspring;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;
/**
* Created by shushanfx on 2016/7/4.
*/
@Controller
public class UploadController {
@RequestMapping(value = "/upload.php", method = RequestMethod.POST)
public void upload(@RequestParam MultipartFile[] upload, HttpServletRequest req, HttpServletResponse resp) throws IOException {
List<String> fileNames = new LinkedList<String>();
if(upload!=null){
for(MultipartFile file : upload){
String fileName = file.getName();
if(fileName!=null){
fileNames.add(fileName + "|" + file.getOriginalFilename() + "|" + file.getSize());
file.transferTo(new File(file.getOriginalFilename()));
}
}
req.setAttribute("fileNames", fileNames);
}
upload(req, resp);
}
@RequestMapping(value="/upload.php", method = RequestMethod.GET)
public void upload(HttpServletRequest req, HttpServletResponse resp) throws IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html>");
out.println("<body>");
out.println("<form id=\"upload-form\" action=\"upload.php\" method=\"post\" enctype=\"multipart/form-data\">\n" +
" <input type=\"file\" id=\"upload\" name=\"upload\" />\n" +
" <input type=\"submit\" value=\"Upload\" />\n" +
"</form>");
if(req.getAttribute("fileNames")!=null){
out.println("fileNames -> ");
List<String> list = (List<String>) req.getAttribute("fileNames");
for(String item : list){
out.print(item + " ");
}
out.println("<br />");
}
out.println("</body>");
out.println("</html>");
}
}
代码分析
- 定义一个Controll使用注解的方式;
- 分别定义GET请求和POST请求的处理方式;
- POST请求通过MutipartFile[]参数获取;
- 解析Multipart参数
总结
使用springMVC的方式,java代码只是其中一部分,配置文件是其中一大部分。详细代码另外具文讨论。
servlet3.0原生态处理
在servlet3.0出现之后,事情就变得简单多了,我们可以通过如下代码完成文件上传。
package com.shushanfx.servlet30;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Created by shushanfx on 2016/7/4.
*/
@WebServlet(name = "uploadServlet", urlPatterns = "/upload.php",
loadOnStartup = 1, initParams = {
@WebInitParam(name = "name", value = "shushanfx提醒您<br />")
})
@MultipartConfig(
location = "upload", //文件存放路径,指定的目录必须存在,否则会抛异常
maxFileSize = 8388608, //最大上传文件大小,经测试应该是字节为单位
fileSizeThreshold = 819200, //当数据量大于该值时,内容将被写入文件。(specification中的解释的大概意思,不知道是不是指Buffer size),大小也是已字节单位
maxRequestSize = 8 * 1024 * 1024 * 6 //针对该 multipart/form-data 请求的最大数量,默认值为 -1,表示没有限制。以字节为单位。
)
public class UploadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
ServletConfig config = getServletConfig();
PrintWriter out = resp.getWriter();
out.println("<html>");
out.println("<body>");
out.println(config.getInitParameter("name"));
out.println("<form id=\"upload-form\" action=\"upload.php\" method=\"post\" enctype=\"multipart/form-data\">\n" +
" <input type=\"file\" id=\"upload\" name=\"upload\" />\n" +
" <input type=\"submit\" value=\"Upload\" />\n" +
"</form>");
if(req.getAttribute("fileNames")!=null){
out.println("fileNames -> ");
List<String> list = (List<String>) req.getAttribute("fileNames");
for(String item : list){
out.print(item + " ");
}
out.println("<br />");
}
out.println("</body>");
out.println("</html>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<String> fileNames = new LinkedList<String>();
req.setCharacterEncoding("UTF-8");
Collection<Part> parts = req.getParts();
//遍历所有的表单内容,将表单中的文件写入上传文件目录
for (Iterator<Part> iterator = parts.iterator(); iterator.hasNext();) {
Part part = iterator.next();
//从Part的content-disposition中提取上传文件的文件名
String fileName = part.getName();
if(fileName!=null){
fileNames.add(fileName + "|" + part.getSubmittedFileName() + "|" + part.getSize());
part.write(fileName);
}
}
req.setAttribute("fileNames", fileNames);
//显示上传的文件列表
doGet(req, resp);
}
}
代码分析
- 使用Servlet3.0的注解
@WebService
,实现Servlet的自动注册(妈妈再也不用担心你的web.xml配置).- 使用
MultipartConfig
注解,通知容器在处理POST请求之前解析BODY,分离文件和普通字段;- 通过
req.getParts()
获取文件上传列表;- 遍历列表,每个Part对象表示一个文件。具体API可以参考Part
总结
很显然,使用servlet3.0文件上传比使用spring上传要方便得多,如果有条件的话,可以考虑使用该方式。具体代码可以参考
参考文献:
- 文件上传:http://www.ruanyifeng.com/blog/2012/08/file_upload.html
- is_uploaded_file: http://www.w3school.com.cn/php/func_filesystem_is_uploaded_file.asp
- Servlet3.0新特性——文件上传接口: http://pisces-java.iteye.com/blog/723125
- Servlet3.0新特性详解: https://www.ibm.com/developerworks/cn/java/j-lo-servlet30/
- Part: http://tool.oschina.net/uploads/apidocs/javaEE6/javax/servlet/http/Part.html