Blogging Upload Feature on Website
Explaining Code + Problems I had
- Original Code
- UploadController.java
- Uploader Code...
- Break down...
- Upload Code...
- Breakdown...
- Upload.html
- MvcConfig.java
- Upload2.html
Original Code
- I had originally written some code that didn't save the data in volumes
- Volumes is a persistent folder meaning items stored there are saved after the project is shut down
- Not storing in Volumes can result in the files getting deleted --> won't be there next time user logs in
- Let's dive into Mr. M's code since mine is just a simplified version of his
UploadController.java
- Two major parts to the controller...
- Uploader
- Responsible for collecting the file, converting it into bytes to be stored, and storing required metadata in database (this feature currently not functioning)
- Upload
- Responsible for taking the uploaded files and displaying them to the user
- I had initially thought the code would loop through volumes and compile the images in a list but it appears it only lists the files the user just uploaded? Or maybe that's because I am working on localhost instead of a deployed site? Or maybe it's cuz the database is not working. Still need to figure these questions out
- Uploader
Uploader Code...
// uploader page: filesystem and database management of uploaded image
@PostMapping("/mvc/uploader")
public String mvcUploader(@RequestParam("filename") MultipartFile formFile, Model modelMap) {
/*
* The static directory is loaded at startup. UploadING images or makING changes
* to any files or
* folders under the static folder will not reflect as ApplicationContext is
* already initialized.
*/
String filePath = "volumes/uploads/"; // thus, uploads defined outside of static for dynamic updates
String webPath = "/" + filePath; // webPath
// A database table, using Upload POJO, is remembers location of upload and
// associated metadata
Upload repoFile = new Upload();
repoFile.setFile(webPath + formFile.getOriginalFilename());
repoFile.setType(formFile.getContentType());
repoFile.setSize(formFile.getSize());
// try/catch is in place, but error handling is not implemented (returns without
// alerts)
try {
// Creating the directory to store file
File dir = new File(filePath);
if (!dir.exists())
dir.mkdirs();
// Create the file on server
byte[] bytes = formFile.getBytes();
// File write alternatives (going with Stream for now as in theory it would be
// non-blocking)
String path = filePath + formFile.getOriginalFilename();
File serverFile = new File(path);
BufferedOutputStream stream = new BufferedOutputStream(
new FileOutputStream(serverFile));
stream.write(bytes);
stream.close();
// JPA save
repo.save(repoFile);
} catch (IOException e) {
e.printStackTrace(); // app stays alive, errors go to run console, /var/log/syslog
}
// Redirect back to action page
return "redirect:/mvc/upload";
}
Break down...
- URL for calling the function is /mvc/uploader
- Takes the multipart file (like an image for example) that the user inputs
- Directs all files to a directory in volumes titled uploads
- The following creates the uploads directory if it didn't already exist. If it does exist, the code will continue without executing this section
File dir = new File(filePath);
if (!dir.exists())
dir.mkdirs();
- The following code is supposed to add metadata of the uploaded file to a database table...but it wasn't doing that...need to figure out why
Upload repoFile = new Upload();
repoFile.setFile(webPath + formFile.getOriginalFilename());
repoFile.setType(formFile.getContentType());
repoFile.setSize(formFile.getSize());
The code following that takes the file and converts it to bytes since that's how the code can read and store the file.
byte[] bytes = formFile.getBytes();
- Then the code puts the file in volumes/uploads and saves it for the next user login
- Utilizes output string to do this --> take bytes and stores them in a location
String path = filePath + formFile.getOriginalFilename();
File serverFile = new File(path);
BufferedOutputStream stream = new BufferedOutputStream(
new FileOutputStream(serverFile));
stream.write(bytes);
stream.close();
// JPA save
repo.save(repoFile);
// user action page: upload controls and displays a history of images
@GetMapping("/mvc/upload")
public String mvcUpload(Model model) {
List<Upload> files = repo.findAll(); // extract image history
System.out.println("Number of files uploaded: " + files.size());
for (int i = 0; i < files.size(); i++) {
System.out.println(files.get(i).getFile());
}
model.addAttribute("files", files);
return "mvc/upload";
}
Breakdown...
- This part of the controller stores in a list all the values associated with the file based on upload class in upload.java
- List
files is a list of the files the user uploaded</li> - The print line and for loop was just for me to test...in the terminal it prints how many files are saved in the upload list --> realized that this only takes into account recently uploaded files
- Code ends with running the html file stored in resources/mvc
</ul>- Originally tried to create upload.html in frontend...although I could call the frontend html file, I got a cross origins error (iirc) and decided to just have the file be in backend
- OMG I JUST REALIZED maybe it's cuz Github pages doesn't support post method? So either way it wouldn't have worked out I think
- (Commented out code is just me testing and trying to display the image lol)
<!DOCTYPE html> <html> <body><p id="para-test">Uploaded Images: </p> <form enctype="multipart/form-data" method="POST"> <!-- <image th:src="@{__${files.file}__}" width="150px"></image> <image src="/volumes/uploads/ahemKamisatoKids.png"></image>--> <div id="image-list" th:each="file : ${files}"> <h4 th:text="${file.file}" /> <image th:src="${file.file}" width="150px"></image> <!-- Other Properties --> </div> </form> </body> </html>
<body><p id="para-test">Uploaded Images: </p> <form enctype="multipart/form-data" method="POST">
- Since uploaded item is an image...enctype is multipart/form-data --> images are multipart files
<div id="image-list" th:each="file : ${files}"> <h4 th:text="${file.file}" /> <image th:src="${file.file}" width="150px"></image>
- The above code snippet takes the image-list from the mvcUpload class in controller and displays it along with the file name --> name taken from the stored metadata
- This is where the resource handlers are stored. Needed to serve static resources (things that aren't server generated but need to be sent to the browser once they are requested)
- Used resourceHandler and resourceLocations...Handler is handling the static files while Locations is assigning a location to permanently store them --> needed to make volumes persistent
@Override public void addResourceHandlers(final ResourceHandlerRegistry registry) { registry.addResourceHandler("/volumes/uploads/**").addResourceLocations("file:volumes/uploads/"); }
<script> function uploadFile() { alert("Hello Prisha"+ fileInput.files[0].name); var innerhtml = document.getElementById("img-test").innerHTML; console.log("testing"); var formdata = new FormData(); formdata.append("filename", fileInput.files[0]); var requestOptions = { method: 'POST', body: formdata, redirect: 'follow' }; console.log("before calling the URL"); fetch("http://localhost:8192/mvc/uploader", requestOptions) .then(response => response.text()) .then(result => console.log(result)) .catch(error => console.log('error', error)); alert("Completed"); } </script>
- Fetch request to backend...alerts and consoles intertwined for testing (initially the code ran but no result was seen, needed to include alerts for debugging)
<html> <body> <form id='formid'> <input type="file" name="fileInput" id="fileInput"> <button onclick="uploadFile()" name="submit">Submit</button> </form> <br> <iframe id="img-test" src="http://localhost:8192/mvc/upload" width="100%" height="100%"> </iframe> </body> </html>
- Html button to call code. The iframe feature is used to display the mvc/upload.html in backend.