Upload images from Angular to SpringBoot - java

I was trying for hours to send images from Angular to SpringBoot. Now I'm getting this error:
org.springframework.web.multipart.MultipartException: Current request is not a multipart request
Frontend(Angular) code looks like this:
saveProduct(productSave: ProductSave, mainImg: File, imageList: File[]): Observable<any>
{
const formData = new FormData();
const formListData = new FormData();
formData.append('mainImg', mainImg, mainImg.name);
imageList.forEach( img => formListData.append('imageList', img));
return this.httpClient.post<ProductSave>(this.saveUrl, {productSave, mainImg, imageList});
}
mainImg and imageList are images uploaded from user, and initialized like so:
mainImg = event.target.files[0];
imageList.push(event.target.files[0]);
My backend (SpringBoot) code looks like this:
#PostMapping("/save")
public void saveProduct(#RequestBody ProductSave productSave, #RequestParam("mainImg")MultipartFile main, #RequestParam("imageList")MultipartFile[] multipartFiles)
{
System.out.println(productSave.getProduct().getName());
}
I really don't have idea how to send those images, I was trying to look around stack but I faild.
Thanks for any help!

The problem is in the Spring Boot Controller method Arguments.
In multipart request, you can send the JSON Request body. Instead, you will have to send, key-value pairs.
So, you have 2 ways to do what you want to do -
Send JSON String and then deserialize it.
Spring Boot API Sample :-
#PostMapping("/save")
public String create(#RequestPart("file")MultipartFile multipartFile, #RequestPart("files") List<MultipartFile> multipartFiles, #RequestPart("jsonString") String jsonString) {
/** To convert string to POJO
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
ProductSave productSave = this.objectMapper.readValue(jsonString,ProductSave.class); **/
return jsonString;
}
HTML/Javascript Sample: -
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<input type="file" id="file">
<button onclick="submitData()">Submit Data</button>
<script type="text/javascript">
function submitData() {
const formData = new FormData();
const fileField = document.querySelector('input[id="file"]');
formData.append('jsonString', JSON.stringify({document: {data: 'value'}}));
formData.append('file', fileField.files[0]);
Array.from(fileField.files).forEach(f => formData.append('files', f));
fetch('http://localhost:8080/save', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(result => {
console.log('Success:', result);
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
</body>
And in front end:-
JSON.stringify(product);
Send Files as Byte Arrays, You don't need to use form data in frontend in this case.
You can convert file object to byte arrays in frontend using:-
const fileToByteArray = async (file) => {
return new Promise((resolve, reject) => {
try {
let reader = new FileReader();
let fileByteArray = [];
reader.readAsArrayBuffer(file);
reader.onloadend = (evt) => {
if (evt.target.readyState === FileReader.DONE) {
const arrayBuffer = evt.target.result;
const array = new Uint8Array(arrayBuffer);
array.forEach((item) => fileByteArray.push(item));
}
resolve(fileByteArray);
};
} catch (e) {
reject(e);
}
});
};

in the application.properties try setting up the following -
spring.servlet.multipart.max-file-size=1024KB
spring.servlet.multipart.max-request-size=1024KB
spring.http.multipart.enabled=false
Play around with the max-file-size.

Related

How to close connection after Multipart Upload completes in VertX

I'm building a streaming upload in VertX so that I can stream an upload directly to a Google Cloud / AWS S3 bucket, but the upload never seems to end when looking at the network tab in the browser.
This is the test upload form I'm using:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form>
<input type="text" value="blah">
<input multiple type="file">
</form>
<div>
</div>
<script>
let input = document.querySelector("input[type='file']")
let div = document.querySelector("div")
input.addEventListener("change", event => {
let files = event.target.files
console.log(files)
const formData = new FormData()
for (let file of files) {
formData.append(file.name, file)
formData.append("blah", "blah")
}
window.fetch("http://localhost:11111/le-upload-test?testing=true", {
method: "POST",
body: formData
}).then(response => {
div.innerHTML = ""
div.append(`${response.statusText} - ${response.status}`)
console.log(response)
})
})
</script>
</body>
</html>
In the backend,
class MyServer(
val port: Int,
) : CoroutineVerticle() {
val log = LoggerFactory.getLogger(this.javaClass)
init {
Vertx.vertx()?.deployVerticle(this)
?: throw Exception("Failed to start VertX")
}
override suspend fun start() {
vertx.createHttpServer().requestHandler { req ->
println(req.path())
if (req.path() == "/le-upload-test") {
req.isExpectMultipart = true
req.uploadHandler { upload ->
println("==================")
println(req.params())
println(upload.filename())
println("==================")
upload.handler { chunk ->
println("chunk.length=${chunk.length()}")
println("total.read=${req.bytesRead()}")
}
}
}
}.listen(port)
}
}
fun main() {
MyServer(
port = 11111,
)
}
When uploading multiple files, this correctly outputs:
/le-upload-test
==================
testing=true
Screenshot_20201026_211340.png
==================
chunk.length=239
total.read=422
chunk.length=8192
total.read=8614
chunk.length=8192
...
==================
testing=true
Screenshot_20201026_181456.png
==================
chunk.length=192
total.read=74150
chunk.length=7770
total.read=81920
...
At what point should I be calling req.response.end("...")?
Unless I restart the server, the browser just hangs there indefinitely?
I've tried doing:
req.uploadHandler { upload ->
val contentLength = req.getHeader("Content-Length")
upload.handler { chunk ->
if (req.bytesRead().toString() == contentLength) {
println("DONE!!")
req.response().setStatusCode(200).end("DONE")
}
}
}
This correctly prints DONE when all the bytes are processed, but the browser then shows failed, EMPTY RESPONSE.
Adding an upload.endHandler after upload.handler
upload.endHandler { end ->
println("DONE!!!")
req.response().setStatusCode(200).end("TEST")
}
does print the DONE!!! part correctly when all the bytes are processed, but also never closes the upload connection with a status 200.
Managed to get it working, seems all it was missing was a CORS header
override suspend fun start() {
vertx.createHttpServer().requestHandler { req ->
println(req.path())
try {
if (req.path() == "/le-upload-test") {
req.response().putHeader("Access-Control-Allow-Origin", "*")
req.isExpectMultipart = true
req.uploadHandler { upload ->
req.endHandler {
req.response().end("BLAH!!")
println("DONE")
}

Uploading image to database using AJAX is not uploading as blob

I have a webpage that is supposed to upload an image to the database with a name to describe the image. Think uploading a logo and the name of the companies logo.
When I select the image file and submit it uploads to the database and I can return that information to the webpage in a list. However, it is not encoded in the manner that I was expecting. I would like the image file to be uploaded as a blob so that I may convert the blob to Base64 and pass it to my web application.
This is what the blob code looks like if I manually upload the images using MySQLs gui.
"iVBORw0KGgoAAAANSUhEUgAACWAAAAnHCAYAAAAIV..." which I'm able to convert to Base64 later.
When I use my ajax web page to upload an image however, I receive
"QzpcZmFrZXBhdGhcU3ByaW5nLnBuZw==".
My question is, how can I have ajax upload it as a blob instead so that my Java application can properly call the blob and convert it to Base64?
ajax.js
$(function (){
var $skills = $('#skills');
var $logo = $('#logo');
var $techName = $('#techName');
$.ajax({
type: 'GET',
url: '/api/technologyList',
success: function(skills) {
$.each(skills, function(i, skill) {
$('#skills-list').append('<tr><td> ' + skill.logo + '</td>' + '<td>' + skill.techName + '</td></tr>')
})
}
})
$('#addSkill').on('click', function () {
var skill = {
techName: $techName.val(),
logo: $logo.val()
}
$.ajax({
type: 'POST',
url:'/api/technologyList',
data: skill,
contentType: "multipart/form-data",
processData: false,
success: function (newSkill) {
$('#skills-list').append('<tr><td> '+ newSkill.logo+ '</td>' +
'<td> '+ newSkill.techName + '</td></tr>')
console.log(skill)
}
})
})
})
addSkill.html
<table id="skills-list">
<tr>
<th>Logo</th>
<th>Technology</th>
</tr>
</table>
<form id="skillForm">
<input type="text" id="techName"/> <br>
<input type="file" enctype="multipart/form-data" id="logo"/>
<button id="addSkill">Add!</button>
</form>
HomeController
#GetMapping(value = "/technology")
public String technologyList(Model theModel) throws IOException {
try {
List<Skills> userSkillsList = skillsService.findSkillList("wmangram");
List<byte[]> logo = skillsService.findLogos();
List<String> base64List = new ArrayList<>();
boolean isBase64 = false;
for (int i = 0; i < logo.size(); i++) {
if (Base64.isBase64(logo.get(i))) {
String base64Encoded = new String((logo.get(i)), "UTF-8");
base64List.add(base64Encoded);
}
else {
byte[] encodeBase64 = Base64.encodeBase64(logo.get(i));
String base64Encoded = new String(encodeBase64, "UTF-8");
base64List.add(base64Encoded);
}
}
theModel.addAttribute("userSkills", userSkillsList);
theModel.addAttribute("userImages", base64List);
return "technology";
}
catch (NullPointerException nexc) {
return "nullexception";
}
}
You have to use a FormData object to upload multipart/form-data1 via ajax.
$('#addSkill').on('click', function () {
var skill = new FormData();
skill.append("techName", $techName.val());
skill.append("logo", $logo.prop("files")[0]);
$.ajax({
type: 'POST',
url:'/api/technologyList',
data: skill,
contentType: false, //don't set this, it will be set automatically and properly
processData: false,
success: function (newSkill) {
$('#skills-list').append('<tr><td> '+ newSkill.logo+ '</td>' +
'<td> '+ newSkill.techName + '</td></tr>')
console.log(skill)
}
})
})
Looking at the java code it doesn't look like it can handle a file upload, so this answer is only for the client side code.
This isn't strictly true but you wouldn't want to have to do it any other way.
The problem was that I wasn't handling the file in a manner that let the program read the files contents. Instead it was just receiving the fake file path with the file name.
Fixed by utilizing #RequestParam and MultipartFile then assigning to the object before passing to the DAO.
RESTController.java
#PostMapping("/technologyList")
public String uploadMultipartFile(#RequestParam("logo") MultipartFile file, #RequestParam("techName")String techName) {
User user = userService.findByUsername("wmangram");
try {
// save file to MySQL
Skills newSkill = new Skills(techName, file.getBytes(), user);
skillsService.createTechnology(newSkill);
return "File uploaded successfully! -> filename = " + file.getOriginalFilename();
} catch (Exception e) {
return "FAIL! Maybe You had uploaded the file before or the file's size > 500KB";
}
}

Passing parameters from java to html to create google chart

There are four query values which I want to put in google chart value1 to value4. The problem is passing values from java to html. Under below I posted relevant codes.
Those
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load("current", {packages:["corechart"]});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Task', 'Hours per Day'],
['value1', **3**],
['value2', **2**],
['value3', **2**],
['value4', **2**]
]);
var options = {
title: 'My Daily Activities',
pieHole: 0.4,
};
var chart = new google.visualization.PieChart(document.getElementById('donutchart'));
chart.draw(data, options);
}
</script>
And this is java class.
public ResponseEntity<Map<String,Object>> status() {
Map<String,Object> map = new HashMap<String,Object>();
long countByXmlSuccessResult = statusRepository.countByXmlSuccessResult();
long countByXmlErrorResult = statusRepository.countByXmlErrorResult();
long countByJsonSuccessResult = statusRepository.countByJsonSuccessResult();
long countByJsonErrorResult = statusRepository.countByJsonErrorResult();
map.put("xml success:", **countByXmlSuccessResult**);
map.put("xml error:", **countByXmlErrorResult**);
map.put("json success:", **countByJsonSuccessResult**);
map.put("json error:", **countByJsonErrorResult**);
return new ResponseEntity<Map<String,Object>>(map, HttpStatus.OK);
}
plus, html file path is under src/main/java folder and java file path is under src/main/resources folder.
You can expose that Java functionality as a service, and then make an ajax call to that service using jquery and finally grab the result and use it in your google chart code.
Ex:
Spring Boot:
#RestController
public class myHomeController{
...
#RequestMapping("/getValues")
public someDomainClass getMyValues(){
...
//someDomainClass is just a reg. pojo to store your values.
someDominClass class1 = new someDomainClass(value1, value2, value3
, value4);
return class1;
}
}
Your HTML file:
...
<script>
$.ajax({
url: "/getValues",
type: "GET",
success: function(result){
//result is a json object containing your values 1..4.
},
failure: functtion(err){...}
});
</script>
Best of luck :)

Angular JS - How to get spring returned map value inside angular controller?

I am new to angular, can anyone tell me how to retrieve spring returned map value inside angular's controller?
Here is my code snippet:
app.js
// Service -----------------------------------------------------------------
myApp.service('FolderService', function ($log, $resource, $http) {
return {
onlineView: function(docId) {
var viwerResource = $resource('processOnlineView', {}, {
get: {method: 'POST', params: {'docId' : docId}}
});
return viwerResource.get();
}
}
})
// Controller -----------------------------------------------------------------
.controller('FolderController', function ($scope, $log, FolderService) {
//click online view
$scope.view = function(doc) {
var rtnMap = FolderService.onlineView(doc.cmObjectId);
console.log('rtnMap: ' + rtnMap );
// it shows rtnMap: [object Object]
var key = 'response';
var value = rtnMap[key];
console.log('value: ' + value );
// I want to get map value, but it shows undefined
// I except get "d:/tomcat/bin/hello.swf" here
$scope.rtnFileName = rtnMap;
}
});
my spring controller java code
#RequestMapping(value = "/processOnlineView", method = RequestMethod.POST)
public #ResponseBody Map<String, String> processOnlineView(#RequestParam(value = "docId") String docId) {
String resultDocName = "";
try {
// get File by docId
File file = queryFile(docId);
// set resultDocName value
resultDocName = file.getAbsolutePath(); // real file path, like: d:/tomcat/bin/hello.swf
} catch (Exception e) {
e.printStackTrace();
}
return Collections.singletonMap("response", resultDocName);
}
chrome log:
I can get expect value in html by using this:
rtnFileName: {{rtnFileName.response}}
html shows:
rtnFileName: d:/tomcat/bin/hello.swf
But how to get map value in angular controller directly?
Any suggestion would be appreciated.
Problem solved.
First, use $http post instead of $resource:
onlineView: function(docId) {
$http({
method: 'POST',
url: urlBase + '/processOnlineView',
params: {
docId: docId
}
})
.success(function(data, status, headers, config) {
console.log('success data: ' + data); // result: success data: [object Object]
for (key in data){
console.log('>> data key: ' + key );
console.log('>> data value: ' + data[key] );
}
var resultDocName = data['response'];
console.log('resultDocName: ' + resultDocName);
runFlexpaper(resultDocName);
})
.error(function(data, status, headers, config) {
});
}
Second, retrieve returned map inside 'success' block, because $http post is asynchronous call.
Use a service. For example:
var app = angular.module('myApp', [])
app.service('sharedProperties', function () {
var mapCoord= 'Test';
return {
getProperty: function () {
return mapCoord;
},
setProperty: function(value) {
mapCoord= value;
}
};
});
Inside your Main controller
app.controller('Main', function($scope, sharedProperties) {
$scope.mapCoord= sharedProperties.setProperty("Main");
});
Inside your Map controller
app.controller('Map', function($scope, sharedProperties) {
$scope.mapCoord= sharedProperties.getProperty();
});

Spring MVC output CSV to highcharts

I am trying to populate highcharts chart using a csv file that I will generate in java back end and send to the from end using spring mvc.
First my controller class which I am almost positive is the issue but I don't know how to correctly send the csv file:
#Controller
public class ChartController {
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String indexHandler() {
return "index";
}
#RequestMapping(value = "/out", method = GET)
public String chartHandler() {
String fileName = "test.csv" //note: I have also tried moving this
//file to my WEB-INF location and it doesn't make a difference
InputParser input = new InputParser();
for (GenericDataObject gdo : input.getDataObjects()) {
CSVOutput.writeCSV(fileName,gdo);
}
return "index";
}
}
The csv file is successfully created as I intend so that's not an issue
here is my Java script for highcharts
<html>
<head>
<script
src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script src="http://code.highcharts.com/highcharts.js"></script>
<script src="http://code.highcharts.com/modules/data.js"></script>
<script src="http://code.highcharts.com/modules/exporting.js"></script>
<some more high charts for font and color that I will leave out because its not currently being used>
</head>
<body>
<div id='container' style="width: 100%; height: 600px;"></div>
<script type="text/javascript">
$(document)ready.function() {
var groupId = [];
var date = [];
var val = [];
var options = {
chart: {
renderTo: 'container',
type: 'line'
},
title: {
text: 'test'
},
xAxis: {
title: {
text: 'group and date'
},
categories: [groupId, date]
},
yAxis: {
title: {
text: 'data'
}
},
series: [{
data: val
}]
};
$.get('http://localhost:8080/web-data-app/out', function(data)) {
alert("success");
var lines = data.split('\n')
$.each(lines, function(lineNo, line) {
var items = line.split(',');
groupId.push(items[1]);
date.push(items[2]);
val.push(items[4]);
});
var cahrt = new Highcharts.Chart(options);
});
});
</script>
</body>
</html>
As of right now I get the outline of highcharts in my container, as well as the success alert so i know that much is working. however no data is being displaying withing the chart.
You can use httpResponse and send your content there. Like this
#RequestMapping(value = "/out", method = GET)
public void chartHandler(HttpServletResponse httpResponse) {
String fileName = "test.csv" //note: I have also tried moving this
//file to my WEB-INF location and it doesn't make a difference
InputParser input = new InputParser();
for (GenericDataObject gdo : input.getDataObjects()) {
CSVOutput.writeCSV(fileName,gdo);
}
httpResponse.setContentType("text/csv");
//you can use the output stream below to pass your content
httpResponse.getOutputStream()
}

Categories