## HTMLInputElement.webkitdirectory

In the browser, we usually select the file to be uploaded by the <input type="file"/> tag. By default, it can only select one file or multiple files, not the whole folder directly.

If the <input/> tag has an attribute called webkitdirectory, the user can select the entire folder and the browser will upload all the files under the folder to the server at once.

The HTMLInputElement.webkitdirectory is a property that reflects the webkitdirectory HTML attribute and indicates that the <input> element should let the user select directories instead of files. When a directory is selected, the directory and its entire hierarchy of contents are included in the set of selected items. The selected file system entries can be obtained using the webkitEntries property.

https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory

## HTML

Create an index.html with the following content.

  1 2 3 4 5 6 7 8 9 10 11 12 13   Uploading a folder to the server


The browser will submit the relative path of the file in the folder as the file name to the server, and the server can create the file locally based on this relative path, thus ensuring consistency with the directory structure of the client.

## Controller

The server needs to write each file to a local folder according to the client’s file layout.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55  import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * * @author KevinBlandy * */ @RestController @RequestMapping("/upload") public class UploadController { private static final Logger LOGGER = LoggerFactory.getLogger(UploadController.class); /** * By default, the public folder in the working directory is a static resource directory, which can be accessed directly by the client. */ private static final Path PUBLIC_DIR = Paths.get(System.getProperty("user.dir"), "public"); @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public String upload (HttpServletRequest request) throws IOException, ServletException{ for (var part : request.getParts()) { // The name of the file is the relative path of the file in the client folder // Convert the client path separator to the server file path separator. String fileName = FilenameUtils.separatorsToSystem(part.getSubmittedFileName()); // Resolve the absolute path to the file based on the public folder Path file = PUBLIC_DIR.resolve(fileName); // Try to create the folder where the file is located if (Files.notExists(file.getParent())) { Files.createDirectories(file.getParent()); } // Write data to file try (var inputStream = part.getInputStream()){ Files.copy(inputStream, file, StandardCopyOption.REPLACE_EXISTING); } LOGGER.info("write file: [{}] {}", part.getSize(), file); } return "ok"; } } 

FilenameUtils is a tool class from commons-io to unify file separators between different operating systems.

 1 2 3 4 5   commons-io commons-io 2.11.0 

Also you may need to configure some configuration in application.yml about file uploads.

 1 2 3 4 5 6 7 8  spring: servlet: multipart: enabled: true max-file-size: -1 # No file size limit max-request-size: -1 # No limit on request size location: \${java.io.tmpdir} file-size-threshold: 10KB 

## Testing

Put index.html in the public folder of the spring boot application and start the application.

Open your browser and visit: http://localhost:8080

Click the Choose Files button to select a folder. A warning dialog box will pop up in your browser telling you that this uploads all the files in the folder.

After clicking the Upload button, you can see the number of files in the folder next to the Choose files button.

The folder foo that is uploaded in this demo has the following structure.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14  C:\Users\KevinBlandy\Desktop\foo>tree /F ... C:. │ 1.txt │ 2.txt │ └─bar │ 3.txt │ └─temp │ 4.txt │ └─spring main.java 

Click the Submit button to submit to the server. And observe the log output from the server’s console.

 1 2 3 4 5  2022-11-29 13:16:58.249 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [1] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\foo\1.txt 2022-11-29 13:16:58.262 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [6] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\foo\2.txt 2022-11-29 13:16:58.276 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [0] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\foo\bar\3.txt 2022-11-29 13:16:58.288 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [4] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\foo\bar\temp\4.txt 2022-11-29 13:16:58.290 INFO 6312 --- [nio-8080-exec-2] i.s.web.controller.UploadController : write file: [2081] C:\eclipse\eclipse-jee-2022-09-R-win32-x86_64\project\springcloud-cache\public\foo\bar\temp\spring\main.java 

As you can see from the logs, the server has successfully written all the files to the expected directory according to the layout of the client files.

Check the folder uploaded by the client to the server, everything is OK.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24  document.querySelector('input[name="file"]').addEventListener('change', e => { let formData = new FormData(); // Iterate through every file in the folder. for (let file of e.target.files){ // The file.webkitRelativePath property is the relative path of the file in the directory. // The browser will submit file.webkitRelativePath as the name of the file to the server. formData.append("file", file); } fetch('/upload', { method: 'POST', body: formData }).then(response => { if(response.ok){ response.text().then(payload => { console.log(payload); }); } }).catch(error => { console.error(error); }); });