- 處理multipart format的data
- 上傳文件,對應的部分可以是binary data
- 在處理文件上傳以前,必須要先配置一個multipart resolver,透過此來告知DispatcherServlet該如何讀取multipart request
------WebKitFormBoundaryqgkaBn8IHJCuNmiW Content-Disposition: form-data; name="firstName" Charles ------WebKitFormBoundaryqgkaBn8IHJCuNmiW Content-Disposition: form-data; name="lastName" Xavier ------WebKitFormBoundaryqgkaBn8IHJCuNmiW Content-Disposition: form-data; name="email" charles@xmen.com ------WebKitFormBoundaryqgkaBn8IHJCuNmiW Content-Disposition: form-data; name="username" professorx ------WebKitFormBoundaryqgkaBn8IHJCuNmiW Content-Disposition: form-data; name="password" letmein01 ------WebKitFormBoundaryqgkaBn8IHJCuNmiW Content-Disposition: form-data; name="profilePicture"; filename="me.jpg" Content-Type: image/jpeg [[ Binary image data goes here ]] ------WebKitFormBoundaryqgkaBn8IHJCuNmiW--
- Configuring a multipart resolver
- DispatcherServlet會將這個任務委託給Spring中implement MultipartResolver的class
- Spring內有兩個MultipartResolver提供選擇
- CommonsMultipartResolver: 使用Jakarta Commons FileUpload resolver multipart request
- StandardServletMultipartResolver: 依賴Servlet 3.0對multipart request的support
- StandardServletMultipartResolver
- constructor內沒有需要設定的parameter,也沒有parameter需要設定,只要加個@Bean即可使用
- 針對其他限制(如img上傳大小,寫入位置等)需要在Servlet指定config
- 一定要設定寫入位置,否則無法正常work
- 必須要在web.xml或servlet initializer class中將multipart的細節作為DispatcherServlet配置的一部份
- 若使用servlet initializer 來配置DispatcherServlet,則應該已經implement WebApplicationInitializer,那麼可以直接在上面呼叫setMultipartConfig()如下:
- 若是繼承AbstractDispatcherServletInitializer或AbstractAnnotationConfigDispatcherServletInitializer,則不會直接建立DispatcherServlet,可以透過customizeRegistration()取得Dynamic parameter,配置multipart
- 除了 temporary location path,還有其他的constructor 能夠接受的parameters如下
- 上傳doc的最大size(byte),default無限制
- multiparty request的最大size(byte),default無限制
- 上傳過程中,文件大小到了一個指定size就會上傳到temporary location,default是0,也就是所有上傳文件都會寫到temporary location
- 假設想限制文件大小不超過2MB,request不超過4MB,所有doc都要寫到temporary location
- 使用web.xml
- Configure Jakarta Commons FileUpload multipart resolver
- Servlet非3.0的container不能使用StandardServletMultipartResolver,因此需要替代方案。
- 使用CommonsMultipartResolver declare為bean:
- 不和StandardServletMultipartResolver一樣,不會強制要求設定temporary location。default是servlet container的temporary location。但也可以透過設定uploadTempDir修改路徑
- 也能夠透過這樣的方法設定其他屬性
- 以下範例為限制文件大小不超過2MB,所有doc都要寫到temporary location。但無法設定request最大size
- Handling multipart request
- 編寫container method接受multipart request,最簡單的方法就是添加@RequestPart
- 如果要在Spittr application中允許上傳一張圖片,則需要
- 修改form
- 修改SpitterController中的prcessRegistration() method接受上傳的圖片
- 修改form:
- 修改prcessRegistration()
- profilePicture會得到的是byte data。如果使用者提交form沒有上傳照片,則這個data(@RequestPart("profilePicture"))會是空的,不是null
- 問題:如何將byte data轉為可儲存文件,name是什麼,是不是empty...?
- Receiving multipartFile
- Spring還提供了MultipartFile interface,為了處理multipart data提供了更豐富的object
- 以下範例可將img寫入filesystem
- 將文件保存到local是簡單的,但需要對文件進行管理,還要有足夠的空間。
- Saving files to Amazon S3
- 另一種方式就是讓別人負責,也就是,保存到雲端
- Receiving the uploaded file as a Part
- Spring MVC也能接受Java.servlet.http.Part作為controller method的parameter
- processRegistration()需要修改signature
- Part的interface(其實和MultipartFile並沒有太大的差異)
- 以下範例為寫入檔案
- 如果在寫controller method時,透過Part parameter接受文件上傳,則不需要配置MultipartResolver。只有在使用MultipartFile才需要使用
@Bean public MultipartResolver multipartResolver() throws IOException { return new StandardServletMultipartResolver(); }
DispatcherServlet ds = new DispatcherServlet(); // create DispatcherServlet instance方法
Dynamic registration = context.addServlet("appServlet", ds); registration.addMapping("/"); registration.setMultipartConfig( new MultipartConfigElement("/tmp/spittr/uploads"));
@Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig( new MultipartConfigElement("/tmp/spittr/uploads", 2097152, 4194304, 0)); }
<servlet> <servlet-name>appServlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> <multipart-config> <location>/tmp/spittr/uploads</location> // necessary <max-file-size>2097152</max-file-size> <max-request-size>4194304</max-request-size> </multipart-config> </servlet>
@Bean public MultipartResolver multipartResolver() { return new CommonsMultipartResolver(); }
@Bean public MultipartResolver multipartResolver() throws IOException { CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); multipartResolver.setUploadTempDir( new FileSystemResource("/tmp/spittr/uploads")); return multipartResolver; }
@Bean public MultipartResolver multipartResolver() throws IOException { CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); multipartResolver.setUploadTempDir( new FileSystemResource("/tmp/spittr/uploads")); multipartResolver.setMaxUploadSize(2097152); multipartResolver.setMaxInMemorySize(0); return multipartResolver; }
<form method="POST" th:object="${spitter}" enctype="multipart/form-data"> // 以multipart data form submit form,而不是form data format ... <label>Profile Picture</label>: <input type="file" name="profilePicture" accept="image/jpeg,image/png,image/gif" /> <br/> ... </form>
@RequestMapping(value="/register", method=POST) public String processRegistration(@RequestPart("profilePicture") byte[] profilePicture, @Valid Spitter spitter, Errors errors) { ... }
package org.springframework.web.multipart; import java.io.File; import java.io.IOException; import java.io.InputStream; public interface MultipartFile { String getName(); String getOriginalFilename(); String getContentType(); boolean isEmpty(); long getSize(); byte[] getBytes() throws IOException; InputStream getInputStream() throws IOException; void transferTo(File dest) throws IOException; // 能夠將File寫到文件系統中。 }
profilePicture.transferTo( new File("/data/spittr/" + profilePicture.getOriginalFilename()));
private void saveImage(MultipartFile image) throws ImageUploadException { try { AWSCredentials awsCredentials = new AWSCredentials(s3AccessKey, s2SecretKey); // 透過injection取得 S3Service s3 = new RestS3Service(awsCredentials);// set up S3 service S3Bucket bucket = s3.getBucket("spittrImages"); S3Object imageObject = new S3Object(image.getOriginalFilename()); // create bucket and object
imageObject.setDataInputStream(image.getInputStream()); // set img data imageObject.setContentLength(image.getSize()); imageObject.setContentType(image.getContentType()); AccessControlList acl = new AccessControlList(); // set permission acl.setOwner(bucket.getOwner()); acl.grantPermission(GroupGrantee.ALL_USERS, Permission.PERMISSION_READ); imageObject.setAcl(acl); //若沒有設定則一般用戶也看不到 s3.putObject(bucket, imageObject); // save omg } catch (Exception e) { throw new ImageUploadException("Unable to save image", e); } }
@RequestMapping(value="/register", method=POST) public String processRegistration( @RequestPart("profilePicture") Part profilePicture, @Valid Spitter spitter, Errors errors) { ... }
package javax.servlet.http; import java.io.*; import java.util.*; public interface Part { public InputStream getInputStream() throws IOException; public String getContentType(); public String getName(); public String getSubmittedFileName(); public long getSize(); public void write(String fileName) throws IOException; // 對應Multipartfile的transferTo() public void delete() throws IOException; public String getHeader(String name); public Collection<String> getHeaders(String name); public Collection<String> getHeaderNames(); }
profilePicture.write("/data/spittr/" + profilePicture.getOriginalFilename());
沒有留言:
張貼留言