From 6374aa554ba88223baa05800acc66e364c9ff3e7 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Mar 2025 19:44:07 -0400 Subject: [PATCH 1/5] start --- avaje-jex-helidon-spi/pom.xml | 20 ++ .../http/spi/GrizzlyHttpExchangeDelegate.java | 193 ++++++++++++++++ .../http/spi/HttpSpiContextHandler.java | 124 ++++++++++ .../helidon/http/spi/JettyHttpContext.java | 84 +++++++ .../helidon/http/spi/JettyHttpServer.java | 215 ++++++++++++++++++ .../spi/VirtualThreadsExecutorService.java | 65 ++++++ pom.xml | 1 + 7 files changed, 702 insertions(+) create mode 100644 avaje-jex-helidon-spi/pom.xml create mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java create mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/HttpSpiContextHandler.java create mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java create mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java create mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/VirtualThreadsExecutorService.java diff --git a/avaje-jex-helidon-spi/pom.xml b/avaje-jex-helidon-spi/pom.xml new file mode 100644 index 00000000..696df4c1 --- /dev/null +++ b/avaje-jex-helidon-spi/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + io.avaje + avaje-jex-parent + 3.0-RC23 + + avaje-jex-helidon-spi + + + + + org.glassfish.grizzly + grizzly-http-server + 4.1.0-M1 + + + + + \ No newline at end of file diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java new file mode 100644 index 00000000..0a571a58 --- /dev/null +++ b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java @@ -0,0 +1,193 @@ +package io.avaje.helidon.http.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; + +/** Jetty implementation of {@link com.sun.net.httpserver.HttpExchange} */ +public class GrizzlyHttpExchangeDelegate extends HttpExchange { + /** Set of headers that RFC9110 says will not have a value list */ + private static final Set SINGLE_VALUE_HEADERS = + Set.of( + "authorization", + "content-length", + "date", + "expires", + "host", + "if-modified-since", + "if-unmodified-since", + "if-range", + "last-modified", + "location", + "referer", + "retry-after", + "user-agent"); + + private final HttpContext context; + + private final Request request; + + private final Headers responseHeaders = new Headers(); + + private Headers requestHeaders = new Headers(); + + private int statusCode = 0; + + private InputStream inputStream; + + private OutputStream outputStream; + + private HttpPrincipal httpPrincipal; + + private Response response; + + GrizzlyHttpExchangeDelegate(HttpContext httpSpiContext, Request request) { + this.context = httpSpiContext; + this.request = request; + this.response = request.getResponse(); + this.inputStream = request.getInputStream(); + this.outputStream = response.getOutputStream(); + } + + @Override + public Headers getRequestHeaders() { + + if (!requestHeaders.isEmpty()) { + return requestHeaders; + } + for (var name : request.getHeaderNames()) { + + if (!SINGLE_VALUE_HEADERS.contains(name.toLowerCase())) { + + for (String value : request.getHeaders(name)) { + requestHeaders.add(name, value); + } + } else { + requestHeaders.add(name, request.getHeader(name)); + } + } + return requestHeaders; + } + + @Override + public Headers getResponseHeaders() { + return responseHeaders; + } + + @Override + public URI getRequestURI() { + return URI.create(request.getRequestURI()); + } + + @Override + public String getRequestMethod() { + return request.getMethod().getMethodString(); + } + + @Override + public HttpContext getHttpContext() { + return context; + } + + @Override + public void close() { + try { + outputStream.close(); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + @Override + public InputStream getRequestBody() { + return inputStream; + } + + @Override + public OutputStream getResponseBody() { + return outputStream; + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException { + this.statusCode = rCode; + + for (Map.Entry> stringListEntry : responseHeaders.entrySet()) { + String name = stringListEntry.getKey(); + List values = stringListEntry.getValue(); + for (String value : values) { + response.addHeader(name, value); + } + } + + if (responseLength == -1) { + response.setContentLengthLong(0); + } else if (responseLength == 0) { + response.setContentLengthLong(-1); + } else { + response.setContentLengthLong(responseLength); + } + + response.setStatus(rCode); + } + + @Override + public InetSocketAddress getRemoteAddress() { + return InetSocketAddress.createUnresolved(request.getRemoteAddr(), request.getRemotePort()); + } + + @Override + public int getResponseCode() { + return statusCode; + } + + @Override + public InetSocketAddress getLocalAddress() { + return new InetSocketAddress(request.getLocalAddr(), request.getLocalPort()); + } + + @Override + public String getProtocol() { + return request.getProtocol().getProtocolString(); + } + + @Override + public Object getAttribute(String name) { + return request.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) { + request.setAttribute(name, value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) { + assert inputStream != null; + if (i != null) inputStream = i; + if (o != null) outputStream = o; + } + + @Override + public HttpPrincipal getPrincipal() { + return httpPrincipal; + } + + public void setPrincipal(HttpPrincipal principal) { + this.httpPrincipal = principal; + } +} diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/HttpSpiContextHandler.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/HttpSpiContextHandler.java new file mode 100644 index 00000000..9103a6db --- /dev/null +++ b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/HttpSpiContextHandler.java @@ -0,0 +1,124 @@ +package io.avaje.helidon.http.spi; + +import java.util.List; +import java.util.Map; + +import javax.security.auth.callback.Callback; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.glassfish.grizzly.http.server.Response; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.Authenticator.Result; +import com.sun.net.httpserver.Filter.Chain; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpPrincipal; +import com.sun.org.slf4j.internal.LoggerFactory; + +/** + * Jetty handler that bridges requests to {@link HttpHandler}. + */ +public class HttpSpiContextHandler extends ContextHandler +{ + public static final Logger LOG = LoggerFactory.getLogger(HttpSpiContextHandler.class); + + private final HttpContext _httpContext; + + private HttpHandler _httpHandler; + + public HttpSpiContextHandler(HttpContext httpContext, HttpHandler httpHandler) + { + this._httpContext = httpContext; + this._httpHandler = httpHandler; + // The default jax-ws web server allows posting to URLs that do not end + // with a trailing '/'; allow it too to be a drop-in replacement. + setAllowNullPathInContext(true); + super.setHandler(new Handler.Abstract() + { + @Override + public boolean handle(Request request, Response response, Callback callback) + { + try (HttpExchange jettyHttpExchange = request.isSecure() + ? new JettyHttpsExchange(_httpContext, request, response) + : new JettyHttpExchange(_httpContext, request, response)) + { + Authenticator auth = _httpContext.getAuthenticator(); + if (auth != null && handleAuthentication(request, response, callback, jettyHttpExchange, auth)) + return true; + + new Chain(_httpContext.getFilters(), _httpHandler).doFilter(jettyHttpExchange); + callback.succeeded(); + } + catch (Exception ex) + { + LOG.debug("Failed to handle", ex); + Response.writeError(request, response, callback, 500, null, ex); + } + return true; + } + }); + } + + @Override + public void setHandler(Handler handler) + { + throw new UnsupportedOperationException(); + } + + private boolean handleAuthentication( + Request request, + Response response, + Callback callback, + HttpExchange httpExchange, + Authenticator auth) + { + Result result = auth.authenticate(httpExchange); + if (result instanceof Authenticator.Failure) + { + int rc = ((Authenticator.Failure)result).getResponseCode(); + for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) + { + for (String value : header.getValue()) + response.getHeaders().add(header.getKey(), value); + } + Response.writeError(request, response, callback, rc); + return true; + } + + if (result instanceof Authenticator.Retry) + { + int rc = ((Authenticator.Retry)result).getResponseCode(); + for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) + { + for (String value : header.getValue()) + { + response.getHeaders().add(header.getKey(), value); + } + } + Response.writeError(request, response, callback, rc); + return true; + } + + if (result instanceof Authenticator.Success) + { + HttpPrincipal principal = ((Authenticator.Success)result).getPrincipal(); + ((JettyExchange)httpExchange).setPrincipal(principal); + return false; + } + + Response.writeError(request, response, callback, 500); + return true; + } + + public HttpHandler getHttpHandler() + { + return _httpHandler; + } + + public void setHttpHandler(HttpHandler handler) + { + this._httpHandler = handler; + } +} \ No newline at end of file diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java new file mode 100644 index 00000000..8ff3292f --- /dev/null +++ b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java @@ -0,0 +1,84 @@ +package io.avaje.helidon.http.spi; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Jetty implementation of {@link com.sun.net.httpserver.HttpContext} + */ +public class JettyHttpContext extends com.sun.net.httpserver.HttpContext +{ + + private final HttpSpiContextHandler _jettyContextHandler; + + private final HttpServer _server; + + private final Map _attributes = new HashMap(); + + private final List _filters = new ArrayList(); + + private Authenticator _authenticator; + + protected JettyHttpContext(HttpServer server, String contextPath, HttpHandler handler) + { + this._server = server; + _jettyContextHandler = new HttpSpiContextHandler(this, handler); + _jettyContextHandler.setContextPath(contextPath); + } + + protected HttpSpiContextHandler getJettyContextHandler() + { + return _jettyContextHandler; + } + + @Override + public HttpHandler getHandler() + { + return _jettyContextHandler.getHttpHandler(); + } + + @Override + public void setHandler(HttpHandler h) + { + _jettyContextHandler.setHttpHandler(h); + } + + @Override + public String getPath() + { + return _jettyContextHandler.getContextPath(); + } + + @Override + public HttpServer getServer() + { + return _server; + } + + @Override + public Map getAttributes() + { + return _attributes; + } + + @Override + public List getFilters() + { + return _filters; + } + + @Override + public Authenticator setAuthenticator(Authenticator auth) + { + Authenticator previous = _authenticator; + _authenticator = auth; + return previous; + } + + @Override + public Authenticator getAuthenticator() + { + return _authenticator; + } +} diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java new file mode 100644 index 00000000..d5d33eb2 --- /dev/null +++ b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java @@ -0,0 +1,215 @@ +package io.avaje.helidon.http.spi; + +import java.io.IOException; +import java.lang.System.Logger.Level; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.http.server.ServerConfiguration; + +import com.sun.jdi.connect.Connector; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpHandler; + +import sun.nio.ch.ThreadPool; + +/** Jetty implementation of {@link com.sun.net.httpserver.HttpServer}. */ +public class JettyHttpServer extends com.sun.net.httpserver.HttpServer { + private static final System.Logger LOG = + System.getLogger(JettyHttpServer.class.getCanonicalName()); + private final HttpServer _server; + private final boolean _serverShared; + private final Map _contexts = new HashMap<>(); + private final Map _connectors = new HashMap<>(); + private InetSocketAddress _addr; + private ServerConfiguration _httpConfiguration; + + public JettyHttpServer(HttpServer server, boolean shared) { + + this(server, shared, server.getServerConfiguration()); + } + + public JettyHttpServer(HttpServer server, boolean shared, ServerConfiguration configuration) { + this._server = server; + this._serverShared = shared; + this._httpConfiguration = configuration; + } + + public ServerConfiguration getHttpConfiguration() { + return _httpConfiguration; + } + + @Override + public void bind(InetSocketAddress addr, int backlog) throws IOException { + this._addr = addr; + // check if there is already a connector listening + var connectors = _server.getListeners(); + if (connectors != null) { + for (var connector : connectors) { + if (connector.getPort() == addr.getPort()) { + LOG.log( + Level.DEBUG, "server already bound to port {}, no need to rebind", addr.getPort()); + return; + } + } + } + + if (_serverShared) + throw new IOException("grizzly server is not bound to port " + addr.getPort()); + + if (LOG.isLoggable(Level.DEBUG)) { + LOG.log(Level.DEBUG, "binding server to port " + addr.getPort()); + } + + NetworkListener listener = new NetworkListener("rizzly", addr.getHostName(), addr.getPort()); + _server.addListener(listener); + _connectors.put(addr.getHostName() + addr.getPort(), listener); + } + + protected HttpServer getServer() { + return _server; + } + + protected NetworkListener newServerConnector(InetSocketAddress addr, int backlog) { + NetworkListener listener = new NetworkListener("rizzly", addr.getHostName(), addr.getPort()); + + return listener; + } + + @Override + public InetSocketAddress getAddress() { + if (_addr.getPort() == 0 && _server.isStarted()) + return new InetSocketAddress(_addr.getHostString(), _server.getListener("rizzly").getPort()); + return _addr; + } + + @Override + public void start() { + if (_serverShared) return; + + try { + _server.start(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Override + public void setExecutor(Executor executor) { + if (executor == null) + throw new IllegalArgumentException("missing required 'executor' argument"); + } + + @Override + public Executor getExecutor() { + ThreadPool threadPool = _server.getThreadPool(); + if (threadPool instanceof DelegatingThreadPool) + return ((DelegatingThreadPool) _server.getThreadPool()).getExecutor(); + return threadPool; + } + + @Override + public void stop(int delay) { + cleanUpContexts(); + cleanUpConnectors(); + + if (_serverShared) return; + + _server.shutdown(); + } + + private void cleanUpContexts() { + for (Map.Entry stringJettyHttpContextEntry : _contexts.entrySet()) { + JettyHttpContext context = stringJettyHttpContextEntry.getValue(); + _server.removeBean(context.getJettyContextHandler()); + } + _contexts.clear(); + } + + private void cleanUpConnectors() { + for (Map.Entry stringConnectorEntry : _connectors.entrySet()) { + Connector connector = stringConnectorEntry.getValue(); + try { + connector.stop(); + } catch (Exception ex) { + LOG.warn("Unable to stop connector {}", connector, ex); + } + _server.removeConnector(connector); + } + _connectors.clear(); + } + + @Override + public HttpContext createContext(String path, HttpHandler httpHandler) { + checkIfContextIsFree(path); + + JettyHttpContext context = new JettyHttpContext(this, path, httpHandler); + HttpSpiContextHandler jettyContextHandler = context.getJettyContextHandler(); + + ContextHandlerCollection contexts = _server.getDescendant(ContextHandlerCollection.class); + + if (contexts == null) + throw new RuntimeException("could not find ContextHandlerCollection, you must configure one"); + + contexts.addHandler(jettyContextHandler); + if (contexts.isStarted()) { + try { + jettyContextHandler.start(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + _contexts.put(path, context); + return context; + } + + @Override + public HttpContext createContext(String path) { + return createContext(path, null); + } + + private void checkIfContextIsFree(String path) { + Handler serverHandler = _server.getHandler(); + if (serverHandler instanceof ContextHandler) { + ContextHandler ctx = (ContextHandler) serverHandler; + if (ctx.getContextPath().equals(path)) + throw new RuntimeException("another context already bound to path " + path); + } + + List handlers = _server.getHandlers(); + for (Handler handler : handlers) { + if (handler instanceof ContextHandler) { + ContextHandler ctx = (ContextHandler) handler; + if (ctx.getContextPath().equals(path)) + throw new RuntimeException("another context already bound to path " + path); + } + } + } + + @Override + public void removeContext(String path) throws IllegalArgumentException { + JettyHttpContext context = _contexts.remove(path); + if (context == null) return; + HttpSpiContextHandler handler = context.getJettyContextHandler(); + + ContextHandlerCollection chc = _server.getDescendant(ContextHandlerCollection.class); + try { + handler.stop(); + } catch (Exception e) { + throw new RuntimeException(e); + } + chc.removeHandler(handler); + } + + @Override + public void removeContext(HttpContext context) { + removeContext(context.getPath()); + } +} diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/VirtualThreadsExecutorService.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/VirtualThreadsExecutorService.java new file mode 100644 index 00000000..121c33d7 --- /dev/null +++ b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/VirtualThreadsExecutorService.java @@ -0,0 +1,65 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package io.avaje.helidon.http.spi; + +import java.util.List; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +/** + * @author Ondro Mihalyi + */ +public class VirtualThreadsExecutorService extends AbstractExecutorService { + + VirtualThreadFactory threadFactory = new VirtualThreadFactory(); + ExecutorService pool = Executors.newThreadPerTaskExecutor(threadFactory); + + @Override + public void shutdown() { + pool.shutdown(); + } + + @Override + public List shutdownNow() { + return pool.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return pool.isShutdown(); + } + + @Override + public boolean isTerminated() { + return pool.isTerminated(); + } + + @Override + public void execute(Runnable r) { + pool.execute(r); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return pool.awaitTermination(timeout, unit); + } + + + private static class VirtualThreadFactory implements ThreadFactory { + + int threadIndex = 0; + + String name = "virtual-thread"; + + @Override + public Thread newThread(Runnable r) { + return Thread.ofVirtual().name(name + "(" + threadIndex++ + ")").unstarted(r); + } + } + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 06ec7825..7028ff5c 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,7 @@ avaje-jex-mustache avaje-jex-htmx avaje-jex-static-content + avaje-jex-helidon-spi From 220388d995d0ccc9578f64acf470abec3e32d910 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 20 Mar 2025 20:10:31 -0400 Subject: [PATCH 2/5] hmm --- .../http/spi/GrizzlyHttpExchangeDelegate.java | 4 +- .../avaje/helidon/http/spi/JettyExchange.java | 14 ++ .../helidon/http/spi/JettyHttpExchange.java | 129 +++++++++++++++++ .../helidon/http/spi/JettyHttpServer.java | 11 +- .../helidon/http/spi/JettyHttpsExchange.java | 137 ++++++++++++++++++ 5 files changed, 287 insertions(+), 8 deletions(-) create mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java create mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java create mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java index 0a571a58..d307c9b5 100644 --- a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java +++ b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java @@ -55,10 +55,10 @@ public class GrizzlyHttpExchangeDelegate extends HttpExchange { private Response response; - GrizzlyHttpExchangeDelegate(HttpContext httpSpiContext, Request request) { + GrizzlyHttpExchangeDelegate(HttpContext httpSpiContext, Request request, Response response) { this.context = httpSpiContext; this.request = request; - this.response = request.getResponse(); + this.response = response; this.inputStream = request.getInputStream(); this.outputStream = response.getOutputStream(); } diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java new file mode 100644 index 00000000..effc7a0e --- /dev/null +++ b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java @@ -0,0 +1,14 @@ +package io.avaje.helidon.http.spi; + +import com.sun.net.httpserver.HttpPrincipal; + +/** + * + */ +public interface JettyExchange +{ + + HttpPrincipal getPrincipal(); + + void setPrincipal(HttpPrincipal principal); +} diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java new file mode 100644 index 00000000..04ce4a0b --- /dev/null +++ b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java @@ -0,0 +1,129 @@ +package io.avaje.helidon.http.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; + +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; + +public class JettyHttpExchange extends HttpExchange implements JettyExchange { + private final GrizzlyHttpExchangeDelegate delegate; + + public JettyHttpExchange(HttpContext context, Request req, Response resp) { + + delegate = new GrizzlyHttpExchangeDelegate(context, req, resp); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public Headers getRequestHeaders() { + return delegate.getRequestHeaders(); + } + + @Override + public Headers getResponseHeaders() { + return delegate.getResponseHeaders(); + } + + @Override + public URI getRequestURI() { + return delegate.getRequestURI(); + } + + @Override + public String getRequestMethod() { + return delegate.getRequestMethod(); + } + + @Override + public HttpContext getHttpContext() { + return delegate.getHttpContext(); + } + + @Override + public void close() { + delegate.close(); + } + + @Override + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + @Override + public InputStream getRequestBody() { + return delegate.getRequestBody(); + } + + @Override + public OutputStream getResponseBody() { + return delegate.getResponseBody(); + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException { + delegate.sendResponseHeaders(rCode, responseLength); + } + + @Override + public InetSocketAddress getRemoteAddress() { + return delegate.getRemoteAddress(); + } + + @Override + public int getResponseCode() { + return delegate.getResponseCode(); + } + + @Override + public InetSocketAddress getLocalAddress() { + return delegate.getLocalAddress(); + } + + @Override + public String getProtocol() { + return delegate.getProtocol(); + } + + @Override + public Object getAttribute(String name) { + return delegate.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) { + delegate.setAttribute(name, value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) { + delegate.setStreams(i, o); + } + + @Override + public HttpPrincipal getPrincipal() { + return delegate.getPrincipal(); + } + + @Override + public void setPrincipal(HttpPrincipal principal) { + delegate.setPrincipal(principal); + } + + @Override + public String toString() { + return delegate.toString(); + } +} diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java index d5d33eb2..98c7acd4 100644 --- a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java +++ b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java @@ -14,7 +14,6 @@ import org.glassfish.grizzly.http.server.NetworkListener; import org.glassfish.grizzly.http.server.ServerConfiguration; -import com.sun.jdi.connect.Connector; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpHandler; @@ -134,14 +133,14 @@ private void cleanUpContexts() { } private void cleanUpConnectors() { - for (Map.Entry stringConnectorEntry : _connectors.entrySet()) { - Connector connector = stringConnectorEntry.getValue(); + for (var stringConnectorEntry : _connectors.entrySet()) { + var connector = stringConnectorEntry.getValue(); try { - connector.stop(); + connector.shutdownNow(); } catch (Exception ex) { - LOG.warn("Unable to stop connector {}", connector, ex); + LOG.log(Level.WARNING, "Unable to stop connector {}", connector, ex); } - _server.removeConnector(connector); + _server.removeListener(stringConnectorEntry.getKey()); } _connectors.clear(); } diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java new file mode 100644 index 00000000..ad86bbb0 --- /dev/null +++ b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java @@ -0,0 +1,137 @@ +package io.avaje.helidon.http.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; + +import javax.net.ssl.SSLSession; + +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpPrincipal; +import com.sun.net.httpserver.HttpsExchange; + +/** */ +public class JettyHttpsExchange extends HttpsExchange implements JettyExchange { + private final GrizzlyHttpExchangeDelegate _delegate; + + public JettyHttpsExchange(HttpContext jaxWsContext, Request req, Response resp) { + super(); + _delegate = new GrizzlyHttpExchangeDelegate(jaxWsContext, req, resp); + } + + @Override + public int hashCode() { + return _delegate.hashCode(); + } + + @Override + public Headers getRequestHeaders() { + return _delegate.getRequestHeaders(); + } + + @Override + public Headers getResponseHeaders() { + return _delegate.getResponseHeaders(); + } + + @Override + public URI getRequestURI() { + return _delegate.getRequestURI(); + } + + @Override + public String getRequestMethod() { + return _delegate.getRequestMethod(); + } + + @Override + public HttpContext getHttpContext() { + return _delegate.getHttpContext(); + } + + @Override + public void close() { + _delegate.close(); + } + + @Override + public boolean equals(Object obj) { + return _delegate.equals(obj); + } + + @Override + public InputStream getRequestBody() { + return _delegate.getRequestBody(); + } + + @Override + public OutputStream getResponseBody() { + return _delegate.getResponseBody(); + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException { + _delegate.sendResponseHeaders(rCode, responseLength); + } + + @Override + public InetSocketAddress getRemoteAddress() { + return _delegate.getRemoteAddress(); + } + + @Override + public int getResponseCode() { + return _delegate.getResponseCode(); + } + + @Override + public InetSocketAddress getLocalAddress() { + return _delegate.getLocalAddress(); + } + + @Override + public String getProtocol() { + return _delegate.getProtocol(); + } + + @Override + public Object getAttribute(String name) { + return _delegate.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) { + _delegate.setAttribute(name, value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) { + _delegate.setStreams(i, o); + } + + @Override + public HttpPrincipal getPrincipal() { + return _delegate.getPrincipal(); + } + + @Override + public void setPrincipal(HttpPrincipal principal) { + _delegate.setPrincipal(principal); + } + + @Override + public String toString() { + return _delegate.toString(); + } + + @Override + public SSLSession getSSLSession() { + return null; + } +} From 1ff92e5d883522b944e12af9a250d10262bde8c7 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 21 Mar 2025 18:47:10 -0400 Subject: [PATCH 3/5] start --- avaje-jex-grizzly-spi/pom.xml | 25 ++ .../helidon/http/spi/GrizzlyHandler.java | 87 +++++++ .../http/spi/GrizzlyHttpExchangeDelegate.java | 0 .../http/spi/GrizzlyHttpServerProvider.java | 50 ++++ .../helidon/http/spi/HttpServerBuilder.java | 80 +++++++ .../avaje/helidon/http/spi/JettyExchange.java | 10 + .../helidon/http/spi/JettyHttpContext.java | 78 +++++++ .../helidon/http/spi/JettyHttpExchange.java | 0 .../helidon/http/spi/JettyHttpServer.java | 173 ++++++++++++++ .../helidon/http/spi/JettyHttpsExchange.java | 3 +- avaje-jex-helidon-spi/pom.xml | 20 -- .../http/spi/HttpSpiContextHandler.java | 124 ---------- .../avaje/helidon/http/spi/JettyExchange.java | 14 -- .../helidon/http/spi/JettyHttpContext.java | 84 ------- .../helidon/http/spi/JettyHttpServer.java | 214 ------------------ .../spi/VirtualThreadsExecutorService.java | 65 ------ pom.xml | 6 +- 17 files changed, 507 insertions(+), 526 deletions(-) create mode 100644 avaje-jex-grizzly-spi/pom.xml create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHandler.java rename {avaje-jex-helidon-spi => avaje-jex-grizzly-spi}/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java (100%) create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpServerProvider.java create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/HttpServerBuilder.java create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java rename {avaje-jex-helidon-spi => avaje-jex-grizzly-spi}/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java (100%) create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java rename {avaje-jex-helidon-spi => avaje-jex-grizzly-spi}/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java (99%) delete mode 100644 avaje-jex-helidon-spi/pom.xml delete mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/HttpSpiContextHandler.java delete mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java delete mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java delete mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java delete mode 100644 avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/VirtualThreadsExecutorService.java diff --git a/avaje-jex-grizzly-spi/pom.xml b/avaje-jex-grizzly-spi/pom.xml new file mode 100644 index 00000000..56054d21 --- /dev/null +++ b/avaje-jex-grizzly-spi/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + io.avaje + avaje-jex-parent + 3.0-RC23 + + avaje-jex-grizzly-spi + + + + org.glassfish.grizzly + grizzly-http-server + 4.1.0-M1 + + + io.avaje + avaje-spi-service + provided + true + + + diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHandler.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHandler.java new file mode 100644 index 00000000..38bb61fe --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHandler.java @@ -0,0 +1,87 @@ +package io.avaje.helidon.http.spi; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Map; + +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.Authenticator.Result; +import com.sun.net.httpserver.Filter.Chain; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpPrincipal; + +class GrizzlyHandler extends org.glassfish.grizzly.http.server.HttpHandler { + + private final HttpContext httpContext; + + private HttpHandler handler; + + GrizzlyHandler(HttpContext httpContext, HttpHandler httpHandler) { + super(httpContext.getPath()); + this.httpContext = httpContext; + this.handler = httpHandler; + } + + @Override + public void service(Request request, Response response) { + + try (HttpExchange exchange = + request.isSecure() + ? new JettyHttpsExchange(httpContext, request, response) + : new JettyHttpExchange(httpContext, request, response)) { + Authenticator auth = httpContext.getAuthenticator(); + + if (auth != null && handleAuthentication(request, response, exchange, auth)) { + return; + } + new Chain(httpContext.getFilters(), handler).doFilter(exchange); + + } catch (IOException ex) { + throw new UncheckedIOException(null); + } + } + + public HttpHandler getHttpHandler() { + return handler; + } + + public void setHttpHandler(HttpHandler handler) { + this.handler = handler; + } + + private boolean handleAuthentication( + Request request, Response response, HttpExchange httpExchange, Authenticator auth) + throws IOException { + Result result = auth.authenticate(httpExchange); + if (result instanceof Authenticator.Failure fail) { + response.sendError(fail.getResponseCode(), ""); + for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) { + for (String value : header.getValue()) response.addHeader(header.getKey(), value); + } + return true; + } + + if (result instanceof Authenticator.Retry ry) { + for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) { + for (String value : header.getValue()) { + response.addHeader(header.getKey(), value); + } + } + response.sendError(ry.getResponseCode(), "Failed to Authenticate"); + return true; + } + + if (result instanceof Authenticator.Success s) { + HttpPrincipal principal = s.getPrincipal(); + ((JettyExchange) httpExchange).setPrincipal(principal); + return false; + } + return true; + } +} diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java similarity index 100% rename from avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java rename to avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpServerProvider.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpServerProvider.java new file mode 100644 index 00000000..997625f7 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpServerProvider.java @@ -0,0 +1,50 @@ +package io.avaje.helidon.http.spi; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.glassfish.grizzly.http.server.HttpServer; + +import com.sun.net.httpserver.HttpsServer; +import com.sun.net.httpserver.spi.HttpServerProvider; + +import io.avaje.spi.ServiceProvider; + +@ServiceProvider +public class GrizzlyHttpServerProvider extends HttpServerProvider { + + private org.glassfish.grizzly.http.server.HttpServer server; + + public GrizzlyHttpServerProvider(HttpServer server) { + + this.server = server; + } + + public GrizzlyHttpServerProvider() { + + this.server = new HttpServer(); + } + + @Override + public com.sun.net.httpserver.HttpServer createHttpServer(InetSocketAddress addr, int backlog) + throws IOException { + + return createServer(addr, backlog); + } + + @Override + public HttpsServer createHttpsServer(InetSocketAddress addr, int backlog) throws IOException { + return createServer(addr, backlog); + } + + private com.sun.net.httpserver.HttpsServer createServer(InetSocketAddress addr, int backlog) + throws IOException { + if (server == null) { + server = new HttpServer(); + } + + JettyHttpServer jettyHttpServer = new JettyHttpServer(server); + if (addr != null) jettyHttpServer.bind(addr, backlog); + return jettyHttpServer; + } +} diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/HttpServerBuilder.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/HttpServerBuilder.java new file mode 100644 index 00000000..6989a0a8 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/HttpServerBuilder.java @@ -0,0 +1,80 @@ +package io.avaje.helidon.http.spi; + +import org.glassfish.grizzly.http.server.HttpHandler; +import org.glassfish.grizzly.http.server.HttpHandlerRegistration; +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.http.server.ServerConfiguration; +import org.glassfish.grizzly.ssl.SSLEngineConfigurator; +import org.glassfish.grizzly.utils.Charsets; + +public class HttpServerBuilder { + + private int port = -1; + private String host = "0.0.0.0"; + private boolean secure; + private SSLEngineConfigurator sslEngineConfigurator; + + private final HttpServer server = new HttpServer(); + + public HttpServerBuilder setPort(int port) { + this.port = port; + return this; + } + + public HttpServerBuilder host(String host) { + this.host = host; + return this; + } + + public HttpServerBuilder sslEngineConfigurator(SSLEngineConfigurator sslEngineConfigurator) { + this.sslEngineConfigurator = sslEngineConfigurator; + return this; + } + + public HttpServerBuilder secure(boolean secure) { + this.secure = secure; + return this; + } + + /** + * Add a handler with the given context. + */ + public HttpServerBuilder handler(HttpHandler handler, String context) { + handler(handler, HttpHandlerRegistration.fromString("/" + context + "/*")); + return this; + } + + /** + * Add a handler given the paths. + */ + public HttpServerBuilder handler(HttpHandler handler, HttpHandlerRegistration... paths) { + server.getServerConfiguration().addHttpHandler(handler, paths); + return this; + } + + /** + * Build and return the grizzly http server. + */ + public HttpServer build() { + + int serverPort = serverPort(); + NetworkListener listener = new NetworkListener("grizzly", host, serverPort); + + // TODO: Configure to use loom thread factory + // listener.getTransport().getWorkerThreadPoolConfig().setThreadFactory() + listener.setSecure(secure); + if (sslEngineConfigurator != null) { + listener.setSSLEngineConfig(sslEngineConfigurator); + } + server.addListener(listener); + ServerConfiguration config = server.getServerConfiguration(); + config.setPassTraceRequest(true); + config.setDefaultQueryEncoding(Charsets.UTF8_CHARSET); + return server; + } + + protected int serverPort() { + return port != -1 ? port : secure ? 8443 : 7001; + } +} \ No newline at end of file diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java new file mode 100644 index 00000000..ac1e82d9 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java @@ -0,0 +1,10 @@ +package io.avaje.helidon.http.spi; + +import com.sun.net.httpserver.HttpPrincipal; + +public interface JettyExchange { + + HttpPrincipal getPrincipal(); + + void setPrincipal(HttpPrincipal principal); +} diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java new file mode 100644 index 00000000..2e69770c --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java @@ -0,0 +1,78 @@ +package io.avaje.helidon.http.spi; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.Filter; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +/** Jetty implementation of {@link com.sun.net.httpserver.HttpContext} */ +public class JettyHttpContext extends com.sun.net.httpserver.HttpContext { + + private final GrizzlyHandler grizzlyHandler; + private final HttpServer server; + + private final Map attributes = new HashMap<>(); + + private final List filters = new ArrayList<>(); + + private Authenticator authenticator; + + private String contextPath; + + protected JettyHttpContext(HttpServer server, String contextPath, HttpHandler handler) { + this.server = server; + this.grizzlyHandler = new GrizzlyHandler(this, handler); + this.contextPath = contextPath; + } + + GrizzlyHandler getGrizzlyHandler() { + return grizzlyHandler; + } + + @Override + public HttpHandler getHandler() { + return grizzlyHandler.getHttpHandler(); + } + + @Override + public void setHandler(HttpHandler h) { + grizzlyHandler.setHttpHandler(h); + } + + @Override + public String getPath() { + return contextPath; + } + + @Override + public HttpServer getServer() { + return server; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public List getFilters() { + return filters; + } + + @Override + public Authenticator setAuthenticator(Authenticator auth) { + Authenticator previous = authenticator; + authenticator = auth; + return previous; + } + + @Override + public Authenticator getAuthenticator() { + return authenticator; + } +} diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java similarity index 100% rename from avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java rename to avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java new file mode 100644 index 00000000..fa6c2015 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java @@ -0,0 +1,173 @@ +package io.avaje.helidon.http.spi; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.System.Logger.Level; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.http.server.ServerConfiguration; +import org.glassfish.grizzly.ssl.SSLEngineConfigurator; + +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpsConfigurator; + +/** Jetty implementation of {@link com.sun.net.httpserver.HttpServer}. */ +public class JettyHttpServer extends com.sun.net.httpserver.HttpsServer { + private static final System.Logger LOG = + System.getLogger(JettyHttpServer.class.getCanonicalName()); + private final HttpServer server; + private final Map contexts = new HashMap<>(); + private InetSocketAddress addr; + private ServerConfiguration httpConfiguration; + private ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); + private HttpsConfigurator httpsConfig; + + public JettyHttpServer(HttpServer server) { + + this(server, server.getServerConfiguration()); + } + + public JettyHttpServer(HttpServer server, ServerConfiguration configuration) { + this.server = server; + this.httpConfiguration = configuration; + } + + public ServerConfiguration getHttpConfiguration() { + return httpConfiguration; + } + + @Override + public void bind(InetSocketAddress addr, int backlog) throws IOException { + + this.addr = addr; + // check if there is already a connector listening + var connectors = server.getListeners(); + if (connectors != null) { + for (var connector : connectors) { + if (connector.getPort() == addr.getPort()) { + LOG.log( + Level.DEBUG, "server already bound to port {}, no need to rebind", addr.getPort()); + return; + } + } + } + + if (LOG.isLoggable(Level.DEBUG)) { + LOG.log(Level.DEBUG, "binding server to port " + addr.getPort()); + } + var listener = new NetworkListener("rizzly", addr.getHostName(), addr.getPort()); + listener.getTransport().setWorkerThreadPool(executor); + if (backlog != 0) { + listener.getTransport().setServerConnectionBackLog(backlog); + } + if (httpsConfig != null) { + listener.setSSLEngineConfig(new SSLEngineConfigurator(httpsConfig.getSSLContext())); + } + + server.addListener(listener); + } + + protected HttpServer getServer() { + return server; + } + + @Override + public InetSocketAddress getAddress() { + if (addr.getPort() == 0 && server.isStarted()) + return new InetSocketAddress(addr.getHostString(), server.getListener("rizzly").getPort()); + return addr; + } + + @Override + public void start() { + + try { + server.start(); + } catch (IOException e) { + + throw new UncheckedIOException(e); + } + } + + @Override + public void setExecutor(Executor executor) { + if (executor instanceof ExecutorService service) { + this.executor = service; + } else { + throw new IllegalArgumentException("Grizzly only accepts ExecutorService"); + } + } + + @Override + public Executor getExecutor() { + return executor; + } + + @Override + public void stop(int delay) { + + for (var context : contexts.values()) { + httpConfiguration.removeHttpHandler(context.getGrizzlyHandler()); + } + contexts.clear(); + + server.shutdown(); + } + + @Override + public HttpContext createContext(String path, HttpHandler httpHandler) { + + JettyHttpContext context = new JettyHttpContext(this, path, httpHandler); + GrizzlyHandler jettyContextHandler = context.getGrizzlyHandler(); + + httpConfiguration.addHttpHandler( + jettyContextHandler, path.transform(this::prependSlash).transform(this::appendSlash)); + + contexts.put(path, context); + return context; + } + + private String prependSlash(String s) { + return s.startsWith("/") ? s : "/" + s; + } + + private String appendSlash(String s) { + return s.endsWith("/") ? s + "*" : s + "/*"; + } + + @Override + public HttpContext createContext(String path) { + return createContext(path, null); + } + + @Override + public void removeContext(String path) throws IllegalArgumentException { + JettyHttpContext context = contexts.remove(path); + if (context == null) return; + GrizzlyHandler handler = context.getGrizzlyHandler(); + httpConfiguration.removeHttpHandler(handler); + } + + @Override + public void removeContext(HttpContext context) { + removeContext(context.getPath()); + } + + @Override + public void setHttpsConfigurator(HttpsConfigurator config) { + httpsConfig = config; + } + + @Override + public HttpsConfigurator getHttpsConfigurator() { + return httpsConfig; + } +} diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java similarity index 99% rename from avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java rename to avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java index ad86bbb0..16b261cd 100644 --- a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java @@ -16,12 +16,11 @@ import com.sun.net.httpserver.HttpPrincipal; import com.sun.net.httpserver.HttpsExchange; -/** */ + public class JettyHttpsExchange extends HttpsExchange implements JettyExchange { private final GrizzlyHttpExchangeDelegate _delegate; public JettyHttpsExchange(HttpContext jaxWsContext, Request req, Response resp) { - super(); _delegate = new GrizzlyHttpExchangeDelegate(jaxWsContext, req, resp); } diff --git a/avaje-jex-helidon-spi/pom.xml b/avaje-jex-helidon-spi/pom.xml deleted file mode 100644 index 696df4c1..00000000 --- a/avaje-jex-helidon-spi/pom.xml +++ /dev/null @@ -1,20 +0,0 @@ - - 4.0.0 - - io.avaje - avaje-jex-parent - 3.0-RC23 - - avaje-jex-helidon-spi - - - - - org.glassfish.grizzly - grizzly-http-server - 4.1.0-M1 - - - - - \ No newline at end of file diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/HttpSpiContextHandler.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/HttpSpiContextHandler.java deleted file mode 100644 index 9103a6db..00000000 --- a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/HttpSpiContextHandler.java +++ /dev/null @@ -1,124 +0,0 @@ -package io.avaje.helidon.http.spi; - -import java.util.List; -import java.util.Map; - -import javax.security.auth.callback.Callback; - -import org.eclipse.jetty.server.handler.ContextHandler; -import org.glassfish.grizzly.http.server.Response; - -import com.sun.net.httpserver.Authenticator; -import com.sun.net.httpserver.Authenticator.Result; -import com.sun.net.httpserver.Filter.Chain; -import com.sun.net.httpserver.HttpContext; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpPrincipal; -import com.sun.org.slf4j.internal.LoggerFactory; - -/** - * Jetty handler that bridges requests to {@link HttpHandler}. - */ -public class HttpSpiContextHandler extends ContextHandler -{ - public static final Logger LOG = LoggerFactory.getLogger(HttpSpiContextHandler.class); - - private final HttpContext _httpContext; - - private HttpHandler _httpHandler; - - public HttpSpiContextHandler(HttpContext httpContext, HttpHandler httpHandler) - { - this._httpContext = httpContext; - this._httpHandler = httpHandler; - // The default jax-ws web server allows posting to URLs that do not end - // with a trailing '/'; allow it too to be a drop-in replacement. - setAllowNullPathInContext(true); - super.setHandler(new Handler.Abstract() - { - @Override - public boolean handle(Request request, Response response, Callback callback) - { - try (HttpExchange jettyHttpExchange = request.isSecure() - ? new JettyHttpsExchange(_httpContext, request, response) - : new JettyHttpExchange(_httpContext, request, response)) - { - Authenticator auth = _httpContext.getAuthenticator(); - if (auth != null && handleAuthentication(request, response, callback, jettyHttpExchange, auth)) - return true; - - new Chain(_httpContext.getFilters(), _httpHandler).doFilter(jettyHttpExchange); - callback.succeeded(); - } - catch (Exception ex) - { - LOG.debug("Failed to handle", ex); - Response.writeError(request, response, callback, 500, null, ex); - } - return true; - } - }); - } - - @Override - public void setHandler(Handler handler) - { - throw new UnsupportedOperationException(); - } - - private boolean handleAuthentication( - Request request, - Response response, - Callback callback, - HttpExchange httpExchange, - Authenticator auth) - { - Result result = auth.authenticate(httpExchange); - if (result instanceof Authenticator.Failure) - { - int rc = ((Authenticator.Failure)result).getResponseCode(); - for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) - { - for (String value : header.getValue()) - response.getHeaders().add(header.getKey(), value); - } - Response.writeError(request, response, callback, rc); - return true; - } - - if (result instanceof Authenticator.Retry) - { - int rc = ((Authenticator.Retry)result).getResponseCode(); - for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) - { - for (String value : header.getValue()) - { - response.getHeaders().add(header.getKey(), value); - } - } - Response.writeError(request, response, callback, rc); - return true; - } - - if (result instanceof Authenticator.Success) - { - HttpPrincipal principal = ((Authenticator.Success)result).getPrincipal(); - ((JettyExchange)httpExchange).setPrincipal(principal); - return false; - } - - Response.writeError(request, response, callback, 500); - return true; - } - - public HttpHandler getHttpHandler() - { - return _httpHandler; - } - - public void setHttpHandler(HttpHandler handler) - { - this._httpHandler = handler; - } -} \ No newline at end of file diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java deleted file mode 100644 index effc7a0e..00000000 --- a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.avaje.helidon.http.spi; - -import com.sun.net.httpserver.HttpPrincipal; - -/** - * - */ -public interface JettyExchange -{ - - HttpPrincipal getPrincipal(); - - void setPrincipal(HttpPrincipal principal); -} diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java deleted file mode 100644 index 8ff3292f..00000000 --- a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.avaje.helidon.http.spi; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -/** - * Jetty implementation of {@link com.sun.net.httpserver.HttpContext} - */ -public class JettyHttpContext extends com.sun.net.httpserver.HttpContext -{ - - private final HttpSpiContextHandler _jettyContextHandler; - - private final HttpServer _server; - - private final Map _attributes = new HashMap(); - - private final List _filters = new ArrayList(); - - private Authenticator _authenticator; - - protected JettyHttpContext(HttpServer server, String contextPath, HttpHandler handler) - { - this._server = server; - _jettyContextHandler = new HttpSpiContextHandler(this, handler); - _jettyContextHandler.setContextPath(contextPath); - } - - protected HttpSpiContextHandler getJettyContextHandler() - { - return _jettyContextHandler; - } - - @Override - public HttpHandler getHandler() - { - return _jettyContextHandler.getHttpHandler(); - } - - @Override - public void setHandler(HttpHandler h) - { - _jettyContextHandler.setHttpHandler(h); - } - - @Override - public String getPath() - { - return _jettyContextHandler.getContextPath(); - } - - @Override - public HttpServer getServer() - { - return _server; - } - - @Override - public Map getAttributes() - { - return _attributes; - } - - @Override - public List getFilters() - { - return _filters; - } - - @Override - public Authenticator setAuthenticator(Authenticator auth) - { - Authenticator previous = _authenticator; - _authenticator = auth; - return previous; - } - - @Override - public Authenticator getAuthenticator() - { - return _authenticator; - } -} diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java deleted file mode 100644 index 98c7acd4..00000000 --- a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java +++ /dev/null @@ -1,214 +0,0 @@ -package io.avaje.helidon.http.spi; - -import java.io.IOException; -import java.lang.System.Logger.Level; -import java.net.InetSocketAddress; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Executor; - -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.ContextHandlerCollection; -import org.glassfish.grizzly.http.server.HttpServer; -import org.glassfish.grizzly.http.server.NetworkListener; -import org.glassfish.grizzly.http.server.ServerConfiguration; - -import com.sun.net.httpserver.HttpContext; -import com.sun.net.httpserver.HttpHandler; - -import sun.nio.ch.ThreadPool; - -/** Jetty implementation of {@link com.sun.net.httpserver.HttpServer}. */ -public class JettyHttpServer extends com.sun.net.httpserver.HttpServer { - private static final System.Logger LOG = - System.getLogger(JettyHttpServer.class.getCanonicalName()); - private final HttpServer _server; - private final boolean _serverShared; - private final Map _contexts = new HashMap<>(); - private final Map _connectors = new HashMap<>(); - private InetSocketAddress _addr; - private ServerConfiguration _httpConfiguration; - - public JettyHttpServer(HttpServer server, boolean shared) { - - this(server, shared, server.getServerConfiguration()); - } - - public JettyHttpServer(HttpServer server, boolean shared, ServerConfiguration configuration) { - this._server = server; - this._serverShared = shared; - this._httpConfiguration = configuration; - } - - public ServerConfiguration getHttpConfiguration() { - return _httpConfiguration; - } - - @Override - public void bind(InetSocketAddress addr, int backlog) throws IOException { - this._addr = addr; - // check if there is already a connector listening - var connectors = _server.getListeners(); - if (connectors != null) { - for (var connector : connectors) { - if (connector.getPort() == addr.getPort()) { - LOG.log( - Level.DEBUG, "server already bound to port {}, no need to rebind", addr.getPort()); - return; - } - } - } - - if (_serverShared) - throw new IOException("grizzly server is not bound to port " + addr.getPort()); - - if (LOG.isLoggable(Level.DEBUG)) { - LOG.log(Level.DEBUG, "binding server to port " + addr.getPort()); - } - - NetworkListener listener = new NetworkListener("rizzly", addr.getHostName(), addr.getPort()); - _server.addListener(listener); - _connectors.put(addr.getHostName() + addr.getPort(), listener); - } - - protected HttpServer getServer() { - return _server; - } - - protected NetworkListener newServerConnector(InetSocketAddress addr, int backlog) { - NetworkListener listener = new NetworkListener("rizzly", addr.getHostName(), addr.getPort()); - - return listener; - } - - @Override - public InetSocketAddress getAddress() { - if (_addr.getPort() == 0 && _server.isStarted()) - return new InetSocketAddress(_addr.getHostString(), _server.getListener("rizzly").getPort()); - return _addr; - } - - @Override - public void start() { - if (_serverShared) return; - - try { - _server.start(); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void setExecutor(Executor executor) { - if (executor == null) - throw new IllegalArgumentException("missing required 'executor' argument"); - } - - @Override - public Executor getExecutor() { - ThreadPool threadPool = _server.getThreadPool(); - if (threadPool instanceof DelegatingThreadPool) - return ((DelegatingThreadPool) _server.getThreadPool()).getExecutor(); - return threadPool; - } - - @Override - public void stop(int delay) { - cleanUpContexts(); - cleanUpConnectors(); - - if (_serverShared) return; - - _server.shutdown(); - } - - private void cleanUpContexts() { - for (Map.Entry stringJettyHttpContextEntry : _contexts.entrySet()) { - JettyHttpContext context = stringJettyHttpContextEntry.getValue(); - _server.removeBean(context.getJettyContextHandler()); - } - _contexts.clear(); - } - - private void cleanUpConnectors() { - for (var stringConnectorEntry : _connectors.entrySet()) { - var connector = stringConnectorEntry.getValue(); - try { - connector.shutdownNow(); - } catch (Exception ex) { - LOG.log(Level.WARNING, "Unable to stop connector {}", connector, ex); - } - _server.removeListener(stringConnectorEntry.getKey()); - } - _connectors.clear(); - } - - @Override - public HttpContext createContext(String path, HttpHandler httpHandler) { - checkIfContextIsFree(path); - - JettyHttpContext context = new JettyHttpContext(this, path, httpHandler); - HttpSpiContextHandler jettyContextHandler = context.getJettyContextHandler(); - - ContextHandlerCollection contexts = _server.getDescendant(ContextHandlerCollection.class); - - if (contexts == null) - throw new RuntimeException("could not find ContextHandlerCollection, you must configure one"); - - contexts.addHandler(jettyContextHandler); - if (contexts.isStarted()) { - try { - jettyContextHandler.start(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - _contexts.put(path, context); - return context; - } - - @Override - public HttpContext createContext(String path) { - return createContext(path, null); - } - - private void checkIfContextIsFree(String path) { - Handler serverHandler = _server.getHandler(); - if (serverHandler instanceof ContextHandler) { - ContextHandler ctx = (ContextHandler) serverHandler; - if (ctx.getContextPath().equals(path)) - throw new RuntimeException("another context already bound to path " + path); - } - - List handlers = _server.getHandlers(); - for (Handler handler : handlers) { - if (handler instanceof ContextHandler) { - ContextHandler ctx = (ContextHandler) handler; - if (ctx.getContextPath().equals(path)) - throw new RuntimeException("another context already bound to path " + path); - } - } - } - - @Override - public void removeContext(String path) throws IllegalArgumentException { - JettyHttpContext context = _contexts.remove(path); - if (context == null) return; - HttpSpiContextHandler handler = context.getJettyContextHandler(); - - ContextHandlerCollection chc = _server.getDescendant(ContextHandlerCollection.class); - try { - handler.stop(); - } catch (Exception e) { - throw new RuntimeException(e); - } - chc.removeHandler(handler); - } - - @Override - public void removeContext(HttpContext context) { - removeContext(context.getPath()); - } -} diff --git a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/VirtualThreadsExecutorService.java b/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/VirtualThreadsExecutorService.java deleted file mode 100644 index 121c33d7..00000000 --- a/avaje-jex-helidon-spi/src/main/java/io/avaje/helidon/http/spi/VirtualThreadsExecutorService.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license - * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template - */ -package io.avaje.helidon.http.spi; - -import java.util.List; -import java.util.concurrent.AbstractExecutorService; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -/** - * @author Ondro Mihalyi - */ -public class VirtualThreadsExecutorService extends AbstractExecutorService { - - VirtualThreadFactory threadFactory = new VirtualThreadFactory(); - ExecutorService pool = Executors.newThreadPerTaskExecutor(threadFactory); - - @Override - public void shutdown() { - pool.shutdown(); - } - - @Override - public List shutdownNow() { - return pool.shutdownNow(); - } - - @Override - public boolean isShutdown() { - return pool.isShutdown(); - } - - @Override - public boolean isTerminated() { - return pool.isTerminated(); - } - - @Override - public void execute(Runnable r) { - pool.execute(r); - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return pool.awaitTermination(timeout, unit); - } - - - private static class VirtualThreadFactory implements ThreadFactory { - - int threadIndex = 0; - - String name = "virtual-thread"; - - @Override - public Thread newThread(Runnable r) { - return Thread.ofVirtual().name(name + "(" + threadIndex++ + ")").unstarted(r); - } - } - -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7028ff5c..6942c814 100644 --- a/pom.xml +++ b/pom.xml @@ -39,12 +39,12 @@ avaje-jex - avaje-jex-test avaje-jex-freemarker - avaje-jex-mustache + avaje-jex-grizzly-spi avaje-jex-htmx + avaje-jex-mustache avaje-jex-static-content - avaje-jex-helidon-spi + avaje-jex-test From 5eb8957c5287ad5b4c2b2a7a49b647264c152878 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 21 Mar 2025 19:30:54 -0400 Subject: [PATCH 4/5] working --- avaje-jex-grizzly-spi/pom.xml | 20 ++- .../helidon/http/spi/GrizzlyHandler.java | 87 ------------- .../helidon/http/spi/HttpServerBuilder.java | 80 ------------ .../grizzly/spi/GrizzlyExchange.java} | 4 +- .../avaje/jex/grizzly/spi/GrizzlyHandler.java | 48 +++++++ .../grizzly/spi/GrizzlyHttpContext.java} | 7 +- .../grizzly/spi/GrizzlyHttpExchange.java} | 6 +- .../spi/GrizzlyHttpExchangeDelegate.java | 8 +- .../grizzly/spi/GrizzlyHttpServer.java} | 42 +++---- .../spi/GrizzlyHttpServerProvider.java | 4 +- .../grizzly/spi/GrizzlyHttpsExchange.java} | 59 ++++----- .../src/main/java/module-info.java | 15 +++ .../io/avaje/jex/grizzly/spi/FilterTest.java | 118 ++++++++++++++++++ 13 files changed, 248 insertions(+), 250 deletions(-) delete mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHandler.java delete mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/HttpServerBuilder.java rename avaje-jex-grizzly-spi/src/main/java/io/avaje/{helidon/http/spi/JettyExchange.java => jex/grizzly/spi/GrizzlyExchange.java} (52%) create mode 100644 avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHandler.java rename avaje-jex-grizzly-spi/src/main/java/io/avaje/{helidon/http/spi/JettyHttpContext.java => jex/grizzly/spi/GrizzlyHttpContext.java} (84%) rename avaje-jex-grizzly-spi/src/main/java/io/avaje/{helidon/http/spi/JettyHttpExchange.java => jex/grizzly/spi/GrizzlyHttpExchange.java} (92%) rename avaje-jex-grizzly-spi/src/main/java/io/avaje/{helidon/http => jex/grizzly}/spi/GrizzlyHttpExchangeDelegate.java (96%) rename avaje-jex-grizzly-spi/src/main/java/io/avaje/{helidon/http/spi/JettyHttpServer.java => jex/grizzly/spi/GrizzlyHttpServer.java} (75%) rename avaje-jex-grizzly-spi/src/main/java/io/avaje/{helidon/http => jex/grizzly}/spi/GrizzlyHttpServerProvider.java (91%) rename avaje-jex-grizzly-spi/src/main/java/io/avaje/{helidon/http/spi/JettyHttpsExchange.java => jex/grizzly/spi/GrizzlyHttpsExchange.java} (57%) create mode 100644 avaje-jex-grizzly-spi/src/main/java/module-info.java create mode 100644 avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java diff --git a/avaje-jex-grizzly-spi/pom.xml b/avaje-jex-grizzly-spi/pom.xml index 56054d21..d9bfb46a 100644 --- a/avaje-jex-grizzly-spi/pom.xml +++ b/avaje-jex-grizzly-spi/pom.xml @@ -7,6 +7,7 @@ avaje-jex-parent 3.0-RC23 + 0.1 avaje-jex-grizzly-spi @@ -15,11 +16,18 @@ grizzly-http-server 4.1.0-M1 - - io.avaje - avaje-spi-service - provided - true - + + io.avaje + avaje-spi-service + provided + true + + + + io.avaje + avaje-jex-test + test + + diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHandler.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHandler.java deleted file mode 100644 index 38bb61fe..00000000 --- a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHandler.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.avaje.helidon.http.spi; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.List; -import java.util.Map; - -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; - -import com.sun.net.httpserver.Authenticator; -import com.sun.net.httpserver.Authenticator.Result; -import com.sun.net.httpserver.Filter.Chain; -import com.sun.net.httpserver.HttpContext; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpPrincipal; - -class GrizzlyHandler extends org.glassfish.grizzly.http.server.HttpHandler { - - private final HttpContext httpContext; - - private HttpHandler handler; - - GrizzlyHandler(HttpContext httpContext, HttpHandler httpHandler) { - super(httpContext.getPath()); - this.httpContext = httpContext; - this.handler = httpHandler; - } - - @Override - public void service(Request request, Response response) { - - try (HttpExchange exchange = - request.isSecure() - ? new JettyHttpsExchange(httpContext, request, response) - : new JettyHttpExchange(httpContext, request, response)) { - Authenticator auth = httpContext.getAuthenticator(); - - if (auth != null && handleAuthentication(request, response, exchange, auth)) { - return; - } - new Chain(httpContext.getFilters(), handler).doFilter(exchange); - - } catch (IOException ex) { - throw new UncheckedIOException(null); - } - } - - public HttpHandler getHttpHandler() { - return handler; - } - - public void setHttpHandler(HttpHandler handler) { - this.handler = handler; - } - - private boolean handleAuthentication( - Request request, Response response, HttpExchange httpExchange, Authenticator auth) - throws IOException { - Result result = auth.authenticate(httpExchange); - if (result instanceof Authenticator.Failure fail) { - response.sendError(fail.getResponseCode(), ""); - for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) { - for (String value : header.getValue()) response.addHeader(header.getKey(), value); - } - return true; - } - - if (result instanceof Authenticator.Retry ry) { - for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) { - for (String value : header.getValue()) { - response.addHeader(header.getKey(), value); - } - } - response.sendError(ry.getResponseCode(), "Failed to Authenticate"); - return true; - } - - if (result instanceof Authenticator.Success s) { - HttpPrincipal principal = s.getPrincipal(); - ((JettyExchange) httpExchange).setPrincipal(principal); - return false; - } - return true; - } -} diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/HttpServerBuilder.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/HttpServerBuilder.java deleted file mode 100644 index 6989a0a8..00000000 --- a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/HttpServerBuilder.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.avaje.helidon.http.spi; - -import org.glassfish.grizzly.http.server.HttpHandler; -import org.glassfish.grizzly.http.server.HttpHandlerRegistration; -import org.glassfish.grizzly.http.server.HttpServer; -import org.glassfish.grizzly.http.server.NetworkListener; -import org.glassfish.grizzly.http.server.ServerConfiguration; -import org.glassfish.grizzly.ssl.SSLEngineConfigurator; -import org.glassfish.grizzly.utils.Charsets; - -public class HttpServerBuilder { - - private int port = -1; - private String host = "0.0.0.0"; - private boolean secure; - private SSLEngineConfigurator sslEngineConfigurator; - - private final HttpServer server = new HttpServer(); - - public HttpServerBuilder setPort(int port) { - this.port = port; - return this; - } - - public HttpServerBuilder host(String host) { - this.host = host; - return this; - } - - public HttpServerBuilder sslEngineConfigurator(SSLEngineConfigurator sslEngineConfigurator) { - this.sslEngineConfigurator = sslEngineConfigurator; - return this; - } - - public HttpServerBuilder secure(boolean secure) { - this.secure = secure; - return this; - } - - /** - * Add a handler with the given context. - */ - public HttpServerBuilder handler(HttpHandler handler, String context) { - handler(handler, HttpHandlerRegistration.fromString("/" + context + "/*")); - return this; - } - - /** - * Add a handler given the paths. - */ - public HttpServerBuilder handler(HttpHandler handler, HttpHandlerRegistration... paths) { - server.getServerConfiguration().addHttpHandler(handler, paths); - return this; - } - - /** - * Build and return the grizzly http server. - */ - public HttpServer build() { - - int serverPort = serverPort(); - NetworkListener listener = new NetworkListener("grizzly", host, serverPort); - - // TODO: Configure to use loom thread factory - // listener.getTransport().getWorkerThreadPoolConfig().setThreadFactory() - listener.setSecure(secure); - if (sslEngineConfigurator != null) { - listener.setSSLEngineConfig(sslEngineConfigurator); - } - server.addListener(listener); - ServerConfiguration config = server.getServerConfiguration(); - config.setPassTraceRequest(true); - config.setDefaultQueryEncoding(Charsets.UTF8_CHARSET); - return server; - } - - protected int serverPort() { - return port != -1 ? port : secure ? 8443 : 7001; - } -} \ No newline at end of file diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyExchange.java similarity index 52% rename from avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java rename to avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyExchange.java index ac1e82d9..10c7fb4f 100644 --- a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyExchange.java +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyExchange.java @@ -1,8 +1,8 @@ -package io.avaje.helidon.http.spi; +package io.avaje.jex.grizzly.spi; import com.sun.net.httpserver.HttpPrincipal; -public interface JettyExchange { +sealed interface GrizzlyExchange permits GrizzlyHttpExchange, GrizzlyHttpsExchange { HttpPrincipal getPrincipal(); diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHandler.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHandler.java new file mode 100644 index 00000000..4ea79c85 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHandler.java @@ -0,0 +1,48 @@ +package io.avaje.jex.grizzly.spi; + +import java.io.IOException; +import java.io.UncheckedIOException; + +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; + +import com.sun.net.httpserver.Filter.Chain; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +final class GrizzlyHandler extends org.glassfish.grizzly.http.server.HttpHandler { + + private final HttpContext httpContext; + + private HttpHandler handler; + + GrizzlyHandler(HttpContext httpContext, HttpHandler httpHandler) { + super(httpContext.getPath()); + this.httpContext = httpContext; + this.handler = httpHandler; + } + + @Override + public void service(Request request, Response response) { + + try (HttpExchange exchange = + request.isSecure() + ? new GrizzlyHttpsExchange(httpContext, request, response) + : new GrizzlyHttpExchange(httpContext, request, response)) { + + new Chain(httpContext.getFilters(), handler).doFilter(exchange); + + } catch (IOException ex) { + throw new UncheckedIOException(null); + } + } + + public HttpHandler getHttpHandler() { + return handler; + } + + public void setHttpHandler(HttpHandler handler) { + this.handler = handler; + } +} diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpContext.java similarity index 84% rename from avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java rename to avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpContext.java index 2e69770c..5ede6a56 100644 --- a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpContext.java +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpContext.java @@ -1,4 +1,4 @@ -package io.avaje.helidon.http.spi; +package io.avaje.jex.grizzly.spi; import java.util.ArrayList; import java.util.HashMap; @@ -10,8 +10,7 @@ import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; -/** Jetty implementation of {@link com.sun.net.httpserver.HttpContext} */ -public class JettyHttpContext extends com.sun.net.httpserver.HttpContext { +final class GrizzlyHttpContext extends com.sun.net.httpserver.HttpContext { private final GrizzlyHandler grizzlyHandler; private final HttpServer server; @@ -24,7 +23,7 @@ public class JettyHttpContext extends com.sun.net.httpserver.HttpContext { private String contextPath; - protected JettyHttpContext(HttpServer server, String contextPath, HttpHandler handler) { + protected GrizzlyHttpContext(HttpServer server, String contextPath, HttpHandler handler) { this.server = server; this.grizzlyHandler = new GrizzlyHandler(this, handler); this.contextPath = contextPath; diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchange.java similarity index 92% rename from avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java rename to avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchange.java index 04ce4a0b..38fc59c8 100644 --- a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpExchange.java +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchange.java @@ -1,4 +1,4 @@ -package io.avaje.helidon.http.spi; +package io.avaje.jex.grizzly.spi; import java.io.IOException; import java.io.InputStream; @@ -14,10 +14,10 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpPrincipal; -public class JettyHttpExchange extends HttpExchange implements JettyExchange { +final class GrizzlyHttpExchange extends HttpExchange implements GrizzlyExchange { private final GrizzlyHttpExchangeDelegate delegate; - public JettyHttpExchange(HttpContext context, Request req, Response resp) { + public GrizzlyHttpExchange(HttpContext context, Request req, Response resp) { delegate = new GrizzlyHttpExchangeDelegate(context, req, resp); } diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchangeDelegate.java similarity index 96% rename from avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java rename to avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchangeDelegate.java index d307c9b5..a6b6982c 100644 --- a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpExchangeDelegate.java +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpExchangeDelegate.java @@ -1,4 +1,4 @@ -package io.avaje.helidon.http.spi; +package io.avaje.jex.grizzly.spi; import java.io.IOException; import java.io.InputStream; @@ -18,8 +18,8 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpPrincipal; -/** Jetty implementation of {@link com.sun.net.httpserver.HttpExchange} */ -public class GrizzlyHttpExchangeDelegate extends HttpExchange { +final class GrizzlyHttpExchangeDelegate extends HttpExchange { + /** Set of headers that RFC9110 says will not have a value list */ private static final Set SINGLE_VALUE_HEADERS = Set.of( @@ -141,7 +141,7 @@ public void sendResponseHeaders(int rCode, long responseLength) throws IOExcepti } else { response.setContentLengthLong(responseLength); } - + response.setStatus(rCode); } diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServer.java similarity index 75% rename from avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java rename to avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServer.java index fa6c2015..cdd9f79c 100644 --- a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpServer.java +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServer.java @@ -1,11 +1,9 @@ -package io.avaje.helidon.http.spi; +package io.avaje.jex.grizzly.spi; import java.io.IOException; import java.io.UncheckedIOException; import java.lang.System.Logger.Level; import java.net.InetSocketAddress; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -19,23 +17,21 @@ import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpsConfigurator; -/** Jetty implementation of {@link com.sun.net.httpserver.HttpServer}. */ -public class JettyHttpServer extends com.sun.net.httpserver.HttpsServer { +final class GrizzlyHttpServer extends com.sun.net.httpserver.HttpsServer { private static final System.Logger LOG = - System.getLogger(JettyHttpServer.class.getCanonicalName()); + System.getLogger(GrizzlyHttpServer.class.getCanonicalName()); private final HttpServer server; - private final Map contexts = new HashMap<>(); private InetSocketAddress addr; private ServerConfiguration httpConfiguration; private ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); private HttpsConfigurator httpsConfig; - public JettyHttpServer(HttpServer server) { + public GrizzlyHttpServer(HttpServer server) { this(server, server.getServerConfiguration()); } - public JettyHttpServer(HttpServer server, ServerConfiguration configuration) { + public GrizzlyHttpServer(HttpServer server, ServerConfiguration configuration) { this.server = server; this.httpConfiguration = configuration; } @@ -81,8 +77,9 @@ protected HttpServer getServer() { @Override public InetSocketAddress getAddress() { - if (addr.getPort() == 0 && server.isStarted()) + if (addr.getPort() == 0 && server.isStarted()) { return new InetSocketAddress(addr.getHostString(), server.getListener("rizzly").getPort()); + } return addr; } @@ -92,7 +89,6 @@ public void start() { try { server.start(); } catch (IOException e) { - throw new UncheckedIOException(e); } } @@ -102,7 +98,7 @@ public void setExecutor(Executor executor) { if (executor instanceof ExecutorService service) { this.executor = service; } else { - throw new IllegalArgumentException("Grizzly only accepts ExecutorService"); + throw new IllegalArgumentException("Grizzly only accepts an instance of ExecutorService"); } } @@ -113,25 +109,18 @@ public Executor getExecutor() { @Override public void stop(int delay) { - - for (var context : contexts.values()) { - httpConfiguration.removeHttpHandler(context.getGrizzlyHandler()); - } - contexts.clear(); - - server.shutdown(); + server.shutdownNow(); } @Override public HttpContext createContext(String path, HttpHandler httpHandler) { - JettyHttpContext context = new JettyHttpContext(this, path, httpHandler); + GrizzlyHttpContext context = new GrizzlyHttpContext(this, path, httpHandler); GrizzlyHandler jettyContextHandler = context.getGrizzlyHandler(); httpConfiguration.addHttpHandler( jettyContextHandler, path.transform(this::prependSlash).transform(this::appendSlash)); - contexts.put(path, context); return context; } @@ -149,16 +138,15 @@ public HttpContext createContext(String path) { } @Override - public void removeContext(String path) throws IllegalArgumentException { - JettyHttpContext context = contexts.remove(path); - if (context == null) return; - GrizzlyHandler handler = context.getGrizzlyHandler(); - httpConfiguration.removeHttpHandler(handler); + public void removeContext(String path) { + + throw new UnsupportedOperationException("notImplemented"); } @Override public void removeContext(HttpContext context) { - removeContext(context.getPath()); + + throw new UnsupportedOperationException("notImplemented"); } @Override diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpServerProvider.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServerProvider.java similarity index 91% rename from avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpServerProvider.java rename to avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServerProvider.java index 997625f7..bd21367d 100644 --- a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/GrizzlyHttpServerProvider.java +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpServerProvider.java @@ -1,4 +1,4 @@ -package io.avaje.helidon.http.spi; +package io.avaje.jex.grizzly.spi; import java.io.IOException; import java.net.InetSocketAddress; @@ -43,7 +43,7 @@ private com.sun.net.httpserver.HttpsServer createServer(InetSocketAddress addr, server = new HttpServer(); } - JettyHttpServer jettyHttpServer = new JettyHttpServer(server); + GrizzlyHttpServer jettyHttpServer = new GrizzlyHttpServer(server); if (addr != null) jettyHttpServer.bind(addr, backlog); return jettyHttpServer; } diff --git a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpsExchange.java similarity index 57% rename from avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java rename to avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpsExchange.java index 16b261cd..d1e686d6 100644 --- a/avaje-jex-grizzly-spi/src/main/java/io/avaje/helidon/http/spi/JettyHttpsExchange.java +++ b/avaje-jex-grizzly-spi/src/main/java/io/avaje/jex/grizzly/spi/GrizzlyHttpsExchange.java @@ -1,4 +1,4 @@ -package io.avaje.helidon.http.spi; +package io.avaje.jex.grizzly.spi; import java.io.IOException; import java.io.InputStream; @@ -16,117 +16,106 @@ import com.sun.net.httpserver.HttpPrincipal; import com.sun.net.httpserver.HttpsExchange; +final class GrizzlyHttpsExchange extends HttpsExchange implements GrizzlyExchange { + private final GrizzlyHttpExchangeDelegate delegate; -public class JettyHttpsExchange extends HttpsExchange implements JettyExchange { - private final GrizzlyHttpExchangeDelegate _delegate; - - public JettyHttpsExchange(HttpContext jaxWsContext, Request req, Response resp) { - _delegate = new GrizzlyHttpExchangeDelegate(jaxWsContext, req, resp); - } - - @Override - public int hashCode() { - return _delegate.hashCode(); + public GrizzlyHttpsExchange(HttpContext jaxWsContext, Request req, Response resp) { + delegate = new GrizzlyHttpExchangeDelegate(jaxWsContext, req, resp); } @Override public Headers getRequestHeaders() { - return _delegate.getRequestHeaders(); + return delegate.getRequestHeaders(); } @Override public Headers getResponseHeaders() { - return _delegate.getResponseHeaders(); + return delegate.getResponseHeaders(); } @Override public URI getRequestURI() { - return _delegate.getRequestURI(); + return delegate.getRequestURI(); } @Override public String getRequestMethod() { - return _delegate.getRequestMethod(); + return delegate.getRequestMethod(); } @Override public HttpContext getHttpContext() { - return _delegate.getHttpContext(); + return delegate.getHttpContext(); } @Override public void close() { - _delegate.close(); - } - - @Override - public boolean equals(Object obj) { - return _delegate.equals(obj); + delegate.close(); } @Override public InputStream getRequestBody() { - return _delegate.getRequestBody(); + return delegate.getRequestBody(); } @Override public OutputStream getResponseBody() { - return _delegate.getResponseBody(); + return delegate.getResponseBody(); } @Override public void sendResponseHeaders(int rCode, long responseLength) throws IOException { - _delegate.sendResponseHeaders(rCode, responseLength); + delegate.sendResponseHeaders(rCode, responseLength); } @Override public InetSocketAddress getRemoteAddress() { - return _delegate.getRemoteAddress(); + return delegate.getRemoteAddress(); } @Override public int getResponseCode() { - return _delegate.getResponseCode(); + return delegate.getResponseCode(); } @Override public InetSocketAddress getLocalAddress() { - return _delegate.getLocalAddress(); + return delegate.getLocalAddress(); } @Override public String getProtocol() { - return _delegate.getProtocol(); + return delegate.getProtocol(); } @Override public Object getAttribute(String name) { - return _delegate.getAttribute(name); + return delegate.getAttribute(name); } @Override public void setAttribute(String name, Object value) { - _delegate.setAttribute(name, value); + delegate.setAttribute(name, value); } @Override public void setStreams(InputStream i, OutputStream o) { - _delegate.setStreams(i, o); + delegate.setStreams(i, o); } @Override public HttpPrincipal getPrincipal() { - return _delegate.getPrincipal(); + return delegate.getPrincipal(); } @Override public void setPrincipal(HttpPrincipal principal) { - _delegate.setPrincipal(principal); + delegate.setPrincipal(principal); } @Override public String toString() { - return _delegate.toString(); + return delegate.toString(); } @Override diff --git a/avaje-jex-grizzly-spi/src/main/java/module-info.java b/avaje-jex-grizzly-spi/src/main/java/module-info.java new file mode 100644 index 00000000..a848f5f2 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/main/java/module-info.java @@ -0,0 +1,15 @@ +import com.sun.net.httpserver.spi.HttpServerProvider; + +module io.avaje.jex.grizzly { + + exports io.avaje.jex.grizzly.spi; + + requires transitive jdk.httpserver; + requires transitive org.glassfish.grizzly.http.server; + requires transitive org.glassfish.grizzly.http; + requires transitive org.glassfish.grizzly; + + requires static io.avaje.spi; + + provides HttpServerProvider with io.avaje.jex.grizzly.spi.GrizzlyHttpServerProvider; +} diff --git a/avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java b/avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java new file mode 100644 index 00000000..b4e14ea6 --- /dev/null +++ b/avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java @@ -0,0 +1,118 @@ +package io.avaje.jex.grizzly.spi; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.http.HttpHeaders; +import java.net.http.HttpResponse; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; + +class FilterTest { + + static final TestPair pair = init(); + static final AtomicReference afterAll = new AtomicReference<>(); + static final AtomicReference afterTwo = new AtomicReference<>(); + + static TestPair init() { + final Jex app = + Jex.create() + .routing( + routing -> + routing + .get("/", ctx -> ctx.text("roo")) + .get( + "/noResponse", + ctx -> { + ctx.header("Content-Type", ""); + }) + .get("/one", ctx -> ctx.text("one")) + .get("/two", ctx -> ctx.text("two")) + .get("/two/{id}", ctx -> ctx.text("two-id")) + .before(ctx -> ctx.header("before-all", "set")) + .filter( + (ctx, chain) -> { + if (ctx.path().contains("/two/")) { + ctx.header("before-two", "set"); + } + chain.proceed(); + }) + .after(ctx -> afterAll.set("set")) + .filter( + (ctx, chain) -> { + chain.proceed(); + if (ctx.path().contains("/two/")) { + afterTwo.set("set"); + } + }) + .get("/dummy", ctx -> ctx.text("dummy"))); + + return TestPair.create(app); + } + + @AfterAll + static void end() { + pair.shutdown(); + } + + void clearAfter() { + afterAll.set(null); + afterTwo.set(null); + } + + @Test + void get() { + clearAfter(); + HttpResponse res = pair.request().GET().asString(); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + + clearAfter(); + res = pair.request().path("one").GET().asString(); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + + clearAfter(); + res = pair.request().path("two").GET().asString(); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + } + + @Test + void getNoResponse() { + clearAfter(); + HttpResponse res = pair.request().path("noResponse").GET().asString(); + assertThat(res.statusCode()).isEqualTo(204); + assertHasBeforeAfterAll(res); + assertNoBeforeAfterTwo(res); + } + + @Test + void get_two_expect_extraFilters() { + clearAfter(); + HttpResponse res = pair.request().path("two/42").GET().asString(); + + final HttpHeaders headers = res.headers(); + assertHasBeforeAfterAll(res); + assertThat(headers.firstValue("before-two")).get().isEqualTo("set"); + assertThat(afterTwo.get()).isEqualTo("set"); + } + + private void assertNoBeforeAfterTwo(HttpResponse res) { + assertThat(res.statusCode()).isLessThan(300); + assertThat(res.headers().firstValue("before-two")).isEmpty(); + assertThat(afterTwo.get()).isNull(); + } + + private void assertHasBeforeAfterAll(HttpResponse res) { + assertThat(res.statusCode()).isLessThan(300); + assertThat(res.headers().firstValue("before-all")).get().isEqualTo("set"); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(2)); + assertThat(afterAll.get()).isEqualTo("set"); + } +} From 248a812848cb855c1f7c429e725d64b72cd03b03 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 21 Mar 2025 19:44:40 -0400 Subject: [PATCH 5/5] basic test --- avaje-jex-grizzly-spi/pom.xml | 6 ++++++ avaje-jex-grizzly-spi/src/main/java/module-info.java | 2 ++ .../src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java | 1 + .../src/main/java/io/avaje/jex/core/RoutingHandler.java | 2 +- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/avaje-jex-grizzly-spi/pom.xml b/avaje-jex-grizzly-spi/pom.xml index d9bfb46a..e7c61d20 100644 --- a/avaje-jex-grizzly-spi/pom.xml +++ b/avaje-jex-grizzly-spi/pom.xml @@ -11,6 +11,12 @@ avaje-jex-grizzly-spi + + + io.avaje + avaje-jex + + org.glassfish.grizzly grizzly-http-server diff --git a/avaje-jex-grizzly-spi/src/main/java/module-info.java b/avaje-jex-grizzly-spi/src/main/java/module-info.java index a848f5f2..6378b580 100644 --- a/avaje-jex-grizzly-spi/src/main/java/module-info.java +++ b/avaje-jex-grizzly-spi/src/main/java/module-info.java @@ -4,12 +4,14 @@ exports io.avaje.jex.grizzly.spi; + requires transitive io.avaje.jex; requires transitive jdk.httpserver; requires transitive org.glassfish.grizzly.http.server; requires transitive org.glassfish.grizzly.http; requires transitive org.glassfish.grizzly; requires static io.avaje.spi; + requires static java.net.http; provides HttpServerProvider with io.avaje.jex.grizzly.spi.GrizzlyHttpServerProvider; } diff --git a/avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java b/avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java index b4e14ea6..fc6cc485 100644 --- a/avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java +++ b/avaje-jex-grizzly-spi/src/test/java/io/avaje/jex/grizzly/spi/FilterTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test; import io.avaje.jex.Jex; +import io.avaje.jex.test.TestPair; class FilterTest { diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java index cd91a6ef..f224049d 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/RoutingHandler.java @@ -60,7 +60,7 @@ public void handle(HttpExchange exchange) { } private void handleNoResponse(HttpExchange exchange) throws IOException { - if (exchange.getResponseCode() == -1) { + if (exchange.getResponseCode() < 1) { exchange.sendResponseHeaders(204, -1); } }