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