Skip to content

Commit

Permalink
Unit tests for Jersey Netty Http Container
Browse files Browse the repository at this point in the history
Current unit tests only validate Netty's `JerseyClientHandler` against a Grizzly http server. This commit adds test cases to validate the same using Netty's `JerseyServerHandler` using `NettyTestContainerFactory`.

Also adding extra test cases to effectively validate the bug fixes for eclipse-ee4j#3500 and eclipse-ee4j#3568 (that is, these new test cases will fail if ran against the code base older than eclipse-ee4j#4312)

Fixes eclipse-ee4j#3500 and eclipse-ee4j#3568

Signed-off-by: Venkat Ganesh <010gvr@gmail.com>
  • Loading branch information
010gvr authored and Venkat committed Dec 1, 2019
1 parent 8dcfed4 commit 0987c2a
Show file tree
Hide file tree
Showing 6 changed files with 681 additions and 0 deletions.
6 changes: 6 additions & 0 deletions containers/netty-http/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
<artifactId>jersey-netty-connector</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.netty.httpserver;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.netty.connector.NettyConnectorProvider;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

/**
* @author Pavel Bucek
*/
public class HelloWorldTest extends JerseyTest {

private static final Logger LOGGER = Logger.getLogger(HelloWorldTest.class.getName());
private static final String ROOT_PATH = "helloworld";

public HelloWorldTest() {
super(new NettyTestContainerFactory());
}

@Path("helloworld")
public static class HelloWorldResource {
public static final String CLICHED_MESSAGE = "Hello World!";

@GET
@Produces("text/plain")
public String getHello() {
return CLICHED_MESSAGE;
}
}

@Override
protected Application configure() {
ResourceConfig config = new ResourceConfig(HelloWorldResource.class);
config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
return config;
}

@Override
protected void configureClient(ClientConfig config) {
config.property(ClientProperties.ASYNC_THREADPOOL_SIZE, 20);
config.connectorProvider(new NettyConnectorProvider());
}

@Test
public void testConnection() {
Response response = target().path(ROOT_PATH).request("text/plain").get();
assertEquals(200, response.getStatus());
}

@Test
public void testClientStringResponse() {
String s = target().path(ROOT_PATH).request().get(String.class);
assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
}

@Test
public void testAsyncClientRequests() throws InterruptedException {
final int REQUESTS = 20;
final CountDownLatch latch = new CountDownLatch(REQUESTS);
final long tic = System.currentTimeMillis();
for (int i = 0; i < REQUESTS; i++) {
final int id = i;
target().path(ROOT_PATH).request().async().get(new InvocationCallback<Response>() {
@Override
public void completed(Response response) {
try {
final String result = response.readEntity(String.class);
assertEquals(HelloWorldResource.CLICHED_MESSAGE, result);
} finally {
latch.countDown();
}
}

@Override
public void failed(Throwable error) {
error.printStackTrace();
latch.countDown();
}
});
}
assertTrue(latch.await(10 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS));
final long toc = System.currentTimeMillis();
Logger.getLogger(HelloWorldTest.class.getName()).info("Executed in: " + (toc - tic));
}

@Test
public void testHead() {
Response response = target().path(ROOT_PATH).request().head();
assertEquals(200, response.getStatus());
assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType());
}

@Test
public void testFooBarOptions() {
Response response = target().path(ROOT_PATH).request().header("Accept", "foo/bar").options();
assertEquals(200, response.getStatus());
final String allowHeader = response.getHeaderString("Allow");
_checkAllowContent(allowHeader);
assertEquals("foo/bar", response.getMediaType().toString());
assertEquals(0, response.getLength());
}

@Test
public void testTextPlainOptions() {
Response response = target().path(ROOT_PATH).request().header("Accept", MediaType.TEXT_PLAIN).options();
assertEquals(200, response.getStatus());
final String allowHeader = response.getHeaderString("Allow");
_checkAllowContent(allowHeader);
assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType());
final String responseBody = response.readEntity(String.class);
_checkAllowContent(responseBody);
}

public void testJson() {
Response response = target().path(ROOT_PATH).request().header("Accept", MediaType.APPLICATION_JSON).options();
assertEquals(200, response.getStatus());
final String allowHeader = response.getHeaderString("Allow");
_checkAllowContent(allowHeader);
assertEquals(MediaType.APPLICATION_JSON, response.getMediaType());
final String responseBody = response.readEntity(String.class);
_checkAllowContent(responseBody);

}

private void _checkAllowContent(final String content) {
assertTrue(content.contains("GET"));
assertTrue(content.contains("HEAD"));
assertTrue(content.contains("OPTIONS"));
}

@Test
public void testMissingResourceNotFound() {
Response response;

response = target().path(ROOT_PATH + "arbitrary").request().get();
assertEquals(404, response.getStatus());
response.close();

response = target().path(ROOT_PATH).path("arbitrary").request().get();
assertEquals(404, response.getStatus());
response.close();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.netty.httpserver;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import org.glassfish.jersey.netty.httpserver.HugeEntityTest.TestEntity;

public class Helper {

public static final int ONE_MB_IN_BYTES = 1024 * 1024; // 1M
public static final long TWENTY_GB_IN_BYTES = 20L * 1024L * 1024L * 1024L; // 20G seems sufficient

public static long drainAndCountInputStream(InputStream in) throws IOException {
long totalBytesRead = 0L;

byte[] buffer = new byte[ONE_MB_IN_BYTES];
int read;
do {
read = in.read(buffer);
if (read > 0) {
totalBytesRead += read;
}
} while (read != -1);

return totalBytesRead;
}

/**
* Utility writer that generates that many zero bytes as given by the input entity size field.
*/
public static class TestEntityWriter implements MessageBodyWriter<TestEntity> {

@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == TestEntity.class;
}

@Override
public long getSize(TestEntity t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1; // no matter what we return here, the output will get chunk-encoded
}

@Override
public void writeTo(TestEntity t,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException, WebApplicationException {

final byte[] buffer = new byte[Helper.ONE_MB_IN_BYTES];
final long bufferCount = t.size / Helper.ONE_MB_IN_BYTES;
final int remainder = (int) (t.size % Helper.ONE_MB_IN_BYTES);

for (long b = 0; b < bufferCount; b++) {
entityStream.write(buffer);
}

if (remainder > 0) {
entityStream.write(buffer, 0, remainder);
}
}
}


}
Loading

0 comments on commit 0987c2a

Please sign in to comment.