Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: copy headers from StreamResource to VaadinResponse #209

Merged
merged 3 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Expand Up @@ -15,13 +15,13 @@
*/
package com.vaadin.flow.portal;

import java.net.URI;
import java.net.URISyntaxException;

import javax.portlet.MimeResponse;
import javax.portlet.PortletResponse;
import javax.portlet.ResourceURL;

import java.net.URI;
import java.net.URISyntaxException;

import com.vaadin.flow.server.AbstractStreamResource;
import com.vaadin.flow.server.StreamRegistration;
import com.vaadin.flow.server.StreamResource;
Expand All @@ -42,7 +42,8 @@ class PortletStreamResourceRegistry extends StreamResourceRegistry {
/**
* Creates stream resource registry for provided {@code session}.
*
* @param session Vaadin portlet session
* @param session
* Vaadin portlet session
*/
public PortletStreamResourceRegistry(VaadinPortletSession session) {
super(session);
Expand All @@ -54,15 +55,16 @@ public URI getTargetURI(AbstractStreamResource resource) {
}

@Override
public StreamRegistration registerResource(AbstractStreamResource resource) {
StreamRegistration streamRegistration = super.registerResource(resource);
public StreamRegistration registerResource(
AbstractStreamResource resource) {
StreamRegistration streamRegistration = super.registerResource(
resource);
return new RegistrationWrapper(streamRegistration);
}

/**
* StreamRegistration implementation which embeds dynamic resource url
* into portlet url as a 'resourceUrl' query parameter, see
* {@link ResourceURL}.
* StreamRegistration implementation which embeds dynamic resource url into
* portlet url as a 'resourceUrl' query parameter, see {@link ResourceURL}.
*/
private final class RegistrationWrapper implements StreamRegistration {

Expand Down Expand Up @@ -96,7 +98,13 @@ private URI doGetUri(AbstractStreamResource resource) {
ResourceURL resourceURL = mimeResponse.createResourceURL();
resourceURL.setResourceID(startWithSlash(getURI(resource)));
try {
return new URI("." + resourceURL);
// In Liferay resourceURL is absolute, whereas in Pluto it is
// relative
URI uri = new URI(resourceURL.toString());
if (!uri.isAbsolute()) {
uri = new URI("." + uri);
}
return uri;
} catch (URISyntaxException e) {
// should not happen
throw new RuntimeException(e);
Expand All @@ -108,7 +116,7 @@ private URI doGetUri(AbstractStreamResource resource) {

private String startWithSlash(URI uri) {
String uriString = uri.toString();
if(uriString.startsWith("/")) {
if (uriString.startsWith("/")) {
return uriString;
}
return "/" + uriString;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.vaadin.flow.portal;

import javax.portlet.MimeResponse;
import javax.portlet.PortletRequest;

import java.io.ByteArrayInputStream;
import java.io.IOException;

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

import com.vaadin.flow.server.StreamResource;

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,38 @@ public void getResourceUri_mimeContent_returnsEmbeddedUrl() {
}
}

@Test
public void getResourceUri_mimeContent_returnsAbsoluteEmbeddedUrl() {
MimeResponse mimeResponse = Mockito.mock(MimeResponse.class);

final String resourceUrl = "http://localhost:8888/pluto/portal/Test/__pdtestsuite.TestPortlet1%21764587357%7C0;0/__rs2/__clcacheLevelPage/__ri0x3uidl/__ws0;normal";
Mockito.when(mimeResponse.createResourceURL()).thenReturn(
new ResourceUrlMock(resourceUrl));

VaadinPortletResponse vaadinPortletResponse =
new VaadinPortletResponse(mimeResponse, service);

VaadinResponse vaadinResponse = CurrentInstance.get(VaadinResponse.class);
UI currentUI = CurrentInstance.get(UI.class);
try {
CurrentInstance.set(VaadinResponse.class, vaadinPortletResponse);
CurrentInstance.set(UI.class, this.ui);
StreamRegistration registration = registry.registerResource(streamResourceMock);
URI resourceUri = registration.getResourceUri();
String expected = resourceUrl + "/VAADIN/dynamic/resource/42/"
+ resourceId + "/test.xml";
Assert.assertEquals(expected, resourceUri.toString());
} finally {
if (vaadinResponse != null) {
CurrentInstance.set(VaadinResponse.class, vaadinResponse);
}
if (currentUI != null) {
CurrentInstance.set(UI.class, currentUI);
}
}
}


@Test
public void getResourceUri_nonMimeContent_returnsDirectUrl() {
VaadinPortletResponse responseMock = Mockito.mock(VaadinPortletResponse.class);
Expand Down