Multipart request giving issues when received in rest service - java

I am trying to upload a file using multipart request through angularjs and receive the content on my Rest service. I am putting up this question here after trying several helps for last 4 days and tiring myself to utmost level. I would appreciate if you can fine tune my approach or suggest another approach (I am open to any suggestions which may work as I am out of ideas now).
Just a pointer, I have tried writing a servlet to read the multipart request sent through angularjs and I got the parts correctly. But I am still putting the angular code here for your reference as I am not better on both angular and rest.
Following is the html extract for file upload:
<div>
<input type="file" data-file-upload multiple/>
<ul>
<li data-ng-repeat="file in files">{{file.name}}</li>
</ul>
</div>
Followingis the angularjs directive code extract:
.directive('fileUpload', function () {
return {
scope: true, //create a new scope
link: function (scope, el, attrs) {
el.bind('change', function (event) {
var files = event.target.files;
//iterate files since 'multiple' may be specified on the element
for (var i = 0;i<files.length;i++) {
//emit event upward
scope.$emit("fileSelected", { file: files[i] });
}
});
}
};
})
Following is the angularjs controller code extract
//a simple model to bind to and send to the server
$scope.model = {
name: "test",
comments: "TC"
};
//an array of files selected
$scope.files = [];
//listen for the file selected event
$scope.$on("fileSelected", function (event, args) {
$scope.$apply(function () {
//add the file object to the scope's files collection
$scope.files.push(args.file);
});
});
//the save method
$scope.save = function() {
$http({
method: 'POST',
url: "/services/testApp/settings/api/vsp/save",
headers: { 'Content-Type': undefined },
transformRequest: function (data) {
var formData = new FormData();
formData.append("model", angular.toJson(data.model));
for (var i = 0; i < data.files.length; i++) {
formData.append("file" , data.files[i]);
}
return formData;
},
data: { model: $scope.model, files: $scope.files }
}).
success(function (data, status, headers, config) {
alert("success!");
}).
error(function (data, status, headers, config) {
alert("failed!");
});
};
And here is my rest service code:
#Path("/vsp")
public class SampleService{
#Path("/save")
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public void saveProfile(#FormParam("model") String theXml,
#FormParam("file") List<File> files) throws ServletException, IOException {
final String response = "theXML: " + theXml + " and " + files.size() + " file(s) received";
System.out.println(response);
}
}
And here is the response:
theXML: {"name":"test","comments":"TC"} and 1 file(s) received
The problem is that the content of the file is coming in path and I am not able to get the input stream to read the file. I even tried using
new ByteArrayInputStream(files.get(0).getPath().getBytes())
If the content is text (like txt or csv) it works, but if the content is any other file like xls etc, the retrieved content is corrupt and unusable. Also tried using Jeresy api, but with same result. Am I missing anything obvious? Any help is appreciated.

I came across a few links, but none worked for me. So finally, I had to write a servlet to read the multipart request and added the files and the request parameters as request attributes. Once the request attributes are set, I forwarded the request to my Rest service.
Just for the record, if the multipart request is read once to extract the parts, the request will not have the parts in the forwarded servlet. So I had to set them as request attributes before forwarding.
Here is the servlet code:
public class UploadServlet extends HttpServlet {
#Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// process only if its multipart content
RequestContext reqContext = new ServletRequestContext(request);
if (ServletFileUpload.isMultipartContent(reqContext)) {
try {
List<FileItem> multiparts = new ServletFileUpload(
new DiskFileItemFactory()).parseRequest(request);
ArrayList<FileItem> fileList = new ArrayList<FileItem>();
request.setAttribute("files", fileList);
for (FileItem item : multiparts) {
if (!item.isFormField()) {
fileList.add(item);
} else {
request.setAttribute(item.getFieldName(),
item.getString());
}
}
request.setAttribute("message", "success");
} catch (Exception ex) {
request.setAttribute("message", "fail"
+ ex);
}
} else {
request.setAttribute("message",
"notMultipart");
}
System.out.println(request.getRequestURI().substring(request.getRequestURI().indexOf("upload")+6));
String forwardUri = "/api" + request.getRequestURI().substring(request.getRequestURI().indexOf("upload")+6);
request.getRequestDispatcher(forwardUri)
.forward(request, response);
}
}
Any request starting with /upload/<rest api path> will be received by the servlet and once the attributes are set, they will be forwarded to /api/<rest api path>.
In the rest api, I used the following code to retrieve the parameters.
#Path("/vsp")
public class SampleService{
#Path("/save")
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public void saveProfile(#Context HttpServletRequest request,
#Context HttpServletResponse response) throws Exception {
// getting the uploaded files
ArrayList<FileItem> items = (ArrayList<FileItem>)request.getAttribute("files");
FileItem item = items.get(0);
String name = new File(item.getName()).getName();
item.write( new File("C:" + File.separator + name));
// getting the data
String modelString = (String)request.getAttribute("model");
// Getting JSON from model string
JSONObject obj = JSONObject.parse(modelString);
String responseString = "model.name: " + obj.get("name") + " and " + items.size() + " file(s) received";
System.out.println(responseString);
}
}

Related

How to return response from rest api when uploading media file in an Angular 8 app?

I'm using a file upload example from the following link:
enter link description here
You can see in the example that the server need to return status "progress"
in order to see the progress bar.
What I have in my rest api at the moment:
#POST
#Path("Trip/{tripId}")
#Consumes("multipart/form-data")
#Produces("application/json")
public Response uploadTripVideo(#PathParam("tripId") Integer tripId, MultipartFormDataInput input){
String fileName = "";
Map<String, InputPart> uploadForm = input.getFormData();
InputPart inputPart = uploadForm.get("uploadedFile");
try {
MultivaluedMap<String, String> header = inputPart.getHeaders();
fileName = getFileName(header);
//convert the uploaded file to inputstream
InputStream inputStream = inputPart.getBody(InputStream.class,null);
byte [] bytes = IOUtils.toByteArray(inputStream);
//constructs upload file path
fileName = "C:\\Users\\name\\Documents\\myfolder\\trip\\"+ tripId + "\\video\\" + fileName;
writeFile(bytes,fileName);
} catch (IOException e) {
e.printStackTrace();
}
return Response.status(200)
.entity("uploadFile is called, Uploaded file name : " + fileName).build();
}
here is my service call:
uploadVideo(url: string, file: File): Observable<any> {
let formData = new FormData();
formData.append('uploadedFile', file, file.name);
return this.http.post<any>(this.baseUrl + url, formData, {
reportProgress: true,
observe: 'events'
}).pipe(
map(event => this.getEventMessage(event, formData)),
catchError(this.handleError)
);
}
Any idea how to return a response that should indicate on the progress? The probrem is that the event is not coming when calling the service, here is
the code where I subscribe to the post request:
this.upload.uploadVideo(url, this.videoToUpload)
.subscribe(
(event) => {
console.log(event);
if (event.type === HttpEventType.DownloadProgress) {
console.log("download progress");
}
if (event.type === HttpEventType.Response) {
console.log("donwload completed");
}
this.videoUpload = event;
//console.log("POST call successful value returned in body", val);
},
err => {
this.videoUploadError = err;
//console.log("POST call in error", response);
},
() => {
//console.log("The POST observable is now completed.");
});
What I'm getting is error in the console:
Backend returned code undefined, body was: undefined
UPDATE
I've removed the following code and things start moving:
//.pipe(
// map(event => this.getEventMessage(event, formData)),
// catchError(this.handleError)
// );
You can easily do this by setting the reportProgress flag to true in your POST HttpRequest.
The key here is to create a HttpRequest and pasing it to the HttpClient.request method rather than directly calling the post() method.
Once subscribed to the request, you need to check for the event type as
event.type == HttpEventType.UploadProgress
to perform the logic to show loading percentage as
100 * event.loaded / event.total
and check for the completion as
event.type === HttpEventType.Response
Demo at https://stackblitz.com/edit/angular-http-post-status

Downloading a Csv file From angularjs Jax-Rs and java

Hello everyone i am using angularjs java and rest to implement one report. Based on UI field selected there is a call to Java Layer and from java there is some database call and the returned input stream i am downloading in a csv file.
There is one problem happening if i do the same with hitting the the same url by browser which i m passing through angularjs than i m able to download the file but if by using UI i m making the request than there is no download option and data is returned as a stream in http response to angular.
java code:
enter code here
#Path("/files")
public class DownloadCsvFile {
#GET
#Path("/csv")
#Produces({MediaType.APPLICATION_OCTET_STREAM})
public Response getFile() {
StreamingOutput outp = new StreamingOutput() {
#Override
public void write(OutputStream out) throws IOException,
WebApplicationException {
String url ="http://someurl?
indent=on&q=RCE_POST:2016&sort=id%20asc
&rows=100000&start=0&wt=csv";
final InputStreamReader is = new InputStreamReader(
((HttpURLConnection) (new URL(url)).openConnection())
.getInputStream(),
Charset.forName("UTF-8"));
IOUtils.copy(is, out);
}
};
ResponseBuilder response = Response.ok(outp);
response.header("Content-Disposition", "attachment;
filename=\"testFile_file.csv\"");
return response.build();
} }
AngularJs controller code :
enter code here
var app = angular.module('myApp', ['ngProgress']);
app.controller('myCtrl', function($scope,$http,ngProgressFactory) {
// on submit the fun is called
$scope.LMALLPeriodReport =function()
{
return $http.get("http://localhost:8080/IsaveIdeas/rest/files/csv?
parameters="+parameter)
//parameter contain the selected field in UI
.then(function (response) {
var result = response.data;
alert("printing data");
});
};
The same request from the browser http://localhost:8080/IsaveIdeas/rest/files/csv? parameters={parameter} enable me to download the file.
You can use Blob in your angularjs code like this:
....
.then(function (response) {
var fileName = "yourFileName.csv";
var a = document.createElement("a");
document.body.appendChild(a);
response.data = "\ufeff" + response.data;
var file = new Blob([response.data], {encoding:"UTF-8",type:'application/csv;charset=UTF-8'});
var fileURL = URL.createObjectURL(file);
a.href = fileURL;
a.download = fileName;
a.click();
}

Trouble downloading binary file with angular $http or jquery.ajax

My problem is that I am getting the wrong sized file on the client side. Here is my #Controller ...
#RequestMapping(value = "/download/{id}", method = RequestMethod.GET)
public ResponseEntity<?> download(final HttpServletRequest request,
final HttpServletResponse response,
#PathVariable("id") final int id) throws IOException {
try {
// Pseudo-code for retrieving file from ID.
Path zippath = getZipFile(id);
if (!Files.exists(zippath)) {
throw new IOException("File not found.");
}
ResponseEntity<InputStreamResource> result;
return ResponseEntity.ok()
.contentLength(Files.size(zippath))
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(new FileInputStream(zippath.toFile())));
} catch (Exception ex) {
// ErrorInfo is another class, unimportant
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorInfo(ex));
}
}
... and here is my client-side code using angular-file-saver ...
$http({url: "export/download/" + exportitem.exportId, withCredentials: true})
.then(function(response) {
function str2bytes(str) {
var bytes = new Uint8Array(str.length);
for (var i=0; i<str.length; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
var blob = new Blob([str2bytes(response.data)], {type: 'application/octet-stream'});
FileSaver.saveAs(blob, "download.zip");
}, $exceptionHandler);
The original file is 935673 bytes but response.data is 900728 and passing it through the transformation to Uint8Array results in a Blob that is 900728 in size as well. Either way, the resulting saved file is 900728 bytes (34945 bytes shy). Also it is not quite the same in what gets written. It seems to slightly get bloated but then the last part just seems to be truncated. Any ideas what I might be doing wrong?
UPDATE
I just updated my controller method to be the following and got the exact same result. Grrr.
#RequestMapping(value = "/download/{id}", method = RequestMethod.GET)
public void download(final HttpServletRequest request,
final HttpServletResponse response,
#PathVariable("id") final int id) throws IOException {
// Pseudo-code for retrieving file from ID.
Path zippath = getZipFile(id);
if (!Files.exists(zippath)) {
throw new IOException("File not found.");
}
response.setContentType("application/zip");
response.setHeader("Content-Disposition",
"attachment; filename=download.zip");
InputStream inputStream = new FileInputStream(zippath.toFile());
org.apache.commons.io.IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
}
So the problem turned out to be angular's $http service. I also tried jQuery's ajax method. Both gave the same result. If I instead use the native XMLHttpRequest it works correctly. So the Java code was sound. I first verified this by exposing the file directly to the internet and then both using curl and directly accessing in the browser I managed to download the file of the correct size. Then I found this solution so that I could also download the file via javascript.
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = "blob";
xhr.withCredentials = true;
xhr.onreadystatechange = function (){
if (xhr.readyState === 4) {
var blob = xhr.response;
FileSaver.saveAs(blob, filename);
}
};
xhr.send();
Why does angular or jQuery give the wrong result? I still don't know but if anyone wishes to give an answer that uses those it would be appreciated.
responseType: blob
did the trick for a zip file
Angular 2 +
this.http.get('http://localhost:8080/export', { responseType: ResponseContentType.Blob })
.subscribe((res: any) => {
const blob = new Blob([res._body], { type: 'application/zip' });
saveAs(blob, "fileName.zip");
i just stumbled over the 'responseType' in $http requests, you are probably looking for 'blob': https://docs.angularjs.org/api/ng/service/$http#usage

How to send and catch parameter from JavaScript (AJAX request) to Servlet

I created InformationServlet that whenever I need some details I can send it what I want (with AJAX) and it will return me the information.
I searched how to do it on Ajax and according to:
How to send parameter to a servlet using Ajax Call
I used: url: "InformationServlet?param=numberOfPlayers"
But on the servlet the request's attributes doesn't contain the parameter I sent so I suppose I am not doing it correctly:
you can see that the attributes size is zero
Servlet:
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
Gson gson = new Gson();
Engine engine = (Engine)getServletContext().getAttribute("engine");
String responseJson = "";
if(request.getAttribute("numberOfPlayers") != null)
{
String numberOfPlayers = "";
numberOfPlayers = gson.toJson(String.valueOf(engine.GetNumOfPlayers()));
responseJson = numberOfPlayers;
}
out.print(responseJson);
} finally {
out.close();
}
}
JavaScript (AJAX request):
function getNumberOfPlayersAndPrintSoldiers()
{
$.ajax({
url: "InformationServlet?param=numberOfPlayers",
timeout: 2000,
error: function() {
console.log("Failed to send ajax");
},
success: function(numberOfPlayers) {
var r = numberOfPlayers;
}
});
}
Edit:
you probably want to use getParameter and not getAttribute
Moreover, please pay attention to the order of parameter name and his value:
request.getParameter("param");
instad of:
request.getParameter("numberOfPlayers");
because the url form contains parameter name first and then the parameter value. for example:
myurl.html?param=17
and if more parameters needed then use the separator & sign
myurl.html?firstName=bob&age=5

jquery ajax call returns error on successful call to a servlet

I have a servlet that adds a user to a file on the server side.
I invoke it with a jqueries ajax call.
I can see on the server that the method is being called correctly and my user is added, but the error callback is being invoked on the jquery call. All the status text says is error.
Using firebug the response seems to be empty. Why can I not get a success jquery callback?
//Servlet Code
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action = request.getParameter("action");
String responseStr = "";
if(action.equals("addUser"))
{
responseStr = addUser(request);
}
System.out.println("Reponse:" + responseStr);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().println(responseStr);
}
private String addUser(HttpServletRequest request) throws IOException
{
Storage s;
s = Storage.load();
String name = request.getParameter("name");
String imageUrl = request.getParameter("imageUrl");
User u = new User();
u.setName(name);
u.setImageUrl(imageUrl);
s.addUser(u);
s.save();
return "success";
}
.
//javascript code
function addUser() {
var name = $('#name').val();
var imageUrl = $('#imageUrl').val();
var url = "http://ws06525:8080/QCHounds/QCHoundServlet?action=addUser&name=${name}&imageUrl=${imageUrl}";
url = url.replace("${name}", name);
url = url.replace("${imageUrl}", imageUrl);
$('#result').html(url);
$.ajax({
url: url,
success: function( data ) {
$('#result').html(data);
},
error: function(jqXHR, textStatus, errorThrown)
{
alert("error: " + textStatus);
alert("error: " + errorThrown);
}
});
}
Aaargh! Feel like an idiot. It's a cross site scripting issue.
I was testing the call to the server from the html file on disk so my browser address was
file://projects/myproject/content/Users.html <<< Fail
instead of:
http://myboxname:8080/appname/Users.html <<< Works
The actual code is fine...
use this for learn what is the problem, it will be better for get solution i think
error: function(e){
alert(JSON.stringify(e))
}
For one thing the string "success" isn't valid json. If your ajax query is expecting json, that would fail it.
What if you returned "{ \"success\": true }" ?
EDIT
It looks like from your ajax call that the response shouldn't be json, why is your return content type json?
If it is true that firebug shows no response, your problem must be in the java code that writes the response.

Categories

Resources