Skip to content

Commit

Permalink
fix: copy headers from StreamResource to VaadinResponse
Browse files Browse the repository at this point in the history
Copy all headers set on a StreamResource to VaadinResponse before
writing contents.

Fixes #203
  • Loading branch information
mcollovati authored and mshabarov committed Mar 8, 2022
1 parent 7feac4c commit 3e6de7b
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.vaadin.flow.portal.streamresource;

import java.io.ByteArrayInputStream;

import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.server.StreamResource;

public class StreamResourceContent extends VerticalLayout {

static final String FILENAME = "export.xlsx";

public StreamResourceContent() {
StreamResource downloadResource = new StreamResource(FILENAME,
() -> new ByteArrayInputStream(new byte[0]));
downloadResource.setContentType("application/xls");
downloadResource.setHeader("Content-Disposition",
"attachment;filename=export.xlsx");
Anchor link = new Anchor(downloadResource, "Download File");
link.setId("downloadLink");
add(link);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.vaadin.flow.portal.streamresource;

import com.vaadin.flow.portal.VaadinPortlet;

public class StreamResourcePortlet
extends VaadinPortlet<StreamResourceContent> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,15 @@
</supports>
</portlet>

<portlet>
<portlet-name>streamresource</portlet-name>
<display-name>Flow Portlet with StreamResource download</display-name>
<portlet-class>com.vaadin.flow.portal.streamresource.StreamResourcePortlet</portlet-class>
<expiration-cache>0</expiration-cache>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>VIEW</portlet-mode>
</supports>
</portlet>

</portlet-app>
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.vaadin.flow.portal.streamresource;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.junit.Assert;
import org.junit.Test;
import org.openqa.selenium.JavascriptExecutor;

import com.vaadin.flow.component.html.testbench.AnchorElement;
import com.vaadin.flow.portal.AbstractPlutoPortalTest;

public class StreamResourceIT extends AbstractPlutoPortalTest {

public StreamResourceIT() {
super("tests-generic", "streamresource");
}

@Test
public void downloadStreamResource_responseHeadersAreSent() {
AnchorElement link = getVaadinPortletRootElement()
.$(AnchorElement.class).id("downloadLink");
String url = link.getAttribute("href");
getDriver().manage().timeouts().setScriptTimeout(15, TimeUnit.SECONDS);

Map<String, String> headers = downloadAndGetResponseHeaders(url);

Assert.assertEquals(
"attachment;filename=" + StreamResourceContent.FILENAME,
headers.getOrDefault("content-disposition", null));
}

/*
* Stolen from stackexchange.
*
* It's not possible to use a straight way to download the link externally
* since it will use another session and the link will be invalid in this
* session. So either this pure client side way or external download with
* cookies copy (which allows preserve the session) needs to be used.
*/
@SuppressWarnings("unchecked")
public Map<String, String> downloadAndGetResponseHeaders(String url) {
String script = "var url = arguments[0];"
+ "var callback = arguments[arguments.length - 1];"
+ "var xhr = new XMLHttpRequest();"
+ "xhr.open('GET', url, true);"
+ "xhr.responseType = \"arraybuffer\";" +
// force the HTTP response, response-type header to be array
// buffer
"xhr.onload = function() {"
// Get the raw header string "
+ " var headers = xhr.getAllResponseHeaders();"
// Convert the header string into an array
// of individual headers
+ " var arr = headers.trim().split(/[\\r\\n]+/);"
// Create a map of header names to values
+ " var headerMap = {};" + " arr.forEach(function (line) { "
+ " var parts = line.split(': '); "
+ " var header = parts.shift().toLowerCase(); "
+ " var value = parts.join(': '); "
+ " headerMap[header] = value;" + " }); "
+ " callback(headerMap);" + "};" + "xhr.send();";
Object response = ((JavascriptExecutor) getDriver())
.executeAsyncScript(script, url);
return (Map<String, String>) response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public void handleRequest(VaadinSession session, VaadinRequest request,
try {
setResponseContentType(request, response, streamResource);
response.setCacheTime(streamResource.getCacheTime());
streamResource.getHeaders().forEach(response::setHeader);
writer = streamResource.getWriter();
if (writer == null) {
throw new IOException(
Expand Down Expand Up @@ -56,17 +57,15 @@ public void handleRequest(VaadinSession session, VaadinRequest request,
}

private void setResponseContentType(VaadinRequest request,
VaadinResponse response,
StreamResource streamResource) {
VaadinResponse response, StreamResource streamResource) {
PortletContext context = ((VaadinPortletRequest) request)
.getPortletContext();
try {
response.setContentType(streamResource.getContentTypeResolver()
.apply(streamResource, null));
} catch (NullPointerException e) {
response.setContentType(Optional
.ofNullable(
context.getMimeType(streamResource.getName()))
.ofNullable(context.getMimeType(streamResource.getName()))
.orElse("application/octet-stream"));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.vaadin.flow.portal;

import javax.portlet.MimeResponse;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.server.AbstractStreamResource;
import com.vaadin.flow.server.StreamResource;

import static org.junit.Assert.*;

public class PortletStreamResourceHandlerTest {

private PortletStreamResourceHandler handler = new PortletStreamResourceHandler();
private VaadinPortletSession session;
private VaadinPortletService service;

@Before
public void init() {
service = Mockito.mock(VaadinPortletService.class);
session = new VaadinPortletSession(service) {
@Override
public boolean hasLock() {
return true;
}

@Override
public void lock() {
}

@Override
public void unlock() {
}

@Override
public void checkHasLock() {

}
};
}

@Test
public void handleRequest_shouldApplyStreamResourceHeaders()
throws IOException {
PortletRequest portletRequest = Mockito.mock(PortletRequest.class);
VaadinPortletRequest request = new VaadinPortletRequest(portletRequest,
service);
MimeResponse portletResponse = Mockito.mock(MimeResponse.class);
VaadinPortletResponse response = new VaadinPortletResponse(
portletResponse, service);

StreamResource resource = new StreamResource("export.xlsx",
() -> new ByteArrayInputStream(new byte[0]));
resource.setContentType("application/xls");
String headerName = "Content-Disposition";
String headerValue = "attachment;filename=export.xlsx";
resource.setHeader(headerName, headerValue);
handler.handleRequest(session, request, response, resource);
Mockito.verify(portletResponse, Mockito.atLeastOnce()).setProperty(headerName, headerValue);
}

}

0 comments on commit 3e6de7b

Please sign in to comment.