0

I'm deploying JSF portlet on HCL Portal 9.5 that includes a servlet for PDF opened in a new tab. I don't know if that's a common behavior but when I click a download button in a newly opened tab, servlet is called again. Why? Can I avoid this?

servlet class:

public class PdfDownloadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    // za shranjevanje SNMP pasti
    private static HashMap<SnmpNapakeEnum, Date> snmpMap = new HashMap<SnmpNapakeEnum, Date>();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        processRequest(req, resp);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        processRequest(req, resp);
    }

    protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String param1 = ... // reading some url params

        try {
            writeOutputStream(response);
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            OutputStream os = null;
            try {
                os = response.getOutputStream();
                os.write(e.getMessage().getBytes("UTF-8"));
            } finally {
                if (ObjectUtils.isNotEmpty(os)) {
                    os.close();
                    os = null;
                }
            }
        }
    }

    private void writeOutputStream(HttpServletResponse response, String param1) throws IOException, ParseException {
        OutputStream os = null;
        try {
            byte[] data = ... // calling some ejb method that returns byte array 

            response.setContentType("application/pdf");
            response.setContentLength(data.length);

            response.setHeader("Content-Disposition", "inline;filename=Sample.pdf");

            os = response.getOutputStream();
            os.write(data);
        } finally {
            if (ObjectUtils.isNotEmpty(os)) {
                os.close();
                os = null;
            }
        }
    }
}

web.xml

<servlet>
        <description>Pdf download</description>
        <display-name>PdfDownloadServlet</display-name>
        <servlet-name>PdfDownloadServlet</servlet-name>
<servlet-class>si.nijz.rizddz.portlet.skupno.servlets.ObvestiloOStDelavcaServlet</servlet-class>
    </servlet>
<servlet-mapping>
        <servlet-name>PdfDownloadServlet</servlet-name>
        <url-pattern>/PdfDownloadServlet</url-pattern>
</servlet-mapping>

EDIT1:

I'm calling JS function (that calls servlet) from my backing bean.

action listener:

public void onBtnGeneratePdf() {
    String date1AsStr = "2020-02-13";
    
    PrimeFaces.current().executeScript("generatePdf('" + date1AsStr + "');");
}

JS function:

generatePdf : function(date1AsStr) {
    try {
            var path = $('[id$=hidContextPath]').val() + '/PdfDownloadServlet?date1AsStr=' + date1AsStr;
            window.open(path);
    } catch (e) {
        ...
    }
    return true;
}

button html:

<p:commandButton id="btnGenerate" icon="ui-icon-document" actionListener="#{myBean.onBtnGeneratePdf}" value="Generate" />
peterremec
  • 488
  • 1
  • 15
  • 46
  • Is the `download` button designed by you? – Arun Sudhakaran May 29 '23 at 07:37
  • @ArunSudhakaran yes, it is, see edit1. I'm sure that it can be done in a different (easier way), but still I can't understand why servlet is called again when i hit Download button of pdf preview. – peterremec May 29 '23 at 12:09
  • Your question is that the `PdfDownloadServlet` is being called when the `download` button is clicked, right? – Arun Sudhakaran May 29 '23 at 12:53
  • @ArunSudhakaran I'm sorry, I misunderstood your question. Button, that is designed by me, is `btnGenerate` (see code above). It triggers action listener `onBtnGeneratePdf` that calls JS function `generatePdf`. That function opens pdf in new tab (calls servlet) and when I try to download pdf (using download button in newly opened tab - Chrome's Pdf Viever), servlet is called the second time. This is what I don't understand - why is servlet called the second? – peterremec May 29 '23 at 13:31
  • 1
    You can try telling the client to cache it: https://stackoverflow.com/a/29991447 But if that also doesn't work then it's beyond your control. It's the client who's acting inefficiently, not the server. – BalusC May 30 '23 at 10:19
  • @BalusC I understand it's a client "problem" but I must admit I didn't know that servlet is called again if user wants to downolad pdf from Pdf Viewer. I thought that pdf is already cached in browser when preview is opened. I've just set `Content-Disposition` to `attachment` this time but it's good to know that another solution exists. Thanks. – peterremec May 30 '23 at 17:23

1 Answers1

1

Your servlet is designed to process both GET and POST requests. When you click the button, it triggers the onBtnGeneratePdf method in your backing bean, which calls the generatePdf JavaScript function. This function opens a new window with the URL of your servlet, which results in a GET request to your servlet.

Now, when you say you click a download button in the new tab and the servlet is called again, I suspect this download button is part of the browser's UI for handling PDF files, and not a button you have defined in your application.

when I try to download pdf (using download button in newly opened tab - Chrome's Pdf Viever)

That would confirm it.

When a PDF file is served with the content-disposition header set to inline, the browser displays the PDF within the browser window and typically adds a toolbar with options to zoom, print, and download the file. When you click the download button on this toolbar, it sends another GET request to the same URL to download the file, which is why your servlet is called again.

If you want to avoid this, you can try changing the content-disposition header from inline to attachment. This will force the browser to download the file directly instead of displaying it inline, which might be more in line with what you are trying to achieve.

You can do this by changing this line in your writeOutputStream method:

response.setHeader("Content-Disposition", "inline;filename=Sample.pdf");

to:

response.setHeader("Content-Disposition", "attachment;filename=Sample.pdf");

As seen here, you might also need to change the content type:
response.setContentType("application/x-download");

This should cause the file to be downloaded immediately when you call the servlet, and it will not be displayed in the browser, so there should be no download button to click and trigger a second call to your servlet. However, this will also mean that users will not be able to view the PDF in their browser before downloading it, which might not be what you want.

You could also consider generating a unique URL for each PDF download request. This would mean that when a user clicks the download button in the PDF viewer, it would not trigger a second call to your servlet with the same parameters. Instead, the browser would download the PDF from the URL directly. This approach might require more changes to your code and server configuration, though.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250