浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用Servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以Java中Web端可以用的上传组件有两种:
try{
//1.得到解析器工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//2.得到解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//3.判断上传表单的类型
if(!upload.isMultipartContent(request)){
//上传表单为普通表单,则按照传统方式获取数据即可
return;
}
//为上传表单,则调用解析器解析上传数据
List list = upload.parseRequest(request); //FileItem
//遍历list,得到用于封装第一个上传输入项数据fileItem对象
for(FileItem item : list){
if(item.isFormField()){
//得到的是普通输入项
String name = item.getFieldName(); //得到输入项的名称
String value = item.getString();
System.out.println(name + "=" + value);
}else{
//得到上传输入项
String filename = item.getName(); //得到上传文件名 C:\Documents and Settings\ThinkPad\桌面\1.txt
filename = filename.substring(filename.lastIndexOf("\\")+1);
InputStream in = item.getInputStream(); //得到上传数据
int len = 0;
byte buffer[]= new byte[1024];
String savepath = this.getServletContext().getRealPath("/upload");
FileOutputStream out = new FileOutputStream(savepath + "\\" + filename); //向upload目录中写入文件
while((len=in.read(buffer))>0){
out.write(buffer, 0, len);
}
in.close();
out.close();
}
}
}catch (Exception e) {
e.printStackTrace();
}
//实例化组件
SmartUpload smartUpload = new SmartUpload();
//初始化上传操作
smartUpload.initialize(this.getServletConfig(), request, response);
try {
//上传准备
smartUpload.upload();
//对于普通数据,单纯到request对象是无法获取得到提交参数的。也是需要依赖smartUpload
String password = smartUpload.getRequest().getParameter("password");
System.out.println(password);
//上传到uploadFile文件夹中
smartUpload.save("uploadFile");
} catch (SmartUploadException e) {
e.printStackTrace();
}
此时浏览器端只需要利用file组件和form就可以提交文件。
而如果不是webapp,而是Http客户端,可以利用Httpclient,其API操作也比较简单。
Android 6中移除了HttpClient组件,推荐使用HttpURLConnection API, 需要手动按照格式去实现文件上传:上传文件数据是经过MIME协议进行分割的,表单进行了二进制封装。也就是说:getParameter()无法获取得到上传文件的数据。
下述代码的BOUNDARY可以是随机字符串。
private static final String BOUNDARY = "----WebKitFormBoundaryT1HoybnYeFOGFlBR";
/**
*
* @param params
* 传递的普通参数
* @param uploadFile
* 需要上传的文件名
* @param fileFormName
* 需要上传文件表单中的名字
* @param newFileName
* 上传的文件名称,不填写将为uploadFile的名称
* @param urlStr
* 上传的服务器的路径
* @throws IOException
*/
public void uploadForm(Map params, String fileFormName,
File uploadFile, String newFileName, String urlStr)
throws IOException {
if (newFileName == null || newFileName.trim().equals("")) {
newFileName = uploadFile.getName();
}
StringBuilder sb = new StringBuilder();
/**
* 普通的表单数据
*/
for (String key : params.keySet()) {
sb.append("--" + BOUNDARY + "\r\n");
sb.append("Content-Disposition: form-data; name=\"" + key + "\""
+ "\r\n");
sb.append("\r\n");
sb.append(params.get(key) + "\r\n");
}
/**
* 上传文件的头
*/
sb.append("--" + BOUNDARY + "\r\n");
sb.append("Content-Disposition: form-data; name=\"" + fileFormName
+ "\"; filename=\"" + newFileName + "\"" + "\r\n");
sb.append("Content-Type: image/jpeg" + "\r\n");// 如果服务器端有文件类型的校验,必须明确指定ContentType
sb.append("\r\n");
byte[] headerInfo = sb.toString().getBytes("UTF-8");
byte[] endInfo = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8");
System.out.println(sb.toString());
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY);
conn.setRequestProperty("Content-Length", String
.valueOf(headerInfo.length + uploadFile.length()
+ endInfo.length));
conn.setDoOutput(true);
OutputStream out = conn.getOutputStream();
InputStream in = new FileInputStream(uploadFile);
out.write(headerInfo);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) != -1)
out.write(buf, 0, len);
out.write(endInfo);
in.close();
out.close();
if (conn.getResponseCode() == 200) {
System.out.println("上传成功");
}
}
基本原理:
使用HttpUrlConnection上传有一个很致命的问题就是,当上传文件很大时,会发生内存溢出,手机分配给我们app的内存更小,所以就更需要解决这个问题,于是我们可以使用Socket模拟POST进行HTTP文件上传。
/**
*
* @param params
* 传递的普通参数
* @param uploadFile
* 需要上传的文件名
* @param fileFormName
* 需要上传文件表单中的名字
* @param newFileName
* 上传的文件名称,不填写将为uploadFile的名称
* @param urlStr
* 上传的服务器的路径
* @throws IOException
*/
public void uploadFromBySocket(Map params,
String fileFormName, File uploadFile, String newFileName,
String urlStr) throws IOException {
if (newFileName == null || newFileName.trim().equals("")) {
newFileName = uploadFile.getName();
}
StringBuilder sb = new StringBuilder();
/**
* 普通的表单数据
*/
if (params != null)
for (String key : params.keySet()) {
sb.append("--" + BOUNDARY + "\r\n");
sb.append("Content-Disposition: form-data; name=\"" + key
+ "\"" + "\r\n");
sb.append("\r\n");
sb.append(params.get(key) + "\r\n");
} else{ab.append("\r\n");}
/**
* 上传文件的头
*/
sb.append("--" + BOUNDARY + "\r\n");
sb.append("Content-Disposition: form-data; name=\"" + fileFormName
+ "\"; filename=\"" + newFileName + "\"" + "\r\n");
sb.append("Content-Type: image/jpeg" + "\r\n");// 如果服务器端有文件类型的校验,必须明确指定ContentType
sb.append("\r\n");
byte[] headerInfo = sb.toString().getBytes("UTF-8");
byte[] endInfo = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8");
System.out.println(sb.toString());
URL url = new URL(urlStr);
Socket socket = new Socket(url.getHost(), url.getPort());
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os, true, "UTF-8");
// 写出请求头
ps.println("POST " + urlStr + " HTTP/1.1");
ps.println("Content-Type: multipart/form-data; boundary=" + BOUNDARY);
ps.println("Content-Length: "
+ String.valueOf(headerInfo.length + uploadFile.length()
+ endInfo.length));
ps.println("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
InputStream in = new FileInputStream(uploadFile);
// 写出数据
os.write(headerInfo);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) != -1)
os.write(buf, 0, len);
os.write(endInfo);
in.close();
os.close();
}
如果觉得以上API过于复杂,可以使用okhttp,甚至是封装好的第三方框架: volley, Retrofit。 retrofit依赖于okhttp。
//通过“addFormDataPart”可以添加多个上传的文件。
public class OkHttpCallBackWrap {
public void post(String url) throws IOException{
File file = new File("D:/app/dgm/3.mp4");
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("application/octet-stream", "1.mp4", fileBody)
.build();
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
final okhttp3.OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
OkHttpClient okHttpClient = httpBuilder
//设置超时
.connectTimeout(100, TimeUnit.SECONDS)
.writeTimeout(150, TimeUnit.SECONDS)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
@Override
public void onFailure(Call arg0, IOException e) {
// TODO Auto-generated method stub
System.out.println(e.toString());
}
});
}
}
(1)、为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。
(2)、为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。
(3)、为防止一个目录下面出现太多文件,要使用hash算法打散存储。
(4)、要限制上传文件的最大值。
(5)、要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。
(6)、大文件的上传也可以实现为断点上传,需要服务端配合实现,和断点下载实现类似,也采用RandomAccessFile来做文件移动。
文件上传的方式:1.multipart/from-data方式上传。2.binary方式上传。multipart上传方式html的上传方式代码这中上传方式是我们最常用的上传方式。比如我们使用网页上传文件,其中html代码大致为这样:
multipart/form-data方式的一个重要组成部分请求头Content-Type必须为: Content-Type:multipart/form-data;boundarty=一个32字节的随机数,用来分割每个part。 然后每个Part之间用“双横杠”加bundary来分割,最后一个Part分割符末尾也要加“双横杠”。
每个Part中必须包含Content-Disposition字段来注明字段文件名等信息,也可以包含Content-Type来说明文件的MeidaType。
最后提供一个文件下载的Servlet实现:直接设置响应response的响应头和输出流即可。Content-Disposition (以附件形式传输)
package me.gacl.web.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DownLoadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//得到要下载的文件名
String fileName = request.getParameter("filename"); //23239283-92489-阿凡达.avi
fileName = new String(fileName.getBytes("iso8859-1"),"UTF-8");
//上传的文件都是保存在/WEB-INF/upload目录下的子目录当中
String fileSaveRootPath=this.getServletContext().getRealPath("/WEB-INF/upload");
//通过文件名找出文件的所在目录
String path = findFileSavePathByFileName(fileName,fileSaveRootPath);
//得到要下载的文件
File file = new File(path + "\\" + fileName);
//如果文件不存在
if(!file.exists()){
request.setAttribute("message", "您要下载的资源已被删除!!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return;
}
//处理文件名
String realname = fileName.substring(fileName.indexOf("_")+1);
//设置响应头,控制浏览器下载该文件
response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(realname, "UTF-8"));
//读取要下载的文件,保存到文件输入流
FileInputStream in = new FileInputStream(path + "\\" + fileName);
//创建输出流
OutputStream out = response.getOutputStream();
//创建缓冲区
byte buffer[] = new byte[1024];
int len = 0;
//循环将输入流中的内容读取到缓冲区当中
while((len=in.read(buffer))>0){
//输出缓冲区的内容到浏览器,实现文件下载
out.write(buffer, 0, len);
}
//关闭文件输入流
in.close();
//关闭输出流
out.close();
}
/**
* @Method: findFileSavePathByFileName
* @Description: 通过文件名和存储上传文件根目录找出要下载的文件的所在路径
* @Anthor:孤傲苍狼
* @param filename 要下载的文件名
* @param saveRootPath 上传文件保存的根目录,也就是/WEB-INF/upload目录
* @return 要下载的文件的存储目录
*/
public String findFileSavePathByFileName(String filename,String saveRootPath){
int hashcode = filename.hashCode();
int dir1 = hashcode&0xf; //0--15
int dir2 = (hashcode&0xf0)>>4; //0-15
String dir = saveRootPath + "\\" + dir1 + "\\" + dir2; //upload\2\3 upload\3\5
File file = new File(dir);
if(!file.exists()){
//创建目录
file.mkdirs();
}
return dir;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}