首页 > 代码库 > Java web文件服务器的简单实现
Java web文件服务器的简单实现
我们公司在做一个在线考试平台,平台在发布文章、编辑试题时需要将文章生成的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很稳定,支持超大文件上传,从此看来果然名不虚传。
Java web文件服务器的简单实现