diff --git a/containers/pom.xml b/containers/pom.xml
index e0fb55ae33..f42224e193 100644
--- a/containers/pom.xml
+++ b/containers/pom.xml
@@ -68,6 +68,7 @@
jetty-servlet
netty-http
simple-http
+ undertow-http
diff --git a/containers/undertow-http/pom.xml b/containers/undertow-http/pom.xml
new file mode 100644
index 0000000000..99e1851a01
--- /dev/null
+++ b/containers/undertow-http/pom.xml
@@ -0,0 +1,99 @@
+
+
+
+
+ 4.0.0
+
+
+ org.glassfish.jersey.containers
+ project
+ 2.26
+
+
+ jersey-container-undertow-http
+ jar
+ jersey-container-undertow-http
+
+ Undertow Http Container
+
+
+
+ io.undertow
+ undertow-core
+
+
+
+
+
+
+ com.sun.istack
+ maven-istack-commons-plugin
+ true
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ true
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+
+
+
+ ${basedir}/src/main/java
+
+ META-INF/**/*
+
+
+
+ ${basedir}/src/main/resources
+ true
+
+
+
+
+
diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainer.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainer.java
new file mode 100644
index 0000000000..f1ca12b647
--- /dev/null
+++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainer.java
@@ -0,0 +1,116 @@
+package org.glassfish.jersey.undertow;
+
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.HeaderValues;
+import org.glassfish.jersey.internal.MapPropertiesDelegate;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+import org.glassfish.jersey.server.spi.Container;
+
+import javax.ws.rs.core.Application;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * Undertow {@code Container} implementation based on Undertow's {@link io.undertow.server.HttpHandler}.
+ *
+ * @author Jonathan Como (jonathan.como at gmail.com)
+ */
+public class UndertowHttpContainer implements HttpHandler, Container {
+ private volatile ApplicationHandler appHandler;
+
+ UndertowHttpContainer(final Application application) {
+ appHandler = new ApplicationHandler(application);
+
+ // No lifecycle hooks for Undertow's server, so we do this for
+ // completeness but not accuracy.
+ appHandler.onStartup(this);
+ }
+
+ @Override
+ public ApplicationHandler getApplicationHandler() {
+ return appHandler;
+ }
+
+ @Override
+ public ResourceConfig getConfiguration() {
+ return appHandler.getConfiguration();
+ }
+
+ @Override
+ public void reload() {
+ reload(getConfiguration());
+ }
+
+ @Override
+ public void reload(final ResourceConfig configuration) {
+ appHandler.onShutdown(this);
+
+ appHandler = new ApplicationHandler(configuration);
+ appHandler.onReload(this);
+ appHandler.onStartup(this);
+ }
+
+ @Override
+ public void handleRequest(final HttpServerExchange exchange) throws Exception {
+ // If we ware on the IO thread (where the raw HTTP request is processed),
+ // we want to dispatch to a worker. This is the preferred approach.
+ if (exchange.isInIoThread()) {
+ exchange.dispatch(this);
+ return;
+ }
+
+ exchange.startBlocking();
+ URI baseUri = getBaseUri(exchange);
+ ContainerRequest request = new ContainerRequest(baseUri, getRequestUri(exchange, baseUri),
+ exchange.getRequestMethod().toString(), new UndertowSecurityContext(exchange),
+ new MapPropertiesDelegate());
+
+ request.setEntityStream(exchange.getInputStream());
+ request.setWriter(new UndertowResponseWriter(exchange));
+ for (HeaderValues values : exchange.getRequestHeaders()) {
+ String name = values.getHeaderName().toString();
+ for (String value : values) {
+ request.header(name, value);
+ }
+ }
+
+ appHandler.handle(request);
+ }
+
+ // TODO: No easy way to get the mount point without using Undertow servlets library
+ private URI getBaseUri(final HttpServerExchange exchange) {
+ try {
+ return new URI(exchange.getRequestScheme(), null, exchange.getHostName(),
+ exchange.getHostPort(), "/", null, null);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private URI getRequestUri(final HttpServerExchange exchange, final URI baseUri) {
+ String uri = getServerAddress(baseUri) + exchange.getRequestURI();
+ String query = exchange.getQueryString();
+ if (query != null && !query.isEmpty()) {
+ uri += "?" + ContainerUtils.encodeUnsafeCharacters(query);
+ }
+
+ try {
+ return new URI(uri);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private String getServerAddress(final URI baseUri) {
+ String serverAddress = baseUri.toString();
+ if (serverAddress.charAt(serverAddress.length() - 1) == '/') {
+ return serverAddress.substring(0, serverAddress.length() - 1);
+ }
+
+ return serverAddress;
+ }
+}
diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerFactory.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerFactory.java
new file mode 100644
index 0000000000..fd931a6cba
--- /dev/null
+++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerFactory.java
@@ -0,0 +1,76 @@
+package org.glassfish.jersey.undertow;
+
+import io.undertow.Undertow;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.undertow.internal.LocalizationMessages;
+
+import javax.net.ssl.SSLContext;
+import java.net.URI;
+
+/**
+ * Factory for creating and starting Undertow server handlers. This returns
+ * a handle to the started server as {@link Undertow} instances, which allows
+ * the server to be stopped by invoking the {@link Undertow#stop()} method.
+ *
+ * To start the server in HTTPS mode an {@link SSLContext} can be provided.
+ * This will be used to decrypt and encrypt information sent over the
+ * connected TCP socket channel.
+ *
+ * @author Jonathan Como (jonathan.como at gmail.com)
+ */
+public class UndertowHttpContainerFactory {
+ public static Undertow createServer(URI uri, ResourceConfig config) {
+ return createServer(uri, null, config, true);
+ }
+
+ public static Undertow createServer(URI uri, ResourceConfig config, boolean start) {
+ return createServer(uri, null, config, start);
+ }
+
+ public static Undertow createServer(URI uri, SSLContext sslContext, ResourceConfig config) {
+ return createServer(uri, sslContext, config, true);
+ }
+
+ public static Undertow createServer(URI uri, SSLContext sslContext, ResourceConfig config, boolean start) {
+ if (uri == null) {
+ throw new IllegalArgumentException(LocalizationMessages.URI_CANNOT_BE_NULL());
+ }
+
+ int defaultPort;
+ String scheme = uri.getScheme();
+
+ if (sslContext != null) {
+ defaultPort = Container.DEFAULT_HTTPS_PORT;
+ if (!"https".equals(scheme)) {
+ throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTPS());
+ }
+ } else {
+ defaultPort = Container.DEFAULT_HTTP_PORT;
+ if (!"http".equals(scheme)) {
+ throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTP());
+ }
+ }
+
+ int port = uri.getPort();
+ if (port == -1) {
+ port = defaultPort;
+ }
+
+ Undertow.Builder builder = Undertow.builder()
+ .setHandler(new UndertowHttpContainer(config));
+
+ if (sslContext != null) {
+ builder.addHttpsListener(port, uri.getHost(), sslContext);
+ } else {
+ builder.addHttpListener(port, uri.getHost());
+ }
+
+ Undertow server = builder.build();
+ if (start) {
+ server.start();
+ }
+
+ return server;
+ }
+}
diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerProvider.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerProvider.java
new file mode 100644
index 0000000000..c51ad64fd7
--- /dev/null
+++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowHttpContainerProvider.java
@@ -0,0 +1,24 @@
+package org.glassfish.jersey.undertow;
+
+import io.undertow.server.HttpHandler;
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+/**
+ * Container provider for containers based on Undertow Server {@link io.undertow.Undertow}.
+ *
+ * @author Jonathan Como (jonathan.como at gmail.com)
+ */
+public final class UndertowHttpContainerProvider implements ContainerProvider {
+
+ @Override
+ public T createContainer(final Class type, final Application application) throws ProcessingException {
+ if (HttpHandler.class == type || UndertowHttpContainer.class == type) {
+ return type.cast(new UndertowHttpContainer(application));
+ }
+
+ return null;
+ }
+}
diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowResponseWriter.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowResponseWriter.java
new file mode 100644
index 0000000000..fdff705e1c
--- /dev/null
+++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowResponseWriter.java
@@ -0,0 +1,74 @@
+package org.glassfish.jersey.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.HttpString;
+import io.undertow.util.StatusCodes;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+class UndertowResponseWriter implements ContainerResponseWriter {
+ private final HttpServerExchange exchange;
+
+ UndertowResponseWriter(final HttpServerExchange exchange) {
+ this.exchange = exchange;
+ }
+
+ @Override
+ public OutputStream writeResponseStatusAndHeaders(
+ final long contentLength,
+ final ContainerResponse context) throws ContainerException {
+
+ exchange.setStatusCode(context.getStatus());
+
+ Map> headers = context.getStringHeaders();
+ for (Map.Entry> e : headers.entrySet()) {
+ HttpString name = new HttpString(e.getKey());
+ exchange.getResponseHeaders().addAll(name, e.getValue());
+ }
+
+ return exchange.getOutputStream();
+ }
+
+ @Override
+ public boolean suspend(final long timeOut, final TimeUnit timeUnit, final TimeoutHandler timeoutHandler) {
+ return false;
+ }
+
+ @Override
+ public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit) throws IllegalStateException {
+ }
+
+ @Override
+ public void commit() {
+ exchange.endExchange();
+ }
+
+ @Override
+ public void failure(final Throwable error) {
+ if (!exchange.isResponseStarted()) {
+ exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
+ exchange.getResponseSender().send("Internal Server Error");
+ }
+
+ commit();
+
+ // Rethrow the original exception as required by JAX-RS, 3.3.4.
+ if (error instanceof RuntimeException) {
+ throw (RuntimeException) error;
+ } else {
+ throw new ContainerException(error);
+ }
+ }
+
+ @Override
+ public boolean enableResponseBuffering() {
+ return false;
+ }
+}
+
diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowSecurityContext.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowSecurityContext.java
new file mode 100644
index 0000000000..5aec980128
--- /dev/null
+++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/UndertowSecurityContext.java
@@ -0,0 +1,49 @@
+package org.glassfish.jersey.undertow;
+
+import io.undertow.security.idm.Account;
+import io.undertow.server.HttpServerExchange;
+
+import javax.ws.rs.core.SecurityContext;
+import java.security.Principal;
+
+class UndertowSecurityContext implements SecurityContext {
+ private final HttpServerExchange exchange;
+
+ UndertowSecurityContext(final HttpServerExchange exchange) {
+ this.exchange = exchange;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ Account account = getAccount();
+ if (account != null) {
+ return account.getPrincipal();
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isUserInRole(final String role) {
+ Account account = getAccount();
+ return account != null && account.getRoles().contains(role);
+ }
+
+ @Override
+ public boolean isSecure() {
+ return exchange.isSecure();
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return exchange.getSecurityContext().getMechanismName();
+ }
+
+ private Account getAccount() {
+ if (exchange.getSecurityContext() != null) {
+ return exchange.getSecurityContext().getAuthenticatedAccount();
+ }
+
+ return null;
+ }
+}
diff --git a/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/package-info.java b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/package-info.java
new file mode 100644
index 0000000000..3795102dea
--- /dev/null
+++ b/containers/undertow-http/src/main/java/org/glassfish/jersey/undertow/package-info.java
@@ -0,0 +1,44 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2011-2017 Oracle and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://oss.oracle.com/licenses/CDDL+GPL-1.1
+ * or LICENSE.txt. See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+/**
+ * Jersey Undertow container classes.
+ */
+package org.glassfish.jersey.undertow;
diff --git a/containers/undertow-http/src/main/resources/META-INF.services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/undertow-http/src/main/resources/META-INF.services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000000..ddcacdf3f5
--- /dev/null
+++ b/containers/undertow-http/src/main/resources/META-INF.services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.undertow.UndertowHttpContainerProvider
diff --git a/containers/undertow-http/src/main/resources/org.glassfish.jersey.undertow.internal/localization.properties b/containers/undertow-http/src/main/resources/org.glassfish.jersey.undertow.internal/localization.properties
new file mode 100644
index 0000000000..abba9ec2ea
--- /dev/null
+++ b/containers/undertow-http/src/main/resources/org.glassfish.jersey.undertow.internal/localization.properties
@@ -0,0 +1,43 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright (c) 2013-2017 Oracle and/or its affiliates. All rights reserved.
+#
+# The contents of this file are subject to the terms of either the GNU
+# General Public License Version 2 only ("GPL") or the Common Development
+# and Distribution License("CDDL") (collectively, the "License"). You
+# may not use this file except in compliance with the License. You can
+# obtain a copy of the License at
+# https://oss.oracle.com/licenses/CDDL+GPL-1.1
+# or LICENSE.txt. See the License for the specific
+# language governing permissions and limitations under the License.
+#
+# When distributing the software, include this License Header Notice in each
+# file and include the License file at LICENSE.txt.
+#
+# GPL Classpath Exception:
+# Oracle designates this particular file as subject to the "Classpath"
+# exception as provided by Oracle in the GPL Version 2 section of the License
+# file that accompanied this code.
+#
+# Modifications:
+# If applicable, add the following below the License Header, with the fields
+# enclosed by brackets [] replaced by your own identifying information:
+# "Portions Copyright [year] [name of copyright owner]"
+#
+# Contributor(s):
+# If you wish your version of this file to be governed by only the CDDL or
+# only the GPL Version 2, indicate your decision by adding "[Contributor]
+# elects to include this software in this distribution under the [CDDL or GPL
+# Version 2] license." If you don't indicate a single choice of license, a
+# recipient has the option to distribute your version of this file under
+# either the CDDL, the GPL Version 2 or to extend the choice of license to
+# its licensees as provided above. However, if you add GPL Version 2 code
+# and therefore, elected the GPL Version 2 license, then the option applies
+# only if the new code is made subject to such option by the copyright
+# holder.
+#
+
+uri.cannot.be.null=The URI must not be null.
+wrong.scheme.when.using.http=The URI scheme should be 'http' when not using SSL.
+wrong.scheme.when.using.https=The URI scheme should be 'https' when using SSL.
diff --git a/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/AbstractUndertowServerTester.java b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/AbstractUndertowServerTester.java
new file mode 100644
index 0000000000..7114b63524
--- /dev/null
+++ b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/AbstractUndertowServerTester.java
@@ -0,0 +1,86 @@
+package org.glassfish.jersey.undertow;
+
+import io.undertow.Undertow;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.After;
+
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+import java.security.AccessController;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public abstract class AbstractUndertowServerTester {
+ private static final Logger LOGGER = Logger.getLogger(AbstractUndertowServerTester.class.getName());
+ private static final int DEFAULT_PORT = 9998;
+
+ /**
+ * Get the port to be used for test application deployments.
+ *
+ * @return The HTTP port of the URI
+ */
+ private int getPort() {
+ final String value = AccessController
+ .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+ if (value != null) {
+
+ try {
+ final int i = Integer.parseInt(value);
+ if (i <= 0) {
+ throw new NumberFormatException("Value not positive.");
+ }
+ return i;
+ } catch (NumberFormatException e) {
+ LOGGER.log(Level.CONFIG,
+ "Value of 'jersey.config.test.container.port'"
+ + " property is not a valid positive integer [" + value + "]."
+ + " Reverting to default [" + DEFAULT_PORT + "].",
+ e);
+ }
+ }
+ return DEFAULT_PORT;
+ }
+
+ private volatile Undertow server;
+
+ UriBuilder getUri() {
+ return UriBuilder.fromUri("http://localhost").port(getPort());
+ }
+
+ void startServer(Class... resources) {
+ ResourceConfig config = new ResourceConfig(resources);
+ config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+ final URI baseUri = getBaseUri();
+ server = UndertowHttpContainerFactory.createServer(baseUri, config);
+ LOGGER.log(Level.INFO, "Undertow-http server started on base uri: " + baseUri);
+ }
+
+ void startServer(ResourceConfig config) {
+ final URI baseUri = getBaseUri();
+ server = UndertowHttpContainerFactory.createServer(baseUri, config);
+ LOGGER.log(Level.INFO, "Undertow-http server started on base uri: " + baseUri);
+ }
+
+ private URI getBaseUri() {
+ return UriBuilder.fromUri("http://localhost/").port(getPort()).build();
+ }
+
+ private void stopServer() {
+ try {
+ server.stop();
+ server = null;
+ LOGGER.log(Level.INFO, "Undertow-http server stopped.");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @After
+ public void tearDown() {
+ if (server != null) {
+ stopServer();
+ }
+ }
+}
diff --git a/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/BasicRequestTest.java b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/BasicRequestTest.java
new file mode 100644
index 0000000000..b8c42cbf8f
--- /dev/null
+++ b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/BasicRequestTest.java
@@ -0,0 +1,67 @@
+package org.glassfish.jersey.undertow;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+
+public class BasicRequestTest extends AbstractUndertowServerTester {
+
+ @Path("/")
+ @Produces(MediaType.TEXT_PLAIN)
+ public static class TestResource {
+
+ @GET
+ @Path("/dummy")
+ public String dummyResponse(@QueryParam("answer") String answer) {
+ return answer;
+ }
+
+ @GET
+ @Path("/exception/{status}")
+ public void exceptionResponse(@PathParam("status") int status) {
+ throw new WebApplicationException(status);
+ }
+ }
+
+ @Before
+ public void setup() {
+ startServer(TestResource.class);
+ }
+
+ @Test
+ public void test400StatusCode() {
+ Client client = ClientBuilder.newClient();
+ WebTarget target = client.target(getUri().path("exception/400").build());
+ assertEquals(400, target.request().get(Response.class).getStatus());
+ }
+
+ @Test
+ public void test500StatusCode() {
+ Client client = ClientBuilder.newClient();
+ WebTarget target = client.target(getUri().path("exception/500").build());
+ assertEquals(500, target.request().get(Response.class).getStatus());
+ }
+
+ @Test
+ public void testValidResponse() {
+ String answer = "Tested, working.";
+ Client client = ClientBuilder.newClient();
+ URI uri = getUri().path("dummy").queryParam("answer", answer).build();
+ WebTarget target = client.target(uri);
+ assertEquals(answer, target.request().get(String.class));
+ }
+}
\ No newline at end of file
diff --git a/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/LifecycleListenerTest.java b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/LifecycleListenerTest.java
new file mode 100644
index 0000000000..33ee7a841b
--- /dev/null
+++ b/containers/undertow-http/src/test/java/org/glassfish/jersey/undertow/LifecycleListenerTest.java
@@ -0,0 +1,54 @@
+package org.glassfish.jersey.undertow;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener;
+import org.glassfish.jersey.server.spi.Container;
+import org.junit.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import static org.junit.Assert.assertEquals;
+
+public class LifecycleListenerTest extends AbstractUndertowServerTester {
+
+ @Path("/")
+ public static class TestResource {
+ @GET
+ public void doNothing() {
+ }
+ }
+
+ public static class Reloader extends AbstractContainerLifecycleListener {
+ Container container;
+
+ public void reload(ResourceConfig newConfig) {
+ container.reload(newConfig);
+ }
+
+ @Override
+ public void onStartup(Container container) {
+ this.container = container;
+ }
+ }
+
+ @Test
+ public void testReload() throws Exception {
+ Reloader reloader = new Reloader();
+ ResourceConfig config = new ResourceConfig();
+ config.registerInstances(reloader);
+
+ startServer(config);
+
+ Client client = ClientBuilder.newClient();
+ WebTarget target = client.target(getUri().path("/").build());
+ assertEquals(404, target.request().get(Response.class).getStatus());
+
+ reloader.reload(new ResourceConfig(TestResource.class));
+ assertEquals(204, target.request().get(Response.class).getStatus());
+ }
+}
diff --git a/pom.xml b/pom.xml
index f42ccf6991..991ab12ba4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1421,6 +1421,12 @@
${simple.version}
+
+ io.undertow
+ undertow-core
+ ${undertow.version}
+
+
org.codehaus.jettison
jettison
@@ -1908,6 +1914,7 @@
6.0.1
1.7.12
4.3.4.RELEASE
+ 1.4.21.Final
5.1.3.Final
2.2.14.Final
3.0.0.Final