דוגמת העלאת קובץ Servlet והורדתו

העלאת קובץ Servlet והורדת קובץ הם משימות נפוצות באפליקציות אינטרנט ב־java. מאכלל שכתבתי הרבה על servlet ב-java לאחרונה, חשבתי לספק דוגמה של העלאת קובץ ב-servlet לשרת ואז הורדה מהשרת ללקוח.

העלאת קובץ Servlet

המקרה שלנו הוא לספק דף HTML פשוט בו הלקוח יכול לבחור קובץ מקומי להעלאה לשרת. בעת הגשת הבקשה להעלאת הקובץ, תכנת ה-servlet שלנו תעלה את הקובץ לתיקייה בשרת ותספק את כתובת ה-URL דרך אותה המשתמש יכול להוריד את הקובץ. מסיבות בטחון, למשתמש לא יינתן ישירות ה-URL להורדת הקובץ, אלא יינתן לו קישור להורדת הקובץ וה-servlet שלנו יעבד את הבקשה וישלח את הקובץ למשתמש. ניצור פרויקט ווב דינמי ב-Eclipse ומבנה הפרויקט ייראה כמו בדימוי התמונה שלמטה. בואו נבחן את כל רכיבי אפליקציה האינטרנט שלנו ונבין את המימוש.

עמוד HTML להעלאת קובץ Java לשרת

ניתן להעלות קובץ לשרת על ידי שליחת בקשת post אל סרבלט ושליחת הטופס. לא ניתן להשתמש בשיטת GET להעלאת קובץ. נקודה נוספת לשים לב אליה היא שה־enctype של הטופס צריך להיות multipart/form-data. כדי לבחור קובץ ממערכת הקבצים של המשתמש, עלינו להשתמש באלמנט input עם type כ file. לכן נוכל לקבל עמוד HTML פשוט בשם index.html להעלאת קובץ כך:

<html>
<head></head>
<body>
<form action="UploadDownloadFileServlet" method="post" enctype="multipart/form-data">
Select File to Upload:<input type="file" name="fileName">
<br>
<input type="submit" value="Upload">
</form>
</body>
</html>

מיקום קובץ השרת להעלאת קובץ

עלינו לאחסן את הקובץ בתיקייה כלשהי בשרת, ניתן להגדיר קבוע את התיקייה הזו בתוך התוכנית, אך למרבית הגמישות, נשאיר אותה ניתנת להגדרה בקובץ תיאור התפשטות (deployment descriptor) כפרמטרי הקשר. כמו כן, נוסיף את עמוד ה־HTML להעלאת הקובץ שלנו לרשימת עמודי הברוך. קובץ ה־web.xml שלנו יראה כך:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://java.sun.com/xml/ns/javaee" xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
  <display-name>ServletFileUploadDownloadExample</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
  <context-param>
    <param-name>tempfile.dir</param-name>
    <param-value>tmpfiles</param-value>
  </context-param>
</web-app>

ספק של ה־ServletContextListener למיקום העלאת הקובץ

מאחר ונצטרך לקרוא פרמטר ההקשר עבור מיקום הקובץ וליצור אובייקט קובץ ממנו, נוכל לכתוב ServletContextListener לעשות זאת כאשר ההקשר מאותחל. נוכל להגדיר מיקום ספציפי של ספריית הקבצים ואובייקט קובץ כמאפיין ההקשר שישמש על ידי סרבלים אחרים. קוד המימוש שלנו של ServletContextListener נראה כמו במקטע הבא.

package com.journaldev.servlet;

import java.io.File;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class FileLocationContextListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent servletContextEvent) {
    	String rootPath = System.getProperty("catalina.home");
    	ServletContext ctx = servletContextEvent.getServletContext();
    	String relativePath = ctx.getInitParameter("tempfile.dir");
    	File file = new File(rootPath + File.separator + relativePath);
    	if(!file.exists()) file.mkdirs();
    	System.out.println("File Directory created to be used for storing files");
    	ctx.setAttribute("FILES_DIR_FILE", file);
    	ctx.setAttribute("FILES_DIR", rootPath + File.separator + relativePath);
    }

	public void contextDestroyed(ServletContextEvent servletContextEvent) {
		//עשוי לבצע ניקיון אם נדרש
	}
	
}

סרבל להעלאה והורדת קבצים

עדכון: ספקי Servlet 3 הוסיפו תמיכה בהעלאת קבצים לשרת ב- API, כך שלא יהיה צורך להשתמש ב- API של צד שלישי. אנא בדוק את העלאת קובץ Servlet 3. להעלאת קבצים, נשתמש בכלי Apache Commons FileUpload, לפרויקט שלנו אנו משתמשים בגרסה 1.3, FileUpload תלוי ב- jar של Apache Commons IO, כך שעלינו להניח שניים בתיקיית lib של הפרויקט, כפי שאתה יכול לראות בתמונה למעלה של מבנה הפרויקט. נשתמש במפענח DiskFileItemFactory המספק שיטה לפענח את אובייקט ה- HttpServletRequest ולהחזיר רשימת FileItem. FileItem מספק שיטות שימושיות לקבלת שם הקובץ, שם השדה בטופס, גודל ופרטי סוג התוכן של הקובץ שיש להעלות. כדי לכתוב קובץ לתיקייה, הכל שעלינו לעשות הוא ליצור אובייקט File ולהעביר אותו כארגומנט לשיטת write() של FileItem. מאחר שמטרת ה- servlet כולה היא להעלות קובץ, נדרוס את שיטת init() כדי לאתחל את אובייקט ה- DiskFileItemFactory של ה- servlet. נשתמש באובייקט זה במימוש של שיטת doPost() כדי להעלות את הקובץ לתיקייה של השרת. לאחר ההעלאה המוצלחת של הקובץ, נשלח תגובה ללקוח עם כתובת URL להורדת הקובץ, מכיוון שקישורי HTML משתמשים בשיטת GET, נוסיף את הפרמטר לשם הקובץ ב- URL ונוכל להשתמש באותה שיטת doGet() של ה- servlet כדי ליישם את תהליך הורדת הקובץ. למימוש של servlet להורדת קובץ, נפתח תחילה את InputStream לקובץ ונשתמש בשיטת ServletContext.getMimeType() כדי לקבל את סוג MIME של הקובץ ולהגדיר אותו כסוג תוכן של התגובה. נצטרך גם להגדיר את אורך תוכן התגובה כאורך של הקובץ. כדי לוודא שהלקוח מבין שאנו שולחים קובץ כתגובה, עלינו להגדיר את הכותרת "Content-Disposition" עם הערך "attachment; filename=“fileName”. לאחר שסיימנו להגדיר את התצורה של התגובה, נוכל לקרוא את תוכן הקובץ מ- InputStream ולכתוב אותו ל- ServletOutputStream ולשטוף את הפלט ללקוח. המימוש הסופי של ה- servlet UploadDownloadFileServlet נראה כך:

package com.journaldev.servlet;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

@WebServlet("/UploadDownloadFileServlet")
public class UploadDownloadFileServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
    private ServletFileUpload uploader = null;
	@Override
	public void init() throws ServletException{
		DiskFileItemFactory fileFactory = new DiskFileItemFactory();
		File filesDir = (File) getServletContext().getAttribute("FILES_DIR_FILE");
		fileFactory.setRepository(filesDir);
		this.uploader = new ServletFileUpload(fileFactory);
	}
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String fileName = request.getParameter("fileName");
		if(fileName == null || fileName.equals("")){
			throw new ServletException("File Name can't be null or empty");
		}
		File file = new File(request.getServletContext().getAttribute("FILES_DIR")+File.separator+fileName);
		if(!file.exists()){
			throw new ServletException("File doesn't exists on server.");
		}
		System.out.println("File location on server::"+file.getAbsolutePath());
		ServletContext ctx = getServletContext();
		InputStream fis = new FileInputStream(file);
		String mimeType = ctx.getMimeType(file.getAbsolutePath());
		response.setContentType(mimeType != null? mimeType:"application/octet-stream");
		response.setContentLength((int) file.length());
		response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
		
		ServletOutputStream os = response.getOutputStream();
		byte[] bufferData = new byte[1024];
		int read=0;
		while((read = fis.read(bufferData))!= -1){
			os.write(bufferData, 0, read);
		}
		os.flush();
		os.close();
		fis.close();
		System.out.println("File downloaded at client successfully");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		if(!ServletFileUpload.isMultipartContent(request)){
			throw new ServletException("Content type is not multipart/form-data");
		}
		
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		out.write("<html><head></head><body>");
		try {
			List<FileItem> fileItemsList = uploader.parseRequest(request);
			Iterator<FileItem> fileItemsIterator = fileItemsList.iterator();
			while(fileItemsIterator.hasNext()){
				FileItem fileItem = fileItemsIterator.next();
				System.out.println("FieldName="+fileItem.getFieldName());
				System.out.println("FileName="+fileItem.getName());
				System.out.println("ContentType="+fileItem.getContentType());
				System.out.println("Size in bytes="+fileItem.getSize());
				
				File file = new File(request.getServletContext().getAttribute("FILES_DIR")+File.separator+fileItem.getName());
				System.out.println("Absolute Path at server="+file.getAbsolutePath());
				fileItem.write(file);
				out.write("File "+fileItem.getName()+ " uploaded successfully.");
				out.write("<br>");
				out.write("<a href=\"UploadDownloadFileServlet?fileName="+fileItem.getName()+"\">Download "+fileItem.getName()+"</a>");
			}
		} catch (FileUploadException e) {
			out.write("Exception in uploading file.");
		} catch (Exception e) {
			out.write("Exception in uploading file.");
		}
		out.write("</body></html>");
	}

}

ההרצה המדוגמת של הפרויקט מוצגת בתמונות למטה.

הורד קובץ Servlet העלאת הורדה של הפרויקט

ניתן להוריד את קובץ Apache Commons IO ואת קובץ Apache Commons FileUpload מכתובות ה-URL הבאות. https://commons.apache.org/proper/commons-fileupload/download_fileupload.cgi https://commons.apache.org/proper/commons-io/download_io.cgi

הורד קובץ Servlet העלאת הורדה של דוגמה לפרויקט

תבדוק את המאמר הבא בסדרת המאמרים על טיפול בחריגות של Servlet Exception Handling.

Source:
https://www.digitalocean.com/community/tutorials/servlet-upload-file-download-example