PreCompressedResourceTestCase.java
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.server.handlers.file;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPOutputStream;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.DecompressingEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import io.undertow.predicate.Predicates;
import io.undertow.server.handlers.CanonicalPathHandler;
import io.undertow.server.handlers.PathHandler;
import io.undertow.server.handlers.encoding.ContentEncodingRepository;
import io.undertow.server.handlers.encoding.EncodingHandler;
import io.undertow.server.handlers.encoding.GzipEncodingProvider;
import io.undertow.server.handlers.resource.PathResourceManager;
import io.undertow.server.handlers.resource.PreCompressedResourceSupplier;
import io.undertow.server.handlers.resource.ResourceHandler;
import io.undertow.testutils.DefaultServer;
import io.undertow.testutils.HttpClientUtils;
import io.undertow.testutils.TestHttpClient;
import io.undertow.util.Headers;
import io.undertow.util.StatusCodes;
/**
* @author Stuart Douglas
*/
@RunWith(DefaultServer.class)
public class PreCompressedResourceTestCase {
@After
public void clean() throws IOException, URISyntaxException {
Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent();
if (Files.exists(rootPath.resolve("page.html.gz"))) {
Files.delete(rootPath.resolve("page.html.gz"));
}
if (Files.exists(rootPath.resolve("page.html.gzip"))) {
Files.delete(rootPath.resolve("page.html.gzip"));
}
if (Files.exists(rootPath.resolve("page.html.nonsense"))) {
Files.delete(rootPath.resolve("page.html.nonsense"));
}
if (Files.exists(rootPath.resolve("page.html.gzip.nonsense"))) {
Files.delete(rootPath.resolve("page.html.gzip.nonsense"));
}
}
@Test
public void testContentEncodedResource() throws IOException, URISyntaxException {
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/page.html");
TestHttpClient client = new TestHttpClient();
Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent();
try (CloseableHttpClient compClient = HttpClientBuilder.create().build()){
DefaultServer.setRootHandler(new CanonicalPathHandler()
.setNext(new PathHandler()
.addPrefixPath("/path", new ResourceHandler(new PreCompressedResourceSupplier(new PathResourceManager(rootPath, 10485760)).addEncoding("gzip", ".gz"))
.setDirectoryListingEnabled(true))));
//assert response without compression
final String plainResponse = assertResponse(client.execute(get), false);
//assert compressed response, that doesn't exists, so returns plain
assertResponse(compClient.execute(get), false, plainResponse);
//generate compressed resource with extension .gz
generatePreCompressedResource("gz");
//assert compressed response that was pre compressed
assertResponse(compClient.execute(get), true, plainResponse, "gz", "text/html");
} finally {
client.getConnectionManager().shutdown();
}
}
@Test
public void testContentEncodedJsonResource() throws IOException, URISyntaxException {
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/data1.json");
TestHttpClient client = new TestHttpClient();
Path rootPath = Paths.get(getClass().getResource("data1.json").toURI()).getParent();
try (CloseableHttpClient compClient = HttpClientBuilder.create().build()){
DefaultServer.setRootHandler(new CanonicalPathHandler()
.setNext(new PathHandler()
.addPrefixPath("/path", new ResourceHandler(new PreCompressedResourceSupplier(new PathResourceManager(rootPath, 10485760)).addEncoding("gzip", ".gz"))
.setDirectoryListingEnabled(true))));
//assert response without compression
final String plainResponse = assertResponse(client.execute(get), false, null, "web", "application/json");
//assert compressed response, that doesn't exists, so returns plain
assertResponse(compClient.execute(get), false, plainResponse, "web", "application/json");
//generate compressed resource with extension .gz
Path json = rootPath.resolve("data1.json");
generateGZipFile(json, rootPath.resolve("data1.json.gz"));
//assert compressed response that was pre compressed
assertResponse(compClient.execute(get), true, plainResponse, "gz", "application/json");
} finally {
client.getConnectionManager().shutdown();
}
}
@Test
public void testContentEncodedJsonResourceWithoutUncompressed() throws IOException, URISyntaxException {
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/data3.json");
TestHttpClient client = new TestHttpClient();
Path rootPath = Paths.get(getClass().getResource("data2.json").toURI()).getParent();
try (CloseableHttpClient compClient = HttpClientBuilder.create().build()){
DefaultServer.setRootHandler(new CanonicalPathHandler()
.setNext(new PathHandler()
.addPrefixPath("/path", new ResourceHandler(new PreCompressedResourceSupplier(new PathResourceManager(rootPath, 10485760)).addEncoding("gzip", ".gz"))
.setDirectoryListingEnabled(true))));
//generate compressed resource with extension .gz and delete the uncompressed
Path json = rootPath.resolve("data2.json");
Path jsonFileToBeZippedAndDeleted = rootPath.resolve("data3.json");
Files.copy(json, jsonFileToBeZippedAndDeleted);
// data3.json.gz has no corresponding data3.json in the filesystem (UNDERTOW-1950)
generateGZipFile(jsonFileToBeZippedAndDeleted, rootPath.resolve("data3.json.gz"));
Files.delete(jsonFileToBeZippedAndDeleted);
//assert compressed response even with missing uncompressed
assertResponse(compClient.execute(get), true, null, "gz", "application/json");
} finally {
client.getConnectionManager().shutdown();
}
}
@Test
public void testCorrectResourceSelected() throws IOException, URISyntaxException {
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/path/page.html");
TestHttpClient client = new TestHttpClient();
Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent();
try (CloseableHttpClient compClient = HttpClientBuilder.create().build()){
DefaultServer.setRootHandler(new CanonicalPathHandler()
.setNext(new PathHandler()
.addPrefixPath("/path", new EncodingHandler(new ContentEncodingRepository()
.addEncodingHandler("gzip", new GzipEncodingProvider(), 50, Predicates.truePredicate()))
.setNext(new ResourceHandler(new PreCompressedResourceSupplier(new PathResourceManager(rootPath, 10485760)).addEncoding("gzip", ".gzip"))
.setDirectoryListingEnabled(true)))
));
//assert response without compression
final String plainResponse = assertResponse(client.execute(get), false);
//assert compressed response generated by filter
assertResponse(compClient.execute(get), true, plainResponse);
//generate resources
generatePreCompressedResource("gzip");
generatePreCompressedResource("nonsense");
generatePreCompressedResource("gzip.nonsense");
//assert compressed response that was pre compressed
assertResponse(compClient.execute(get), true, plainResponse, "gzip", "text/html");
} finally {
client.getConnectionManager().shutdown();
}
}
private void generateGZipFile(Path source, Path target) throws IOException {
byte[] buffer = new byte[1024];
GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(target.toFile()));
FileInputStream in = new FileInputStream(source.toFile());
int len;
while ((len = in.read(buffer)) > 0) {
gzos.write(buffer, 0, len);
}
in.close();
gzos.finish();
gzos.close();
}
private void replaceStringInFile(Path file, String original, String replacement) throws IOException {
String content = new String(Files.readAllBytes(file), StandardCharsets.UTF_8);
content = content.replaceAll(original, replacement);
Files.write(file, content.getBytes(StandardCharsets.UTF_8));
}
private String assertResponse(HttpResponse response, boolean encoding) throws IOException {
return assertResponse(response, encoding, null, null, "text/html");
}
private String assertResponse(HttpResponse response, boolean encoding, String compareWith) throws IOException {
return assertResponse(response, encoding, compareWith, "web", "text/html");
}
/**
* Series of assertions checking response code, headers and response content
*/
private String assertResponse(HttpResponse response, boolean encoding, String compareWith, String extension, String contentType) throws IOException {
Assert.assertEquals(StatusCodes.OK, response.getStatusLine().getStatusCode());
String body = HttpClientUtils.readResponse(response);
Header[] headers = response.getHeaders(Headers.CONTENT_TYPE_STRING);
Assert.assertEquals(contentType, headers[0].getValue());
if (encoding) {
assert response.getEntity() instanceof DecompressingEntity; //no other nice way to be sure we get back gzipped content
} else {
Assert.assertNull(response.getFirstHeader(Headers.CONTENT_ENCODING_STRING));
}
if (compareWith != null) {
Assert.assertEquals(compareWith.replace("\r", "").replace("web", extension), body.replace("\r", "")); //ignore line ending differences and change inside of html page
}
return body;
}
/**
* Creates compressed resource made by compressing page.html which content is updated before with {@param extension}
* and after compression returned to original content
*/
private void generatePreCompressedResource(String extension) throws IOException, URISyntaxException{
Path rootPath = Paths.get(getClass().getResource("page.html").toURI()).getParent();
Path html = rootPath.resolve("page.html");
replaceStringInFile(html, "web", extension);
generateGZipFile(html, rootPath.resolve("page.html." + extension));
replaceStringInFile(html, extension, "web");
}
}