我们公司在做一个在线考试平台,平台在发布文章、编辑试题时需要将文章生成的html文档以及题中的图片上传到一个专门文件服务器,然后再界面中使用http链接上传的文件。
基于这个考虑,需要实现一个文件上传程序,上传后程序返回文件的http URL地址。
我在网上找了些解决方案,大多不太适合我们的业务场景,加上文件上传程序本身并不复杂,于是就决定自己写一个。
下面谈谈我的思路及实现。
首先,服务器是暴露在外网的,需要考虑安全问题,不能任何人都可以往上面上传,所以要设计登录机制。
登录原理于web应用登录类似,都是带上账号、密码请求服务器,服务器验证通过后改变session中登录状态。
浏览器再往服务器上传数据,上传完数据后,服务器存储文件并生成URL地址并返回。
本例子基于servlet实现,包含web服务端和客户端,涉及到的类:
服务端 Login.java -- 上传权限验证类,负责验证登录请求,设置session登录状态值
服务端 FileUpload.java -- 文件监听及存储类,负责判断登录状态,存储文件并返回URL
客户端 UploadFileToFileServer.java -- 文件上传类,负责登录并上传文件到服务器。
由于java后台非浏览器环境,需要自行实现POST请求及session操作。
代码:
1.文件服务器为一个web程序,监听并接收POST方式传来的数据,包含两个Servlet,一个客户端登录、一个接收并保存文件,文件上传使用cos.jar包。
FileUpload.java Servlet:
1 package com.chanjet.exam.fileserver;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.PrintWriter;
6 import java.text.SimpleDateFormat;
7 import java.util.Date;
8 import java.util.Enumeration;
9
10 import javax.servlet.ServletException;
11 import javax.servlet.http.HttpServlet;
12 import javax.servlet.http.HttpServletRequest;
13 import javax.servlet.http.HttpServletResponse;
14
15 import com.oreilly.servlet.MultipartRequest;
16 import com.oreilly.servlet.multipart.FileRenamePolicy;
17
18 /***
19 * 文件接收servlet,集成自HttpServlet并实现cos.jar的FileRenamePolicy接口实现文件接收。
20 * 本类实现了FileRenamePolicy接口的rename方法对文件进行重命名
21 * @author jsper 2014-12-6
22 *
23 */
24 public class FileUpload extends HttpServlet implements FileRenamePolicy {
25
26
27
28 private static final long serialVersionUID = 1L;
29
30 // 定义限制文件大小:
31 private int maxSize = 102400 * 1024;// 102400KB以内(100MB)
32
33 // 要保存到web目录下的哪个路径下
34 private String dectory = "uploads";
35
36 // 指定文件后缀名范围,多个用“,”隔开
37 private String fp = "png,gif,jpg,bmp,html,htm,rar,zip,doc,docx,xml,xls,xlsx,txt,tmp";
38
39 //本类构造方法
40 public FileUpload() {
41 super();
42 }
43
44
45
46 //重写HttpServlet的Get方法
47 public void doGet(HttpServletRequest request, HttpServletResponse response)
48 throws ServletException, IOException {
49
50 doPost(request, response);
51
52 }
53
54
55 //重写HttpServlet的Post方法
56 public void doPost(HttpServletRequest request, HttpServletResponse response)
57 throws ServletException, IOException {
58
59
60 request.setCharacterEncoding("UTF-8");
61 response.setCharacterEncoding("UTF-8");
62
63 response.setContentType("text/html");
64 PrintWriter out = response.getWriter();
65
66
67 //验证是否已登录
68 Boolean islogined = (Boolean)request.getSession().getAttribute("islogined");
69 if(islogined==null || (islogined!=null && islogined!=true)){
70 out.println("Error,You are not logged!");
71 return;
72 }
73
74
75 // 获得web目录的绝对路径
76 String path = this.getServletContext().getRealPath("/");
77
78
79 //根据日期构建文件存放目录
80 SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd/HH");
81 String dectory2 = df.format(new Date());
82
83
84 // 要保存文件的绝对路径
85 String buildPath = path+dectory+"/"+dectory2+"/";
86 //目标目录不存在的话就自动创建
87 File f1 = new File(buildPath);
88 if (!f1.exists()) {
89 f1.mkdirs();//建立目录
90 }
91
92 FileUpload fu = new FileUpload();
93 try {
94 MultipartRequest multi = new MultipartRequest(request, buildPath,
95 maxSize, "UTF-8", fu);
96 Enumeration<?> enums = multi.getFileNames();
97
98
99
100 while (enums.hasMoreElements()) {
101 String fileName = (String) enums.nextElement();
102 File file = multi.getFile(fileName);
103 if (file != null) {
104 String name = multi.getFilesystemName(fileName);
105 String webroot = request.getScheme() + "://"
106 + request.getServerName() + ":"
107 + request.getServerPort()
108 + request.getContextPath();
109 String fileurl = webroot+"/"+dectory+"/"+dectory2+"/"+name;
110
111 out.println(fileurl);
112 }
113 }
114 } catch (Exception e) {
115 out.println("Server Exception!");
116 e.printStackTrace();
117 }
118
119 out.flush();
120 out.close();
121 }
122
123
124
125 @Override
126 public File rename(File file) {
127
128 int index = file.getName().lastIndexOf("."); //得到文件名中最后一个.的位置
129 String postfix = file.getName().substring(index); //得到文件名后缀
130
131 //构建新文件名(使用当前服务器时间戳)
132 String newFileName = System.currentTimeMillis() + postfix;
133
134 // 判断文件类型是否符合限定范围
135 String[] ps = fp.split(",");
136 boolean flag = false;
137 for (String p : ps) {
138 if (postfix.equals(("." + p))) {
139 flag = true;
140 break;
141 }
142 }
143 if (flag) {
144 return new File(file.getParent(), newFileName);
145 } else {
146 return null;
147 }
148
149 }
150
151 }
Login.java Servlet:
1 package com.chanjet.exam.fileserver;
2
3 import java.io.IOException;
4 import java.io.PrintWriter;
5
6 import javax.servlet.ServletException;
7 import javax.servlet.http.HttpServlet;
8 import javax.servlet.http.HttpServletRequest;
9 import javax.servlet.http.HttpServletResponse;
10
11
12 /***
13 * 客户端登录类
14 *
15 * 本servlet将验证用户登录,并为用户创建session状态
16 *
17 * @author chenjye 2014-12-6
18 *
19 */
20 public class Login extends HttpServlet {
21
22 /**
23 * The doGet method of the servlet.
24 */
25 public void doGet(HttpServletRequest request, HttpServletResponse response)
26 throws ServletException, IOException {
27
28 response.setContentType("text/html");
29 PrintWriter out = response.getWriter();
30 out.println("Error");
31 out.flush();
32 out.close();
33 }
34
35 /**
36 * The doPost method of the servlet.
37 */
38 public void doPost(HttpServletRequest request, HttpServletResponse response)
39 throws ServletException, IOException {
40
41 response.setContentType("text/html");
42 PrintWriter out = response.getWriter();
43
44 System.out.println("fileserver:user logining.");
45 //客户端登录
46 String username = request.getParameter("username");
47 String password = request.getParameter("password");
48 if("admin".equals(username) && "123456".equals(password)){
49 request.getSession().setAttribute("islogined",true);
50 out.print(true);
51 System.out.println("fileserver:login success.");
52 }else{
53 out.print("the username or password error.");
54 System.out.println("fileserver:login failure.");
55 }
56
57 out.flush();
58 out.close();
59 }
60
61 }
2.客户端实现了post表单提交方法,一个用于登录并获得session,一个用于执行上传。
UploadFileToFileServer.java 代码如下:
1 package com.chanjet.core.util;
2
3 import java.io.BufferedReader;
4 import java.io.DataOutputStream;
5 import java.io.InputStreamReader;
6 import java.io.OutputStream;
7 import java.net.HttpURLConnection;
8 import java.net.URL;
9 import java.util.Map;
10
11
12 /***
13 * 上传文件到文件服务器类
14 * 上传步骤:1.使用login方法登录文件服务器,2.使用fileUpload方法传输文件
15 * 说明:必须先登录,再传输,登录后需要得到服务器分配的session,上传文件时需要将session发给服务器,以便服务器确认身份。
16 * @author Admin
17 *
18 */
19 public class UploadFileToFileServer {
20
21
22 //登录文件服务器的地址
23 private String loginUrl = "http://localhost/fileserver/servlet/Login";
24
25 //接收文件的服务器地址
26 private String serverUrl = "http://localhost/fileserver/servlet/FileUpload";
27
28 private String[] session = null;
29
30 /**
31 * 模拟浏览器POST提交数据方法
32 * 将装进HashMap的参数及值组合成URL并POST提交到web,
33 * @param action
34 * @param parMap
35 * @return
36 * @throws Exception
37 */
38 public String login(Map<String,String> parMap) throws Exception{
39 String resultSet = null;
40
41 try{
42 HttpURLConnection huc = (HttpURLConnection) new URL(loginUrl).openConnection();
43 //指定HTTP内容类型及URL格式为form表单格式
44 //huc.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
45
46
47 // 设置允许output
48 huc.setDoOutput(true);
49 // 设置提交方式为post方式
50 huc.setRequestMethod("POST");
51 String parameter="";
52 for(String key:parMap.keySet()){
53 //组建参数URL并指定URL及参数编码格式
54 parameter+=key+"="+ java.net.URLEncoder.encode(parMap.get(key),"utf-8")+"&";
55 }
56 parameter=parameter.substring(0, parameter.length()-1);
57 OutputStream os = huc.getOutputStream();
58 os.write(parameter.getBytes("utf-8"));//指定URL及参数编码格式
59 os.flush();
60 os.close();
61 //执行提交后获取执行结果
62 BufferedReader br = new BufferedReader(new InputStreamReader(huc .getInputStream()));
63 huc.connect();
64 String line=null ;
65 resultSet = br.readLine();
66 //循环按行读取文本流
67 while ((line = br.readLine()) != null) {
68 resultSet += line;//此处未加上\r\n
69 }
70 br.close();
71 resultSet = resultSet.trim();
72
73 //得到本次会话session,以便传文件时服务器确认身份
74 session = huc.getHeaderField("Set-Cookie").split(";");
75 System.out.println("sessionId:"+session[0]);
76
77 huc.disconnect();
78 }catch(Exception e){
79 throw e;
80 }
81
82 return resultSet;
83 }
84
85
86
87 /***
88 * 上传文件到文件服务器,得到返回的文件的网络地址并返回给调用程序
89 * chenjye 2014-12-6
90 * 参考:http://314858770.iteye.com/blog/720456
91 *
92 * @param f
93 * @param url
94 * @return
95 * @throws Exception
96 */
97 public String fileUpload(byte[] bytes) throws Exception{
98 String resultSet = null;
99
100
101
102
103 try{
104 HttpURLConnection huc = (HttpURLConnection) new URL(serverUrl).openConnection();
105
106
107 huc.setRequestMethod("POST");// 设置提交方式为post方式
108 huc.setDoInput(true);
109 huc.setDoOutput(true);//设置允许output
110 huc.setUseCaches(false);//POST不能使用缓存
111
112 //同步会话session
113 if(session!=null && session.length>0){
114 huc.setRequestProperty("Cookie", session[0]);
115 }else{
116 return "Session Error";
117 }
118
119 //设置请求头信息
120 huc.setRequestProperty("Connection", "Keep-Alive");
121 huc.setRequestProperty("Charset", "UTF-8");
122
123 // 设置边界
124 String boundary = "----------" + System.currentTimeMillis();
125 huc.setRequestProperty("Content-Type", "multipart/form-data; boundary="+ boundary);
126
127
128 // 头部:
129 StringBuilder sb = new StringBuilder();
130 sb.append("--"); // 必须多两道线
131 sb.append(boundary);
132 sb.append("\r\n");
133 sb.append("Content-Disposition: form-data; name=\"file\"; filename=\"uploaded_file.html\"\r\n");
134 sb.append("Content-Type: application/octet-stream\r\n\r\n");
135
136 // 获得输出流
137 OutputStream out = new DataOutputStream(huc.getOutputStream());
138 out.write(sb.toString().getBytes("utf-8"));//写入header
139 // 文件数据部分
140 out.write(bytes, 0, bytes.length);//写入文件数据
141
142 // 结尾部分
143 byte[] foot = ("\r\n--" + boundary + "--\r\n").getBytes("utf-8");// 定义最后数据分隔线
144 out.write(foot);//写入尾信息
145
146 out.flush();
147 out.close();
148
149
150 //执行提交后获取执行结果
151 BufferedReader br = new BufferedReader(new InputStreamReader(huc.getInputStream()));
152 huc.connect();
153 String line=null ;
154 resultSet = br.readLine();
155
156 //循环按行读取文本流
157 while ((line = br.readLine()) != null) {
158 resultSet += line+"\r\n";//此处未加上\r\n
159 }
160 br.close();
161 resultSet = resultSet.trim();
162 huc.disconnect();
163 }catch(Exception e){
164 throw e;
165 }
166
167
168 return resultSet;
169
170
171 }
172
173 }
3.程序调用:
1 //虚拟模板,并将内容填充进模板
2 String title = "动态内容静态化测试";
3 String content ="测试";
4
5 StringBuilder sb = new StringBuilder();
6 sb.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
7 sb.append("<html>\r\n");
8 sb.append(" <head>\r\n");
9 sb.append(" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\r\n");
10 sb.append(" <title>"+title+"</title>\r\n");
11 sb.append(" </head>\r\n");
12 sb.append(" <body>\r\n");
13 sb.append(content+"\r\n");
14 sb.append(" </body>\r\n");
15 sb.append("</html>\r\n");
16
17 //创建文件传输对象,将内容发送给文件服务器并得到返回的url
18 UploadFileToFileServer uftfs = new UploadFileToFileServer();
19 Map<String,String> parMap = new HashMap<String,String>();
20 parMap.put("username", "admin");
21 parMap.put("password", "123456");
22 String loginFlag = uftfs.login(parMap);//登录
23 if("true".equals(loginFlag)){
24 byte[] bytes = sb.toString().getBytes("UTF-8");
25 String url = uftfs.fileUpload(bytes);//上传
26 System.out.println("文件URL:"+url);
27 }
28
29
30 //直接传二进制文件
31 byte[] buffer = null;
32 try {
33 File file = new File("E:\\girl.jpg");
34 FileInputStream fis = new FileInputStream(file);
35 ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
36 byte[] b = new byte[1000];
37 int n;
38 while ((n = fis.read(b)) != -1) {
39 bos.write(b, 0, n);
40 }
41 fis.close();
42 bos.close();
43 buffer = bos.toByteArray();
44 } catch (FileNotFoundException e) {
45 e.printStackTrace();
46 } catch (IOException e) {
47 e.printStackTrace();
48 }
49
50 String url = uftfs.fileUpload(buffer);//上传
51 System.out.println("文件URL:"+url);
后注:
1.以上代码虽然在上传前进行了登录验证防止黑客攻击,但文件的url是暴露的,缺乏防盗链机制,可以通过判断来路Referer避免常见的盗链。
2.以上例子,文件存储路径由文件服务器安排,但在某些需求下,可能需要由client指定,这时,可以在登录时一并将要放置的路径传给文件服务器,文件服务器验证登录成功后将收到的路径存于session,在接收文件时取出并使用。但这样做需要每次传文件时执行一次登录,但方式并不限于这一种,对程序稍作调整可达到更加合理的效果。
PS:该程序已经持续运行了3年了,没死过机,卡过机,选择上传组件时特意对比了一些测试评论,得知cos.jar很稳定,支持超大文件上传,从此看来果然名不虚传。