How to close connection after Multipart Upload completes in VertX - java

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")
}

Related

Upload images from Angular to SpringBoot

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.

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";
}
}

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()
}

Stream videos in client app using Ajax and java Restlet

Hi I want to stream videos in client app but videos are located in server app. I am using java Restlet and Jquery Ajax to connect client app to server app. Through Ajax call i am connecting to Restlet. I don't know how to send response to ajax after streaming video from server side, how ajax receives response and how to play video in browser. Can any one help me to handle this.
Here is my code
Html:
<button id="playVideo" class="btn-primary">PlayVideo</button>
<video id="videoTab" height="300" width="500" style="display: none" controls ></video>
Ajax Call to server
$('#playVideo').click(function (){
var jsonObj = {};
jsonObj.userId = "siva";
jsonObj.file = "sample.mp4";
//console.log("json obje :"+ JSON.stringify(jsonObj))
// Rest call to play videos.
$.ajax({
type : 'GET',
url : config.streamVideo,
//dataType : 'json',
data : JSON.stringify(jsonObj),
contentType : "application/json",
mimeType : "video/mp4",
processData : false,
crossDomain : true,
success : function(result) {
//console.log("login result : " + JSON.stringify(result));
if (result) {
console.log("success.....");
srcPath = "data:video/mp4;"+result;
$('#videoTab').attr('src', srcPath);
$('#videoTab').css('display', 'block');
$('#videoTab').attr('autoplay', true);
} else {
alert('failed...');
}
},
error : function(){
alert('error')
}
});
});
RestletCode:
#Get
public InputRepresentation handleRequest(Representation entity) throws IOException, ResourceException {
// Set response headers
Series<Header> responseHeaders = (Series<Header>) getResponse().getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Series<Header>(Header.class);
getResponse().getAttributes().put("org.restlet.http.headers", responseHeaders);
}
responseHeaders.add(new Header("Access-Control-Allow-Origin", "*"));
logger.debug("+++++++++++++++++++Entered in play video restlet +++++++++++++++");
// Convert Rest type request to Servlet request
httpServletRequest = ServletUtils.getRequest(getRequest());
// Get Servlet context object.
sc = httpServletRequest.getServletContext();
// Get input file path.
logger.debug("------->getRealPath " + sc.getRealPath("/"));
String filePath = sc.getRealPath("/") + "WEB-INF\\data\\videos\\sample.mp4";
final File file = new File(filePath);
if (file.exists()) {
logger.debug("Requested file path : " + file.getAbsolutePath());
logger.debug("inputRepresentation :" + inputRepresentation);
fis = new FileInputStream(file);
inputRepresentation = new InputRepresentation(new InputStream() {
private boolean waited = false;
#Override
public int read() throws IOException {
waited = false;
// read the next byte of the FileInputStream, when reaching the
// end of the file, wait for 2 seconds and try again, in case
// the file was not completely created yet
while (true) {
byte[] b = new byte[1];
if (fis.read(b, 0, 1) > 0) {
return b[0] + 256;
} else {
if (waited) {
return -1;
} else {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
logger.error("Exception while streaming video : ", ex);
}
waited = true;
}
}
}
}
}, MediaType.VIDEO_MP4);
} else {
logger.debug("Requested file not found : " + filePath);
}
//logger.debug("inputRepresentation :");
return inputRepresentation;
}
Thanks in advance
After reading your comment, here is my understanding of what you should do.
I would not send json to a resource in order to get something, I would just send a simple GET request.
You need:
a resource that returns the file of a video according to its identifier. For the matter of illustration, let's say its url template is /videos/{videoid}
a web page that contains the links, and the empty video player
some javascript that set the "src" attribute video player with the url defined above: /videos/{videoid}. The way you compute the videoid is your own business.
Here is the server code:
the Restlet application, that defines the URI templates
#Override
public Restlet createInboundRoot() {
Router router = new Router(getContext());
// attaches the resource that represents a video, according to its identifier
router.attach("/videos/{videoid}", VideoServerResource.class);
// ... other instructions
return router;
}
the video server resource:
public class VideoServerResource extends ServerResource {
private File video;
#Override
protected void doInit() throws ResourceException {
String videoId = getAttribute("videoid");
// Compute path
String path = "/tmp/" + videoId + ".mp4";
video = new File(path);
// takes care of not found status responses.
setExisting(video.isFile());
}
#Get("mp4")
public File represent() {
return video;
}
}
Here is the client code. This is a sample Web page, with an empty video player. When clicking on the button, the video player is asked to play the http://example.com:9000/videos/testvideo video. In your case, the value testvideo is simply deduced from the link the user click on.
<!DOCTYPE html>
<html>
<head>
<script src="/static/jquery.js"></script>
<script>
$('#playVideo').click(function (){
srcPath = "http://127.0.0.1:9000/videos/testvideo";
$('#videoTab').attr('src', srcPath);
$('#videoTab').css('display', 'block');
$('#videoTab').attr('autoplay', true);
});
</script>
</head>
<body>
<button id="playVideo" class="btn-primary">PlayVideo</button>
<video id="videoTab" height="300" width="500" style="display: none" controls ></video>
</body>
</html>
I hope this will help you.

How to print a page content (div, form, jqgrid) using a single button click?

i.e. When I click the button from my page, the desired page content should get printed in a sheet. The main goal over here is, it should not show me with the print dialog box/print preview of the page asking for OK or CANCEL button where we can also choose for multiple prints of a particular page. Thanks in advance.
Create a print.js file by this code :
// -----------------------------------------------------------------------
(function($) {
var opt;
$.fn.jqprint = function (options) {
opt = $.extend({}, $.fn.jqprint.defaults, options);
var $element = (this instanceof jQuery) ? this : $(this);
if (opt.operaSupport && $.browser.opera)
{
var tab = window.open("","jqPrint-preview");
tab.document.open();
var doc = tab.document;
}
else
{
var $iframe = $("");
if (!opt.debug) { $iframe.css({ position: "absolute", width: "0px", height: "0px", left: "-600px", top: "-600px" }); }
$iframe.appendTo("body");
var doc = $iframe[0].contentWindow.document;
}
if (opt.importCSS)
{
if ($("link[media=print]").length > 0)
{
$("link[media=print]").each( function() {
doc.write("");
});
}
else
{
$("link").each( function() {
doc.write("");
});
}
}
if (opt.printContainer) { doc.write($element.outer()); }
else { $element.each( function() { doc.write($(this).html()); }); }
doc.close();
(opt.operaSupport && $.browser.opera ? tab : $iframe[0].contentWindow).focus();
setTimeout( function() { (opt.operaSupport && $.browser.opera ? tab : $iframe[0].contentWindow).print(); if (tab) { tab.close(); } }, 1000);
}
$.fn.jqprint.defaults = {
debug: false,
importCSS: true,
printContainer: true,
operaSupport: true
};
// Thanks to 9__, found at http://users.livejournal.com/9__/380664.html
jQuery.fn.outer = function() {
return $($('').html(this.clone())).html();
}
})(jQuery);
And then include your print.js on an html page and see the demo of this :
<script>
jQuery(document).ready(function () {
jQuery("#printBtn").click(function(){
jQuery("#print").jqprint();
});
});
</script>
<input type="button" id="printBtn" value="Print" />
<div id="print">
This will print this content.
</div>
What is the browser you are targeting? There are some browser specific ways of doing this.
For IE :
<script language='VBScript'>
Sub Print()
OLECMDID_PRINT = 6
OLECMDEXECOPT_DONTPROMPTUSER = 2
OLECMDEXECOPT_PROMPTUSER = 1
call WB.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER,1)
End Sub
document.write "<object ID='WB' WIDTH=0 HEIGHT=0 CLASSID='CLSID:8856F961-340A-11D0-A96B-00C04FD705A2'></object>
</script>
window.print();
Ref : msdn blog

Categories