Tomcat 也可以上傳檔案到伺服器,而且檔案的儲存位置可以超出網站的根目錄。雖說這有安全性的問題,但只要處理的好,其實非常方便。致於PHP,則只能把檔案儲存在網站根目錄之內,如果要超出網站根目錄,在Linux下就要使用 mount –bind 把根目錄外的目錄掛載到根目錄之內,而在Windows下則要使用junction建立連結。
Tomcat 自10版開始,為了避開 Java EE版權的束縛,全面改用Jakarta EE, 套件也由 javax 改寫成 jakarta。也就是說,9版及之前的版本都是使用 import javax套件,到了10版就必需使用 import jakarta套件。
也因如此,Tomcat 9 傳送檔案的代碼就無法在 Tomcat 10 運作。可是目前 95% 以上的網站都只說明 Tomcat 9 的傳送方式。致於如何在Tomcat 10運作,很難在網路上查得。所以本篇全部以 Tomcat 10 的版本作說明。
上傳套件
上傳檔案可以套用Apache開發的二個套件,分別為 commons-fileupload及commons-io。
當然,不一定要使用這二個套件,可以直接使用 Java 的 InputStream/OutputStream 進行讀取。但考慮到傳送中網路中斷造成整個 app卡住,甚至閃退問題,還是建議使用此二個套件,因為這二個套件都考慮到了我們意想不到的例外。如果有興趣直接使用Stream的朋友,可以參考如下網友的說明。
Stream 直接傳送參考網站 :
https://blog.judysocute.com/2020/03/13/%E7%AC%AC-6-%E9%80%B1-servlet-%E6%AA%94%E6%A1%88%E8%99%95%E7%90%86/
https://openhome.cc/Gossip/JavaEssence/InputStreamOutputStream.html
下載 fileupload及commonsIO的官網如下
fileupload : http://commons.apache.org/proper/commons-fileupload/ 目前最新版本為1.4版
commons IO : https://commons.apache.org/proper/commons-io/ 目前最新版本為 2.10.0版
請將二個檔解開後,將jar copy 到C:\Program Files\Apache Software Foundation\Tomcat 10.0\lib 之下
設定環境變數
請到系統設定新增環境變數
CATALINA_HOME : C:\Program Files\Apache Software Foundation\Tomcat 10.0
CLASSPATH : 新增如下三個值
C:\Program Files\Apache Software Foundation\Tomcat 10.0\lib\servlet-api.jar
C:\Program Files\Apache Software Foundation\Tomcat 10.0\lib\commons-fileupload-1.4.jar
C:\Program Files\Apache Software Foundation\Tomcat 10.0\lib\commons-io-2.10.0.jar
如果是在Linux之下,則在 /etc/profile下新增如下
export CLASSPATH=/opt/tomcat/lib/servlet-api.jar:/opt/tomcat/lib/commons-fileupload-1.4.jar:/opt/tomcat/lib/commons-io-2.10.0.jar
上述的 CLASSPATH 設定,主要是在手動使用 javac 編譯時,所要參考的 jar檔,所以一定要設定。
WEB-INF 目錄
在網頁根目錄下,必需產生如下WEB_INF的目錄,請手動一個一個加入
thomas #網頁根目錄
WEB-INF #設定檔子目錄
classes # .class存放位置
web.xml # 設定檔
upload.jsp
UploadServlet.java
messag.jsp
web.xml
web.xml只是描述 java 類別與網頁中所對應的url網址,並不會自動進行編譯,編譯的動作需手動操作,將於後面說明。
web.xml的設定如下,請注意紅色的部份一定要寫入,否則會出現 “Unable to process parts as no multi-part configuration has been provided” 例外錯誤。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>UploadServlet</servlet-class>
<multipart-config>
<max-file-size>20848820</max-file-size>
<max-request-size>418018841</max-request-size>
<file-size-threshold>1048576</file-size-threshold>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/UploadServlet</url-pattern>
</servlet-mapping>
</web-app>
代碼
上傳表單 upload.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上傳表單</title>
</head>
<body>
<h1>文件上傳表單</h1>
<form method="post" action="/UploadServlet" enctype="multipart/form-data">
帳號 : <input type="text" name="txtAccount"><br>
密碼 : <input type="password" name="txtPassword"><br><br>
請選擇文件:<input type="file" name="photo" /><br/><br/>
<input type="submit" value="上傳" />
</form>
</body>
</html>
上傳代碼 UploadServlet.java
UploadServlet必需覆寫doPost() 或 doGet()方法,如下所示。
另請注意藍色的部份,這就是跟其它網站不一樣的地方,需全改成jakarta才可在Tomcat 10正常運作
//package com.asuscomm.mahaljsp; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import java.util.Collection; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.Part; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; @WebServlet("/UploadServlet") public class UploadServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final String UPLOAD_DIRECTORY = "upload"; private static final int MEMORY_THRESHOLD = 1024 * 1024 * 3; // 3MB private static final int MAX_FILE_SIZE = 1024 * 1024 * 40; // 40MB private static final int MAX_REQUEST_SIZE = 1024 * 1024 * 50; // 50MB protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setSizeThreshold(MEMORY_THRESHOLD); factory.setRepository(new File(System.getProperty("java.io.tmpdir"))); ServletFileUpload upload = new ServletFileUpload(factory); upload.setFileSizeMax(MAX_FILE_SIZE); upload.setSizeMax(MAX_REQUEST_SIZE); upload.setHeaderEncoding("UTF-8"); String uploadPath = getServletContext().getRealPath("/") + UPLOAD_DIRECTORY; PrintWriter out=response.getWriter(); File uploadDir = new File(uploadPath); if (!uploadDir.exists()) { uploadDir.mkdir(); } out.println("<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"); out.println("<html xmlns='http://www.w3.org/1999/xhtml'>"); out.println("<head>"); out.println("<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />"); out.println("</head>"); out.println("<body>"); try { //@SuppressWarnings("unchecked") String userAccount=request.getParameter("txtAccount"); String userPassword=request.getParameter("txtPassword"); if(userAccount.equals("student") && userPassword.equals("1234")){ Part part=request.getPart("photo"); String fileName=part.getSubmittedFileName(); if(!fileName.equals("")){ String fullName=uploadPath+File.separator+fileName; part.write(fullName); out.println("儲存檔案成功! 儲存位置 : "+ fullName); } else{ out.println("沒有指定上傳檔案"); } //below could upload multi-file /* final Collection parts = request.getParts(); for (final Part part:parts){ String fileName=part.getSubmittedFileName(); if(!fileName.equals("")){ part.write(uploadPath+File.separator+fileName); } } */ } else{ out.println("帳號或密碼錯誤"); } }
catch (Exception ex) { out.println("error:"+ex.getMessage()); } out.println("</body>"); out.println("</html>"); } }
編譯
進入Dos模式,再進入網頁根目錄下,看到 UploadServlet.java 後,下達
javac -encoding utf-8 UploadServlet.java
此時在網頁根目錄下會產生UploadServlet.class檔案,再把此檔案copy到 classes目錄中。記得最後要重新啟動Tomcat Server。
然後在瀏覽器上輸入 http://localhost:8080/upload.jsp,隨便上傳一個檔,就可以看到檔案被上傳到網站根目錄下 upload 的目錄。
Linux注意事項
權限問題
Tomcat使用tomcat:tomcat進行存取,所以如果希望當前登入者 student 帳號也能存取檔案的話,可以將 student 帳號加入tomcat群組。
sudo usermod -a -G tomcat student
另外因為Tomcat一般都會架設在nginx/Apache之下,所以為了讓php的網頁也能讀取到tomcat上傳的檔案,建議也將www-data帳號加入 tomcat群組之中。
sudo usermod -a -G tomcat www-data
Linux編譯
編譯java時,請記得如上說明,將目前帳號加入tomcat群組後,再使用底下指令
javac -encoding utf-8 UploadServlet.java
其他參考
Stream寫法 :
https://blog.judysocute.com/2020/03/13/%E7%AC%AC-6-%E9%80%B1-servlet-%E6%AA%94%E6%A1%88%E8%99%95%E7%90%86/
https://openhome.cc/Gossip/JavaEssence/InputStreamOutputStream.html