struts2 file upload loosing parameters - java

Using Struts 2.3.15.1
Implementing file upload in struts2. This is something I've done a number of times, however, I'm trying to include some sanity checks (i.e. max file size primarily). I have the fileUpload interceptor in place as the last interceptor in my stack (i.e. struts.xml). My stack includes a few in-house interceptors as well as the validationWorkflowStack. I've set the following property in my struts.properties file:
struts.multipart.maxSize = 2000000
In addition to the file upload, I'm passing a few other params in my form. Form is defined as:
<s:form action="addResource" method="post" enctype="multipart/form-data">
<s:hidden name="rfqId" value='%{rfq.id}' />
<s:file name="uploadFile" id="uploadFile" label="File" size="40" value=""/>
....
</s:form>
As I'm sure we all know, the validationWorkflowStack includes the params interceptor, which sets the request params onto the action. Here's the issue, when the file being uploaded exceeds the maxSize, there are no params for the params interceptor to set. I've stepped through is and there's nothing in the actionContext. This is not good, because I need those params to handle the INPUT error that will result.
Am I missing something?

Problem solved !
From the updated documentation, now the problem can be solved by using the new JakartaStreamMultiPartRequest :
As from Struts version 2.3.18 a new implementation of MultiPartRequest
was added - JakartaStreamMultiPartRequest. It can be used to handle
large files, see WW-3025 for more details, but you can simple set
<constant name="struts.multipart.parser" value="jakarta-stream" />
in struts.xml to start using it.
From the linked JIRA body :
When any size limits exceed, immediately a
FileUploadBase.SizeLimitExceededException or
FileUploadBase.FileSizeLimitExceededException is thrown and parsing of
the multipart request terminates without providing request parameters
for further processing.
This basically makes it impossible for any web application to handle
size limit exceeded cases gracefully.
My proposal is that request parsing should always complete to deliver
the request parameters. Size limit exceeded cases/exceptions might be
collected for later retrieval, FileSizeLimitExeededException should be
mapped to the FileItem to allow some validation on the FileItem on
application level. This would allow to mark upload input fields as
erronous if the uploaded file was too big.
Actually I made a patch for that (see attachment). With this patch,
commons-fileupload always completes request parsing in case of size
limit exceedings and only after complete parsing will throw an
exception if one was detected.
and Chris Cranford's comment:
I am working on a new multipart parser for Struts2 I am calling
JakartaStreamMultiPartRequest.
This multi-part parser behaves identical to the existing Jakarta
multi-part parser except that it uses the Commons FileUpload Streaming
API and rather than delegating maximum request size check to the File
Upload API, it's done internally to avoid the existing problem of the
Upload API breaking the loop iteration and parameters being lost.
Awesome, thanks guys :)
Old answer
I guess it is due to the different behavior of
a single file (or more files) that is exceeding its maximum defined size, and then can be redirected back at the end of a normal process with the INPUT result, and
the violation of the maximum size of the entire Request, that will (probably?) break any other element parsing, because it is a security mechanism, not a feature like the file size check;
When the files are parsed first (it should depend on their order in the page), if a file breaks the limit of the multipart request size, the other fields (the form fields) won't be read and hence not returned back with the INPUT result.
Struts2 uses the Jakarta implementation for the MultiPartRequestWrapper:
struts.multipart.parser - This property should be set to a class that extends MultiPartRequest. Currently, the framework ships with the Jakarta FileUpload implementation.
You can find the source code on Struts2 official site or here (faster to google); this is what is called when posting a multipart form:
public void parse(HttpServletRequest request, String saveDir) throws IOException {
try {
setLocale(request);
processUpload(request, saveDir);
} catch (FileUploadBase.SizeLimitExceededException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Request exceeded size limit!", e);
}
String errorMessage = buildErrorMessage(e, new Object[]{e.getPermittedSize(), e.getActualSize()});
if (!errors.contains(errorMessage)) {
errors.add(errorMessage);
}
} catch (Exception e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Unable to parse request", e);
}
String errorMessage = buildErrorMessage(e, new Object[]{});
if (!errors.contains(errorMessage)) {
errors.add(errorMessage);
}
}
}
then, this is where it cycles the multipart Items, both files and form fields:
private void processUpload(HttpServletRequest request, String saveDir) throws FileUploadException, UnsupportedEncodingException {
for (FileItem item : parseRequest(request, saveDir)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Found item " + item.getFieldName());
}
if (item.isFormField()) {
processNormalFormField(item, request.getCharacterEncoding());
} else {
processFileField(item);
}
}
}
that will end, in the FileUploadBase, in this implementation for each item:
FileItemStreamImpl(String pName, String pFieldName,
String pContentType, boolean pFormField,
long pContentLength) throws IOException {
name = pName;
fieldName = pFieldName;
contentType = pContentType;
formField = pFormField;
final ItemInputStream itemStream = multi.newInputStream();
InputStream istream = itemStream;
if (fileSizeMax != -1) {
if (pContentLength != -1
&& pContentLength > fileSizeMax) {
FileSizeLimitExceededException e =
new FileSizeLimitExceededException(
format("The field %s exceeds its maximum permitted size of %s bytes.",
fieldName, fileSizeMax),
pContentLength, fileSizeMax);
e.setFileName(pName);
e.setFieldName(pFieldName);
throw new FileUploadIOException(e);
}
istream = new LimitedInputStream(istream, fileSizeMax) {
#Override
protected void raiseError(long pSizeMax, long pCount)
throws IOException {
itemStream.close(true);
FileSizeLimitExceededException e =
new FileSizeLimitExceededException(
format("The field %s exceeds its maximum permitted size of %s bytes.",
fieldName, pSizeMax),
pCount, pSizeMax);
e.setFieldName(fieldName);
e.setFileName(name);
throw new FileUploadIOException(e);
}
};
}
stream = istream;
}
as you can see, it handles pretty differently the file size cap and the request size cap;
I've looked at the source for fun but you could really confirm (or correct) this assumptions, trying to debug the MultiPartRequestWrapper to see if what happens inside is what I think is going on... good luck and have fun.

Here's how I've worked around this issue. I wouldn't call this a solution.

Try putting a javascript check at at the early stage :
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function checkSize(max_img_size)
{
var input = document.getElementById("upload");
// check for browser support (may need to be modified)
if(input.files && input.files.length == 1)
{
if (input.files[0].size > max_img_size)
{
alert("The file must be less than " + (max_img_size/1024/1024) + "MB");
return false;
}
}
return true;
}
</script>
</head>
<body>
<form action="demo_post_enctype.asp" method="post" enctype="multipart/form-data"
onsubmit="return checkSize(2097152)">
<input type="file" id="upload" />
<input type="submit" />
</body>
</html>

Related

response has already been committed [duplicate]

This method throws
java.lang.IllegalStateException: Cannot forward after response has been committed
and I am unable to spot the problem. Any help?
int noOfRows = Integer.parseInt(request.getParameter("noOfRows"));
String chkboxVal = "";
// String FormatId=null;
Vector vRow = new Vector();
Vector vRow1 = new Vector();
String GroupId = "";
String GroupDesc = "";
for (int i = 0; i < noOfRows; i++) {
if ((request.getParameter("chk_select" + i)) == null) {
chkboxVal = "notticked";
} else {
chkboxVal = request.getParameter("chk_select" + i);
if (chkboxVal.equals("ticked")) {
fwdurl = "true";
Statement st1 = con.createStatement();
GroupId = request.getParameter("GroupId" + i);
GroupDesc = request.getParameter("GroupDesc" + i);
ResultSet rs1 = st1
.executeQuery("select FileId,Description from cs2k_Files "
+ " where FileId like 'M%' and co_code = "
+ ccode);
ResultSetMetaData rsm = rs1.getMetaData();
int cCount = rsm.getColumnCount();
while (rs1.next()) {
Vector vCol1 = new Vector();
for (int j = 1; j <= cCount; j++) {
vCol1.addElement(rs1.getObject(j));
}
vRow.addElement(vCol1);
}
rs1 = st1
.executeQuery("select FileId,NotAllowed from cs2kGroupSub "
+ " where FileId like 'M%' and GroupId = '"
+ GroupId + "'" + " and co_code = " + ccode);
rsm = rs1.getMetaData();
cCount = rsm.getColumnCount();
while (rs1.next()) {
Vector vCol2 = new Vector();
for (int j = 1; j <= cCount; j++) {
vCol2.addElement(rs1.getObject(j));
}
vRow1.addElement(vCol2);
}
// throw new Exception("test");
break;
}
}
}
if (fwdurl.equals("true")) {
// throw new Exception("test");
// response.sendRedirect("cs2k_GroupCopiedUpdt.jsp") ;
request.setAttribute("GroupId", GroupId);
request.setAttribute("GroupDesc", GroupDesc);
request.setAttribute("vRow", vRow);
request.setAttribute("vRow1", vRow1);
getServletConfig().getServletContext().getRequestDispatcher(
"/GroupCopiedUpdt.jsp").forward(request, response);
}
forward/sendRedirect/sendError do NOT exit the method!
A common misunderstanding among starters is that they think that the call of a forward(), sendRedirect(), or sendError() would magically exit and "jump" out of the method block, hereby ignoring the remnant of the code. For example:
protected void doXxx() {
if (someCondition) {
sendRedirect();
}
forward(); // This is STILL invoked when someCondition is true!
}
This is thus actually not true. They do certainly not behave differently than any other Java methods (expect of System#exit() of course). When the someCondition in above example is true and you're thus calling forward() after sendRedirect() or sendError() on the same request/response, then the chance is big that you will get the exception:
java.lang.IllegalStateException: Cannot forward after response has been committed
If the if statement calls a forward() and you're afterwards calling sendRedirect() or sendError(), then below exception will be thrown:
java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed
To fix this, you need either to add a return; statement afterwards
protected void doXxx() {
if (someCondition) {
sendRedirect();
return;
}
forward();
}
... or to introduce an else block.
protected void doXxx() {
if (someCondition) {
sendRedirect();
}
else {
forward();
}
}
To naildown the root cause in your code, just search for any line which calls a forward(), sendRedirect() or sendError() without exiting the method block or skipping the remnant of the code. This can be inside the same servlet before the particular code line, but also in any servlet or filter which was been called before the particular servlet.
In case of sendError(), if your sole purpose is to set the response status, use setStatus() instead.
Do not write any string before forward/sendRedirect/sendError
Another probable cause is that the servlet writes to the response while a forward() will be called, or has been called in the very same method.
protected void doXxx() {
out.write("<p>some html</p>");
// ...
forward(); // Fail!
}
The response buffer size defaults in most server to 2KB, so if you write more than 2KB to it, then it will be committed and forward() will fail the same way:
java.lang.IllegalStateException: Cannot forward after response has been committed
Solution is obvious, just don't write to the response in the servlet. That's the responsibility of the JSP. You just set a request attribute like so request.setAttribute("data", "some string") and then print it in JSP like so ${data}. See also our Servlets wiki page to learn how to use Servlets the right way.
Do not write any file before forward/sendRedirect/sendError
Another probable cause is that the servlet writes a file download to the response after which e.g. a forward() is called.
protected void doXxx() {
out.write(bytes);
// ...
forward(); // Fail!
}
This is technically not possible. You need to remove the forward() call. The enduser will stay on the currently opened page. If you actually intend to change the page after a file download, then you need to move the file download logic to page load of the target page. Basically: first create a temporary file on disk using the way mentioned in this answer How to save generated file temporarily in servlet based web application, then send a redirect with the file name/identifier as request param, and in the target page conditionally print based on the presence of that request param a <script>window.location='...';</script> which immediately downloads the temporary file via one of the ways mentioned in this answer Simplest way to serve static data from outside the application server in a Java web application.
Do not call forward/sendRedirect/sendError in JSP
Yet another probable cause is that the forward(), sendRedirect() or sendError() methods are invoked via Java code embedded in a JSP file in form of old fashioned way <% scriptlets %>, a practice which was officially discouraged since 2003. For example:
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
...
<% sendRedirect(); %>
...
</body>
</html>
The problem here is that JSP internally immediately writes template text (i.e. HTML code) via out.write("<!DOCTYPE html> ... etc ...") as soon as it's encountered. This is thus essentially the same problem as explained in previous section.
Solution is obvious, just don't write Java code in a JSP file. That's the responsibility of a normal Java class such as a Servlet or a Filter. See also our Servlets wiki page to learn how to use Servlets the right way.
See also:
What exactly does "Response already committed" mean? How to handle exceptions then?
Unrelated to your concrete problem, your JDBC code is leaking resources. Fix that as well. For hints, see also How often should Connection, Statement and ResultSet be closed in JDBC?
even adding a return statement brings up this exception, for which only solution is this code:
if(!response.isCommitted())
// Place another redirection
Typically you see this error after you have already done a redirect and then try to output some more data to the output stream. In the cases where I have seen this in the past, it is often one of the filters that is trying to redirect the page, and then still forwards through to the servlet. I cannot see anything immediately wrong with the servlet, so you might want to try having a look at any filters that you have in place as well.
Edit: Some more help in diagnosing the problem…
The first step to diagnosing this problem is to ascertain exactly where the exception is being thrown. We are assuming that it is being thrown by the line
getServletConfig().getServletContext()
.getRequestDispatcher("/GroupCopiedUpdt.jsp")
.forward(request, response);
But you might find that it is being thrown later in the code, where you are trying to output to the output stream after you have tried to do the forward. If it is coming from the above line, then it means that somewhere before this line you have either:
output data to the output stream, or
done another redirect beforehand.
Good luck!
You should add return statement while you are forwarding or redirecting the flow.
Example:
if forwardind,
request.getRequestDispatcher("/abs.jsp").forward(request, response);
return;
if redirecting,
response.sendRedirect(roundTripURI);
return;
This is because your servlet is trying to access a request object which is no more exist..
A servlet's forward or include statement does not stop execution of method block. It continues to the end of method block or first return statement just like any other java method.
The best way to resolve this problem just set the page (where you suppose to forward the request) dynamically according your logic. That is:
protected void doPost(request , response){
String returnPage="default.jsp";
if(condition1){
returnPage="page1.jsp";
}
if(condition2){
returnPage="page2.jsp";
}
request.getRequestDispatcher(returnPage).forward(request,response); //at last line
}
and do the forward only once at last line...
you can also fix this problem using return statement after each forward() or put each forward() in if...else block
I removed
super.service(req, res);
Then it worked fine for me
Bump...
I just had the same error. I noticed that I was invoking super.doPost(request, response); when overriding the doPost() method as well as explicitly invoking the superclass constructor
public ScheduleServlet() {
super();
// TODO Auto-generated constructor stub
}
As soon as I commented out the super.doPost(request, response); from within doPost() statement it worked perfectly...
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//super.doPost(request, response);
// More code here...
}
Needless to say, I need to re-read on super() best practices :p
After return forward method you can simply do this:
return null;
It will break the current scope.
If you see this on a Spring based web application, make sure you have your method annotated with #ResponseBody or the controller annotated with #RestController instead of #Controller. It will also throw this exception if a method returns JSON, but has not been configured to have that as the response, Spring will instead look for a jsp page to render and throw this exception.

Servlet with java.lang.IllegalStateException: Cannot forward after response has been committed [Apache POI] when export to Excel [duplicate]

This method throws
java.lang.IllegalStateException: Cannot forward after response has been committed
and I am unable to spot the problem. Any help?
int noOfRows = Integer.parseInt(request.getParameter("noOfRows"));
String chkboxVal = "";
// String FormatId=null;
Vector vRow = new Vector();
Vector vRow1 = new Vector();
String GroupId = "";
String GroupDesc = "";
for (int i = 0; i < noOfRows; i++) {
if ((request.getParameter("chk_select" + i)) == null) {
chkboxVal = "notticked";
} else {
chkboxVal = request.getParameter("chk_select" + i);
if (chkboxVal.equals("ticked")) {
fwdurl = "true";
Statement st1 = con.createStatement();
GroupId = request.getParameter("GroupId" + i);
GroupDesc = request.getParameter("GroupDesc" + i);
ResultSet rs1 = st1
.executeQuery("select FileId,Description from cs2k_Files "
+ " where FileId like 'M%' and co_code = "
+ ccode);
ResultSetMetaData rsm = rs1.getMetaData();
int cCount = rsm.getColumnCount();
while (rs1.next()) {
Vector vCol1 = new Vector();
for (int j = 1; j <= cCount; j++) {
vCol1.addElement(rs1.getObject(j));
}
vRow.addElement(vCol1);
}
rs1 = st1
.executeQuery("select FileId,NotAllowed from cs2kGroupSub "
+ " where FileId like 'M%' and GroupId = '"
+ GroupId + "'" + " and co_code = " + ccode);
rsm = rs1.getMetaData();
cCount = rsm.getColumnCount();
while (rs1.next()) {
Vector vCol2 = new Vector();
for (int j = 1; j <= cCount; j++) {
vCol2.addElement(rs1.getObject(j));
}
vRow1.addElement(vCol2);
}
// throw new Exception("test");
break;
}
}
}
if (fwdurl.equals("true")) {
// throw new Exception("test");
// response.sendRedirect("cs2k_GroupCopiedUpdt.jsp") ;
request.setAttribute("GroupId", GroupId);
request.setAttribute("GroupDesc", GroupDesc);
request.setAttribute("vRow", vRow);
request.setAttribute("vRow1", vRow1);
getServletConfig().getServletContext().getRequestDispatcher(
"/GroupCopiedUpdt.jsp").forward(request, response);
}
forward/sendRedirect/sendError do NOT exit the method!
A common misunderstanding among starters is that they think that the call of a forward(), sendRedirect(), or sendError() would magically exit and "jump" out of the method block, hereby ignoring the remnant of the code. For example:
protected void doXxx() {
if (someCondition) {
sendRedirect();
}
forward(); // This is STILL invoked when someCondition is true!
}
This is thus actually not true. They do certainly not behave differently than any other Java methods (expect of System#exit() of course). When the someCondition in above example is true and you're thus calling forward() after sendRedirect() or sendError() on the same request/response, then the chance is big that you will get the exception:
java.lang.IllegalStateException: Cannot forward after response has been committed
If the if statement calls a forward() and you're afterwards calling sendRedirect() or sendError(), then below exception will be thrown:
java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed
To fix this, you need either to add a return; statement afterwards
protected void doXxx() {
if (someCondition) {
sendRedirect();
return;
}
forward();
}
... or to introduce an else block.
protected void doXxx() {
if (someCondition) {
sendRedirect();
}
else {
forward();
}
}
To naildown the root cause in your code, just search for any line which calls a forward(), sendRedirect() or sendError() without exiting the method block or skipping the remnant of the code. This can be inside the same servlet before the particular code line, but also in any servlet or filter which was been called before the particular servlet.
In case of sendError(), if your sole purpose is to set the response status, use setStatus() instead.
Do not write any string before forward/sendRedirect/sendError
Another probable cause is that the servlet writes to the response while a forward() will be called, or has been called in the very same method.
protected void doXxx() {
out.write("<p>some html</p>");
// ...
forward(); // Fail!
}
The response buffer size defaults in most server to 2KB, so if you write more than 2KB to it, then it will be committed and forward() will fail the same way:
java.lang.IllegalStateException: Cannot forward after response has been committed
Solution is obvious, just don't write to the response in the servlet. That's the responsibility of the JSP. You just set a request attribute like so request.setAttribute("data", "some string") and then print it in JSP like so ${data}. See also our Servlets wiki page to learn how to use Servlets the right way.
Do not write any file before forward/sendRedirect/sendError
Another probable cause is that the servlet writes a file download to the response after which e.g. a forward() is called.
protected void doXxx() {
out.write(bytes);
// ...
forward(); // Fail!
}
This is technically not possible. You need to remove the forward() call. The enduser will stay on the currently opened page. If you actually intend to change the page after a file download, then you need to move the file download logic to page load of the target page. Basically: first create a temporary file on disk using the way mentioned in this answer How to save generated file temporarily in servlet based web application, then send a redirect with the file name/identifier as request param, and in the target page conditionally print based on the presence of that request param a <script>window.location='...';</script> which immediately downloads the temporary file via one of the ways mentioned in this answer Simplest way to serve static data from outside the application server in a Java web application.
Do not call forward/sendRedirect/sendError in JSP
Yet another probable cause is that the forward(), sendRedirect() or sendError() methods are invoked via Java code embedded in a JSP file in form of old fashioned way <% scriptlets %>, a practice which was officially discouraged since 2003. For example:
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
...
<% sendRedirect(); %>
...
</body>
</html>
The problem here is that JSP internally immediately writes template text (i.e. HTML code) via out.write("<!DOCTYPE html> ... etc ...") as soon as it's encountered. This is thus essentially the same problem as explained in previous section.
Solution is obvious, just don't write Java code in a JSP file. That's the responsibility of a normal Java class such as a Servlet or a Filter. See also our Servlets wiki page to learn how to use Servlets the right way.
See also:
What exactly does "Response already committed" mean? How to handle exceptions then?
Unrelated to your concrete problem, your JDBC code is leaking resources. Fix that as well. For hints, see also How often should Connection, Statement and ResultSet be closed in JDBC?
even adding a return statement brings up this exception, for which only solution is this code:
if(!response.isCommitted())
// Place another redirection
Typically you see this error after you have already done a redirect and then try to output some more data to the output stream. In the cases where I have seen this in the past, it is often one of the filters that is trying to redirect the page, and then still forwards through to the servlet. I cannot see anything immediately wrong with the servlet, so you might want to try having a look at any filters that you have in place as well.
Edit: Some more help in diagnosing the problem…
The first step to diagnosing this problem is to ascertain exactly where the exception is being thrown. We are assuming that it is being thrown by the line
getServletConfig().getServletContext()
.getRequestDispatcher("/GroupCopiedUpdt.jsp")
.forward(request, response);
But you might find that it is being thrown later in the code, where you are trying to output to the output stream after you have tried to do the forward. If it is coming from the above line, then it means that somewhere before this line you have either:
output data to the output stream, or
done another redirect beforehand.
Good luck!
You should add return statement while you are forwarding or redirecting the flow.
Example:
if forwardind,
request.getRequestDispatcher("/abs.jsp").forward(request, response);
return;
if redirecting,
response.sendRedirect(roundTripURI);
return;
This is because your servlet is trying to access a request object which is no more exist..
A servlet's forward or include statement does not stop execution of method block. It continues to the end of method block or first return statement just like any other java method.
The best way to resolve this problem just set the page (where you suppose to forward the request) dynamically according your logic. That is:
protected void doPost(request , response){
String returnPage="default.jsp";
if(condition1){
returnPage="page1.jsp";
}
if(condition2){
returnPage="page2.jsp";
}
request.getRequestDispatcher(returnPage).forward(request,response); //at last line
}
and do the forward only once at last line...
you can also fix this problem using return statement after each forward() or put each forward() in if...else block
I removed
super.service(req, res);
Then it worked fine for me
Bump...
I just had the same error. I noticed that I was invoking super.doPost(request, response); when overriding the doPost() method as well as explicitly invoking the superclass constructor
public ScheduleServlet() {
super();
// TODO Auto-generated constructor stub
}
As soon as I commented out the super.doPost(request, response); from within doPost() statement it worked perfectly...
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//super.doPost(request, response);
// More code here...
}
Needless to say, I need to re-read on super() best practices :p
After return forward method you can simply do this:
return null;
It will break the current scope.
If you see this on a Spring based web application, make sure you have your method annotated with #ResponseBody or the controller annotated with #RestController instead of #Controller. It will also throw this exception if a method returns JSON, but has not been configured to have that as the response, Spring will instead look for a jsp page to render and throw this exception.

How can I download data from Java back-end? [duplicate]

I have a Struts2 action in the server side for file downloading.
<action name="download" class="com.xxx.DownAction">
<result name="success" type="stream">
<param name="contentType">text/plain</param>
<param name="inputName">imageStream</param>
<param name="contentDisposition">attachment;filename={fileName}</param>
<param name="bufferSize">1024</param>
</result>
</action>
However when I call the action using the jQuery:
$.post(
"/download.action",{
para1:value1,
para2:value2
....
},function(data){
console.info(data);
}
);
in Firebug I see the data is retrieved with the Binary stream. I wonder how to open the file downloading window with which the user can save the file locally?
2019 modern browsers update
This is the approach I'd now recommend with a few caveats:
A relatively modern browser is required
If the file is expected to be very large you should likely do something similar to the original approach (iframe and cookie) because some of the below operations could likely consume system memory at least as large as the file being downloaded and/or other interesting CPU side effects.
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// the filename you want
a.download = 'todo-1.json';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
alert('your file has downloaded!'); // or you know, something with better UX...
})
.catch(() => alert('oh no!'));
2012 Original jQuery/iframe/Cookie based approach
Bluish is completely right about this, you can't do it through Ajax because JavaScript cannot save files directly to a user's computer (out of security concerns). Unfortunately pointing the main window's URL at your file download means you have little control over what the user experience is when a file download occurs.
I created jQuery File Download which allows for an "Ajax like" experience with file downloads complete with OnSuccess and OnFailure callbacks to provide for a better user experience. Take a look at my blog post on the common problem that the plugin solves and some ways to use it and also a demo of jQuery File Download in action. Here is the source
Here is a simple use case demo using the plugin source with promises. The demo page includes many other, 'better UX' examples as well.
$.fileDownload('some/file.pdf')
.done(function () { alert('File download a success!'); })
.fail(function () { alert('File download failed!'); });
Depending on what browsers you need to support you may be able to use https://github.com/eligrey/FileSaver.js/ which allows more explicit control than the IFRAME method jQuery File Download uses.
Noone posted this #Pekka's solution... so I'll post it. It can help someone.
You don't need to do this through Ajax. Just use
window.location="download.action?para1=value1...."
You can with HTML5
NB: The file data returned MUST be base64 encoded because you cannot JSON encode binary data
In my AJAX response I have a data structure that looks like this:
{
result: 'OK',
download: {
mimetype: string(mimetype in the form 'major/minor'),
filename: string(the name of the file to download),
data: base64(the binary data as base64 to download)
}
}
That means that I can do the following to save a file via AJAX
var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
// Do it the HTML5 compliant way
var blob = base64ToBlob(result.download.data, result.download.mimetype);
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = result.download.filename;
a.click();
window.URL.revokeObjectURL(url);
}
The function base64ToBlob was taken from here and must be used in compliance with this function
function base64ToBlob(base64, mimetype, slicesize) {
if (!window.atob || !window.Uint8Array) {
// The current browser doesn't have the atob function. Cannot continue
return null;
}
mimetype = mimetype || '';
slicesize = slicesize || 512;
var bytechars = atob(base64);
var bytearrays = [];
for (var offset = 0; offset < bytechars.length; offset += slicesize) {
var slice = bytechars.slice(offset, offset + slicesize);
var bytenums = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
bytenums[i] = slice.charCodeAt(i);
}
var bytearray = new Uint8Array(bytenums);
bytearrays[bytearrays.length] = bytearray;
}
return new Blob(bytearrays, {type: mimetype});
};
This is good if your server is dumping filedata to be saved. However, I've not quite worked out how one would implement a HTML4 fallback
The simple way to make the browser downloads a file is to make the request like that:
function downloadFile(urlToSend) {
var req = new XMLHttpRequest();
req.open("GET", urlToSend, true);
req.responseType = "blob";
req.onload = function (event) {
var blob = req.response;
var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
var link=document.createElement('a');
link.href=window.URL.createObjectURL(blob);
link.download=fileName;
link.click();
};
req.send();
}
This opens the browser download pop up.
1. Framework agnostic: Servlet downloading file as attachment
<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
download
</a>
<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>
2. Struts2 Framework: Action downloading file as attachment
<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
download
</a>
<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>
It would be better to use <s:a> tag pointing with OGNL to an URL created with <s:url> tag:
<!-- without JS, with Struts tags: THE RIGHT WAY -->
<s:url action="downloadAction.action" var="url">
<s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>
In the above cases, you need to write the Content-Disposition header to the response, specifying that the file needs to be downloaded (attachment) and not opened by the browser (inline). You need to specify the Content Type too, and you may want to add the file name and length (to help the browser drawing a realistic progressbar).
For example, when downloading a ZIP:
response.setContentType("application/zip");
response.addHeader("Content-Disposition",
"attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...
With Struts2 (unless you are using the Action as a Servlet, an hack for direct streaming, for example), you don't need to directly write anything to the response; simply using the Stream result type and configuring it in struts.xml will work: EXAMPLE
<result name="success" type="stream">
<param name="contentType">application/zip</param>
<param name="contentDisposition">attachment;filename="${fileName}"</param>
<param name="contentLength">${fileLength}</param>
</result>
3. Framework agnostic (/ Struts2 framework): Servlet(/Action) opening file inside the browser
If you want to open the file inside the browser, instead of downloading it, the Content-disposition must be set to inline, but the target can't be the current window location; you must target a new window created by javascript, an <iframe> in the page, or a new window created on-the-fly with the "discussed" target="_blank":
<!-- From a parent page into an IFrame without javascript -->
<a href="downloadServlet?param1=value1" target="iFrameName">
download
</a>
<!-- In a new window without javascript -->
<a href="downloadServlet?param1=value1" target="_blank">
download
</a>
<!-- In a new window with javascript -->
<a href="javascript:window.open('downloadServlet?param1=value1');" >
download
</a>
I have created little function as workaround solution (inspired by #JohnCulviner plugin):
// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name
function ajax_download(url, data, input_name) {
var $iframe,
iframe_doc,
iframe_html;
if (($iframe = $('#download_iframe')).length === 0) {
$iframe = $("<iframe id='download_iframe'" +
" style='display: none' src='about:blank'></iframe>"
).appendTo("body");
}
iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
if (iframe_doc.document) {
iframe_doc = iframe_doc.document;
}
iframe_html = "<html><head></head><body><form method='POST' action='" +
url +"'>" +
"<input type=hidden name='" + input_name + "' value='" +
JSON.stringify(data) +"'/></form>" +
"</body></html>";
iframe_doc.open();
iframe_doc.write(iframe_html);
$(iframe_doc).find('form').submit();
}
Demo with click event:
$('#someid').on('click', function() {
ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});
I faced the same issue and successfully solved it. My use-case is this.
"Post JSON data to the server and receive an excel file.
That excel file is created by the server and returned as a response to the client. Download that response as a file with custom name in browser"
$("#my-button").on("click", function(){
// Data to post
data = {
ids: [1, 2, 3, 4, 5]
};
// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
var a;
if (xhttp.readyState === 4 && xhttp.status === 200) {
// Trick for making downloadable link
a = document.createElement('a');
a.href = window.URL.createObjectURL(xhttp.response);
// Give filename you wish to download
a.download = "test-file.xls";
a.style.display = 'none';
document.body.appendChild(a);
a.click();
}
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});
The above snippet is just doing following
Posting an array as JSON to the server using XMLHttpRequest.
After fetching content as a blob(binary), we are creating a downloadable URL and attaching it to invisible "a" link then clicking it. I did a POST request here. Instead, you can go for a simple GET too. We cannot download the file through Ajax, must use XMLHttpRequest.
Here we need to carefully set few things on the server side. I set few headers in Python Django HttpResponse. You need to set them accordingly if you use other programming languages.
# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
Since I download xls(excel) here, I adjusted contentType to above one. You need to set it according to your file type. You can use this technique to download any kind of files.
Ok, based on ndpu's code heres an improved (I think) version of ajax_download;-
function ajax_download(url, data) {
var $iframe,
iframe_doc,
iframe_html;
if (($iframe = $('#download_iframe')).length === 0) {
$iframe = $("<iframe id='download_iframe'" +
" style='display: none' src='about:blank'></iframe>"
).appendTo("body");
}
iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
if (iframe_doc.document) {
iframe_doc = iframe_doc.document;
}
iframe_html = "<html><head></head><body><form method='POST' action='" +
url +"'>"
Object.keys(data).forEach(function(key){
iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";
});
iframe_html +="</form></body></html>";
iframe_doc.open();
iframe_doc.write(iframe_html);
$(iframe_doc).find('form').submit();
}
Use this like this;-
$('#someid').on('click', function() {
ajax_download('/download.action', {'para1': 1, 'para2': 2});
});
The params are sent as proper post params as if coming from an input rather than as a json encoded string as per the previous example.
CAVEAT: Be wary about the potential for variable injection on those forms. There might be a safer way to encode those variables. Alternatively contemplate escaping them.
My approach is completly based on jQuery. The problem for me was that it has to be a POST-HTTP call. And I wanted it to be done by jQuery alone.
The solution:
$.ajax({
type: "POST",
url: "/some/webpage",
headers: {'X-CSRF-TOKEN': csrfToken},
data: additionalDataToSend,
dataType: "text",
success: function(result) {
let blob = new Blob([result], { type: "application/octetstream" });
let a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = "test.xml";;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(a.href);
...
},
error: errorDialog
});
Explanation:
What I and many others do is to create a link on the webpage, indicating that the target should be downloaded and putting the result of the http-request as the target. After that I append the link to the document than simply clicking the link and removing the link afterwards. You don't need an iframe anymore.
The magic lies in the lines
let blob = new Blob([result], { type: "application/octetstream" });
a.href = window.URL.createObjectURL(blob);
The interesting point is that this solution is only working with a "blob". As you can see in other answers, some are simply using a blob but not explaining why and how to create it.
As you can read e.g. in the Mozilla developer documentation you need a file, media ressource or blob for the function "createObjectURL()" to work. The problem is that your http-response might not be any of those.
Therefore the first thing you must do is to convert your response to a blob. This is what the first line does. Then you can use the "createObjectURL" with your newly created blob.
If you than click the link your browser will open a file-save dialog and you can save your data. Obviously it s possible that you cannot define a fixed filename for your file to download. Then you must make your response more complex like in the answer from Luke.
And don't forget to free up the memory especially when you are working with large files. For more examples and information you can look at the details of the JS blob object
Here is what I did, pure javascript and html. Did not test it but this should work in all browsers.
Javascript Function
var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
console.log("FILE LOAD DONE.. Download should start now");
});
Using just components that is supported in all browsers no additional
libraries.
Here is my server side JAVA Spring controller code.
#RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(#RequestParam(value = "param1", required = false) String param1,
HttpServletRequest request, HttpServletResponse response)
throws ParseException {
Workbook wb = service.getWorkbook(param1);
if (wb != null) {
try {
String fileName = "myfile_" + sdf.format(new Date());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
wb.write(response.getOutputStream());
response.getOutputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
How to DOWNLOAD a file after receiving it by AJAX
It’s convenient when the file is created for a long time and you need to show PRELOADER
Example when submitting a web form:
<script>
$(function () {
$('form').submit(function () {
$('#loader').show();
$.ajax({
url: $(this).attr('action'),
data: $(this).serialize(),
dataType: 'binary',
xhrFields: {
'responseType': 'blob'
},
success: function(data, status, xhr) {
$('#loader').hide();
// if(data.type.indexOf('text/html') != -1){//If instead of a file you get an error page
// var reader = new FileReader();
// reader.readAsText(data);
// reader.onload = function() {alert(reader.result);};
// return;
// }
var link = document.createElement('a'),
filename = 'file.xlsx';
// if(xhr.getResponseHeader('Content-Disposition')){//filename
// filename = xhr.getResponseHeader('Content-Disposition');
// filename=filename.match(/filename="(.*?)"/)[1];
// filename=decodeURIComponent(escape(filename));
// }
link.href = URL.createObjectURL(data);
link.download = filename;
link.click();
}
});
return false;
});
});
</script>
Optional functional is commented out to simplify the example.
No need to create temporary files on the server.
On jQuery v2.2.4 OK. There will be an error on the old version:
Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').
function downloadURI(uri, name)
{
var link = document.createElement("a");
link.download = name;
link.href = uri;
link.click();
}
I try to download a CSV file and then do something after download has finished. So I need to implement an appropriate callback function.
Using window.location="..." is not a good idea because I cannot operate the program after finishing download. Something like this, change header so it is not a good idea.
fetch is a good alternative however it cannot support IE 11. And window.URL.createObjectURL cannot support IE 11.You can refer this.
This is my code, it is similar to the code of Shahrukh Alam. But you should take care that window.URL.createObjectURL maybe create memory leaks. You can refer this. When response has arrived, data will be stored into memory of browser. So before you click a link, the file has been downloaded. It means that you can do anything after download.
$.ajax({
url: 'your download url',
type: 'GET',
}).done(function (data, textStatus, request) {
// csv => Blob
var blob = new Blob([data]);
// the file name from server.
var fileName = request.getResponseHeader('fileName');
if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else { // for others
var url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
//Do something after download
...
}
}).then(after_download)
}
Adding some more things to above answer for downloading a file
Below is some java spring code which generates byte Array
#RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
public ResponseEntity<byte[]> downloadReport(
#RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {
OutputStream out = new ByteArrayOutputStream();
// write something to output stream
HttpHeaders respHeaders = new HttpHeaders();
respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
respHeaders.add("X-File-Name", name);
ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
}
Now in javascript code using FileSaver.js ,can download a file with below code
var json=angular.toJson("somejsobject");
var url=apiEndPoint+'some url';
var xhr = new XMLHttpRequest();
//headers('X-File-Name')
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 201) {
var res = this.response;
var fileName=this.getResponseHeader('X-File-Name');
var data = new Blob([res]);
saveAs(data, fileName); //this from FileSaver.js
}
}
xhr.open('POST', url);
xhr.setRequestHeader('Authorization','Bearer ' + token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.send(json);
The above will download file
In Rails, I do it this way:
function download_file(file_id) {
let url = '/files/' + file_id + '/download_file';
$.ajax({
type: 'GET',
url: url,
processData: false,
success: function (data) {
window.location = url;
},
error: function (xhr) {
console.log(' Error: >>>> ' + JSON.stringify(xhr));
}
});
}
The trick is the window.location part. The controller's method looks like:
# GET /files/{:id}/download_file/
def download_file
send_file(#file.file,
:disposition => 'attachment',
:url_based_filename => false)
end
Use window.open https://developer.mozilla.org/en-US/docs/Web/API/Window/open
For example, you can put this line of code in a click handler:
window.open('/file.txt', '_blank');
It will open a new tab (because of the '_blank' window-name) and that tab will open the URL.
Your server-side code should also have something like this:
res.set('Content-Disposition', 'attachment; filename=file.txt');
And that way, the browser should prompt the user to save the file to disk, instead of just showing them the file. It will also automatically close the tab that it just opened.
The HTML Code :
<button type="button" id="GetFile">Get File!</button>
The jQuery Code :
$('#GetFile').on('click', function () {
$.ajax({
url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/172905/test.pdf',
method: 'GET',
xhrFields: {
responseType: 'blob'
},
success: function (data) {
var a = document.createElement('a');
var url = window.URL.createObjectURL(data);
a.href = url;
a.download = 'myfile.pdf';
document.body.append(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
}
});
});
Ok so here is the working code when Using MVC and you are getting your file from a controller
lets say you have your byte array declare and populate, the only thing you need to do is to use the File function (using System.Web.Mvc)
byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");
and then, in the same controller, add thoses 2 functions
protected override void OnResultExecuting(ResultExecutingContext context)
{
CheckAndHandleFileResult(context);
base.OnResultExecuting(context);
}
private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";
/// <summary>
/// If the current response is a FileResult (an MVC base class for files) then write a
/// cookie to inform jquery.fileDownload that a successful file download has occured
/// </summary>
/// <param name="context"></param>
private void CheckAndHandleFileResult(ResultExecutingContext context)
{
if (context.Result is FileResult)
//jquery.fileDownload uses this cookie to determine that a file download has completed successfully
Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
else
//ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
}
and then you will be able to call your controller to download and get the "success" or "failure" callback
$.fileDownload(mvcUrl('name of the controller'), {
httpMethod: 'POST',
successCallback: function (url) {
//insert success code
},
failCallback: function (html, url) {
//insert fail code
}
});
I found a fix that while it's not actually using ajax it does allow you to use a javascript call to request the download and then get a callback when the download actually starts. I found this helpful if the link runs a server side script that takes a little bit to compose the file before sending it. so you can alert them that it's processing, and then when it does finally send the file remove that processing notification. which is why I wanted to try to load the file via ajax to begin with so that I could have an event happen when the file is requested and another when it actually starts downloading.
the js on the front page
function expdone()
{
document.getElementById('exportdiv').style.display='none';
}
function expgo()
{
document.getElementById('exportdiv').style.display='block';
document.getElementById('exportif').src='test2.php?arguments=data';
}
the iframe
<div id="exportdiv" style="display:none;">
<img src="loader.gif"><br><h1>Generating Report</h1>
<iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
</div>
then the other file:
<!DOCTYPE html>
<html>
<head>
<script>
function expdone()
{
window.parent.expdone();
}
</script>
</head>
<body>
<iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
<script>document.getElementById('exportif').onload= expdone;</script>
</body></html>
I think there's a way to read get data using js so then no php would be needed. but I don't know it off hand and the server I'm using supports php so this works for me. thought I'd share it in case it helps anyone.
If the server is writing the file back in the response (including cookies if
you use them to determine whether the file download started), Simply create a form with the values and submit it:
function ajaxPostDownload(url, data) {
var $form;
if (($form = $('#download_form')).length === 0) {
$form = $("<form id='download_form'" + " style='display: none; width: 1px; height: 1px; position: absolute; top: -10000px' method='POST' action='" + url + "'></form>");
$form.appendTo("body");
}
//Clear the form fields
$form.html("");
//Create new form fields
Object.keys(data).forEach(function (key) {
$form.append("<input type='hidden' name='" + key + "' value='" + data[key] + "'>");
});
//Submit the form post
$form.submit();
}
Usage:
ajaxPostDownload('/fileController/ExportFile', {
DownloadToken: 'newDownloadToken',
Name: $txtName.val(),
Type: $txtType.val()
});
Controller Method:
[HttpPost]
public FileResult ExportFile(string DownloadToken, string Name, string Type)
{
//Set DownloadToken Cookie.
Response.SetCookie(new HttpCookie("downloadToken", DownloadToken)
{
Expires = DateTime.UtcNow.AddDays(1),
Secure = false
});
using (var output = new MemoryStream())
{
//get File
return File(output.ToArray(), "application/vnd.ms-excel", "NewFile.xls");
}
}
I have tried Ajax and HttpRequest ways to get my result download file but I've failed, finally I've solved my problem using these steps:
implemented a simple hidden form in my html code:
<form method="post" id="post_form" style="display:none" action="amin.php" >
<input type="hidden" name="action" value="export_xlsx" />
<input type="hidden" name="post_form_data" value="" />
</form>
input with 'action' name is for calling function in my php code,
input with 'post_form_data' name for sending long data of a table which were not possible to send with GET. this data was encoded to json, and put json in input:
var list = new Array();
$('#table_name tr').each(function() {
var row = new Array();
$(this).find('td').each(function() {
row.push($(this).text());
});
list.push(row);
});
list = JSON.stringify(list);
$("input[name=post_form_data]").val(list);
now, the form is ready with my desire values in inputs, just need to trigger the submit.
document.getElementById('post_form').submit();
and done!
while my result is a file (xlsx file for me) the page wouldn't be redirected and instantly the file starts to download in last page, so no need to useiframe or window.open etc.
if you are trying to do something like this, this should be an easy trick 😉.
If you want to use jQuery File Download , please note this for IE.
You need to reset the response or it will not download
//The IE will only work if you reset response
getServletResponse().reset();
//The jquery.fileDownload needs a cookie be set
getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
//Do the reset of your action create InputStream and return
Your action can implement ServletResponseAware to access getServletResponse()
It is certain that you can not do it through Ajax call.
However, there is a workaround.
Steps :
If you are using form.submit() for downloading the file, what you can do is :
Create an ajax call from client to server and store the file stream inside the session.
Upon "success" being returned from server, call your form.submit() to just stream the file stream stored in the session.
This is helpful in case when you want to decide whether or not file needs to be downloaded after making form.submit(), eg: there can be a case where on form.submit(), an exception occurs on the server side and instead of crashing, you might need to show a custom message on the client side, in such case this implementation might help.
there is another solution to download a web page in ajax. But I am referring to a page that must first be processed and then downloaded.
First you need to separate the page processing from the results download.
1) Only the page calculations are made in the ajax call.
$.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" },
function(data, status)
{
if (status == "success")
{
/* 2) In the answer the page that uses the previous calculations is downloaded. For example, this can be a page that prints the results of a table calculated in the ajax call. */
window.location.href = DownloadPage.php+"?ID="+29;
}
}
);
// For example: in the CalculusPage.php
if ( !empty($_POST["calculusFunction"]) )
{
$ID = $_POST["ID"];
$query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
...
}
// For example: in the DownloadPage.php
$ID = $_GET["ID"];
$sede = "SELECT * FROM ExamplePage WHERE id = ".$ID;
...
$filename="Export_Data.xls";
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: inline; filename=$filename");
...
I hope this solution can be useful for many, as it was for me.
That's it works so fine in any browser (I'm using asp.net core)
function onDownload() {
const api = '#Url.Action("myaction", "mycontroller")';
var form = new FormData(document.getElementById('form1'));
fetch(api, { body: form, method: "POST"})
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
$('#linkdownload').attr('download', 'Attachement.zip');
$('#linkdownload').attr("href", url);
$('#linkdownload')
.fadeIn(3000,
function() { });
})
.catch(() => alert('An error occurred'));
}
<button type="button" onclick="onDownload()" class="btn btn-primary btn-sm">Click to Process Files</button>
<a role="button" href="#" style="display: none" class="btn btn-sm btn-secondary" id="linkdownload">Click to download Attachments</a>
<form asp-controller="mycontroller" asp-action="myaction" id="form1"></form>
function onDownload() {
const api = '#Url.Action("myaction", "mycontroller")';
//form1 is your id form, and to get data content of form
var form = new FormData(document.getElementById('form1'));
fetch(api, { body: form, method: "POST"})
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
$('#linkdownload').attr('download', 'Attachments.zip');
$('#linkdownload').attr("href", url);
$('#linkdownload')
.fadeIn(3000,
function() {
});
})
.catch(() => alert('An error occurred'));
}
I struggled with this issue for a long time. Finally an elegant external library suggested here helped me out.

Which Java-Datatype is required when calling JavaScript-Method

I'm trying to call a JavaScript-Method from within my JavaFX-WebView via:
JSObject win = (JSObject)getEngine().executeScript("window");
win.call("showPDF", myPDFFile /*typeof java.io.File*/);
the result of this call is:
Invalid parameter object: need either .data, .range or .url
This is the JavaScript-Part (not by me), which throws the error:
var source;
if (typeof src === 'string') {
source = { url: src };
} else if (isArrayBuffer(src)) {
source = { data: src };
} else if (src instanceof PDFDataRangeTransport) {
source = { range: src };
} else {
if (typeof src !== 'object') {
error('Invalid parameter in getDocument, need either Uint8Array, ' +
'string or a parameter object');
}
if (!src.url && !src.data && !src.range) {
error('Invalid parameter object: need either .data, .range or .url');
}
}
Implementation of isArrayBuffer:
function isArrayBuffer(v) {
return typeof v === 'object' && v !== null && v.byteLength !== undefined;
}
So my question is:
What type of (java) object could be used so that this call might work?
win.call("showPDF", ???);
EDIT 1:
String cannot be used, because it will be treated as a URL.
I would like to commit something like a ByteArray (my concrete file), but using a byte[] (instead of java.io.File) causes the same error.
Here are some comments from the JS function above:
https://github.com/mozilla/pdf.js/blob/master/src/display/api.js#L234
This is the main entry point for loading a PDF and interacting with it.
NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
is used, which means it must follow the same origin rules that any XHR does
e.g. No cross domain requests without CORS.
#param {string|TypedArray|DocumentInitParameters|PDFDataRangeTransport} src
Can be a url to where a PDF is located, a typed array (Uint8Array)
already populated with data or parameter object.
What Datatype i have to use (in JAVA), so it will be a (e.g) TypedArray (Uint8Array) in JAVASCRIPT?
EDIT 2:
I was trying Aarons suggestion:
String encoded = Base64.getEncoder().encodeToString(Files.readAllBytes(myPDFFile.toPath()));
engine.executeScript("var src = _base64ToArrayBuffer('"+encoded+"'); showPDF(src);");
This causes a new problem:
Error: Invalid PDF binary data: either typed array, string or array-like object is expected in the data property.
This (new) error is thrown (few lines later) here: https://github.com/mozilla/pdf.js/blob/master/src/display/api.js#L291
console.log(_base64ToArrayBuffer(encoded)) returns: [object ArrayBuffer]
Solution:
I managed to make this work (with help of Aaron):
pdfViewer.html
<!doctype html>
<html>
<head>
<script src="web/compatibility.js"></script>
<script src="build/pdf.js"></script>
<script>
function _base64ToBinaryString(base64) {
var binary_string = window.atob(base64);
return binary_string;
}
function showPDF(pdfFile) {
console.log('calling showPDF...');
'use strict';
PDFJS.disableWorker = true; /* IMPORTANT TO DISABLE! */
PDFJS.workerSrc = 'build/pdf.worker.js';
console.log('TRYING TO GET DOCUMENT FROM BINARY DATA...');
PDFJS.getDocument({data: pdfFile}).then(function(pdf)
{
console.log('PDF LOADED.');
console.log('TRYING TO GET PAGE 1...');
pdf.getPage(1).then(function(page) {
console.log('PAGE 1 LOADED.');
var scale = 1.0;
var viewport = page.getViewport(scale);
var canvas = document.getElementById('the-canvas');
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
var renderContext = {
canvasContext: context,
viewport: viewport
};
console.log('RENDERING PAGE 1...');
page.render(renderContext);
});
});
};
</script>
</head>
<body>
<canvas id="the-canvas" style="border:1px solid black;"/>
</body>
</html>
After loading the above HTML-Page into the WebView,
following Java-Code is used to load a PDF-File into the Page:
String encoded = Base64.getEncoder().encodeToString(Files.readAllBytes(myPdfFile.toPath()));
webEngine.executeScript("var src = _base64ToBinaryString('"+encoded+"'); showPDF(src);");
Try myPDFFile.getURI().getURL().toString() since the method accepts URLs in the form of Strings. Since WebKit is running locally, you should be able to read file:// URLs.
If you want to try the ArrayBuffer, then you can find some examples in the MDN JavaScript documentation at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
That means you need to create a FileReader, pass the URL, handle the asynchronous events. I only recommend this if you know JavaScript fairly well.
Unfortunately, the examples are all geared towards handling files in a web page, not passing data into a page.
[EDIT] Here is an example how to convert a Base64 encoded string to ArrayBuffer: Convert base64 string to ArrayBuffer
So you need the load the file into a Java byte[] array. Then convert the array to a Base64 encoded Java String. You can then paste this into dynamic JavaScript:
String encoded = ...byte[] -> Base64...
getEngine().executeScript("var src = _base64ToArrayBuffer('"+encoded+"'); showPDF(src);");

How to get meaningful data from formdata with Java?

I have a form like this :
<form id="test" name="test" action="/pages/font/getFontTitle.jsp" method="POST" enctype="multipart/form-data">
<div class="dialog">
<table>
<tbody>
<tr class="prop"><td valign="top">Name</td><td><input type="text" name="name"></td></tr>
<tr class="prop"><td valign="top"><input type="file" name="fname" size="62" value="" id="fname"/></td><td><span class="button"><s:submit/></span></td></tr>
</tbody>
</table>
</div>
</form>
And my servlet look like this :
String contentType=request.getContentType(),Location="0.";
out.println("Done");
System.out.println("contentType = "+contentType);
boolean isMultipart=ServletFileUpload.isMultipartContent(request); // Check that we have a file upload request
System.out.println("isMultipart = "+isMultipart);
int formDataLength=request.getContentLength(); // We are taking the length of Content type data
byte dataBytes[]=new byte[formDataLength];
System.out.println("dataBytes = "+dataBytes.length+"\n"+dataBytes.toString());
java.util.Map<java.lang.String,java.lang.String[]> ParameterMap=request.getParameterMap();
Iterator it=ParameterMap.entrySet().iterator();
while (it.hasNext())
{
Map.Entry pair=(Map.Entry)it.next();
System.out.println(pair.getKey()+" = "+(pair.getValue()).toString());
// it.remove(); // avoids a ConcurrentModificationException
}
System.out.println("request = \n"+request.getParameterNames().toString());
if (isMultipart)
{
FileItemFactory factory=new DiskFileItemFactory();
ServletFileUpload upload=new ServletFileUpload(factory);
List items=null;
Location+="1.";
try { items=upload.parseRequest(request); }
catch (FileUploadException e) { e.printStackTrace(); }
Location+="2.";
Iterator itr=items.iterator();
Location+="3.";
while (itr.hasNext())
{
Location+="4.";
FileItem item=(FileItem)itr.next();
if (item.isFormField())
{
String name=item.getFieldName(),value=item.getString();
System.out.println("name = "+name+" value = "+value);
Location+="5.";
}
else
{
try
{
Location+="6.";
String itemName=item.getName(),savedFilePath=itemName;
File savedFile=new File(savedFilePath);
System.out.println("savedFile = "+savedFile.getAbsolutePath());
item.write(savedFile);
}
catch (Exception e) { e.printStackTrace(); }
}
}
}
Location+="e.";
out.println(" [ "+Location+" ]");
out.flush();
The result I get was :
contentType = multipart/form-data; boundary=---------------------------15890672924370
isMultipart = true
dataBytes = 35909
[B#14d3ba9
name = [Ljava.lang.String;#187f9d7
request =
java.util.Vector$1#23ca6e
dataBytes has a file in it, but it never got into the while (itr.hasNext()) loop, the jsp output was : Done [ 0.1.2.3.e. ] , Location 4,5,6 was never reached.
[1] Why ?
[2] How to turn "dataBytes = [B#14d3ba9 " into something readable or a file ?
[3] How to turn "name = [Ljava.lang.String;#187f9d7" into the original string value ?
[4] I'm using "(pair.getValue()).toString()", but why it's not a readable string ?
The client will send the HTTP request body only once.
Your code is however trying to read the request body twice. The first time by the getParameterMap() call:
java.util.Map<java.lang.String,java.lang.String[]> ParameterMap=request.getParameterMap();
and the second time by Apache Commons FileUpload (which will face an empty request):
try { items=upload.parseRequest(request); }
This isn't going to work.
Use either the standard Servlet API methods, or Apache Commons FileUpload exclusively.
See also:
How to upload files to server using JSP/Servlet? - contains concrete examples of both the Commons FileUpload and standard Servlet API methods.
use should use servelt 3.0 spec and annotate your servlet accordingly and then usin can user request.getParts() with all the multi-part data. Read about it.
Subquestion 1,3,4: if you cannot use the file upload feature added in Servlet 3.0 I think you're better off using Commons Fileupload, more or less the de facto standard for uploading files to servlets. It's robust, well tested and just works.
Subquestion 2: the Javadoc for ServletRequest's getParameterMap states:
Returns: an immutable java.util.Map containing parameter names as keys
and parameter values as map values. The keys in the parameter map are
of type String. The values in the parameter map are of type String
array.
Emphasis added: the values returned are arrays of Strings, not Strings. That's why you get the strange output value. Try
System.out.println( pair.getKey()+" = "+ pair.getValue()[0] );
But also consider using a logger in your servlets, it's better practice than dumping stuff in System.out
ps: use the code formatting feature that's probaby available in your IDE, your code is pretty hard to read.

Categories