1. 程式人生 > >Java web文件服務器的簡單實現

Java web文件服務器的簡單實現

是否 auth cor multi 同步 登錄驗證 stp while break

我們公司在做一個在線考試平臺,平臺在發布文章、編輯試題時需要將文章生成的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文件服務器的簡單實現