I have a file uploader on my website, since i cannot use php I'm using jsp pages.
My main page uses a hidden iframe to post data to a second jsp page that handles the upload. However the uploaded images is always corrupted, more specifically it's larger in size than the original file.
Any hints or tips would be appreciated.
Main page code:
<form id="uploadForm">
<input type="file" name="datafile" /></br>
<input type="button" value="upload" onClick="fileUpload(document.getElementById('uploadForm'),'single_upload_page.jsp','upload'); return false;" >
</form>
The code for fileUpload concerning the form:
form.setAttribute("target","upload_iframe");
form.setAttribute("action", action_url);
form.setAttribute("method","post");
form.setAttribute("enctype","multipart/form-data");
form.setAttribute("encoding","multipart/form-data");
// Submit the form...
form.submit();
The code that handles the upload:
DataInputStream in = new DataInputStream(request.getInputStream());
int dataLength = request.getContentLength();
Because of the varying size of dataLength I'm assuming that the request.getInputStream receives extra data.
I only posted the code I think matters, if I need to post more or if you need any more information don't hesitate to ask.
Simple request
The problem is request.getContentLength() gives the length of the whole request, containing headers and all.
You have to search for Content-Length header value, convert it to Long and that's the proper size.
if you can't get it (it can be unavailable) simply consume the whole input stream. But of course, you'll not have idea of the file size.
Multipart request
Anyway... as your form is multipart/form-data you should use some library to parse all the different parts, find the one you need (the file part) and read it. You can use commons-fileupload.
Sample from real life
#Override
protected void doPost(HttpServletRequest request, HttpServletResponse resp)
throws ServletException, IOException
{
// (....)
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload sfu = new ServletFileUpload(factory);
FileItemIterator it = sfu.getItemIterator(request);
// TAKE THE FIRST PART FROM REQUEST (HERE COMES THE FILE)
if (it.hasNext())
{
FileItemStream fis = it.next();
// grab data from fis (content type, name)
...fis.getContentType()...
...fis.getName()...
// GET CONTENT LENGTH SEARCH FOR THE LENGTH HEADER
...getContentLength(fis.getHeaders(), request)...
// here I use an own method to process data
// but FileItemStream has an openStream method
FileItem item = processUpload(factory, fis, uploadInfo);
(....)
}
private long getContentLength(FileItemHeaders pHeaders, HttpServletRequest request)
{
try
{
return Long.parseLong(pHeaders.getHeader("Content-length"));
}
catch (Exception e)
{
// if I can't grab the value return an approximate (in my case I don't care)
return request.getContentLength();
}
}
Related
I use JSF and I have an h:commandButton to prompt for a file download. The file is in a PDF format. The downloaded file has correct number of pages but they are blank.
When the file is opened in the browser I get this message:
This PDF document might not be displayed correctly.
This is my commandButton:
<h:form>
<h:commandButton action="#{fileDownloadView.fileDownloadView}" value="Download"/>
</h:form>
And this is my class:
#ManagedBean
public class FileDownloadView {
private static final String FILENAME = "manual.pdf";
private static final String CONTENT_TYPE = "application/pdf";
public FileDownloadView() throws IOException, DocumentException {
Resource resource = new ClassPathResource(FILENAME);
InputStream stream = resource.getInputStream();
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
externalContext.responseReset();
externalContext.setResponseContentType(CONTENT_TYPE);
externalContext.setResponseHeader("Content-Disposition", "attachment; filename=\"" + FILENAME + "\"");
OutputStream outputStream = externalContext.getResponseOutputStream();
byte[] bytes = IOUtils.toByteArray(stream);
outputStream.write(bytes);
facesContext.responseComplete();
}
}
What could be the cause of this?
EDIT:
Claimed duplicate post gives this pice of code:
public void download() throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
ec.responseReset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
ec.setResponseContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ExternalContext#getMimeType() for auto-detection based on filename.
ec.setResponseContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.
OutputStream output = ec.getResponseOutputStream();
// Now you can write the InputStream of the file to the above OutputStream the usual way.
// ...
fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.
}
If you look closer my code is the same with the exception of the comment part that writes:
// Now you can write the InputStream of the file to the above
OutputStream the usual way.
// ...
What i have writen for this is
byte[] bytes = IOUtils.toByteArray(stream);
outputStream.write(bytes);
What is wrong with that? Isn't that a byte array that is written in the output stream? Why is this not working?
You are right that the body of your beans' constructor does the same as the body of the download method given in the example.
Your command link <h:commandButton action="#{fileDownloadView.fileDownloadView}" .../> action method expression is trying to find and invoke a method named fileDownloadView on your bean but that method does not exist.
Your public FileDownloadView is a constructor because it has no return value type and the same name as the class. If you'd change that to public void download the bean would have no explicit constructor anymore but finally a method that is invokable by a command button like this:
<h:commandButton action="#{fileDownloadView.download}" value="Download"/>
Capitalization matters, so don't call the method public void Download or the like.
I'm not sure what actually happens in your current implementation, but I guess while #{fileDownloadView.fileDownloadView} is beeing resolved, there is a new instance created of the bean fileDownloadView from first part of the expression and the code in constructor is successfully executed. Some CPU cylcles later the ELResolver fails to resolve second .fileDownloadView part of the expression and throws an exception which kind of messes things up.
I have an html form to submit like this:
<form enctype="multipart/form-data" id="formToSubmit" action="/create_components" method="POST">
<input type="file" name="component_1" id="component_1">
Other inputs here...
On the server side, I wanna get all the inputs (both files and text inputs)... and the form is dynamically created so I don't know in advance the name of the IDs.
On the server I need both content and IDs.
On the server I first tried to loop on the parameters, but this skip the inputs that are of type file and return only the text ones:
Enumeration<?> enums = request.getParameterNames();
while (enums.hasMoreElements()) {
Object inputName = enums.nextElement();
// Here I get all the input that are not files
}
Then I tried in that way:
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Set set = multipartRequest.getFileMap().entrySet();
Iterator i = set.iterator();
while(i.hasNext()) {
Map.Entry me = (Map.Entry)i.next();
String fileName = (String)me.getKey();
MultipartFile multipartFile = (MultipartFile)me.getValue();
byte[] bytes = multipartFile.getBytes();
}
In this way I actually get the files, but I don't know how to get their IDs.
I need both: the file content and the ID (in this example "component_1"). How can I do that? Is there a way I can change the last code in order to get also the ID?
You have the name of file by calling to:
String valueOfNameAttribute = ((MultipartFile)me.getValue()).getName()
Isn't that what you need?
In my application, a json object is created on client side. This object is posted to a HttpServlet which creates a pdf file based on the POST data.
The file is send back to the user. The succes function is called, and stream data is logged. I want however, that the file is downloaded.
How to achieve this?
My clientside code:
$(document).ready(function() {
// when the print button is clicked
$('#exportButton').click(function() {
var tableIdx = performanceDetailTableController.getTableIdx();
var allData = {
"shipTable1":{
"rows":[
{ "latitude":"12323","longitude":"213213"},
{ "latitude":"213213","longitude":"543543"}
]},
"shipTable2":{
"rows":[
{ "latitude":"12323", "longitude":"213213"},
{ "latitude":"213213","longitude":"543543"}
]}
}
var postData = JSON.stringify(allData);
$.ajax({
type : "POST",
url : 'pdfServlet',
contentType: "application/json; charset=utf-8",
data : postData,
async : false,
success : function(data) {
alert("got some data");
console.log(data);
},
});
});
});
And the servlet creating the file:
#Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// get the json content
StringBuffer jsonContent = getPostedContent(request);
log.info(jsonContent.toString());
// convert json to pojo's
Tables tables = getTablesFromString(jsonContent);
// create a xml stream
ByteArrayOutputStream xml = new XmlConverter().getXMLSource(tables);
// put the xml on the request
request = setXmlOnRequest(request, xml);
// create pdf data of the pdf-able xml content
ByteArrayOutputStream pdf = new PdfHandler().createPdfDataStream(request);
// response = createResponseheaders(response, request);
response.setContentType("application/pdf");
response.setContentLength(pdf.size());
response.setHeader("Content-disposition", "attachment; filename=test.pdf");
response.setCharacterEncoding("utf-8");
response.getOutputStream().write(pdf.toByteArray());
//close the streams
pdf.close();
response.getOutputStream().close();
}
The ouput in the log:
%PDF-1.4
%
4 0 obj
<<
/Producer (Apache FOP Version SVN branches/fop-0_95)
/CreationDate (D:20130725162007+02'00')
>>
endobj
5 0 obj
<<
/N 3
/Length 11 0 R
/Filter /FlateDecode
>>
stream
xwTSϽ7PhRHH.*1 J
*MY SOLUTION: *
see http://www.particletree.com/notebook/ajax-file-download-or-not/ for a pointer
I created a form with one hidden field:
<button id="exportButton">export</button>
<form id="exportForm" method="post" action="pdfServlet">
<input type="hidden" value="empty" id="pdf_data" name="pdf_data" />
</form>
than i changed my jquery post data controller to:
$('#exportButton').click(function() {
var tableIdx = performanceDetailTableController.getTableIdx();
var allData = {
"shipTable1":{
"rows":[
{ "latitude":"12323","longitude":"213213"},
{ "latitude":"213213","longitude":"543543"}
]},
"shipTable2":{
"rows":[
{ "latitude":"12323", "longitude":"213213"},
{ "latitude":"213213","longitude":"543543"}
]}
}
var postData = JSON.stringify(allData);
// put the data on the hidden form field in the export form
$('#pdf_data').val(postData);
// and submit the form
$('#exportForm').submit();
});
so now when i click the export button, the hidden field in the form gets the data to post and the data is posted as www-form encoded.
This way the servlet can handle the request and the the file is downloaded to the client.
You can't download files with ajax. JavaScript has for obvious security reasons no facilities to trigger a Save As dialogue with arbitrarily retrieved/generated content in JavaScript context. The world wide web would have looked very different if that was possible.
If you insist in using JS/jQuery for that, you need to send a synchronus GET request instead. You can do that with window.location (you only need to rename doPost() to doGet()).
window.location = 'pdfServlet?param1=value1¶m2=value2';
Alternatively, just throw away all that unnecessary JS/jQuery and just use plain HTML <form action="pdfServlet" method="post"> with <input type="submit">. Additional bonus is that it works in browsers with JS disabled.
If your sole reason to grab ajax is actually a naive attempt to avoid the page being refreshed, then I can tell you that this really won't happen if the response has a Content-Disposition: attachment header. So that part is already safe.
I have an h:datatable that contains a column with a link that will call the backing bean and "should" return a PDF document generated.
The column looks like this :
<h:form>
...
<h:datatable>
...
<h:column >
<h:commandLink action="#{bean.downloadPDF}" target="_blank" >
<f:param name="value1" value="#{bean.val1}"/>
<f:param name="value2" value="#{bean.val2}"/>
<f:param name="value3" value="#{bean.val3}"/>
<h:graphicImage name="certificate.jpg" library="images"/>
</h:commandLink>
</h:column>
...
</h:datatable>
...
</h:form>
I have no javascript errors on my page (according to chrome and firebug). The backing bean looks like this :
public void downloadPDF() {
...
File outputPDF = new File(outputFileName);
//Get ready to return pdf to user
BufferedInputStream input = null;
BufferedOutputStream output = null;
try {
// Open file.
input = new BufferedInputStream(new FileInputStream(outputPDF), 10240);
//Return PDF to user
// Init servlet response.
response.reset();
response.setHeader("Content-Type", "application/pdf");
response.setHeader("Content-Length", String.valueOf(outputPDF.length()));
response.setHeader("Content-Disposition", "inline; filename=\"" + pdfName + "\"");
output = new BufferedOutputStream(response.getOutputStream(), 10240);
// Write file contents to response.
byte[] buffer = new byte[10240];
int length;
while ((length = input.read(buffer)) > 0) {
output.write(buffer, 0, length);
}
// Finalize task.
output.flush();
} catch(IOException e) {
e.printStackTrace();
} finally {
output.close();
input.close();
}
facesContext.responseComplete();
}
Now, I know my PDF generation works because I can create the custom PDF and save it on the drive.
Then, if I add <f:ajax/> to the h:commandLink The method is called. Without that, it reloads the current page without the action method ever being called.
I have tried a few different things... with or without any f:params. Adding a String return value to the downloadPDF() function. EDIT : Taking away the target="_blank" parameter. Using actionListener instead of action. When using action, the bean method does not get called, but I get this error message in the h:messages : Conversion Error setting value '' for 'null Converter'..
Cannot get it to call the function. What I am hoping to get is that when one clicks on the downloadPDF link, it opens a new window and downloads the PDF.
Any help would be greatly appreciated.
EDIT 2 / Solution (temporary)
I have managed to get this working using the attribute immediate="true". Still havn't found exact field that is giving me the Conversion Error, but am assuming is a field in the form that does not need to be submitted for this specific function.
Logic associated with the question was good. The issue was with other items within the form that were staying null. As with the Edit 2, adding the immediate="true" made it so that the fields I needed alone were sent to the servlet.
Lesson learned
When submitting part of a form in JSF, make sure only the fields you need are being submitted. Especially in large forms and integrating ajax with sub sections of the form.
I got the following to work already:
User can upload a file (i.e. a compressed archive)
User can uncompress the file on the server
User can execute some stuff on these files, which results in more files to be generated
Now I need to get step 4 to work:
User can download the files to his own computer again
Can anyone give me a hint? I tried to understand the stuff I found on Google, but it does not work quite as expected. Do I have to set a content type? When I set application/octet stream only txt and csv files would display correctly (in the browser, not as download popup as I wanted) other files would not work...
JSP:
<a4j:commandLink value="Download" action="#{appController.downloadFile}" rendered="#{!file.directory}">
<f:param name="file" value="#{file.absoluteFilename}" />
</a4j:commandLink>
appController:
public String downloadFile() {
String filename = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("file");
File file = new File(filename);
HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
writeOutContent(response, file, file.getName());
FacesContext.getCurrentInstance().responseComplete();
return null;
}
private void writeOutContent(final HttpServletResponse res, final File content, final String theFilename) {
if (content == null) {
return;
}
try {
res.setHeader("Pragma", "no-cache");
res.setDateHeader("Expires", 0);
res.setHeader("Content-disposition", "attachment; filename=" + theFilename);
FileInputStream fis = new FileInputStream(content);
ServletOutputStream os = res.getOutputStream();
int bt = fis.read();
while (bt != -1) {
os.write(bt);
bt = fis.read();
}
os.flush();
fis.close();
os.close();
} catch (final IOException ex) {
Logger.getLogger(ApplicationController.class.getName()).log(Level.SEVERE, null, ex);
}
}
Your concrete problem is that you're attempting to download files by Ajax. This is not correct. JavaScript can't deal with binary responses nor has it any facilities to force a Save As dialogue. You need to make it a normal synchronous request instead so that it's the webbrowser itself who has to deal with it.
<h:commandLink value="Download" action="#{appController.downloadFile}" rendered="#{!file.directory}">
<f:param name="file" value="#{file.absoluteFilename}" />
</h:commandLink>
As to setting the content type, if you have a file name with extension at your hands, you could use ServletContext#getMimeType() to resolve it based on <mime-mapping> in web.xml (either the server's default one or your webapp's one).
ServletContext servletContext = (ServletContext) externalContext.getContext();
String contentType = servletContext.getMimeType(file.getName());
if (contentType == null) {
contentType = "application/octet-stream";
}
response.setContentType(contentType);
// ...
(note that I assume that you're using JSF 1.x, seeing the way how you obtained the servlet response, you could since JSF 2.x otherwise also use ExternalContext#getMimeType())
I have done the step 4 some weeks ago, and let me give you some advices:
Use a link html tag component. For this, I recommend the a4j:htmlCommandLink tag component (it's like the common h:commandLink with the difference that the <f:param /> components are always rendered, you can check more in the component documentation).
If you don't know the type of the file to download (), then you must set the Content as application/octet-stream.
After setting up the file to download to your response, you should set that the response has been completed.
I'll put my Backing Bean code for this request:
public void descargaArchivo() {
//sorry but the programming standard says that we MUST declare
//our variables at the beginning of any function =(
HttpServletResponse objResponse;
FileInputStream objFileInputStream;
String strNombreCompletoArchivo, strNombreArchivo;
byte[] arrDatosArchivo;
try {
//Here I get the <f:param> with the full name of the file. It encapsulates
// the Faces.getCurrentInstance... call.
strNombreCompletoArchivo = UManejadorSesionWeb.obtieneParametro("nombreCompletoArchivo");
//The function obtieneNombreArchivo retrieves the name of the file
//based on the full name and the file separator (/ for Windows, \ for Linux)
strNombreArchivo = UFuncionesGenerales.obtieneNombreArchivo(strNombreCompletoArchivo);
//Getting the response from Faces.getCurrentInstance...
objResponse = UManejadorSesionWeb.obtieneHttpResponse();
//Setting up the response content type and header (don't set the length!)
objResponse.setContentType("application/octet-stream");
objResponse.setHeader("Content-Disposition", "attachment; filename=\"" + strNombreArchivo + "\"");
//Create the FileInputStream for the file to download
objFileInputStream = new FileInputStream(strNombreCompletoArchivo);
//Setting the file on the response
arrDatosArchivo = new byte[UConstante.BUFFER_SIZE];
while(objFileInputStream.read(arrDatosArchivo, 0, UConstante.BUFFER_SIZE) != -1) {
objResponse.getOutputStream().write(arrDatosArchivo, 0, UConstante.BUFFER_SIZE);
}
objFileInputStream.close();
objResponse.getOutputStream().flush();
objResponse.getOutputStream().close();
//Telling the framework that the response has been completed.
FacesContext.getCurrentInstance().responseComplete();
} catch (Exception objEx) {
//manage the errors...
}
}
//The constant used for byte array size
public class UConstante {
public static final int BUFFER_SIZE = 2048;
}
The jsp fragment I used looks like this:
<a4j:htmlCommandLink rendered="#{documento.rutaDestino != null}"
action="#{documentoRequerido.descargaArchivo}">
<f:param name="nombreCompletoArchivo" value="#{documento.rutaDestino}" />
<h:graphicImage value="/Resource/iconos/mover-abajo.png" styleClass="pic" />
</a4j:htmlCommandLink>
Hope this helps you.
EDIT: I had some spare time, sorry but we are kinda busy in the project. The Java code was updated and tested in IE8, Firefox 9 and 10, and Chrome 16. For the value of buffer size constant, I did some research and found a good answer in this site.
P.S.: I don't take this as a competition, I just try to help people when I can. If my code is not good then thanks for letting me know it, that's the better way for everyone to grow better and healthy :).
EDIT: Thanks to #lisa
To achieve the following manually, just changing this part in the snippet
//strNombreCompletoArchivo = UManejadorSesionWeb.obtieneParametro("nombreCompletoArchivo");
String parameterStr = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap.get("nombreCompletoArchivo");
strNombreCompletoArchivo = parameterStr;
You don't need the richfaces a4j:commandLink tag for the download, the standard jsf tag h:commandLink would be enough.
Then make sure you have the following headers set on your response (you can check with firebug in firefox):
Content-Disposition attachment; filename="your_file_name.xxx"
Content-Type application/xxx
Content-Length 1234
Content-Length: number of bytes.