From d21c854422e4f505132be581d88244cda99cce29 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Thu, 8 Feb 2024 21:11:54 +0100 Subject: [PATCH 01/40] gh-30 update processor --- .../unknow/server/http/HttpConnection.java | 24 ++++++--- .../unknow/server/http/HttpProcessor.java | 52 +++++-------------- .../unknow/server/http/HttpProcessor11.java | 25 ++++----- 3 files changed, 40 insertions(+), 61 deletions(-) diff --git a/unknow-server-http/src/main/java/unknow/server/http/HttpConnection.java b/unknow-server-http/src/main/java/unknow/server/http/HttpConnection.java index a1bdc4fa..39210a64 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/HttpConnection.java +++ b/unknow-server-http/src/main/java/unknow/server/http/HttpConnection.java @@ -4,6 +4,8 @@ package unknow.server.http; import java.io.IOException; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @@ -14,6 +16,7 @@ import jakarta.servlet.DispatcherType; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; +import unknow.server.http.HttpProcessor.HttpProcessorFactory; import unknow.server.http.servlet.ServletContextImpl; import unknow.server.http.servlet.ServletRequestImpl; import unknow.server.http.servlet.ServletResponseImpl; @@ -23,6 +26,8 @@ public class HttpConnection extends NIOConnection { private static final Logger logger = LoggerFactory.getLogger(HttpConnection.class); + private static final List VERSIONS = Arrays.asList(HttpProcessor11.Factory); + private final ExecutorService executor; private final ServletContextImpl ctx; private final int keepAliveIdle; @@ -45,16 +50,17 @@ protected HttpConnection(ExecutorService executor, ServletContextImpl ctx, int k this.req = new ServletRequestImpl(this, DispatcherType.REQUEST); } - @Override - protected void onInit() { - p = new HttpProcessor11(getCtx(), keepAliveIdle); - } - @Override public final void onRead() throws InterruptedException { if (!exec.isDone()) return; - if (!p.init(this)) + + for (HttpProcessorFactory f : VERSIONS) { + p = f.create(this); + if (p != null) + break; + } + if (p == null) return; exec = executor.submit(p); } @@ -65,8 +71,7 @@ public final void onWrite() { // OK private void cleanup() { exec.cancel(true); - p.close(); - p = new HttpProcessor11(getCtx(), keepAliveIdle); + p = null; pendingRead.clear(); } @@ -127,4 +132,7 @@ public ServletContextImpl getCtx() { return ctx; } + public int getkeepAlive() { + return keepAliveIdle; + } } \ No newline at end of file diff --git a/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor.java b/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor.java index 2b13d147..eff0c2af 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor.java +++ b/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor.java @@ -1,8 +1,6 @@ package unknow.server.http; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,40 +11,31 @@ import unknow.server.http.utils.EventManager; import unknow.server.http.utils.ServletManager; import unknow.server.nio.NIOConnection.Out; -import unknow.server.util.io.Buffers; public abstract class HttpProcessor implements Runnable { private static final Logger logger = LoggerFactory.getLogger(HttpProcessor.class); - protected HttpConnection co; + protected final HttpConnection co; protected final ServletContextImpl ctx; protected final ServletManager servlets; protected final EventManager events; protected final int keepAliveIdle; - protected HttpProcessor(ServletContextImpl ctx, int keepAliveIdle) { - this.ctx = ctx; + protected HttpProcessor(HttpConnection co) { + this.co = co; + this.ctx = co.getCtx(); this.servlets = ctx.getServletManager(); this.events = ctx.getEvents(); - this.keepAliveIdle = keepAliveIdle; + this.keepAliveIdle = co.getkeepAlive(); } - protected abstract boolean canProcess(HttpConnection co) throws InterruptedException; - protected abstract boolean fillRequest(ServletRequestImpl req) throws InterruptedException, IOException; protected abstract void doRun(ServletRequestImpl req, ServletResponseImpl res) throws IOException; - public final boolean init(HttpConnection co) throws InterruptedException { - if (!canProcess(co)) - return false; - this.co = co; - return true; - } - @SuppressWarnings("resource") @Override - public void run() { + public final void run() { boolean close = false; ServletRequestImpl req = co.req; ServletResponseImpl res = co.res; @@ -80,7 +69,6 @@ public void run() { } catch (@SuppressWarnings("unused") IOException e1) { //ok } } finally { - co = null; if (close) out.close(); else @@ -88,25 +76,13 @@ public void run() { } } - public final void close() { - co = null; - } - - protected Buffers readBuffer() { - if (co == null) - throw new ProcessDoneException(); - return co.pendingRead; - } - - public OutputStream getOut() { - if (co == null) - throw new ProcessDoneException(); - return co.getOut(); - } - - public InputStream getIn() { - if (co == null) - throw new ProcessDoneException(); - return co.getIn(); + public static interface HttpProcessorFactory { + /** + * create a processor if it can process it + * @param co the connection + * @return the processor or null + * @throws InterruptedException on interrupt + */ + HttpProcessor create(HttpConnection co) throws InterruptedException; } } diff --git a/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor11.java b/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor11.java index 3a455ec5..a10e5eac 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor11.java +++ b/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor11.java @@ -11,16 +11,16 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.UnavailableException; -import unknow.server.http.servlet.ServletContextImpl; import unknow.server.http.servlet.ServletRequestImpl; import unknow.server.http.servlet.ServletResponseImpl; import unknow.server.util.io.Buffers; import unknow.server.util.io.BuffersUtils; -import unknow.server.util.io.BuffersUtils.IndexOfBloc; public class HttpProcessor11 extends HttpProcessor { private static final Logger logger = LoggerFactory.getLogger(HttpProcessor11.class); + private static final byte[] END = new byte[] { '\r', '\n', '\r', '\n' }; + private static final byte[] CRLF = { '\r', '\n' }; private static final byte[] PARAM_SEP = { '&', '=' }; private static final byte[] SPACE_SLASH = { ' ', '/' }; @@ -39,29 +39,19 @@ public class HttpProcessor11 extends HttpProcessor { private static final int MAX_START_SIZE = 8192; - private final IndexOfBloc end; - private final StringBuilder sb; private final Decode decode; - public HttpProcessor11(ServletContextImpl ctx, int keepAliveIdle) { - super(ctx, keepAliveIdle); + public HttpProcessor11(HttpConnection co) { + super(co); - end = new IndexOfBloc(new byte[] { '\r', '\n', '\r', '\n' }); sb = new StringBuilder(); decode = new Decode(sb); } - @Override - protected boolean canProcess(HttpConnection co) throws InterruptedException { - end.reset(); - co.pendingRead.walk(end, 0, MAX_START_SIZE); - return end.index() > 0; - } - @Override protected boolean fillRequest(ServletRequestImpl req) throws InterruptedException, IOException { - Buffers b = readBuffer(); + Buffers b = co.pendingRead; int i = BuffersUtils.indexOf(b, SPACE_SLASH, 0, MAX_METHOD_SIZE); if (i < 0) { co.sendError(HttpError.BAD_REQUEST.code, null, null); @@ -196,4 +186,9 @@ protected final void doRun(ServletRequestImpl req, ServletResponseImpl res) thro } } + public static final HttpProcessorFactory Factory = co -> { + if (BuffersUtils.indexOf(co.pendingRead, END, 0, MAX_START_SIZE) > 0) + return new HttpProcessor11(co); + return null; + }; } From c229cbc225dae00d0ea1ed3ca38b9fea78ca4042 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 24 Mar 2024 11:27:20 +0100 Subject: [PATCH 02/40] gh-30 refactor --- pom.xml | 2 +- .../unknow/server/http/HttpConnection.java | 139 ------ .../unknow/server/http/HttpProcessor.java | 88 ---- .../unknow/server/http/HttpProcessor11.java | 194 -------- .../unknow/server/http/HttpProcessor2.java | 200 -------- .../server/http/ProcessDoneException.java | 5 - .../http/servlet/ServletResponseImpl.java | 464 ------------------ .../servlet/out/ChunckedOutputStream.java | 158 ------ .../server/http/servlet/out/EmptyStream.java | 51 -- .../http/servlet/out/LengthOutputStream.java | 113 ----- .../server/http/servlet/out/Output.java | 50 -- .../http/servlet/out/ServletWriter.java | 133 ----- .../java/unknow/server/nio/NIOConnection.java | 8 + .../server/protobuf/ProtoStuffConnection.java | 6 +- .../pom.xml | 2 +- .../server/servlet}/AbstractHttpServer.java | 12 +- .../server/servlet}/AccessLogFilter.java | 2 +- .../java/unknow/server/servlet}/Decode.java | 2 +- .../unknow/server/servlet/HttpAdapter.java | 25 + .../unknow/server/servlet/HttpConnection.java | 89 ++++ .../unknow/server/servlet}/HttpError.java | 2 +- .../unknow/server/servlet/HttpProcessor.java | 23 + .../unknow/server/servlet/HttpWorker.java | 71 +++ .../server/servlet/ProcessDoneException.java | 5 + .../servlet/http11/ChunckedOutputStream.java | 57 +++ .../server/servlet/http11/EmptyStream.java | 29 ++ .../servlet/http11/Http11Processor.java | 56 +++ .../server/servlet/http11/Http11Worker.java | 380 ++++++++++++++ .../servlet/http11/LengthOutputStream.java | 74 +++ .../servlet/http2/Http2FlowControl.java | 15 + .../server/servlet/http2/Http2Headers.java | 282 +++++++++++ .../server/servlet/http2/Http2Huffman.java | 129 +++++ .../server/servlet/http2/Http2Processor.java | 422 ++++++++++++++++ .../server/servlet/http2/Http2Stream.java | 9 + .../server/servlet/impl}/FilterChainImpl.java | 2 +- .../servlet/impl}/FilterConfigImpl.java | 2 +- .../servlet/impl}/ServletConfigImpl.java | 2 +- .../servlet/impl}/ServletContextImpl.java | 8 +- .../impl}/ServletCookieConfigImpl.java | 2 +- .../server/servlet/impl}/ServletJsp.java | 2 +- .../servlet/impl}/ServletRequestImpl.java | 102 ++-- .../servlet/impl/ServletResponseImpl.java | 362 ++++++++++++++ .../servlet/impl}/in/ChunckedInputStream.java | 2 +- .../servlet/impl}/in/EmptyInputStream.java | 2 +- .../servlet/impl}/in/LengthInputStream.java | 2 +- .../impl/out/AbstractServletOutput.java | 127 +++++ .../impl}/session/NoSessionFactory.java | 2 +- .../servlet/impl}/session/SessionFactory.java | 2 +- .../impl}/session/SessionIdGenerator.java | 2 +- .../unknow/server/servlet}/utils/Encoder.java | 2 +- .../server/servlet}/utils/EventManager.java | 2 +- .../server/servlet}/utils/PathTree.java | 4 +- .../servlet}/utils/PathTreeBuilder.java | 15 +- .../server/servlet}/utils/Resource.java | 2 +- .../server/servlet/utils}/ServletDefault.java | 2 +- .../server/servlet}/utils/ServletManager.java | 16 +- .../servlet/utils}/ServletResource.java | 2 +- .../servlet/utils}/ServletResourceStatic.java | 2 +- .../native-image/resource-config.json | 0 .../servlet/http2/Http2HeadersTest.java | 169 +++++++ .../java/unknow/server/servlet}/utils/F.java | 2 +- .../java/unknow/server/servlet}/utils/FC.java | 6 +- .../servlet}/utils/PathTreeBuilderTest.java | 10 +- .../server/servlet}/utils/PathTreeTest.java | 13 +- .../java/unknow/server/servlet}/utils/S.java | 2 +- .../unknow/server/util/data/IntArrayMap.java | 4 - .../java/unknow/server/util/io/Buffers.java | 59 ++- 67 files changed, 2506 insertions(+), 1724 deletions(-) delete mode 100644 unknow-server-http/src/main/java/unknow/server/http/HttpConnection.java delete mode 100644 unknow-server-http/src/main/java/unknow/server/http/HttpProcessor.java delete mode 100644 unknow-server-http/src/main/java/unknow/server/http/HttpProcessor11.java delete mode 100644 unknow-server-http/src/main/java/unknow/server/http/HttpProcessor2.java delete mode 100644 unknow-server-http/src/main/java/unknow/server/http/ProcessDoneException.java delete mode 100644 unknow-server-http/src/main/java/unknow/server/http/servlet/ServletResponseImpl.java delete mode 100644 unknow-server-http/src/main/java/unknow/server/http/servlet/out/ChunckedOutputStream.java delete mode 100644 unknow-server-http/src/main/java/unknow/server/http/servlet/out/EmptyStream.java delete mode 100644 unknow-server-http/src/main/java/unknow/server/http/servlet/out/LengthOutputStream.java delete mode 100644 unknow-server-http/src/main/java/unknow/server/http/servlet/out/Output.java delete mode 100644 unknow-server-http/src/main/java/unknow/server/http/servlet/out/ServletWriter.java rename {unknow-server-http => unknow-server-servlet}/pom.xml (91%) rename {unknow-server-http/src/main/java/unknow/server/http => unknow-server-servlet/src/main/java/unknow/server/servlet}/AbstractHttpServer.java (93%) rename {unknow-server-http/src/main/java/unknow/server/http => unknow-server-servlet/src/main/java/unknow/server/servlet}/AccessLogFilter.java (99%) rename {unknow-server-http/src/main/java/unknow/server/http => unknow-server-servlet/src/main/java/unknow/server/servlet}/Decode.java (93%) create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java rename {unknow-server-http/src/main/java/unknow/server/http => unknow-server-servlet/src/main/java/unknow/server/servlet}/HttpError.java (99%) create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/ProcessDoneException.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http11/EmptyStream.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2FlowControl.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/FilterChainImpl.java (98%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/FilterConfigImpl.java (98%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/ServletConfigImpl.java (98%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/ServletContextImpl.java (98%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/ServletCookieConfigImpl.java (98%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/ServletJsp.java (96%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/ServletRequestImpl.java (88%) create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/in/ChunckedInputStream.java (98%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/in/EmptyInputStream.java (96%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/in/LengthInputStream.java (96%) create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/session/NoSessionFactory.java (90%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/session/SessionFactory.java (94%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/impl}/session/SessionIdGenerator.java (96%) rename {unknow-server-http/src/main/java/unknow/server/http => unknow-server-servlet/src/main/java/unknow/server/servlet}/utils/Encoder.java (95%) rename {unknow-server-http/src/main/java/unknow/server/http => unknow-server-servlet/src/main/java/unknow/server/servlet}/utils/EventManager.java (99%) rename {unknow-server-http/src/main/java/unknow/server/http => unknow-server-servlet/src/main/java/unknow/server/servlet}/utils/PathTree.java (98%) rename {unknow-server-http/src/main/java/unknow/server/http => unknow-server-servlet/src/main/java/unknow/server/servlet}/utils/PathTreeBuilder.java (94%) rename {unknow-server-http/src/main/java/unknow/server/http => unknow-server-servlet/src/main/java/unknow/server/servlet}/utils/Resource.java (90%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/utils}/ServletDefault.java (95%) rename {unknow-server-http/src/main/java/unknow/server/http => unknow-server-servlet/src/main/java/unknow/server/servlet}/utils/ServletManager.java (91%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/utils}/ServletResource.java (98%) rename {unknow-server-http/src/main/java/unknow/server/http/servlet => unknow-server-servlet/src/main/java/unknow/server/servlet/utils}/ServletResourceStatic.java (98%) rename {unknow-server-http => unknow-server-servlet}/src/main/resources/META-INF/native-image/resource-config.json (100%) create mode 100644 unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java rename {unknow-server-http/src/test/java/unknow/server/http => unknow-server-servlet/src/test/java/unknow/server/servlet}/utils/F.java (94%) rename {unknow-server-http/src/test/java/unknow/server/http => unknow-server-servlet/src/test/java/unknow/server/servlet}/utils/FC.java (82%) rename {unknow-server-http/src/test/java/unknow/server/http => unknow-server-servlet/src/test/java/unknow/server/servlet}/utils/PathTreeBuilderTest.java (90%) rename {unknow-server-http/src/test/java/unknow/server/http => unknow-server-servlet/src/test/java/unknow/server/servlet}/utils/PathTreeTest.java (87%) rename {unknow-server-http/src/test/java/unknow/server/http => unknow-server-servlet/src/test/java/unknow/server/servlet}/utils/S.java (89%) diff --git a/pom.xml b/pom.xml index 571c6193..b025c371 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ unknow-server-bom unknow-server-nio unknow-server-maven - unknow-server-http + unknow-server-servlet unknow-server-util unknow-server-jaxb unknow-server-jaxws diff --git a/unknow-server-http/src/main/java/unknow/server/http/HttpConnection.java b/unknow-server-http/src/main/java/unknow/server/http/HttpConnection.java deleted file mode 100644 index 272540a7..00000000 --- a/unknow-server-http/src/main/java/unknow/server/http/HttpConnection.java +++ /dev/null @@ -1,139 +0,0 @@ -/** - * - */ -package unknow.server.http; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jakarta.servlet.DispatcherType; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import unknow.server.http.HttpProcessor.HttpProcessorFactory; -import unknow.server.http.servlet.ServletContextImpl; -import unknow.server.http.servlet.ServletRequestImpl; -import unknow.server.http.servlet.ServletResponseImpl; -import unknow.server.http.utils.ServletManager; -import unknow.server.nio.NIOConnection; - -public class HttpConnection extends NIOConnection { - private static final Logger logger = LoggerFactory.getLogger(HttpConnection.class); - - private static final List VERSIONS = Arrays.asList(HttpProcessor11.Factory); - - private final ExecutorService executor; - private final ServletContextImpl ctx; - private final int keepAliveIdle; - - private Future exec = CompletableFuture.completedFuture(null); - private HttpProcessor p; - - protected final ServletResponseImpl res; - protected final ServletRequestImpl req; - - /** - * create new RequestBuilder - * @param executor the executor - * @param ctx the servlet context - */ - protected HttpConnection(ExecutorService executor, ServletContextImpl ctx, int keepAliveIdle) { - this.executor = executor; - this.keepAliveIdle = keepAliveIdle; - this.ctx = ctx; - this.res = new ServletResponseImpl(this); - this.req = new ServletRequestImpl(this, DispatcherType.REQUEST); - } - - @Override - public final void onRead() throws InterruptedException { - if (!exec.isDone()) - return; - - for (HttpProcessorFactory f : VERSIONS) { - p = f.create(this); - if (p != null) - break; - } - if (p == null) - return; - exec = executor.submit(p); - } - - @Override - public final void onWrite() { // OK - } - - private void cleanup() { - exec.cancel(true); - p = null; - pendingRead.clear(); - } - - @Override - public boolean closed(long now, boolean stop) { - if (stop) - return exec.isDone(); - - if (isClosed()) - return true; - if (!exec.isDone()) - return false; - - if (pendingWrite.isEmpty() && keepAliveIdle > 0) { - long e = now - keepAliveIdle; - if (lastRead() <= e && lastWrite() <= e) - return true; - } - - // TODO check request timeout - return false; - } - - @Override - protected final void onFree() { - exec.cancel(true); - cleanup(); - } - - public void sendError(int sc, Throwable t, String msg) throws IOException { - res.checkCommited(); - res.setStatus(sc); - ServletManager manager = getCtx().getServletManager(); - FilterChain f = manager.getError(sc, t); - if (f != null) { - ServletRequestImpl r = new ServletRequestImpl(this, DispatcherType.ERROR); - r.setMethod("GET"); - r.setAttribute("javax.servlet.error.status_code", sc); - if (t != null) { - r.setAttribute("javax.servlet.error.exception_type", t.getClass()); - r.setAttribute("javax.servlet.error.message", t.getMessage()); - r.setAttribute("javax.servlet.error.exception", t); - } - r.setAttribute("javax.servlet.error.request_uri", r.getRequestURI()); - r.setAttribute("javax.servlet.error.servlet_name", ""); - res.reset(); - try { - f.doFilter(r, res); - return; - } catch (ServletException e) { - logger.error("failed to send error", e); - } - } - res.sendError(HttpError.fromStatus(sc), sc, msg); - } - - public ServletContextImpl getCtx() { - return ctx; - } - - public int getkeepAlive() { - return keepAliveIdle; - } -} \ No newline at end of file diff --git a/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor.java b/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor.java deleted file mode 100644 index eff0c2af..00000000 --- a/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor.java +++ /dev/null @@ -1,88 +0,0 @@ -package unknow.server.http; - -import java.io.IOException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import unknow.server.http.servlet.ServletContextImpl; -import unknow.server.http.servlet.ServletRequestImpl; -import unknow.server.http.servlet.ServletResponseImpl; -import unknow.server.http.utils.EventManager; -import unknow.server.http.utils.ServletManager; -import unknow.server.nio.NIOConnection.Out; - -public abstract class HttpProcessor implements Runnable { - private static final Logger logger = LoggerFactory.getLogger(HttpProcessor.class); - - protected final HttpConnection co; - protected final ServletContextImpl ctx; - protected final ServletManager servlets; - protected final EventManager events; - protected final int keepAliveIdle; - - protected HttpProcessor(HttpConnection co) { - this.co = co; - this.ctx = co.getCtx(); - this.servlets = ctx.getServletManager(); - this.events = ctx.getEvents(); - this.keepAliveIdle = co.getkeepAlive(); - } - - protected abstract boolean fillRequest(ServletRequestImpl req) throws InterruptedException, IOException; - - protected abstract void doRun(ServletRequestImpl req, ServletResponseImpl res) throws IOException; - - @SuppressWarnings("resource") - @Override - public final void run() { - boolean close = false; - ServletRequestImpl req = co.req; - ServletResponseImpl res = co.res; - - Out out = co.getOut(); - try { - if (!fillRequest(req)) - return; - - if ("100-continue".equals(req.getHeader("expect"))) { - out.write(HttpError.CONTINUE.encoded); - out.write('\r'); - out.write('\n'); - out.flush(); - } - - close = keepAliveIdle == 0 || !"keep-alive".equals(req.getHeader("connection")); - if (!close) - res.setHeader("connection", "keep-alive"); - events.fireRequestInitialized(req); - doRun(req, res); - events.fireRequestDestroyed(req); - res.close(); - } catch (@SuppressWarnings("unused") ProcessDoneException e) { //ok - } catch (@SuppressWarnings("unused") InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (Exception e) { - logger.error("processor error", e); - try { - res.sendError(500); - } catch (@SuppressWarnings("unused") IOException e1) { //ok - } - } finally { - if (close) - out.close(); - else - out.flush(); - } - } - - public static interface HttpProcessorFactory { - /** - * create a processor if it can process it - * @param co the connection - * @return the processor or null - * @throws InterruptedException on interrupt - */ - HttpProcessor create(HttpConnection co) throws InterruptedException; - } -} diff --git a/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor11.java b/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor11.java deleted file mode 100644 index a10e5eac..00000000 --- a/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor11.java +++ /dev/null @@ -1,194 +0,0 @@ -package unknow.server.http; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.UnavailableException; -import unknow.server.http.servlet.ServletRequestImpl; -import unknow.server.http.servlet.ServletResponseImpl; -import unknow.server.util.io.Buffers; -import unknow.server.util.io.BuffersUtils; - -public class HttpProcessor11 extends HttpProcessor { - private static final Logger logger = LoggerFactory.getLogger(HttpProcessor11.class); - - private static final byte[] END = new byte[] { '\r', '\n', '\r', '\n' }; - - private static final byte[] CRLF = { '\r', '\n' }; - private static final byte[] PARAM_SEP = { '&', '=' }; - private static final byte[] SPACE_SLASH = { ' ', '/' }; - private static final byte SPACE = ' '; - private static final byte QUESTION = '?'; - private static final byte COLON = ':'; - private static final byte SEMICOLON = ';'; - private static final byte SLASH = '/'; - private static final byte AMPERSAMP = '&'; - private static final byte EQUAL = '='; - - private static final int MAX_METHOD_SIZE = 10; // max size for method - private static final int MAX_PATH_SIZE = 2000; - private static final int MAX_VERSION_SIZE = 12; - private static final int MAX_HEADER_SIZE = 512; - - private static final int MAX_START_SIZE = 8192; - - private final StringBuilder sb; - private final Decode decode; - - public HttpProcessor11(HttpConnection co) { - super(co); - - sb = new StringBuilder(); - decode = new Decode(sb); - } - - @Override - protected boolean fillRequest(ServletRequestImpl req) throws InterruptedException, IOException { - Buffers b = co.pendingRead; - int i = BuffersUtils.indexOf(b, SPACE_SLASH, 0, MAX_METHOD_SIZE); - if (i < 0) { - co.sendError(HttpError.BAD_REQUEST.code, null, null); - return false; - } - BuffersUtils.toString(sb, b, 0, i); - req.setMethod(sb.toString()); - sb.setLength(0); - int last = i + 1; - - i = BuffersUtils.indexOf(b, SPACE, last, MAX_PATH_SIZE); - if (i < 0) { - co.sendError(HttpError.URI_TOO_LONG.code, null, null); - return false; - } - int q = BuffersUtils.indexOf(b, QUESTION, last, i - last); - if (q < 0) - q = i; - - b.walk(decode, last, q - last); - if (!decode.done()) - return false; - req.setRequestUri(sb.toString()); - sb.setLength(0); - - int s; - while ((s = BuffersUtils.indexOf(b, SLASH, last + 1, q - last - 1)) > 0) { - int c = BuffersUtils.indexOf(b, SEMICOLON, last + 1, s - last - 1); - b.walk(decode, last + 1, (c < 0 ? s : c) - last - 1); - if (!decode.done()) - return false; - req.addPath(sb.toString()); - sb.setLength(0); - last = s; - } - if (s == -2 && last + 1 < q) { - int c = BuffersUtils.indexOf(b, SEMICOLON, last + 1, q - last - 1); - BuffersUtils.toString(sb, b, last + 1, c < 0 ? q - last - 1 : c); - req.addPath(sb.toString()); - sb.setLength(0); - } - - if (q < i) { - BuffersUtils.toString(sb, b, q + 1, i - q - 1); - req.setQuery(sb.toString()); - sb.setLength(0); - } else - req.setQuery(""); - - Map> map = new HashMap<>(); - parseParam(map, b, q + 1, i); - req.setQueryParam(map); - last = i + 1; - - i = BuffersUtils.indexOf(b, CRLF, last, MAX_VERSION_SIZE); - if (i < 0) { - co.sendError(HttpError.BAD_REQUEST.code, null, null); - return false; - } - BuffersUtils.toString(sb, b, last, i - last); - req.setProtocol(sb.toString()); - sb.setLength(0); - last = i + 2; - - map = new HashMap<>(); - while ((i = BuffersUtils.indexOf(b, CRLF, last, MAX_HEADER_SIZE)) > last) { - int c = BuffersUtils.indexOf(b, COLON, last, i - last); - if (c < 0) { - co.sendError(HttpError.BAD_REQUEST.code, null, null); - return false; - } - - BuffersUtils.toString(sb, b, last, c - last); - String k = sb.toString().trim().toLowerCase(); - sb.setLength(0); - - BuffersUtils.toString(sb, b, c + 1, i - c - 1); - String v = sb.toString().trim(); - sb.setLength(0); - - List list = map.get(k); - if (list == null) - map.put(k, list = new ArrayList<>(1)); - list.add(v); - - last = i + 2; - } - req.setHeaders(map); - b.skip(last + 2); - return true; - } - - private boolean parseParam(Map> map, Buffers data, int o, int e) throws InterruptedException { - while (o < e) { - int i = BuffersUtils.indexOfOne(data, PARAM_SEP, o, e - o); - if (i < 0) - i = e; - data.walk(decode, o, i - o); - if (!decode.done()) - return false; - String key = sb.toString(); - sb.setLength(0); - - o = i + 1; - if (i < e && data.get(i) == EQUAL) { - i = BuffersUtils.indexOf(data, AMPERSAMP, o, e - o); - if (i < 0) - i = e; - data.walk(decode, o, i - o); - if (!decode.done()) - return false; - o = i + 1; - } - map.computeIfAbsent(key, k -> new ArrayList<>(1)).add(sb.toString()); - sb.setLength(0); - } - return true; - } - - @Override - protected final void doRun(ServletRequestImpl req, ServletResponseImpl res) throws IOException { - FilterChain s = servlets.find(req); - try { - s.doFilter(req, res); - } catch (UnavailableException e) { - // TODO add page with retry-after - co.sendError(503, e, null); - } catch (Exception e) { - logger.error("failed to service '{}'", s, e); - if (!res.isCommitted()) - res.sendError(500); - } - } - - public static final HttpProcessorFactory Factory = co -> { - if (BuffersUtils.indexOf(co.pendingRead, END, 0, MAX_START_SIZE) > 0) - return new HttpProcessor11(co); - return null; - }; -} diff --git a/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor2.java b/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor2.java deleted file mode 100644 index b7788420..00000000 --- a/unknow-server-http/src/main/java/unknow/server/http/HttpProcessor2.java +++ /dev/null @@ -1,200 +0,0 @@ -package unknow.server.http; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.UnavailableException; -import unknow.server.http.servlet.ServletContextImpl; -import unknow.server.http.servlet.ServletRequestImpl; -import unknow.server.http.servlet.ServletResponseImpl; -import unknow.server.util.io.Buffers; -import unknow.server.util.io.BuffersUtils; -import unknow.server.util.io.BuffersUtils.IndexOfBloc; - -public class HttpProcessor2 extends HttpProcessor { - private static final Logger logger = LoggerFactory.getLogger(HttpProcessor2.class); - - private static final byte[] PRI = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.US_ASCII); - - private static final byte[] CRLF = { '\r', '\n' }; - private static final byte[] PARAM_SEP = { '&', '=' }; - private static final byte[] SPACE_SLASH = { ' ', '/' }; - private static final byte SPACE = ' '; - private static final byte QUESTION = '?'; - private static final byte COLON = ':'; - private static final byte SEMICOLON = ';'; - private static final byte SLASH = '/'; - private static final byte AMPERSAMP = '&'; - private static final byte EQUAL = '='; - - private static final int MAX_METHOD_SIZE = 10; // max size for method - private static final int MAX_PATH_SIZE = 2000; - private static final int MAX_VERSION_SIZE = 12; - private static final int MAX_HEADER_SIZE = 512; - - private static final int MAX_START_SIZE = 8192; - - private final IndexOfBloc end; - - private final StringBuilder sb; - private final Decode decode; - - public HttpProcessor2(ServletContextImpl ctx, int keepAliveIdle) { - super(ctx, keepAliveIdle); - - end = new IndexOfBloc(new byte[] { '\r', '\n', '\r', '\n' }); - sb = new StringBuilder(); - decode = new Decode(sb); - } - - @Override - protected boolean canProcess(HttpConnection co) throws InterruptedException { - return BuffersUtils.startsWith(co.pendingRead, PRI, 0, PRI.length); - } - - @Override - protected boolean fillRequest(ServletRequestImpl req) throws InterruptedException, IOException { - Buffers b = readBuffer(); - int i = BuffersUtils.indexOf(b, SPACE_SLASH, 0, MAX_METHOD_SIZE); - if (i < 0) { - co.sendError(HttpError.BAD_REQUEST.code, null, null); - return false; - } - BuffersUtils.toString(sb, b, 0, i); - req.setMethod(sb.toString()); - sb.setLength(0); - int last = i + 1; - - i = BuffersUtils.indexOf(b, SPACE, last, MAX_PATH_SIZE); - if (i < 0) { - co.sendError(HttpError.URI_TOO_LONG.code, null, null); - return false; - } - int q = BuffersUtils.indexOf(b, QUESTION, last, i - last); - if (q < 0) - q = i; - - b.walk(decode, last, q - last); - if (!decode.done()) - return false; - req.setRequestUri(sb.toString()); - sb.setLength(0); - - int s; - while ((s = BuffersUtils.indexOf(b, SLASH, last + 1, q - last - 1)) > 0) { - int c = BuffersUtils.indexOf(b, SEMICOLON, last + 1, s - last - 1); - b.walk(decode, last + 1, (c < 0 ? s : c) - last - 1); - if (!decode.done()) - return false; - req.addPath(sb.toString()); - sb.setLength(0); - last = s; - } - if (s == -2 && last + 1 < q) { - int c = BuffersUtils.indexOf(b, SEMICOLON, last + 1, q - last - 1); - BuffersUtils.toString(sb, b, last + 1, c < 0 ? q - last - 1 : c); - req.addPath(sb.toString()); - sb.setLength(0); - } - - if (q < i) { - BuffersUtils.toString(sb, b, q + 1, i - q - 1); - req.setQuery(sb.toString()); - sb.setLength(0); - } else - req.setQuery(""); - - Map> map = new HashMap<>(); - parseParam(map, b, q + 1, i); - req.setQueryParam(map); - last = i + 1; - - i = BuffersUtils.indexOf(b, CRLF, last, MAX_VERSION_SIZE); - if (i < 0) { - co.sendError(HttpError.BAD_REQUEST.code, null, null); - return false; - } - BuffersUtils.toString(sb, b, last, i - last); - req.setProtocol(sb.toString()); - sb.setLength(0); - last = i + 2; - - map = new HashMap<>(); - while ((i = BuffersUtils.indexOf(b, CRLF, last, MAX_HEADER_SIZE)) > last) { - int c = BuffersUtils.indexOf(b, COLON, last, i - last); - if (c < 0) { - co.sendError(HttpError.BAD_REQUEST.code, null, null); - return false; - } - - BuffersUtils.toString(sb, b, last, c - last); - String k = sb.toString().trim().toLowerCase(); - sb.setLength(0); - - BuffersUtils.toString(sb, b, c + 1, i - c - 1); - String v = sb.toString().trim(); - sb.setLength(0); - - List list = map.get(k); - if (list == null) - map.put(k, list = new ArrayList<>(1)); - list.add(v); - - last = i + 2; - } - req.setHeaders(map); - b.skip(last + 2); - return true; - } - - private boolean parseParam(Map> map, Buffers data, int o, int e) throws InterruptedException { - while (o < e) { - int i = BuffersUtils.indexOfOne(data, PARAM_SEP, o, e - o); - if (i < 0) - i = e; - data.walk(decode, o, i - o); - if (!decode.done()) - return false; - String key = sb.toString(); - sb.setLength(0); - - o = i + 1; - if (i < e && data.get(i) == EQUAL) { - i = BuffersUtils.indexOf(data, AMPERSAMP, o, e - o); - if (i < 0) - i = e; - data.walk(decode, o, i - o); - if (!decode.done()) - return false; - o = i + 1; - } - map.computeIfAbsent(key, k -> new ArrayList<>(1)).add(sb.toString()); - sb.setLength(0); - } - return true; - } - - @Override - protected final void doRun(ServletRequestImpl req, ServletResponseImpl res) throws IOException { - FilterChain s = servlets.find(req); - try { - s.doFilter(req, res); - } catch (UnavailableException e) { - // TODO add page with retry-after - co.sendError(503, e, null); - } catch (Exception e) { - logger.error("failed to service '{}'", s, e); - if (!res.isCommitted()) - res.sendError(500); - } - } - -} diff --git a/unknow-server-http/src/main/java/unknow/server/http/ProcessDoneException.java b/unknow-server-http/src/main/java/unknow/server/http/ProcessDoneException.java deleted file mode 100644 index 07b3ab56..00000000 --- a/unknow-server-http/src/main/java/unknow/server/http/ProcessDoneException.java +++ /dev/null @@ -1,5 +0,0 @@ -package unknow.server.http; - -public class ProcessDoneException extends RuntimeException { - private static final long serialVersionUID = 1L; -} diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletResponseImpl.java b/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletResponseImpl.java deleted file mode 100644 index b1fb0e8b..00000000 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletResponseImpl.java +++ /dev/null @@ -1,464 +0,0 @@ -/** - * - */ -package unknow.server.http.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletResponse; -import unknow.server.http.HttpConnection; -import unknow.server.http.HttpError; -import unknow.server.http.servlet.out.ChunckedOutputStream; -import unknow.server.http.servlet.out.LengthOutputStream; -import unknow.server.http.servlet.out.Output; -import unknow.server.http.servlet.out.ServletWriter; -import unknow.server.nio.NIOConnection.Out; - -/** - * @author unknow - */ -public class ServletResponseImpl implements HttpServletResponse { - private static final Logger logger = LoggerFactory.getLogger(ServletResponseImpl.class); - - private static final String UNKNOWN = "Unknown"; - - private static final byte[] CRLF = new byte[] { '\r', '\n' }; - private static final byte[] QUOTE = new byte[] { '\\', '"' }; - private static final byte[] CHUNKED = new byte[] { 't', 'r', 'a', 'n', 's', 'f', 'e', 'r', '-', 'e', 'n', 'c', 'o', 'd', 'i', 'n', 'g', ':', ' ', 'c', 'h', 'u', 'n', 'k', - 'e', 'd', '\r', '\n' }; - private static final byte[] CONTENT_LENGTH = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h', ':', ' ' }; - private static final byte[] CONTENT_LENGTH0 = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h', ':', ' ', '0', '\r', '\n' }; - private static final byte[] CONTENT_TYPE = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 't', 'y', 'p', 'e', ':', ' ' }; - private static final byte[] CONTENT_HTML = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 't', 'y', 'p', 'e', ':', ' ', 't', 'e', 'x', 't', '/', 'h', 't', 'm', 'l', - ';', 'c', 'h', 'a', 'r', 's', 'e', 't', '=', 'u', 't', 'f', '8', '\r', '\n' }; - private static final byte[] CHARSET = new byte[] { ';', 'c', 'h', 'a', 'r', 's', 'e', 't', '=' }; - private static final byte[] LOCATION = new byte[] { 'l', 'o', 'c', 'a', 't', 'i', 'o', 'n', ':', ' ' }; - private static final byte[] COOKIE = new byte[] { 's', 'e', 't', '-', 'c', 'o', 'o', 'k', 'i', 'e', ':', ' ' }; - private static final byte[] PATH = new byte[] { ';', 'p', 'a', 't', 'h', '=' }; - private static final byte[] DOMAIN = new byte[] { ';', 'd', 'o', 'm', 'a', 'i', 'n', '=' }; - private static final byte[] MAX_AGE = new byte[] { ';', 'm', 'a', 'x', '-', 'a', 'g', 'e' }; - private static final byte[] SECURE = new byte[] { ';', 's', 'e', 'c', 'u', 'r', 'e' }; - private static final byte[] HTTP_ONLY = new byte[] { ';', 'h', 't', 't', 'p', 'o', 'n', 'l', 'y' }; - private static final byte[] ERROR_START = new byte[] { '<', 'h', 't', 'm', 'l', '>', '<', 'b', 'o', 'd', 'y', '>', '<', 'h', '1', '>' }; - private static final byte[] ERROR_END = new byte[] { '<', '/', 'h', '1', '>', '<', '/', 'b', 'o', 'd', 'y', '>', '<', '/', 'h', 't', 'm', 'l', '>' }; - - private static final DateTimeFormatter RFC1123 = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC); - - private final HttpConnection p; - private Output servletOut; - - private boolean commited = false; - - private int status; - private Charset charset; - private String type; - private final Map> headers; - private final List cookies; - - private long contentLength = -1; - private int bufferSize = -1; - - // Content-Language - private Locale locale = null; - - /** - * create new ServletResponseImpl - * - * @param p the connection - */ - public ServletResponseImpl(HttpConnection p) { - this.p = p; - headers = new HashMap<>(); - cookies = new ArrayList<>(); - - status = 200; - } - - /** - * commit the response - * - * @throws IOException - */ - public final void commit() throws IOException { - if (commited) - return; - commited = true; - - HttpError http = HttpError.fromStatus(status); - Out out = p.getOut(); - out.write(http == null ? HttpError.encodeStatusLine(status, UNKNOWN) : http.encoded); - for (Entry> e : headers.entrySet()) - writeHeader(e.getKey(), e.getValue()); - if (type != null && !headers.containsKey("content-type")) { - out.write(CONTENT_TYPE); - out.write(type.getBytes(StandardCharsets.US_ASCII)); - if (charset != null) { - out.write(CHARSET); - out.write(charset.name().getBytes(StandardCharsets.US_ASCII)); - } - out.write(CRLF); - } - if (servletOut != null && servletOut.isChuncked()) - out.write(CHUNKED); - else if (servletOut == null || contentLength == 0) - out.write(CONTENT_LENGTH0); - else { - out.write(CONTENT_LENGTH); - out.write(Long.toString(contentLength).getBytes(StandardCharsets.US_ASCII)); - out.write(CRLF); - } - for (Cookie c : cookies) - writeCookie(c); - out.write(CRLF); - } - - public void writeHeader(String name, List values) throws IOException { - Out out = p.getOut(); - out.write(name.getBytes(StandardCharsets.US_ASCII)); - out.write(':'); - for (String s : values) { - out.write(' '); - writeString(s); - } - out.write(CRLF); - } - - public void writeCookie(Cookie c) throws IOException { - Out out = p.getOut(); - out.write(COOKIE); - out.write(c.getName().getBytes(StandardCharsets.US_ASCII)); - out.write('='); - out.write(c.getValue().getBytes(StandardCharsets.US_ASCII)); - if (c.getPath() != null) { - out.write(PATH); - writeString(c.getPath()); - } - if (c.getDomain() != null) { - out.write(DOMAIN); - writeString(c.getDomain()); - } - if (c.getMaxAge() > 0) { - out.write(MAX_AGE); - writeString(Integer.toString(c.getMaxAge())); - } - if (c.getSecure()) - out.write(SECURE); - if (c.isHttpOnly()) - out.write(HTTP_ONLY); - } - - public void writeString(String s) throws IOException { - boolean escape = shouldEscape(s); - Out out = p.getOut(); - if (escape) - out.write('"'); - int l = 0; - int i; - while ((i = s.indexOf('"', l)) != -1) { - out.write(s.substring(l, i).getBytes(StandardCharsets.US_ASCII)); - out.write(QUOTE); - l = i + 1; - } - out.write(s.substring(l).getBytes(StandardCharsets.US_ASCII)); - if (escape) - out.write('"'); - } - - public void close() throws IOException { - if (servletOut != null) - servletOut.close(); - commit(); - } - - public void checkCommited() { - if (commited) - throw new IllegalStateException("already commited"); - } - - public void sendError(HttpError e, int sc, String msg) throws IOException { - Out out = p.getOut(); - if (msg == null) { - out.write(e == null ? HttpError.encodeEmptyReponse(sc, UNKNOWN) : e.empty()); - return; - } - - out.write(e == null ? HttpError.encodeStatusLine(sc, UNKNOWN) : e.encoded); - byte[] bytes = msg.getBytes(StandardCharsets.UTF_8); - - out.write(CONTENT_HTML); - out.write(CONTENT_LENGTH); - out.write(Integer.toString(bytes.length + ERROR_START.length + ERROR_END.length).getBytes(StandardCharsets.US_ASCII)); - out.write(CRLF); - out.write(CRLF); - out.write(ERROR_START); - out.write(bytes); - out.write(ERROR_END); - } - - @SuppressWarnings({ "unchecked", "resource" }) - private T createStream() { - if (contentLength < 0) - return (T) new ChunckedOutputStream(p.getOut(), this, bufferSize); - return (T) new LengthOutputStream(p.getOut(), this, contentLength); - } - - @Override - public String getCharacterEncoding() { - return charset == null ? p.getCtx().getResponseCharacterEncoding() : charset.name(); - } - - @Override - public void setCharacterEncoding(String charset) { - this.charset = Charset.forName(charset); - } - - @Override - public void setContentType(String type) { - int i = type.indexOf("charset="); - if (i > 0) { - String encoding = type.substring(i + 8); - i = encoding.indexOf(' '); - if (i > 0) - encoding = encoding.substring(0, i); - i = encoding.indexOf(';'); - if (i > 0) - encoding = encoding.substring(0, i); - setCharacterEncoding(encoding); - } - this.type = type; - } - - @Override - public String getContentType() { - if (!type.contains("charset=")) - type += "; charset=" + getCharacterEncoding(); - return type; - } - - @Override - public ServletOutputStream getOutputStream() throws IOException { - if (servletOut == null) - servletOut = createStream(); - if (servletOut instanceof ServletOutputStream) - return (ServletOutputStream) servletOut; - throw new IllegalStateException("output already got created with getWriter()"); - } - - @SuppressWarnings({ "rawtypes", "unchecked", "resource" }) - @Override - public PrintWriter getWriter() throws IOException { - if (servletOut == null) - servletOut = new ServletWriter(createStream(), charset == null ? Charset.forName(p.getCtx().getResponseCharacterEncoding()) : charset); - if (servletOut instanceof Writer) - return new PrintWriter((Writer) servletOut); - throw new IllegalStateException("output already got created with getWriter()"); - } - - @Override - public void setContentLength(int len) { - contentLength = len; - } - - @Override - public void setContentLengthLong(long len) { - contentLength = len; - } - - @Override - public void setBufferSize(int size) { - if (servletOut != null) - servletOut.setBufferSize(size); - else - bufferSize = size; - } - - @Override - public int getBufferSize() { - return servletOut == null ? bufferSize : servletOut.getBufferSize(); - } - - @Override - public void flushBuffer() throws IOException { - commit(); - if (servletOut != null) - servletOut.flush(); - p.getOut().flush(); - } - - @Override - public void resetBuffer() { - checkCommited(); - if (servletOut != null) - servletOut.resetBuffers(); - } - - @Override - public void reset() { - resetBuffer(); - status = 200; - List list = headers.get("connection"); - headers.clear(); - if (list != null) - headers.put("connection", list); - servletOut = null; - } - - @Override - public final boolean isCommitted() { - return commited; - } - - @Override - public void setLocale(Locale loc) { - if (charset == null) - setCharacterEncoding(p.getCtx().getEncoding(loc)); - this.locale = loc; - } - - @Override - public Locale getLocale() { - return locale; - } - - @Override - public void addCookie(Cookie cookie) { - cookies.add(cookie); - } - - @Override - public String encodeURL(String url) { - return url; - } - - @Override - public String encodeRedirectURL(String url) { - return url; - } - - @Override - public void sendError(int sc, String msg) throws IOException { - p.sendError(sc, null, msg); - } - - @Override - public void sendError(int sc) throws IOException { - p.sendError(sc, null, null); - } - - @Override - public void sendRedirect(String location) throws IOException { - checkCommited(); - commited = true; - status = HttpError.FOUND.code; - commit(); - Out out = p.getOut(); - out.write(HttpError.FOUND.encoded); - out.write(CONTENT_LENGTH0); - out.write(LOCATION); - writeString(location); - out.write(CRLF); - } - - @Override - public String getHeader(String name) { - List list = headers.get(name.toLowerCase()); - return list == null ? null : list.get(0); - } - - @Override - public Collection getHeaders(String name) { - return headers.get(name.toLowerCase()); - } - - @Override - public Collection getHeaderNames() { - return headers.keySet(); - } - - @Override - public boolean containsHeader(String name) { - return headers.containsKey(name.toLowerCase()); - } - - @Override - public void setHeader(String name, String value) { - name = name.toLowerCase(); - List list = headers.get(name); - if (list == null) - headers.put(name, list = new ArrayList<>(1)); - else - list.clear(); - list.add(value); - } - - @Override - public void addHeader(String name, String value) { - headers.computeIfAbsent(name.toLowerCase(), k -> new ArrayList<>(1)).add(value); - } - - @Override - public void setDateHeader(String name, long date) { - setHeader(name, RFC1123.format(Instant.ofEpochMilli(date))); - } - - @Override - public void addDateHeader(String name, long date) { - addHeader(name, RFC1123.format(Instant.ofEpochMilli(date))); - } - - @Override - public void setIntHeader(String name, int value) { - setHeader(name, Integer.toString(value)); - } - - @Override - public void addIntHeader(String name, int value) { - addHeader(name, Integer.toString(value)); - } - - @Override - public void setStatus(int sc) { - this.status = sc; - } - - @Override - public int getStatus() { - return status; - } - - /** - * check if this string contain a special characters as a header value - * - * @param s the string to check - * @return true is we should quote in the response - */ - public static boolean shouldEscape(String s) { - int l = s.length(); - for (int i = 0; i < l; i++) { - char c = s.charAt(i); - if (c == ' ' || c == '(' || c == ')' || c == '<' || c == '>' || c == '@' || c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' || c == '/' || c == '[' - || c == ']' || c == '?' || c == '=' || c == '{' || c == '}') - return true; - } - return false; - } -} diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/out/ChunckedOutputStream.java b/unknow-server-http/src/main/java/unknow/server/http/servlet/out/ChunckedOutputStream.java deleted file mode 100644 index a6bc5fae..00000000 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/out/ChunckedOutputStream.java +++ /dev/null @@ -1,158 +0,0 @@ -/** - * - */ -package unknow.server.http.servlet.out; - -import java.io.IOException; -import java.io.OutputStream; - -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.WriteListener; -import unknow.server.http.servlet.ServletResponseImpl; - -/** - * Http chuncked entity - * - * @author unknow - */ -public class ChunckedOutputStream extends ServletOutputStream implements Output { - private static final byte[] CRLF = new byte[] { '\r', '\n' }; - private static final byte[] END = new byte[] { '0', '\r', '\n', '\r', '\n' }; - private final OutputStream out; - private final ServletResponseImpl res; - private WriteListener listener; - - private byte[] buf; - private int o = 0; - - private boolean closed; - - /** - * create new ChunckedOutputStream - * - * @param out the real output - * @param res the servlet response (to commit) - * @param bufferSize the inital buffer size - */ - public ChunckedOutputStream(OutputStream out, ServletResponseImpl res, int bufferSize) { - this.out = out; - this.res = res; - if (bufferSize < 4096) - bufferSize = 4096; - buf = new byte[bufferSize]; - closed = false; - } - - private void ensureOpen() throws IOException { - if (closed) - throw new IOException("stream closed"); - } - - @Override - public boolean isReady() { - return true; - } - - @Override - public void setWriteListener(WriteListener writeListener) { - this.listener = writeListener; - if (listener != null) { - try { - listener.onWritePossible(); - } catch (Throwable t) { - listener.onError(t); - } - } - } - - @Override - public boolean isChuncked() { - return true; - } - - @Override - public void close() throws IOException { - if (closed) - return; - res.commit(); - flush(); - out.write(END); - out.flush(); - closed = true; - } - - @Override - public void flush() throws IOException { - if (o == 0) - return; - res.commit(); - writeBlock(buf, 0, o); - o = 0; - } - - private void writeBlock(byte[] b, int o, int l) throws IOException { - out.write(Integer.toString(l, 16).getBytes()); - out.write(CRLF); - out.write(b, o, l); - out.write(CRLF); - } - - @Override - public int getBufferSize() { - return buf.length; - } - - @Override - public void resetBuffers() { - o = 0; - } - - @Override - public void setBufferSize(int size) { - byte[] b = new byte[size]; - if (o > 0) - System.arraycopy(buf, 0, b, 0, o); - buf = b; - } - - @Override - public void write(int b) throws IOException { - ensureOpen(); - buf[o] = (byte) b; - writed(1); - } - - @Override - public void write(byte[] b) throws IOException { - ensureOpen(); - write(b, 0, b.length); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - if (len == 0) - return; - ensureOpen(); - if (o != 0) { - int l = Math.min(len, buf.length - o); - System.arraycopy(b, off, buf, o, l); - writed(l); - len -= l; - } - if (len == 0) - return; - if (len > buf.length) { - res.commit(); - flush(); - writeBlock(b, off, len); - } else { - System.arraycopy(b, off, buf, o, len); - writed(len); - } - } - - private void writed(int i) throws IOException { - if ((o += i) == buf.length) - flush(); - } -} diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/out/EmptyStream.java b/unknow-server-http/src/main/java/unknow/server/http/servlet/out/EmptyStream.java deleted file mode 100644 index 153f73d5..00000000 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/out/EmptyStream.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * - */ -package unknow.server.http.servlet.out; - -import java.io.IOException; - -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.WriteListener; - -/** - * @author unknow - */ -public class EmptyStream extends ServletOutputStream implements Output { - public static final EmptyStream INSTANCE = new EmptyStream(); - - private EmptyStream() { - } - - @Override - public boolean isChuncked() { - return false; - } - - @Override - public void resetBuffers() { // OK - } - - @Override - public void setBufferSize(int size) { - throw new IllegalStateException("unbuffered"); - } - - @Override - public int getBufferSize() { - return 0; - } - - @Override - public boolean isReady() { - return true; - } - - @Override - public void setWriteListener(WriteListener writeListener) { // OK - } - - @Override - public void write(int b) throws IOException { // OK - } -} diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/out/LengthOutputStream.java b/unknow-server-http/src/main/java/unknow/server/http/servlet/out/LengthOutputStream.java deleted file mode 100644 index a329bcf3..00000000 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/out/LengthOutputStream.java +++ /dev/null @@ -1,113 +0,0 @@ -/** - * - */ -package unknow.server.http.servlet.out; - -import java.io.IOException; -import java.io.OutputStream; - -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.WriteListener; -import unknow.server.http.servlet.ServletResponseImpl; - -/** - * http with a Content-Length (can be -1) - * - * @author unknow - */ -public class LengthOutputStream extends ServletOutputStream implements Output { - private final OutputStream out; - private final ServletResponseImpl res; - private WriteListener listener; - private long length; - - /** - * create new LengthOutputStream - * - * @param out the real ouptut - * @param res the servlet response - * @param length the length - */ - public LengthOutputStream(OutputStream out, ServletResponseImpl res, long length) { - this.out = out; - this.res = res; - this.length = length; - } - - private void ensureOpen() throws IOException { - if (length == 0) - throw new IOException("stream closed"); - - } - - @Override - public boolean isChuncked() { - return false; - } - - @Override - public void close() throws IOException { - if (length > 0) { - byte[] b = new byte[4096]; - while (length > 0) { - int l = (int) Math.min(length, 4096); - out.write(b, 0, l); - length -= l; - } - } - } - - @Override - public void resetBuffers() { // OK - } - - @Override - public void setBufferSize(int size) { - throw new IllegalArgumentException("not buffered"); - } - - @Override - public int getBufferSize() { - return 0; - } - - @Override - public boolean isReady() { - return true; - } - - @Override - public void setWriteListener(WriteListener writeListener) { - listener = writeListener; - if (listener != null) { - try { - listener.onWritePossible(); - } catch (Throwable t) { - listener.onError(t); - } - } - } - - @Override - public void write(int b) throws IOException { - ensureOpen(); - res.commit(); - out.write(b); - length--; - } - - @Override - public void write(byte[] b) throws IOException { - write(b, 0, b.length); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - ensureOpen(); - len = (int) Math.min(len, length); - res.commit(); - out.write(b, off, len); - length -= len; - } - -} diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/out/Output.java b/unknow-server-http/src/main/java/unknow/server/http/servlet/out/Output.java deleted file mode 100644 index fd9f8be9..00000000 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/out/Output.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * - */ -package unknow.server.http.servlet.out; - -import java.io.IOException; - -/** - * a servlet output - * - * @author unknow - */ -public interface Output { - /** - * @return true if the content will use the chunked encoding - */ - boolean isChuncked(); - - /** - * flush the buffers - * - * @throws IOException on ioexception - */ - void flush() throws IOException; - - /** - * mark this output as close (don't close the real output) - * - * @throws IOException on ioexception - */ - void close() throws IOException; - - /** - * reset/clear the buffers - */ - void resetBuffers(); - - /** - * set buffers size - * - * @param size the new size - * @throws IllegalArgumentException if this output doens't use buffers - */ - void setBufferSize(int size); - - /** - * @return the current buffer size, 0 is this output doesn't use buffers - */ - int getBufferSize(); -} \ No newline at end of file diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/out/ServletWriter.java b/unknow-server-http/src/main/java/unknow/server/http/servlet/out/ServletWriter.java deleted file mode 100644 index a30ad5c1..00000000 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/out/ServletWriter.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * - */ -package unknow.server.http.servlet.out; - -import java.io.IOException; -import java.io.Writer; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CodingErrorAction; - -import jakarta.servlet.ServletOutputStream; - -public final class ServletWriter extends Writer implements Output { - private final T out; - private final CharsetEncoder enc; - private final byte[] buf; - private final CharBuffer cb; - private final ByteBuffer bb; - - public ServletWriter(T out, Charset charset) { - this.out = out; - this.enc = charset.newEncoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE); - - this.buf = new byte[4096]; - this.cb = CharBuffer.allocate(2048); - this.bb = ByteBuffer.wrap(buf); - } - - @Override - public void write(char[] cbuf, int off, int len) throws IOException { - while (len > 0) { - int l = Math.min(len, cb.remaining()); - cb.put(cbuf, off, l); - cb.flip(); - enc.encode(cb, bb, true); - out.write(buf, 0, bb.position()); - bb.clear(); - off += l; - len -= l; - if (cb.remaining() > 0) - cb.compact(); - else - cb.clear(); - } - if (cb.position() > 0) { - cb.flip(); - while (cb.remaining() > 0) { - enc.encode(cb, bb, true); - out.write(buf, 0, bb.position()); - bb.clear(); - } - } - } - - @Override - public void write(char[] cbuf) throws IOException { - write(cbuf, 0, cbuf.length); - } - - @Override - public void write(int c) throws IOException { - cb.put((char) c); - cb.flip(); - enc.encode(cb, bb, true); - out.write(buf, 0, bb.position()); - cb.clear(); - bb.clear(); - } - - @Override - public void write(String str) throws IOException { - write(str, 0, str.length()); - } - - @Override - public void write(String str, int off, int len) throws IOException { - while (len > 0) { - int l = Math.min(len, cb.remaining()); - cb.put(str, off, off + l); - cb.flip(); - enc.encode(cb, bb, true); - out.write(buf, 0, bb.position()); - bb.clear(); - off += l; - len -= l; - if (cb.remaining() > 0) - cb.compact(); - else - cb.clear(); - } - if (cb.position() > 0) { - cb.flip(); - while (cb.remaining() > 0) { - enc.encode(cb, bb, true); - out.write(buf, 0, bb.position()); - bb.clear(); - } - } - } - - @Override - public void flush() throws IOException { - out.flush(); - } - - @Override - public void close() throws IOException { - out.close(); - } - - @Override - public boolean isChuncked() { - return out.isChuncked(); - } - - @Override - public void resetBuffers() { - out.resetBuffers(); - } - - @Override - public void setBufferSize(int size) { - out.setBufferSize(size); - } - - @Override - public int getBufferSize() { - return out.getBufferSize(); - } -} \ No newline at end of file diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java index e8f4c6d0..a42d43d8 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java @@ -156,6 +156,14 @@ private void toggleKeyOps() { key.interestOps(pendingWrite.isEmpty() ? SelectionKey.OP_READ : SelectionKey.OP_READ | SelectionKey.OP_WRITE); } + @SuppressWarnings("resource") + public void flush() { + if (pendingWrite.isEmpty()) + return; + toggleKeyOps(); + key.selector().wakeup(); + } + /** * timestamp of the last read * diff --git a/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java b/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java index f2b1848b..c792253a 100644 --- a/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java +++ b/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java @@ -39,13 +39,13 @@ protected ProtoStuffConnection(Schema schema, boolean protostuff) { } @Override - protected void onInit() { + protected final void onInit() { out = getOut(); in = new LimitedInputStream(getIn(), Integer.MAX_VALUE); input = new CodedInput(in, protostuff); } - protected > void write(T o) throws IOException { + protected final > void write(T o) throws IOException { LinkedBuffer buffer = LinkedBuffer.allocate(); Output output = protostuff ? new ProtobufOutput(buffer) : new ProtostuffOutput(buffer); schema.writeTo(output, o); @@ -55,7 +55,7 @@ protected > void write(T o) throws IOException { out.flush(); } - protected > void writeMessage(M o) throws IOException { + protected final > void writeMessage(M o) throws IOException { LinkedBuffer buffer = LinkedBuffer.allocate(); Output output = protostuff ? new ProtobufOutput(buffer) : new ProtostuffOutput(buffer); o.cachedSchema().writeTo(output, o); diff --git a/unknow-server-http/pom.xml b/unknow-server-servlet/pom.xml similarity index 91% rename from unknow-server-http/pom.xml rename to unknow-server-servlet/pom.xml index e8f993c0..4fd19da4 100644 --- a/unknow-server-http/pom.xml +++ b/unknow-server-servlet/pom.xml @@ -5,7 +5,7 @@ unknow-server 0.0.1-SNAPSHOT - unknow-server-http + unknow-server-servlet diff --git a/unknow-server-http/src/main/java/unknow/server/http/AbstractHttpServer.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/AbstractHttpServer.java similarity index 93% rename from unknow-server-http/src/main/java/unknow/server/http/AbstractHttpServer.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/AbstractHttpServer.java index ae2f3482..3864c5ad 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/AbstractHttpServer.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/AbstractHttpServer.java @@ -1,4 +1,4 @@ -package unknow.server.http; +package unknow.server.servlet; import java.net.InetSocketAddress; import java.util.ServiceLoader; @@ -13,13 +13,13 @@ import jakarta.servlet.ServletContainerInitializer; import jakarta.servlet.ServletException; -import unknow.server.http.servlet.FilterConfigImpl; -import unknow.server.http.servlet.ServletConfigImpl; -import unknow.server.http.servlet.ServletContextImpl; -import unknow.server.http.utils.EventManager; -import unknow.server.http.utils.ServletManager; import unknow.server.nio.NIOServer; import unknow.server.nio.NIOServerBuilder; +import unknow.server.servlet.impl.FilterConfigImpl; +import unknow.server.servlet.impl.ServletConfigImpl; +import unknow.server.servlet.impl.ServletContextImpl; +import unknow.server.servlet.utils.EventManager; +import unknow.server.servlet.utils.ServletManager; /** * Abstract server diff --git a/unknow-server-http/src/main/java/unknow/server/http/AccessLogFilter.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/AccessLogFilter.java similarity index 99% rename from unknow-server-http/src/main/java/unknow/server/http/AccessLogFilter.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/AccessLogFilter.java index 4f39e103..5eec0486 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/AccessLogFilter.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/AccessLogFilter.java @@ -1,4 +1,4 @@ -package unknow.server.http; +package unknow.server.servlet; import java.io.IOException; import java.time.Duration; diff --git a/unknow-server-http/src/main/java/unknow/server/http/Decode.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/Decode.java similarity index 93% rename from unknow-server-http/src/main/java/unknow/server/http/Decode.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/Decode.java index 8283540a..69af67c0 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/Decode.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/Decode.java @@ -1,4 +1,4 @@ -package unknow.server.http; +package unknow.server.servlet; import java.nio.ByteBuffer; import java.nio.CharBuffer; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java new file mode 100644 index 00000000..c86d3ba0 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java @@ -0,0 +1,25 @@ +package unknow.server.servlet; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import jakarta.servlet.ServletInputStream; +import unknow.server.servlet.impl.ServletContextImpl; +import unknow.server.servlet.impl.out.AbstractServletOutput; + +public interface HttpAdapter { + ServletContextImpl ctx(); + + ServletInputStream createInput(); + + InetSocketAddress getRemote(); + + InetSocketAddress getLocal(); + + AbstractServletOutput createOutput(); + + void commit() throws IOException; + + void sendError(HttpError e, Throwable t, String msg) throws IOException; + +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java new file mode 100644 index 00000000..31dc20d5 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java @@ -0,0 +1,89 @@ +/** + * + */ +package unknow.server.servlet; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import unknow.server.nio.NIOConnection; +import unknow.server.servlet.HttpProcessor.HttpProcessorFactory; +import unknow.server.servlet.http11.Http11Processor; +import unknow.server.servlet.impl.ServletContextImpl; + +public class HttpConnection extends NIOConnection { + private static final Logger logger = LoggerFactory.getLogger(HttpConnection.class); + + private static final HttpProcessorFactory[] VERSIONS = new HttpProcessorFactory[] { /*Http2Processor.Factory, */Http11Processor.Factory }; + + private final ExecutorService executor; + private final ServletContextImpl ctx; + private final int keepAliveIdle; + + private HttpProcessor p; + + /** + * create new RequestBuilder + * @param executor the executor + * @param ctx the servlet context + */ + protected HttpConnection(ExecutorService executor, ServletContextImpl ctx, int keepAliveIdle) { + this.executor = executor; + this.keepAliveIdle = keepAliveIdle; + this.ctx = ctx; + } + + @Override + public final void onRead() throws InterruptedException { + for (int i = 0; p == null && i < VERSIONS.length; i++) + p = VERSIONS[i].create(this); + + if (p != null) + p.process(); + } + + @Override + public boolean closed(long now, boolean stop) { + if (!pendingWrite.isEmpty()) + return false; + + if (stop) + return p == null || p.isClosed(); + + if (isClosed()) + return true; + if (p != null && !p.isClosed()) + return false; + + if (keepAliveIdle > 0) { + long e = now - keepAliveIdle; + if (lastRead() <= e && lastWrite() <= e) + return true; + } + + // TODO check request timeout + return false; + } + + @Override + protected final void onFree() { + p.close(); + p = null; + pendingRead.clear(); + } + + public Future submit(Runnable r) { + return executor.submit(r); + } + + public ServletContextImpl getCtx() { + return ctx; + } + + public int getkeepAlive() { + return keepAliveIdle; + } +} \ No newline at end of file diff --git a/unknow-server-http/src/main/java/unknow/server/http/HttpError.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpError.java similarity index 99% rename from unknow-server-http/src/main/java/unknow/server/http/HttpError.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/HttpError.java index 9539b72f..f02e420a 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/HttpError.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpError.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http; +package unknow.server.servlet; import java.nio.charset.StandardCharsets; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java new file mode 100644 index 00000000..6d7cb5fa --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java @@ -0,0 +1,23 @@ +package unknow.server.servlet; + +public interface HttpProcessor { + + /** called when we have more data */ + void process() throws InterruptedException; + + /** @return true if the connection is closed */ + boolean isClosed(); + + /** close the process */ + void close(); + + public static interface HttpProcessorFactory { + /** + * create a processor if it can process it + * @param co the connection + * @return the processor or null + * @throws InterruptedException on interrupt + */ + HttpProcessor create(HttpConnection co) throws InterruptedException; + } +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java new file mode 100644 index 00000000..ecac9835 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java @@ -0,0 +1,71 @@ +package unknow.server.servlet; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterChain; +import jakarta.servlet.UnavailableException; +import unknow.server.servlet.impl.ServletContextImpl; +import unknow.server.servlet.impl.ServletRequestImpl; +import unknow.server.servlet.impl.ServletResponseImpl; +import unknow.server.servlet.utils.EventManager; + +public abstract class HttpWorker implements Runnable, HttpAdapter { + private static final Logger logger = LoggerFactory.getLogger(HttpWorker.class); + + protected final HttpConnection co; + protected final EventManager events; + protected final ServletRequestImpl req; + protected final ServletResponseImpl res; + + public HttpWorker(HttpConnection co) { + this.co = co; + this.events = co.getCtx().getEvents(); + this.req = new ServletRequestImpl(this, DispatcherType.REQUEST); + this.res = new ServletResponseImpl(this); + } + + @Override + public ServletContextImpl ctx() { + return co.getCtx(); + } + + protected abstract void doStart() throws IOException, InterruptedException; + +// protected abstract void doRun() throws IOException, InterruptedException; + + protected abstract void doDone(); + + @Override + public final void run() { + try { + doStart(); + events.fireRequestInitialized(req); + FilterChain s = co.getCtx().getServletManager().find(req); + try { + s.doFilter(req, res); + } catch (UnavailableException e) { + // TODO add page with retry-after + res.sendError(503, e, null); + } catch (Exception e) { + logger.error("failed to service '{}'", s, e); + if (!res.isCommitted()) + res.sendError(500); + } + events.fireRequestDestroyed(req); + res.close(); + } catch (Exception e) { + logger.error("processor error", e); + try { + if (!res.isCommitted()) + res.sendError(500); + } catch (@SuppressWarnings("unused") IOException e1) { //ok + } + } finally { + doDone(); + } + } +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/ProcessDoneException.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/ProcessDoneException.java new file mode 100644 index 00000000..018adc7c --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/ProcessDoneException.java @@ -0,0 +1,5 @@ +//package unknow.server.servlet; +// +//public class ProcessDoneException extends RuntimeException { +// private static final long serialVersionUID = 1L; +//} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java new file mode 100644 index 00000000..fcd0c6f8 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java @@ -0,0 +1,57 @@ +/** + * + */ +package unknow.server.servlet.http11; + +import java.io.IOException; +import java.io.OutputStream; + +import unknow.server.servlet.impl.ServletResponseImpl; +import unknow.server.servlet.impl.out.AbstractServletOutput; + +/** + * Http chuncked entity + * + * @author unknow + */ +public class ChunckedOutputStream extends AbstractServletOutput { + private static final byte[] CRLF = new byte[] { '\r', '\n' }; + private static final byte[] END = new byte[] { '0', '\r', '\n', '\r', '\n' }; + private final OutputStream out; + + /** + * create new ChunckedOutputStream + * + * @param out the real output + * @param res the servlet response (to commit) + */ + public ChunckedOutputStream(OutputStream out, ServletResponseImpl res) { + super(res); + this.out = out; + } + + @Override + protected void afterClose() throws IOException { + out.write(END); + out.flush(); + } + + @Override + public void flush() throws IOException { + if (buffer.isEmpty()) + return; + res.commit(); + writeBlock(); + } + + private void writeBlock() throws IOException { + out.write(Integer.toString(buffer.length(), 16).getBytes()); + out.write(CRLF); + try { + buffer.read(out, -1, false); + } catch (@SuppressWarnings("unused") InterruptedException e) { + Thread.currentThread().interrupt(); + } + out.write(CRLF); + } +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/EmptyStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/EmptyStream.java new file mode 100644 index 00000000..d4c618e3 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/EmptyStream.java @@ -0,0 +1,29 @@ +/** + * + */ +package unknow.server.servlet.http11; + +import java.io.IOException; + +import unknow.server.servlet.impl.out.AbstractServletOutput; + +/** + * @author unknow + */ +public class EmptyStream extends AbstractServletOutput { + public static final EmptyStream INSTANCE = new EmptyStream(); + + private EmptyStream() { + super(null); + } + + @Override + public void write(int b) throws IOException { + throw new IOException("content lenth = 0"); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + throw new IOException("content lenth = 0"); + } +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java new file mode 100644 index 00000000..ff6f3c76 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java @@ -0,0 +1,56 @@ +package unknow.server.servlet.http11; + +import java.util.concurrent.Future; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import unknow.server.servlet.Decode; +import unknow.server.servlet.HttpConnection; +import unknow.server.servlet.HttpProcessor; +import unknow.server.util.io.BuffersUtils; + +public class Http11Processor implements HttpProcessor { + private static final Logger logger = LoggerFactory.getLogger(Http11Processor.class); + + private static final byte[] END = new byte[] { '\r', '\n', '\r', '\n' }; + + private static final int MAX_START_SIZE = 8192; + + private final HttpConnection co; + private final int keepAliveIdle; + private final StringBuilder sb; + private final Decode decode; + + private volatile Future exec; + + public Http11Processor(HttpConnection co) { + this.co = co; + this.keepAliveIdle = co.getkeepAlive(); + + sb = new StringBuilder(); + decode = new Decode(sb); + } + + @Override + public final void process() { // nothing to do + if (exec != null) + exec = co.submit(new Http11Worker(co)); + } + + @Override + public final boolean isClosed() { + return exec.isDone(); + } + + @Override + public final void close() { + exec.cancel(true); + } + + public static final HttpProcessorFactory Factory = co -> { + if (BuffersUtils.indexOf(co.pendingRead, END, 0, MAX_START_SIZE) > 0) + return new Http11Processor(co); + return null; + }; +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java new file mode 100644 index 00000000..3c390384 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -0,0 +1,380 @@ +package unknow.server.servlet.http11; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.Cookie; +import unknow.server.nio.NIOConnection.Out; +import unknow.server.servlet.Decode; +import unknow.server.servlet.HttpConnection; +import unknow.server.servlet.HttpError; +import unknow.server.servlet.HttpWorker; +import unknow.server.servlet.impl.ServletRequestImpl; +import unknow.server.servlet.impl.in.ChunckedInputStream; +import unknow.server.servlet.impl.in.EmptyInputStream; +import unknow.server.servlet.impl.in.LengthInputStream; +import unknow.server.servlet.impl.out.AbstractServletOutput; +import unknow.server.util.io.Buffers; +import unknow.server.util.io.BuffersUtils; + +public class Http11Worker extends HttpWorker { + private static final Logger logger = LoggerFactory.getLogger(Http11Worker.class); + + private static final byte[] END = new byte[] { '\r', '\n', '\r', '\n' }; + + private static final byte[] CRLF = { '\r', '\n' }; + private static final byte[] PARAM_SEP = { '&', '=' }; + private static final byte[] SPACE_SLASH = { ' ', '/' }; + private static final byte[] QUOTE = new byte[] { '\\', '"' }; + private static final byte[] CHUNKED = new byte[] { 't', 'r', 'a', 'n', 's', 'f', 'e', 'r', '-', 'e', 'n', 'c', 'o', 'd', 'i', 'n', 'g', ':', ' ', 'c', 'h', 'u', 'n', 'k', + 'e', 'd', '\r', '\n' }; + private static final byte[] CONTENT_LENGTH = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h', ':', ' ' }; + private static final byte[] CONTENT_LENGTH0 = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h', ':', ' ', '0', '\r', '\n' }; + private static final byte[] CONTENT_TYPE = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 't', 'y', 'p', 'e', ':', ' ' }; + private static final byte[] CONTENT_HTML = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 't', 'y', 'p', 'e', ':', ' ', 't', 'e', 'x', 't', '/', 'h', 't', 'm', 'l', + ';', 'c', 'h', 'a', 'r', 's', 'e', 't', '=', 'u', 't', 'f', '8', '\r', '\n' }; + private static final byte[] COOKIE = new byte[] { 's', 'e', 't', '-', 'c', 'o', 'o', 'k', 'i', 'e', ':', ' ' }; + private static final byte[] PATH = new byte[] { ';', 'p', 'a', 't', 'h', '=' }; + private static final byte[] DOMAIN = new byte[] { ';', 'd', 'o', 'm', 'a', 'i', 'n', '=' }; + private static final byte[] MAX_AGE = new byte[] { ';', 'm', 'a', 'x', '-', 'a', 'g', 'e' }; + private static final byte[] SECURE = new byte[] { ';', 's', 'e', 'c', 'u', 'r', 'e' }; + private static final byte[] HTTP_ONLY = new byte[] { ';', 'h', 't', 't', 'p', 'o', 'n', 'l', 'y' }; + private static final byte[] ERROR_START = new byte[] { '<', 'h', 't', 'm', 'l', '>', '<', 'b', 'o', 'd', 'y', '>', '<', 'h', '1', '>' }; + private static final byte[] ERROR_END = new byte[] { '<', '/', 'h', '1', '>', '<', '/', 'b', 'o', 'd', 'y', '>', '<', '/', 'h', 't', 'm', 'l', '>' }; + + private static final byte SPACE = ' '; + private static final byte QUESTION = '?'; + private static final byte COLON = ':'; + private static final byte SEMICOLON = ';'; + private static final byte SLASH = '/'; + private static final byte AMPERSAMP = '&'; + private static final byte EQUAL = '='; + + private static final String UNKNOWN = "Unknown"; + + private static final int MAX_METHOD_SIZE = 10; // max size for method + private static final int MAX_PATH_SIZE = 2000; + private static final int MAX_VERSION_SIZE = 12; + private static final int MAX_HEADER_SIZE = 512; + + private final int keepAliveIdle; + private final StringBuilder sb; + private final Decode decode; + + public Http11Worker(HttpConnection co) { + super(co); + this.keepAliveIdle = co.getkeepAlive(); + + sb = new StringBuilder(); + decode = new Decode(sb); + } + + @Override + public InetSocketAddress getRemote() { + return co.getRemote(); + } + + @Override + public InetSocketAddress getLocal() { + return co.getLocal(); + } + + @SuppressWarnings("resource") + @Override + public ServletInputStream createInput() { + String tr = req.getHeader("transfer-encoding"); + if ("chunked".equalsIgnoreCase(tr)) + return new ChunckedInputStream(co.getIn()); + long l = req.getContentLengthLong(); + if (l > 0) + return new LengthInputStream(co.getIn(), l); + return EmptyInputStream.INSTANCE; + } + + @SuppressWarnings("resource") + @Override + public AbstractServletOutput createOutput() { + long contentLength = res.getContentLength(); + if (contentLength < 0) + return new ChunckedOutputStream(co.getOut(), res); + if (contentLength == 0) + return EmptyStream.INSTANCE; + return new LengthOutputStream(co.getOut(), res, contentLength); + } + + @Override + public void commit() throws IOException { + HttpError http = HttpError.fromStatus(res.getStatus()); + @SuppressWarnings("resource") + Out out = co.getOut(); + out.write(http == null ? HttpError.encodeStatusLine(res.getStatus(), UNKNOWN) : http.encoded); + + for (String s : res.getHeaderNames()) + writeHeader(out, s, res.getHeaders(s)); + + if (res.getHeader("content-type") == null && res.getContentType() != null) { + out.write(CONTENT_TYPE); + writeString(out, res.getContentType()); + } + + @SuppressWarnings("resource") + AbstractServletOutput rawStream = res.getRawStream(); + if (rawStream instanceof LengthOutputStream) { + out.write(CONTENT_LENGTH); + out.write(Long.toString(res.getContentLength()).getBytes(StandardCharsets.US_ASCII)); + out.write(CRLF); + } + if (rawStream instanceof ChunckedOutputStream) + out.write(CHUNKED); + else + out.write(CONTENT_LENGTH0); + + for (Cookie c : res.getCookies()) + writeCookie(out, c); + out.write(CRLF); + } + + @SuppressWarnings("resource") + @Override + public void sendError(HttpError e, Throwable t, String msg) throws IOException { + Out out = co.getOut(); + if (msg == null) { + out.write(e.empty()); + return; + } + + out.write(e.encoded); + byte[] bytes = msg.getBytes(StandardCharsets.UTF_8); + + out.write(CONTENT_HTML); + out.write(CONTENT_LENGTH); + out.write(Integer.toString(bytes.length + ERROR_START.length + ERROR_END.length).getBytes(StandardCharsets.US_ASCII)); + out.write(CRLF); + out.write(CRLF); + out.write(ERROR_START); + out.write(bytes); + out.write(ERROR_END); + } + + @SuppressWarnings("resource") + @Override + public final void doStart() throws IOException, InterruptedException { + boolean close = false; + + Out out = co.getOut(); + if (!fillRequest(req)) + return; + + if ("100-continue".equals(req.getHeader("expect"))) { + out.write(HttpError.CONTINUE.encoded); + out.write('\r'); + out.write('\n'); + out.flush(); + } + + close = keepAliveIdle == 0 || !"keep-alive".equals(req.getHeader("connection")); + if (!close) + res.setHeader("connection", "keep-alive"); + } + + @Override + protected void doDone() { + } + + private boolean fillRequest(ServletRequestImpl req) throws InterruptedException, IOException { + Buffers b = co.pendingRead; + int i = BuffersUtils.indexOf(b, SPACE_SLASH, 0, MAX_METHOD_SIZE); + if (i < 0) { + res.sendError(HttpError.BAD_REQUEST.code, null, null); + return false; + } + BuffersUtils.toString(sb, b, 0, i); + req.setMethod(sb.toString()); + sb.setLength(0); + int last = i + 1; + + i = BuffersUtils.indexOf(b, SPACE, last, MAX_PATH_SIZE); + if (i < 0) { + res.sendError(HttpError.URI_TOO_LONG.code, null, null); + return false; + } + int q = BuffersUtils.indexOf(b, QUESTION, last, i - last); + if (q < 0) + q = i; + + b.walk(decode, last, q - last); + if (!decode.done()) + return false; + req.setRequestUri(sb.toString()); + sb.setLength(0); + + int s; + while ((s = BuffersUtils.indexOf(b, SLASH, last + 1, q - last - 1)) > 0) { + int c = BuffersUtils.indexOf(b, SEMICOLON, last + 1, s - last - 1); + b.walk(decode, last + 1, (c < 0 ? s : c) - last - 1); + if (!decode.done()) + return false; + req.addPath(sb.toString()); + sb.setLength(0); + last = s; + } + if (s == -2 && last + 1 < q) { + int c = BuffersUtils.indexOf(b, SEMICOLON, last + 1, q - last - 1); + BuffersUtils.toString(sb, b, last + 1, c < 0 ? q - last - 1 : c); + req.addPath(sb.toString()); + sb.setLength(0); + } + + if (q < i) { + BuffersUtils.toString(sb, b, q + 1, i - q - 1); + req.setQuery(sb.toString()); + sb.setLength(0); + } else + req.setQuery(""); + + Map> map = new HashMap<>(); + parseParam(map, b, q + 1, i); + req.setQueryParam(map); + last = i + 1; + + i = BuffersUtils.indexOf(b, CRLF, last, MAX_VERSION_SIZE); + if (i < 0) { + res.sendError(HttpError.BAD_REQUEST.code, null, null); + return false; + } + BuffersUtils.toString(sb, b, last, i - last); + req.setProtocol(sb.toString()); + sb.setLength(0); + last = i + 2; + + map = new HashMap<>(); + while ((i = BuffersUtils.indexOf(b, CRLF, last, MAX_HEADER_SIZE)) > last) { + int c = BuffersUtils.indexOf(b, COLON, last, i - last); + if (c < 0) { + res.sendError(HttpError.BAD_REQUEST.code, null, null); + return false; + } + + BuffersUtils.toString(sb, b, last, c - last); + String k = sb.toString().trim().toLowerCase(); + sb.setLength(0); + + BuffersUtils.toString(sb, b, c + 1, i - c - 1); + String v = sb.toString().trim(); + sb.setLength(0); + + List list = map.get(k); + if (list == null) + map.put(k, list = new ArrayList<>(1)); + list.add(v); + + last = i + 2; + } + req.setHeaders(map); + b.skip(last + 2); + return true; + } + + private boolean parseParam(Map> map, Buffers data, int o, int e) throws InterruptedException { + while (o < e) { + int i = BuffersUtils.indexOfOne(data, PARAM_SEP, o, e - o); + if (i < 0) + i = e; + data.walk(decode, o, i - o); + if (!decode.done()) + return false; + String key = sb.toString(); + sb.setLength(0); + + o = i + 1; + if (i < e && data.get(i) == EQUAL) { + i = BuffersUtils.indexOf(data, AMPERSAMP, o, e - o); + if (i < 0) + i = e; + data.walk(decode, o, i - o); + if (!decode.done()) + return false; + o = i + 1; + } + map.computeIfAbsent(key, k -> new ArrayList<>(1)).add(sb.toString()); + sb.setLength(0); + } + return true; + } + + /** + * check if this string contain a special characters as a header value + * + * @param s the string to check + * @return true is we should quote in the response + */ + private static boolean shouldEscape(String s) { + int l = s.length(); + for (int i = 0; i < l; i++) { + char c = s.charAt(i); + if (c == ' ' || c == '(' || c == ')' || c == '<' || c == '>' || c == '@' || c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' || c == '/' || c == '[' + || c == ']' || c == '?' || c == '=' || c == '{' || c == '}') + return true; + } + return false; + } + + private static void writeHeader(Out out, String name, Collection values) throws IOException { + out.write(name.getBytes(StandardCharsets.US_ASCII)); + out.write(':'); + for (String s : values) { + out.write(' '); + writeString(out, s); + } + out.write(CRLF); + } + + private static void writeCookie(Out out, Cookie c) throws IOException { + out.write(COOKIE); + out.write(c.getName().getBytes(StandardCharsets.US_ASCII)); + out.write('='); + out.write(c.getValue().getBytes(StandardCharsets.US_ASCII)); + if (c.getPath() != null) { + out.write(PATH); + writeString(out, c.getPath()); + } + if (c.getDomain() != null) { + out.write(DOMAIN); + writeString(out, c.getDomain()); + } + if (c.getMaxAge() > 0) { + out.write(MAX_AGE); + writeString(out, Integer.toString(c.getMaxAge())); + } + if (c.getSecure()) + out.write(SECURE); + if (c.isHttpOnly()) + out.write(HTTP_ONLY); + } + + private static void writeString(Out out, String s) throws IOException { + boolean escape = shouldEscape(s); + if (escape) + out.write('"'); + int l = 0; + int i; + while ((i = s.indexOf('"', l)) != -1) { + out.write(s.substring(l, i).getBytes(StandardCharsets.US_ASCII)); + out.write(QUOTE); + l = i + 1; + } + out.write(s.substring(l).getBytes(StandardCharsets.US_ASCII)); + if (escape) + out.write('"'); + } +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java new file mode 100644 index 00000000..23fb2dd3 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java @@ -0,0 +1,74 @@ +/** + * + */ +package unknow.server.servlet.http11; + +import java.io.IOException; +import java.io.OutputStream; + +import unknow.server.servlet.impl.ServletResponseImpl; +import unknow.server.servlet.impl.out.AbstractServletOutput; + +/** + * http with a Content-Length (can be -1) + * + * @author unknow + */ +public class LengthOutputStream extends AbstractServletOutput { + private final OutputStream out; + private long length; + + /** + * create new LengthOutputStream + * + * @param out the real ouptut + * @param res the servlet response + * @param length the length + */ + public LengthOutputStream(OutputStream out, ServletResponseImpl res, long length) { + super(res); + this.out = out; + this.length = length; + } + + @Override + public void afterClose() throws IOException { + if (length > 0) { + byte[] b = new byte[4096]; + while (length > 0) { + int l = (int) Math.min(length, 4096); + out.write(b, 0, l); + length -= l; + } + } + } + + @Override + public void write(int b) throws IOException { + if (--length <= 0) + throw new IOException("Extraneous data"); + super.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (len > length) + throw new IOException("Extraneous data"); + length -= len; + super.write(b, off, len); + } + + @Override + public void flush() throws IOException { + try { + buffer.read(out, -1, false); + } catch (@SuppressWarnings("unused") InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2FlowControl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2FlowControl.java new file mode 100644 index 00000000..a8da7ea2 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2FlowControl.java @@ -0,0 +1,15 @@ +package unknow.server.servlet.http2; + +public abstract class Http2FlowControl { + private int window; + + protected Http2FlowControl(int window) { + this.window = window; + } + + public void add(int v) { + window += v; + if (window < 0) + window = Integer.MAX_VALUE; + } +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java new file mode 100644 index 00000000..1e530924 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java @@ -0,0 +1,282 @@ +package unknow.server.servlet.http2; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.Objects; +import java.util.function.BiConsumer; + +import unknow.server.util.io.Buffers; +import unknow.server.util.io.BuffersUtils; + +/** + * Manage header encoded in HPACK + * implements https://httpwg.org/specs/rfc7541.html + */ +public class Http2Headers { + protected static final Entry[] TABLE = new Entry[] { //@formatter:off + new Entry(":authority", null), + new Entry(":method", "GET"), + new Entry(":method", "POST"), + new Entry(":path", "/"), + new Entry(":path", "/index.html"), + new Entry(":scheme", "http"), + new Entry(":scheme", "https"), + new Entry(":status", "200"), + new Entry(":status", "204"), + new Entry(":status", "206"), + new Entry(":status", "304"), + new Entry(":status", "400"), + new Entry(":status", "404"), + new Entry(":status", "500"), + new Entry("accept-charset", null), + new Entry("accept-encoding", "gzip,deflate"), + new Entry("accept-language", null), + new Entry("accept-ranges", null), + new Entry("accept", null), + new Entry("access-control-allow-origin", null), + new Entry("age", null), + new Entry("allow", null), + new Entry("authorization", null), + new Entry("cache-control", null), + new Entry("content-disposition", null), + new Entry("content-encoding", null), + new Entry("content-language", null), + new Entry("content-length", null), + new Entry("content-location", null), + new Entry("content-range", null), + new Entry("content-type", null), + new Entry("cookie", null), + new Entry("date", null), + new Entry("etag", null), + new Entry("expect", null), + new Entry("expires", null), + new Entry("from", null), + new Entry("host", null), + new Entry("if-match", null), + new Entry("if-modified-since", null), + new Entry("if-none-match", null), + new Entry("if-range", null), + new Entry("if-unmodified-since", null), + new Entry("last-modified", null), + new Entry("link", null), + new Entry("location", null), + new Entry("max-forwards", null), + new Entry("proxy-authenticate", null), + new Entry("proxy-authorization", null), + new Entry("range", null), + new Entry("referer", null), + new Entry("refresh", null), + new Entry("retry-after", null), + new Entry("server", null), + new Entry("set-cookie", null), + new Entry("strict-transport-security", null), + new Entry("transfer-encoding", null), + new Entry("user-agent", null), + new Entry("vary", null), + new Entry("via", null), + new Entry("www-authenticate", null) + };//@formatter:on + protected final LinkedList dynamic; + + private int settingsSize; + private int size; + private int max; + + public Http2Headers(int maxSize) { + this.settingsSize = maxSize; + this.max = settingsSize; + + this.dynamic = new LinkedList<>(); + this.size = 0; + } + + public void readHeaders(Buffers b, BiConsumer h) throws InterruptedException, IOException { + while (!b.isEmpty()) + readHeader(b, h); + } + + private void readHeader(Buffers b, BiConsumer h) throws InterruptedException, IOException { + int i = b.read(false); + if (i == -1) + throw new IOException("EOF"); + + if ((i & 0b10000000) != 0) { // indexed + Entry e = get(readInt(b, i, 7)); + h.accept(e.name(), e.value()); + } else if ((i & 0b01000000) != 0) { // literal indexed + i = readInt(b, i, 6); + String n; + if (i == 0) { // new name + n = readData(b); + } else + n = get(i).name; + String v = readData(b); + + h.accept(n, v); + add(new Entry(n, v)); + } else if ((i & 0b00100000) != 0) { // update size + i = readInt(b, i, 5); + if (i > settingsSize) + ; // TODO error + max = i; + ensureMax(); + } else { // literal non indexed + i = readInt(b, i, 4); + String n; + if (i == 0) { // new name + n = readData(b); + } else + n = get(i).name; + String v = readData(b); + h.accept(n, v); + } + } + + public void setMax(int size) { + this.settingsSize = size; + this.max = Math.min(size, max); + } + + private String readData(Buffers b) throws InterruptedException, IOException { + int i = b.read(false); + if (i == -1) + throw new IOException("EOF"); + boolean huffman = (i & 0x80) == 0x80; + i = readInt(b, i, 7); + + StringBuilder sb = new StringBuilder(); + if (!huffman) { + BuffersUtils.toString(sb, b, 0, i); + b.skip(i); + } else + Http2Huffman.decode(b, i, sb); +// return new EntryData(sb.toString(), i); + return sb.toString(); + } + + protected Entry get(int i) { + if (--i < TABLE.length) + return TABLE[i]; + return dynamic.get(i - TABLE.length); + } + + protected void add(Entry e) { + size += e.size(); + dynamic.addFirst(e); + ensureMax(); + } + + protected void ensureMax() { + Entry e; + while (size > max && (e = dynamic.pollLast()) != null) + size -= e.size(); + } + + /** + * read an int + * @param b the data + * @param i the first byte + * @param n the prefix + * @return + * @throws InterruptedException + * @throws IOException + */ + public static int readInt(Buffers b, int i, int n) throws InterruptedException, IOException { + i = i & MASK[n]; + if (i == MAX[n]) { + int m = 0; + int j; + do { + j = b.read(false); + if (j == -1) + throw new IOException("EOF"); + i += (j & 0x7F) << m; + m += 7; + } while ((j & 0x10) == 0x10); + } + return i; + } + + private static final int[] MASK = { 0b00000000, 0b00000001, 0b00000011, 0b00000111, 0b00001111, 0b00011111, 0b00111111, 0b01111111, 0b11111111 }; + private static final int[] MAX = { 0, 1, 3, 7, 15, 31, 63, 127, 255 }; + + public static class Entry { + final String name; + final String value; + + public Entry(String name, String value) { + this.name = name; + this.value = value; + } + +// public Entry(String name, String value) { +// this(new EntryData(name, name.length()), value == null ? null : new EntryData(value, value.length())); +// } + + public int size() { + return (value == null ? name.length() : name.length() + value.length()) + 32; + } + + public String name() { + return name; + } + + public String value() { + return value == null ? null : value; + } + + @Override + public String toString() { + return name + ": " + value; + } + + @Override + public int hashCode() { + return Objects.hash(name, value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Entry other = (Entry) obj; + return Objects.equals(name, other.name) && Objects.equals(value, other.value); + } + } + + public static class EntryData { + final String v; + final int l; + + public EntryData(String text, int len) { + this.v = text; + this.l = len; + } + + @Override + public String toString() { + return v + "(" + l + ")"; + } + + @Override + public int hashCode() { + return Objects.hash(l, v); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EntryData other = (EntryData) obj; + return l == other.l && Objects.equals(v, other.v); + } + } +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java new file mode 100644 index 00000000..bd368165 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java @@ -0,0 +1,129 @@ +package unknow.server.servlet.http2; + +import java.io.IOException; + +import unknow.server.util.io.Buffers; + +/** + * HPACK static huffman table + * https://httpwg.org/specs/rfc7541.html#huffman.code + * https://github.com/madler/zlib/blob/master/contrib/puff/puff.c + */ +public class Http2Huffman { + private static final int MINBITS = 5; + private static final int MAXBITS = 30; + + private static final short[] counts = { 0, 0, 0, 0, 0, 10, 26, 32, 6, 0, 5, 3, 2, 6, 2, 3, 0, 0, 0, 3, 8, 13, 26, 29, 12, 4, 15, 19, 29, 0, 4 }; + private static final char[] symbols = { 48, 49, 50, 97, 99, 101, 105, 111, 115, 116, // 5 + 32, 37, 45, 46, 47, 51, 52, 53, 54, 55, 56, 57, 61, 65, 95, 98, 100, 102, 103, 104, 108, 109, 110, 112, 114, 117, // 6 + 58, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 89, 106, 107, 113, 118, 119, 120, 121, 122, // 7 + 38, 42, 44, 59, 88, 90, // 8 + 33, 34, 40, 41, 63, // 10 + 39, 43, 124, // 11 + 35, 62, // 12 + 0, 36, 64, 91, 93, 126, // 13 + 94, 125, // 14 + 60, 96, 123, // 15 + 92, 195, 208, // 19 + 128, 130, 131, 162, 184, 194, 224, 226, // 20 + 153, 161, 167, 172, 176, 177, 179, 209, 216, 217, 227, 229, 230, // 21 + 129, 132, 133, 134, 136, 146, 154, 156, 160, 163, 164, 169, 170, 173, 178, 181, 185, 186, 187, 189, 190, 196, 198, 228, 232, 233, // 22 + 1, 135, 137, 138, 139, 140, 141, 143, 147, 149, 150, 151, 152, 155, 157, 158, 165, 166, 168, 174, 175, 180, 182, 183, 188, 191, 197, 231, 239, //23 + 9, 142, 144, 145, 148, 159, 171, 206, 215, 225, 236, 237, // 24 + 199, 207, 234, 235, // 25 + 192, 193, 200, 201, 202, 205, 210, 213, 218, 219, 238, 240, 242, 243, 255, // 26 + 203, 204, 211, 212, 214, 221, 222, 223, 241, 244, 245, 246, 247, 248, 250, 251, 252, 253, 254, // 27 + 2, 3, 4, 5, 6, 7, 8, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 31, 127, 220, 249, // 28 + 10, 13, 22, 256 // 29, 30 + }; + + private static int bits(S s, int need) throws InterruptedException, IOException { + int val = s.bit; + while (s.cnt < need) { + if (--s.max < 0) { + if (s.bit != (1 << s.cnt) - 1) + throw new IOException("EOF"); + return -1; + } + + val <<= 8; + val |= s.b.read(false); /* load eight bits */ + s.cnt += 8; + } + + int m = ((1 << need) - 1) << (s.cnt - need); + /* drop need bits and update buffer, always zero to seven bits left */ + s.cnt -= need; + s.bit = val & ((1 << s.cnt) - 1); + + /* return need bits, zeroing the bits above that */ + return (val & m) >> s.cnt; + } + + public static String decode(Buffers b, int max, StringBuilder sb) throws InterruptedException, IOException { + S s = new S(b, max); + char c; + while ((s.max > 0 || s.cnt > 0) && (c = read(s)) != 256) + sb.append(c); + return sb.toString(); + } + + private static final char read(S s) throws InterruptedException, IOException { + int first = 0; /* first code of length len */ + int index = 0; /* index of first code of length len in symbol table */ + + int len = MINBITS; + int code = bits(s, 5); + if (code == -1) + return 256; + while (true) { + int count = counts[len]; + if (code - count < first) /* if length len, return symbol */ + return symbols[index + (code - first)]; + + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + + if (++len > MAXBITS) + throw new IOException("Broken code"); + code |= bits(s, 1); /* get next bit */ + if (code == -1) + return 256; + } + } + + static final class S { + final Buffers b; + int max; + + int bit; + int cnt; + + public S(Buffers b, int max) { + this.b = b; + this.max = max; + } + } + + public static void main(String[] arg) throws Exception { +// System.out.println( + Buffers b = new Buffers(); + b.write(0xae); + b.write(0xc3); + b.write(0x77); + b.write(0x1a); + b.write(0x4b); + + b.walk((d, o, l) -> { + for (int i = 0; i < l; i++) + System.out.println(" " + Integer.toString(d[i + o] & 0xFF, 2)); + return true; + }, 0, -1); + + StringBuilder sb = new StringBuilder(); + decode(b, b.length(), sb); + System.out.println(sb); + } +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java new file mode 100644 index 00000000..a9f0580c --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -0,0 +1,422 @@ +//package unknow.server.servlet.http2; +// +//import java.io.IOException; +//import java.nio.charset.StandardCharsets; +// +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import unknow.server.servlet.HttpProcessor; +//import unknow.server.util.data.IntArrayMap; +//import unknow.server.util.io.Buffers; +//import unknow.server.util.io.BuffersUtils; +// +//public class Http2Processor extends Http2FlowControl implements HttpProcessor { +// private static final Logger logger = LoggerFactory.getLogger(Http2Processor.class); +// +// private static final byte[] PRI = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.US_ASCII); +// +// private static final int NO_ERROR = 0; +// private static final int PROTOCOL_ERROR = 1; +// private static final int INTERNAL_ERROR = 2; +// private static final int FLOW_CONTROL_ERROR = 3; +// private static final int SETTINGS_TIMEOUT = 4; +// private static final int STREAM_CLOSED = 5; +// private static final int FRAME_SIZE_ERROR = 6; +// private static final int REFUSED_STREAM = 7; +// private static final int CANCEL = 8; +// private static final int COMPRESSION_ERROR = 9; +// private static final int CONNECT_ERROR = 10; +// private static final int ENHANCE_YOUR_CALM = 12; +// private static final int INADEQUATE_SECURITY = 13; +// private static final int HTTP_1_1_REQUIRED = 14; +// +// private final HttpConnection co; +// private final IntArrayMap streams; +// private final Http2Headers headers; +// +// private boolean allowPush; +// private int concurrent; +// private int initialWindow; +// private int frame; +// private int headerList; +// private FrameReader r; +// +// private int lastId; +// +// public Http2Processor(HttpConnection co) throws InterruptedException { +// super(65535); +// this.co = co; +// this.streams = new IntArrayMap<>(); +// this.headers = new Http2Headers(4096); +// +// this.allowPush = true; +// this.concurrent = Integer.MAX_VALUE; +// this.initialWindow = 65535; +// this.frame = 16384; +// this.headerList = Integer.MAX_VALUE; +// byte[] b = new byte[9]; +// format(b, 0, 4, 0, 0); +// co.pendingWrite.write(b); +// co.flush(); +// } +// +// @Override +// public void process() throws InterruptedException { +// if (r != null) { +// r = r.process(co.pendingRead); +// if (r != null) +// return; +// } +// +// while (co.pendingRead.length() > 9 && r == null) +// readFrame(co.pendingRead); +// } +// +// @Override +// public boolean isClosed() { +// return false; +// } +// +// @Override +// public void close() { +// } +// +// private static final byte[] b = new byte[9]; +// +// /** +// * return true if the frame is done +// * @param buf +// * @return +// * @throws InterruptedException +// */ +// private void readFrame(Buffers buf) throws InterruptedException { +// buf.read(b, 0, 9, false); +// int size = (b[0] & 0xff) << 16 | (b[1] & 0xff) << 8 | (b[2] & 0xff); +// int type = b[3]; +// int flags = b[4]; +// int id = (b[5] & 0x7f) << 24 | (b[6] & 0xff) << 16 | (b[7] & 0xff) << 8 | (b[8] & 0xff); +// logger.debug("{} {} {} {}", size, type, flags, id); +// +// switch (type) { +// case 0: // data +// break; +// case 1: // header +// if (id == 0) { +// goaway(PROTOCOL_ERROR); +// return; +// } +// +// Http2Stream s = new Http2Stream(initialWindow, co); +// streams.set(id, s); +// +// try { +// Buffers b = new Buffers(); +// b.read(co.pendingRead, size, false); +// headers.readHeaders(co.pendingRead, (k, v) -> logger.debug(" {}: {}", k, v)); +// } catch (Exception e) { +// logger.error("Failed to parse headers", e); +// error(PROTOCOL_ERROR); +// } +// +// // TODO +// break; +// case 2: // priority +// break; +// case 3: // rst_stream +// co.pendingRead.read(b, 0, 4, false); +// int err = (b[0] & 0xff) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); +// logger.debug("close stream {}, err: {}", id, err); +// // TODO close stream id +// return; +// case 4: // settings +// if ((flags & 0x1) == 1) { +// if (size != 0) +// goaway(FRAME_SIZE_ERROR); +// logger.info("SETTINGS ACK"); +// return; +// } +// if (id != 0) { +// goaway(PROTOCOL_ERROR); +// } +// if (size % 6 != 0) { +// goaway(FRAME_SIZE_ERROR); +// return; +// } +// r = new FrameSettings(size, flags, id).process(buf); +// return; +// case 5: // push promise +// break; +// case 6: // ping +// if (id != 0) { +// goaway(PROTOCOL_ERROR); +// return; +// } +// if (size != 8) { +// goaway(FRAME_SIZE_ERROR); +// return; +// } +// if ((flags & 0x1) == 1) +// break; +// r = new FramePing(size, flags, id).process(buf); +// return; +// case 7: // go away +// if (id != 0) { +// goaway(PROTOCOL_ERROR); +// return; +// } +// // TODO +// return; +// case 8: // window update +// if (size != 4) { +// goaway(FRAME_SIZE_ERROR); +// return; +// } +// r = new FrameWindowUpdate(size, flags, id).process(buf); +// return; +// case 9: // continuation +// // id == 0 send PROTOCOL_ERROR +// // if previous frame != HEADERS, PUSH_PROMISE or CONTINUATION send PROTOCOL_ERROR +// default: +// } +// +// co.pendingRead.skip(size); +// } +// +// private void goaway(int err) throws InterruptedException { +// byte[] b = new byte[17]; +// format(b, 8, 7, 0, 0); +// b[9] = (byte) ((lastId >> 24) & 0x7f); +// b[10] = (byte) ((lastId >> 16) & 0xff); +// b[11] = (byte) ((lastId >> 8) & 0xff); +// b[12] = (byte) (lastId & 0xff); +// b[13] = (byte) ((err >> 24) & 0x7f); +// b[14] = (byte) ((err >> 16) & 0xff); +// b[15] = (byte) ((err >> 8) & 0xff); +// b[16] = (byte) (err & 0xff); +// co.pendingWrite.write(b); +// co.flush(); +// } +// +// private void format(byte[] b, int size, int type, int flags, int id) { +// b[0] = (byte) ((size >> 16) & 0xff); +// b[1] = (byte) ((size >> 8) & 0xff); +// b[2] = (byte) (size & 0xff); +// b[3] = (byte) (type & 0xff); +// b[4] = (byte) (flags & 0xff); +// b[5] = (byte) ((id >> 24) & 0x7f); +// b[6] = (byte) ((id >> 16) & 0xff); +// b[7] = (byte) ((id >> 8) & 0xff); +// b[8] = (byte) (id & 0xff); +// } +// +// private static String error(int err) { +// switch (err) { +// case 0: +// return "NO_ERROR"; +// case 1: +// return "PROTOCOL_ERROR"; +// case 2: +// return "INTERNAL_ERROR"; +// case 3: +// return "FLOW_CONTROL_ERROR"; +// case 4: +// return "SETTINGS_TIMEOUT"; +// case 5: +// return "STREAM_CLOSED"; +// case 6: +// return "FRAME_SIZE_ERROR"; +// case 7: +// return "REFUSED_STREAM"; +// case 8: +// return "CANCEL"; +// case 9: +// return "COMPRESSION_ERROR"; +// case 10: +// return "CONNECT_ERROR"; +// case 11: +// return "ENHANCE_YOUR_CALM"; +// case 12: +// return "INADEQUATE_SECURITY"; +// case 13: +// return "HTTP_1_1_REQUIRED"; +// default: +// return "unknown " + err; +// } +// } +// +// public static final HttpProcessorFactory Factory = co -> { +// if (BuffersUtils.startsWith(co.pendingRead, PRI, 0, PRI.length)) { +// co.pendingRead.skip(PRI.length); +// return new Http2Processor(co); +// } +// return null; +// }; +// +// private static abstract class FrameReader { +// protected int size; +// protected int flags; +// protected int id; +// +// protected FrameReader(int size, int flags, int id) { +// this.size = size; +// this.flags = flags; +// this.id = id; +// } +// +// /** +// * @param buf +// * @return this or null +// * @throws InterruptedException +// */ +// public abstract FrameReader process(Buffers buf) throws InterruptedException; +// } +// +// private class FrameSettings extends FrameReader { +// +// private final byte[] b; +// +// protected FrameSettings(int size, int flags, int id) { +// super(size, flags, id); +// b = new byte[6]; +// } +// +// @Override +// public final FrameReader process(Buffers buf) throws InterruptedException { +// while (size > 0 && buf.length() > 6) { +// buf.read(b, 0, 6, false); +// size -= 6; +// +// int i = (b[0] & 0xff) << 8 | (b[1] & 0xff); +// int v = (b[2] & 0x7f) << 24 | (b[3] & 0xff) << 16 | (b[4] & 0xff) << 8 | (b[5] & 0xff); +// +// switch (i) { +// case 1: +// logger.debug(" SETTINGS_HEADER_TABLE_SIZE {}", v); +// headers.setMax(v); +// break; +// case 2: +// logger.debug(" SETTINGS_ENABLE_PUSH {}", v); +// if (v < 0 || v > 1) +// ; // send PROTOCOL_ERROR +// allowPush = v == 1; +// break; +// case 3: +// logger.debug(" SETTINGS_MAX_CONCURRENT_STREAMS {}", v); +// concurrent = v; +// break; +// case 4: +// logger.debug(" SETTINGS_INITIAL_WINDOW_SIZE {}", v); +// initialWindow = v; +// break; +// case 5: +// logger.debug(" SETTINGS_MAX_FRAME_SIZE {}", v); +// if (v < 16384 || v > 16777215) +// ; // send PROTOCOL_ERROR +// frame = v; +// break; +// case 6: +// logger.debug(" SETTINGS_MAX_HEADER_LIST_SIZE {}", v); +// headerList = v; +// break; +// default: +// // ignore +// } +// } +// +// if (size != 0) +// return this; +// +// byte[] b = new byte[9]; +// format(b, 0, 4, 1, 0); +// co.pendingWrite.write(b); +// co.flush(); +// return null; +// } +// } +// +// private class FramePing extends FrameReader { +// +// private final byte[] b; +// +// protected FramePing(int size, int flags, int id) { +// super(size, flags, id); +// b = new byte[9 + 8]; +// +// } +// +// @Override +// public final FrameReader process(Buffers buf) throws InterruptedException { +// if (buf.length() < 8) +// return this; +// +// buf.read(b, 9, 8, false); +// format(b, 8, 8, 1, 0); +// co.pendingWrite.write(b); +// co.flush(); +// return null; +// } +// } +// +// private class FrameWindowUpdate extends FrameReader { +// private final byte[] b; +// +// protected FrameWindowUpdate(int size, int flags, int id) { +// super(size, flags, id); +// b = new byte[4]; +// } +// +// @Override +// public final FrameReader process(Buffers buf) throws InterruptedException { +// if (buf.length() < 4) +// return this; +// buf.read(b, 0, 4, false); +// int v = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); +// if (v == 0) { +// goaway(PROTOCOL_ERROR); +// return null; +// } +// +// logger.debug(" window update {}", v); +// +// Http2FlowControl f = id == 0 ? Http2Processor.this : streams.get(id); +// if (f != null) +// f.add(v); +// return null; +// } +// } +// +// private class FrameHeader extends FrameReader { +// private final byte[] b; +// private int pad = 0; +// +// protected FrameHeader(int size, int flags, int id) { +// super(size, flags, id); +// b = new byte[4]; +// } +// +// @Override +// public final FrameReader process(Buffers buf) throws InterruptedException { +// if ((flags & 0x1) != 0) // END_STREAM +// ; // END_STREAM +// if ((flags & 0x4) != 0) // END_HEADERS +// ; +// if ((flags & 0x8) != 0) { // PADDED +// if (co.pendingRead.length() < 1) +// return this; +// pad = (co.pendingRead.read(false) & 0xff); +// flags ^= 0x8; +// } +// +// if ((flags & 0x20) != 0) { // PRIORITY +// if (co.pendingRead.length() < 5) +// return this; +// // TODO priority +// co.pendingRead.skip(5); +// flags ^= 0x20; +// } +// +// return null; +// } +// } +//} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java new file mode 100644 index 00000000..384fbd5a --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java @@ -0,0 +1,9 @@ +package unknow.server.servlet.http2; + +public class Http2Stream extends Http2FlowControl { + + protected Http2Stream(int window) { + super(window); + } + +} diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/FilterChainImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterChainImpl.java similarity index 98% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/FilterChainImpl.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterChainImpl.java index e6ca393a..26509ffe 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/FilterChainImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterChainImpl.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet; +package unknow.server.servlet.impl; import java.io.IOException; diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/FilterConfigImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterConfigImpl.java similarity index 98% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/FilterConfigImpl.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterConfigImpl.java index 10334f32..2115f577 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/FilterConfigImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterConfigImpl.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet; +package unknow.server.servlet.impl; import java.util.Collection; import java.util.Collections; diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletConfigImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletConfigImpl.java similarity index 98% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/ServletConfigImpl.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletConfigImpl.java index 81ad6dee..7f0306d2 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletConfigImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletConfigImpl.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet; +package unknow.server.servlet.impl; import java.util.Collection; import java.util.Collections; diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletContextImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletContextImpl.java similarity index 98% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/ServletContextImpl.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletContextImpl.java index 9e5131c2..46c055b4 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletContextImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletContextImpl.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet; +package unknow.server.servlet.impl; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; @@ -27,9 +27,9 @@ import jakarta.servlet.SessionCookieConfig; import jakarta.servlet.SessionTrackingMode; import jakarta.servlet.descriptor.JspConfigDescriptor; -import unknow.server.http.servlet.session.SessionFactory; -import unknow.server.http.utils.EventManager; -import unknow.server.http.utils.ServletManager; +import unknow.server.servlet.impl.session.SessionFactory; +import unknow.server.servlet.utils.EventManager; +import unknow.server.servlet.utils.ServletManager; import unknow.server.util.data.ArrayMap; /** diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletCookieConfigImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletCookieConfigImpl.java similarity index 98% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/ServletCookieConfigImpl.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletCookieConfigImpl.java index 5cb0edd7..f27f2e15 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletCookieConfigImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletCookieConfigImpl.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet; +package unknow.server.servlet.impl; import java.util.Collections; import java.util.Map; diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletJsp.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletJsp.java similarity index 96% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/ServletJsp.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletJsp.java index 763f9d61..476105c1 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletJsp.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletJsp.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet; +package unknow.server.servlet.impl; import java.io.IOException; diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletRequestImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java similarity index 88% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/ServletRequestImpl.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java index bc773c68..a4bdacf9 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletRequestImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet; +package unknow.server.servlet.impl; import java.io.BufferedReader; import java.io.IOException; @@ -43,24 +43,21 @@ import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpUpgradeHandler; import jakarta.servlet.http.Part; -import unknow.server.http.HttpConnection; -import unknow.server.http.servlet.in.ChunckedInputStream; -import unknow.server.http.servlet.in.EmptyInputStream; -import unknow.server.http.servlet.in.LengthInputStream; -import unknow.server.http.servlet.session.SessionFactory; +import unknow.server.servlet.HttpAdapter; +import unknow.server.servlet.impl.session.SessionFactory; import unknow.server.util.data.ArrayMap; /** * @author unknow */ -public class ServletRequestImpl implements HttpServletRequest { +public final class ServletRequestImpl implements HttpServletRequest { private static final Logger logger = LoggerFactory.getLogger(ServletRequestImpl.class); private static final Cookie[] EMPTY = new Cookie[0]; private final ArrayMap attributes = new ArrayMap<>(); - private final HttpConnection p; + protected final HttpAdapter co; private final DispatcherType type; private String requestUri; @@ -98,11 +95,11 @@ public class ServletRequestImpl implements HttpServletRequest { /** * create new ServletRequestImpl * - * @param p the connection + * @param co the connection adapter * @param type dispatcher type of this request */ - public ServletRequestImpl(HttpConnection p, DispatcherType type) { - this.p = p; + public ServletRequestImpl(HttpAdapter co, DispatcherType type) { + this.co = co; this.type = type; this.path = new ArrayList<>(); @@ -168,20 +165,21 @@ private void parseParam() { * @throws IOException */ private void parseContentParam(Map> p) throws IOException { - BufferedReader r = getReader(); - int c; - String key; - StringBuilder sb = new StringBuilder(); - do { - c = readParam(sb, r, true); - key = sb.toString(); - sb.setLength(0); - if (c == '=') - c = readParam(sb, r, false); - - p.computeIfAbsent(key, k -> new ArrayList<>(1)).add(sb.toString()); - sb.setLength(0); - } while (c != -1); + try (BufferedReader r = getReader()) { + int c; + String key; + StringBuilder sb = new StringBuilder(); + do { + c = readParam(sb, r, true); + key = sb.toString(); + sb.setLength(0); + if (c == '=') + c = readParam(sb, r, false); + + p.computeIfAbsent(key, k -> new ArrayList<>(1)).add(sb.toString()); + sb.setLength(0); + } while (c != -1); + } } private int readParam(StringBuilder sb, Reader r, boolean key) throws IOException { @@ -223,7 +221,7 @@ public void removeAttribute(String name) { @Override public void setAttribute(String name, Object o) { Object old = attributes.put(name, o); - p.getCtx().getEvents().fireRequestAttribute(this, name, o, old); + co.ctx().getEvents().fireRequestAttribute(this, name, o, old); } @Override @@ -240,7 +238,7 @@ public String getCharacterEncoding() { } } if (encoding == null) - encoding = p.getCtx().getRequestCharacterEncoding(); + encoding = co.ctx().getRequestCharacterEncoding(); } return encoding; } @@ -336,7 +334,7 @@ public ServletInputStream getInputStream() throws IOException { if (reader != null) throw new IllegalStateException("getReader() called"); if (input == null) - input = createInput(); + input = co.createInput(); return input; } @@ -345,7 +343,7 @@ public BufferedReader getReader() throws IOException { if (input != null) throw new IllegalStateException("getInputStream() called"); if (reader == null) - reader = new BufferedReader(new InputStreamReader(createInput(), getCharacterEncoding())); + reader = new BufferedReader(new InputStreamReader(co.createInput(), getCharacterEncoding())); return reader; } @@ -400,7 +398,7 @@ public String getProtocol() { @Override public String getServerName() { - return p.getCtx().getVirtualServerName(); + return co.ctx().getVirtualServerName(); } @Override @@ -422,35 +420,35 @@ public RequestDispatcher getRequestDispatcher(String path) { @Override public String getRemoteAddr() { if (remoteAddr == null) - remoteAddr = getAddr(p.getRemote()); + remoteAddr = getAddr(co.getRemote()); return remoteAddr; } @Override public String getRemoteHost() { - return p.getRemote().getHostString(); + return co.getRemote().getHostString(); } @Override public int getRemotePort() { - return p.getRemote().getPort(); + return co.getRemote().getPort(); } @Override public String getLocalName() { - return p.getLocal().getHostString(); + return co.getLocal().getHostString(); } @Override public String getLocalAddr() { if (localAddr == null) - localAddr = getAddr(p.getLocal()); + localAddr = getAddr(co.getLocal()); return localAddr; } @Override public int getLocalPort() { - return p.getLocal().getPort(); + return co.getLocal().getPort(); } @Override @@ -565,8 +563,8 @@ public String getRequestedSessionId() { return sessionFromCookie; if (sessionFromUrl != null) return sessionFromUrl; - p.getCtx().getEffectiveSessionTrackingModes(); // TODO manage other session traking mode - SessionCookieConfig cookieCfg = p.getCtx().getSessionCookieConfig(); + co.ctx().getEffectiveSessionTrackingModes(); // TODO manage other session traking mode + SessionCookieConfig cookieCfg = co.ctx().getSessionCookieConfig(); if (cookieCfg != null) { String name = cookieCfg.getName(); Cookie[] c = getCookies(); @@ -584,7 +582,7 @@ public HttpSession getSession(boolean create) { if (session != null) return session; String sessionId = getRequestedSessionId(); - SessionFactory sessionFactory = p.getCtx().getSessionFactory(); + SessionFactory sessionFactory = co.ctx().getSessionFactory(); // ctx.getSessionCookieConfig(). if (sessionId == null && create) { sessionId = sessionFactory.generateId(); @@ -602,7 +600,7 @@ public HttpSession getSession() { public String changeSessionId() { if (session == null) throw new IllegalStateException("no session"); - SessionFactory sessionFactory = p.getCtx().getSessionFactory(); + SessionFactory sessionFactory = co.ctx().getSessionFactory(); String newId = sessionFactory.generateId(); sessionFactory.changeId(session, newId); return newId; @@ -665,18 +663,18 @@ public T upgrade(Class handlerClass) throws IO @Override public ServletContext getServletContext() { - return p.getCtx(); - } - - private ServletInputStream createInput() { - String tr = getHeader("transfer-encoding"); - if ("chunked".equalsIgnoreCase(tr)) - return new ChunckedInputStream(p.getIn()); - long l = getContentLengthLong(); - if (l > 0) - return new LengthInputStream(p.getIn(), l); - return EmptyInputStream.INSTANCE; - } + return co.ctx(); + } + +// private ServletInputStream createInput() { +// String tr = getHeader("transfer-encoding"); +// if ("chunked".equalsIgnoreCase(tr)) +// return new ChunckedInputStream(co.getIn()); +// long l = getContentLengthLong(); +// if (l > 0) +// return new LengthInputStream(co.getIn(), l); +// return EmptyInputStream.INSTANCE; +// } private static String getAddr(InetSocketAddress a) { if (a == null) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java new file mode 100644 index 00000000..ce84b78c --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java @@ -0,0 +1,362 @@ +/** + * + */ +package unknow.server.servlet.impl; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.nio.charset.Charset; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; +import unknow.server.servlet.HttpAdapter; +import unknow.server.servlet.HttpError; +import unknow.server.servlet.impl.out.AbstractServletOutput; +import unknow.server.servlet.utils.ServletManager; + +/** + * @author unknow + */ +public class ServletResponseImpl implements HttpServletResponse { + private static final Logger logger = LoggerFactory.getLogger(ServletResponseImpl.class); + + private static final DateTimeFormatter RFC1123 = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC); + + private final HttpAdapter co; + private AbstractServletOutput stream; + private PrintWriter writer; + + private boolean commited = false; + + private int status; + private Charset charset; + private String type; + private final Map> headers; + private final List cookies; + + private long contentLength = -1; + private int bufferSize = -1; + + // Content-Language + private Locale locale = null; + + /** + * create new ServletResponseImpl + * + * @param co the connection + */ + public ServletResponseImpl(HttpAdapter co) { + this.co = co; + headers = new HashMap<>(); + cookies = new ArrayList<>(); + + status = 200; + } + + /** + * commit the response + * + * @throws IOException + */ + public final void commit() throws IOException { + if (commited) + return; + commited = true; + co.commit(); + } + + public void sendError(int sc, Throwable t, String msg) throws IOException { + checkCommited(); + status = sc; + ServletManager manager = co.ctx().getServletManager(); + FilterChain f = manager.getError(sc, t); + if (f != null) { + ServletRequestImpl r = new ServletRequestImpl(co, DispatcherType.ERROR); + r.setMethod("GET"); + r.setAttribute("javax.servlet.error.status_code", sc); + if (t != null) { + r.setAttribute("javax.servlet.error.exception_type", t.getClass()); + r.setAttribute("javax.servlet.error.message", t.getMessage()); + r.setAttribute("javax.servlet.error.exception", t); + } + r.setAttribute("javax.servlet.error.request_uri", r.getRequestURI()); + r.setAttribute("javax.servlet.error.servlet_name", ""); + reset(); + try { + f.doFilter(r, this); + return; + } catch (ServletException e) { + logger.error("failed to send error", e); + } + } + co.sendError(HttpError.fromStatus(sc), t, msg); + } + + public void close() throws IOException { + commit(); + if (writer != null) + writer.close(); + if (stream != null) + stream.close(); + } + + public void checkCommited() { + if (commited) + throw new IllegalStateException("already commited"); + } + + public AbstractServletOutput getRawStream() { + return stream; + } + + @Override + public String getCharacterEncoding() { + return charset == null ? co.ctx().getResponseCharacterEncoding() : charset.name(); + } + + @Override + public void setCharacterEncoding(String charset) { + this.charset = Charset.forName(charset); + } + + @Override + public void setContentType(String type) { + int i = type.indexOf("charset="); + if (i > 0) { + String encoding = type.substring(i + 8); + i = encoding.indexOf(' '); + if (i > 0) + encoding = encoding.substring(0, i); + i = encoding.indexOf(';'); + if (i > 0) + encoding = encoding.substring(0, i); + setCharacterEncoding(encoding); + } + this.type = type; + } + + @Override + public String getContentType() { + if (!type.contains("charset=")) + type += "; charset=" + getCharacterEncoding(); + return type; + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + if (writer != null) + throw new IllegalStateException("output already got created with getWriter()"); + if (stream == null) + stream = co.createOutput(); + return stream; + } + + @SuppressWarnings({ "rawtypes", "unchecked", "resource" }) + @Override + public PrintWriter getWriter() throws IOException { + if (writer != null) + return writer; + if (stream != null) + throw new IllegalStateException("output already got created with getWriter()"); + stream = co.createOutput(); + return writer = new PrintWriter(new OutputStreamWriter(stream, charset == null ? Charset.forName(co.ctx().getResponseCharacterEncoding()) : charset), false); + } + + public long getContentLength() { + return contentLength; + } + + @Override + public void setContentLength(int len) { + contentLength = len; + } + + @Override + public void setContentLengthLong(long len) { + contentLength = len; + } + + @Override + public void setBufferSize(int size) { + checkCommited(); + bufferSize = size; + if (stream != null) + stream.setBufferSize(size); + } + + @Override + public int getBufferSize() { + return stream == null ? bufferSize : stream.getBufferSize(); + } + + @Override + public void flushBuffer() throws IOException { + commit(); + if (stream != null) + stream.flush(); + } + + @Override + public void resetBuffer() { + checkCommited(); + if (stream != null) + stream.resetBuffers(); + } + + @Override + public void reset() { + resetBuffer(); + status = 200; + List list = headers.get("connection"); + headers.clear(); + if (list != null) + headers.put("connection", list); + stream = null; + writer = null; + } + + @Override + public final boolean isCommitted() { + return commited; + } + + @Override + public void setLocale(Locale loc) { + if (charset == null) + setCharacterEncoding(co.ctx().getEncoding(loc)); + this.locale = loc; + } + + @Override + public Locale getLocale() { + return locale; + } + + public Collection getCookies() { + return cookies; + } + + @Override + public void addCookie(Cookie cookie) { + cookies.add(cookie); + } + + @Override + public String encodeURL(String url) { + return url; + } + + @Override + public String encodeRedirectURL(String url) { + return url; + } + + @Override + public void sendError(int sc, String msg) throws IOException { + sendError(sc, null, msg); + } + + @Override + public void sendError(int sc) throws IOException { + sendError(sc, null, null); + } + + @Override + public void sendRedirect(String location) throws IOException { + checkCommited(); + reset(); + commited = true; + status = HttpError.FOUND.code; + headers.computeIfAbsent("location", k -> new ArrayList<>(1)).add(location); + commit(); + } + + @Override + public String getHeader(String name) { + List list = headers.get(name.toLowerCase()); + return list == null ? null : list.get(0); + } + + @Override + public Collection getHeaders(String name) { + return headers.get(name.toLowerCase()); + } + + @Override + public Collection getHeaderNames() { + return headers.keySet(); + } + + @Override + public boolean containsHeader(String name) { + return headers.containsKey(name.toLowerCase()); + } + + @Override + public void setHeader(String name, String value) { + name = name.toLowerCase(); + if ("content-type".equals(name)) + setContentType(value); + List list = headers.get(name); + if (list == null) + headers.put(name, list = new ArrayList<>(1)); + else + list.clear(); + list.add(value); + } + + @Override + public void addHeader(String name, String value) { + name = name.toLowerCase(); + if ("content-type".equals(name)) + setContentType(value); + headers.computeIfAbsent(name, k -> new ArrayList<>(1)).add(value); + } + + @Override + public void setDateHeader(String name, long date) { + setHeader(name, RFC1123.format(Instant.ofEpochMilli(date))); + } + + @Override + public void addDateHeader(String name, long date) { + addHeader(name, RFC1123.format(Instant.ofEpochMilli(date))); + } + + @Override + public void setIntHeader(String name, int value) { + setHeader(name, Integer.toString(value)); + } + + @Override + public void addIntHeader(String name, int value) { + addHeader(name, Integer.toString(value)); + } + + @Override + public void setStatus(int sc) { + this.status = sc; + } + + @Override + public int getStatus() { + return status; + } +} \ No newline at end of file diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/in/ChunckedInputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/ChunckedInputStream.java similarity index 98% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/in/ChunckedInputStream.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/ChunckedInputStream.java index 47778d9b..c3ce563a 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/in/ChunckedInputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/ChunckedInputStream.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet.in; +package unknow.server.servlet.impl.in; import java.io.IOException; import java.io.InputStream; diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/in/EmptyInputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/EmptyInputStream.java similarity index 96% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/in/EmptyInputStream.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/EmptyInputStream.java index 65b4f994..9c7006ce 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/in/EmptyInputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/EmptyInputStream.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet.in; +package unknow.server.servlet.impl.in; import java.io.IOException; diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/in/LengthInputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/LengthInputStream.java similarity index 96% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/in/LengthInputStream.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/LengthInputStream.java index ee896f9b..e9338937 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/in/LengthInputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/LengthInputStream.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet.in; +package unknow.server.servlet.impl.in; import java.io.IOException; import java.io.InputStream; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java new file mode 100644 index 00000000..6eef2c30 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java @@ -0,0 +1,127 @@ +package unknow.server.servlet.impl.out; + +import java.io.IOException; + +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.WriteListener; +import unknow.server.servlet.impl.ServletResponseImpl; +import unknow.server.util.io.Buffers; + +/** + * abstract implementation of ServlerOutputStream + */ +public abstract class AbstractServletOutput extends ServletOutputStream { + /** the buffer */ + protected final Buffers buffer; + /** response taht created this stream */ + protected final ServletResponseImpl res; + private int bufferSize; + + private boolean closed; + + /** + * create a new AbstractServlet + * @param res the response + */ + protected AbstractServletOutput(ServletResponseImpl res) { + this.res = res; + if (res != null) { + this.buffer = new Buffers(); + setBufferSize(res.getBufferSize()); + } else { + this.buffer = null; + this.bufferSize = 0; + } + this.closed = false; + } + + /** + * clear the buffer + */ + public final void resetBuffers() { + buffer.clear(); + } + + /** + * @return the current buffer size + */ + public final int getBufferSize() { + return bufferSize; + } + + /** + * set the buffer size + * @param bufferSize the size + */ + public final void setBufferSize(int bufferSize) { + if (!buffer.isEmpty()) + throw new IllegalStateException("data already written"); + + int r = bufferSize % Buffers.BUF_LEN; // make size a multiple of chunk size + this.bufferSize = r == 0 ? bufferSize : bufferSize + Buffers.BUF_LEN - r; + } + + /** + * @throws IOException if the stream is closed + */ + protected final void ensureOpen() throws IOException { + if (closed) + throw new IOException("stream closed"); + } + + /** + * called after the stream is closed + * @throws IOException on error + */ + protected void afterClose() throws IOException { // for override + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) { // ok + } + + @Override + public void write(int b) throws IOException { + ensureOpen(); + try { + buffer.write(b); + } catch (@SuppressWarnings("unused") InterruptedException e) { + Thread.currentThread().interrupt(); + } + if (buffer.length() >= bufferSize) + flush(); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (len == 0) + return; + ensureOpen(); + try { + buffer.write(b, off, len); + } catch (@SuppressWarnings("unused") InterruptedException e) { + Thread.currentThread().interrupt(); + } + if (buffer.length() >= bufferSize) + flush(); + } + + @Override + public final void close() throws IOException { + if (closed) + return; + closed = true; + flush(); + afterClose(); + } +} diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/session/NoSessionFactory.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/session/NoSessionFactory.java similarity index 90% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/session/NoSessionFactory.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/session/NoSessionFactory.java index c6dc6f9b..7551f989 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/session/NoSessionFactory.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/session/NoSessionFactory.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet.session; +package unknow.server.servlet.impl.session; import jakarta.servlet.http.HttpSession; diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/session/SessionFactory.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/session/SessionFactory.java similarity index 94% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/session/SessionFactory.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/session/SessionFactory.java index ce6a119f..139b50f1 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/session/SessionFactory.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/session/SessionFactory.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet.session; +package unknow.server.servlet.impl.session; import jakarta.servlet.http.HttpSession; diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/session/SessionIdGenerator.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/session/SessionIdGenerator.java similarity index 96% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/session/SessionIdGenerator.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/session/SessionIdGenerator.java index 966c1f4e..42e27b0d 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/session/SessionIdGenerator.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/session/SessionIdGenerator.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet.session; +package unknow.server.servlet.impl.session; import java.util.concurrent.atomic.AtomicInteger; diff --git a/unknow-server-http/src/main/java/unknow/server/http/utils/Encoder.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Encoder.java similarity index 95% rename from unknow-server-http/src/main/java/unknow/server/http/utils/Encoder.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Encoder.java index 2b3ac4ea..2a249487 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/utils/Encoder.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Encoder.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.utils; +package unknow.server.servlet.utils; import java.nio.charset.StandardCharsets; diff --git a/unknow-server-http/src/main/java/unknow/server/http/utils/EventManager.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/EventManager.java similarity index 99% rename from unknow-server-http/src/main/java/unknow/server/http/utils/EventManager.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/utils/EventManager.java index e0b97f43..ab028f29 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/utils/EventManager.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/EventManager.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.utils; +package unknow.server.servlet.utils; import java.util.EventListener; import java.util.List; diff --git a/unknow-server-http/src/main/java/unknow/server/http/utils/PathTree.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathTree.java similarity index 98% rename from unknow-server-http/src/main/java/unknow/server/http/utils/PathTree.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathTree.java index ba80f0fd..f2fa03f4 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/utils/PathTree.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathTree.java @@ -1,13 +1,13 @@ /** * */ -package unknow.server.http.utils; +package unknow.server.servlet.utils; import java.nio.charset.StandardCharsets; import java.util.List; import jakarta.servlet.FilterChain; -import unknow.server.http.servlet.ServletRequestImpl; +import unknow.server.servlet.impl.ServletRequestImpl; import unknow.server.util.io.Buffers; /** diff --git a/unknow-server-http/src/main/java/unknow/server/http/utils/PathTreeBuilder.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathTreeBuilder.java similarity index 94% rename from unknow-server-http/src/main/java/unknow/server/http/utils/PathTreeBuilder.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathTreeBuilder.java index f1a254fb..13a55855 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/utils/PathTreeBuilder.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathTreeBuilder.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.utils; +package unknow.server.servlet.utils; import java.util.Arrays; import java.util.Collections; @@ -16,13 +16,12 @@ import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; -import unknow.server.http.servlet.FilterChainImpl; -import unknow.server.http.servlet.FilterChainImpl.ServletFilter; -import unknow.server.http.servlet.FilterConfigImpl; -import unknow.server.http.servlet.ServletConfigImpl; -import unknow.server.http.servlet.ServletContextImpl; -import unknow.server.http.servlet.ServletDefault; -import unknow.server.http.utils.PathTree.PartNode; +import unknow.server.servlet.impl.FilterChainImpl; +import unknow.server.servlet.impl.FilterConfigImpl; +import unknow.server.servlet.impl.ServletConfigImpl; +import unknow.server.servlet.impl.ServletContextImpl; +import unknow.server.servlet.impl.FilterChainImpl.ServletFilter; +import unknow.server.servlet.utils.PathTree.PartNode; import unknow.server.util.data.ArrayMap; import unknow.server.util.data.ArraySet; diff --git a/unknow-server-http/src/main/java/unknow/server/http/utils/Resource.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Resource.java similarity index 90% rename from unknow-server-http/src/main/java/unknow/server/http/utils/Resource.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Resource.java index eccce7e1..78579458 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/utils/Resource.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Resource.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.utils; +package unknow.server.servlet.utils; /** * @author unknow diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletDefault.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletDefault.java similarity index 95% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/ServletDefault.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletDefault.java index 6510c9a8..75806535 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletDefault.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletDefault.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet; +package unknow.server.servlet.utils; import java.io.IOException; diff --git a/unknow-server-http/src/main/java/unknow/server/http/utils/ServletManager.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletManager.java similarity index 91% rename from unknow-server-http/src/main/java/unknow/server/http/utils/ServletManager.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletManager.java index b161e3d4..ad44b9f5 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/utils/ServletManager.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletManager.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.utils; +package unknow.server.servlet.utils; import java.util.Comparator; import java.util.Map; @@ -14,13 +14,13 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.Servlet; import jakarta.servlet.ServletException; -import unknow.server.http.servlet.FilterChainImpl; -import unknow.server.http.servlet.FilterChainImpl.ChangePath; -import unknow.server.http.servlet.FilterChainImpl.ServletFilter; -import unknow.server.http.servlet.FilterConfigImpl; -import unknow.server.http.servlet.ServletConfigImpl; -import unknow.server.http.servlet.ServletContextImpl; -import unknow.server.http.servlet.ServletRequestImpl; +import unknow.server.servlet.impl.FilterChainImpl; +import unknow.server.servlet.impl.FilterConfigImpl; +import unknow.server.servlet.impl.ServletConfigImpl; +import unknow.server.servlet.impl.ServletContextImpl; +import unknow.server.servlet.impl.ServletRequestImpl; +import unknow.server.servlet.impl.FilterChainImpl.ChangePath; +import unknow.server.servlet.impl.FilterChainImpl.ServletFilter; import unknow.server.util.data.IntArrayMap; import unknow.server.util.data.ObjectArrayMap; diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletResource.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResource.java similarity index 98% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/ServletResource.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResource.java index dee58eba..648b3d75 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletResource.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResource.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet; +package unknow.server.servlet.utils; import java.io.IOException; import java.io.InputStream; diff --git a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletResourceStatic.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResourceStatic.java similarity index 98% rename from unknow-server-http/src/main/java/unknow/server/http/servlet/ServletResourceStatic.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResourceStatic.java index faee05c9..d3ae0d5b 100644 --- a/unknow-server-http/src/main/java/unknow/server/http/servlet/ServletResourceStatic.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResourceStatic.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.servlet; +package unknow.server.servlet.utils; import java.io.IOException; import java.io.InputStream; diff --git a/unknow-server-http/src/main/resources/META-INF/native-image/resource-config.json b/unknow-server-servlet/src/main/resources/META-INF/native-image/resource-config.json similarity index 100% rename from unknow-server-http/src/main/resources/META-INF/native-image/resource-config.json rename to unknow-server-servlet/src/main/resources/META-INF/native-image/resource-config.json diff --git a/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java new file mode 100644 index 00000000..d0027d42 --- /dev/null +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java @@ -0,0 +1,169 @@ +package unknow.server.servlet.http2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import unknow.server.servlet.http2.Http2Headers.Entry; +import unknow.server.util.io.Buffers; + +public class Http2HeadersTest { + + public void staticTable() { + assertEquals(61, Http2Headers.TABLE.length); + } + + public static Stream readInt() { + //@formatter:off + return Stream.of( + Arguments.of(new byte[] { b(0b00001010) }, 5, 10), + Arguments.of(new byte[] { b(0b00011111), b(0b10011010), b(0b00001010) }, 5, 1337), + Arguments.of(new byte[] { b(0b00101010) }, 8, 42)); //@formatter:on + } + + @ParameterizedTest + @MethodSource + public void readInt(byte[] data, int prefix, int expected) throws InterruptedException, IOException { + Buffers b = new Buffers(); + b.write(data); + + int value = Http2Headers.readInt(b, b.read(false), prefix); + assertEquals(expected, value); + } + + public static Stream readHeaders() { + Http2Headers requestRaw = new Http2Headers(4096); + Http2Headers responseRaw = new Http2Headers(256); + Http2Headers requestHm = new Http2Headers(4096); + Http2Headers responseHm = new Http2Headers(256); + //@formatter:off + return Stream.of( + Arguments.of( // literal with indexing + new Http2Headers(4096), + new byte[] { b(0x40),b(0x0a),b(0x63),b(0x75),b(0x73),b(0x74),b(0x6f),b(0x6d),b(0x2d),b(0x6b),b(0x65),b(0x79),b(0x0d),b(0x63),b(0x75),b(0x73),b(0x74),b(0x6f),b(0x6d),b(0x2d),b(0x68),b(0x65),b(0x61),b(0x64),b(0x65),b(0x72) }, + new Entry[]{ e("custom-key", "custom-header")}, + new Entry[]{ e("custom-key", "custom-header")} + ), + Arguments.of( // literal without indexing + new Http2Headers(4096), + new byte[] { b(0x04),b(0x0c),b(0x2f),b(0x73),b(0x61),b(0x6d),b(0x70),b(0x6c),b(0x65),b(0x2f),b(0x70),b(0x61),b(0x74),b(0x68)}, + new Entry[]{ e(":path", "/sample/path")}, + new Entry[]{} + ), + Arguments.of( // literal never indexing + new Http2Headers(4096), + new byte[] { b(0x10),b(0x08),b(0x70),b(0x61),b(0x73),b(0x73),b(0x77),b(0x6f),b(0x72),b(0x64),b(0x06),b(0x73),b(0x65),b(0x63),b(0x72),b(0x65),b(0x74)}, + new Entry[]{ e("password", "secret")}, + new Entry[]{}), + Arguments.of( // indexed field + new Http2Headers(4096), + new byte[] { b(0x82)}, + new Entry[]{ e(":method", "GET")}, + new Entry[]{}), + + // request chain without huffman coding + Arguments.of( + requestRaw, + new byte[] { b(0x82),b(0x86),b(0x84),b(0x41),b(0x0f),b(0x77),b(0x77),b(0x77),b(0x2e),b(0x65),b(0x78),b(0x61),b(0x6d),b(0x70),b(0x6c),b(0x65),b(0x2e),b(0x63),b(0x6f),b(0x6d)}, + new Entry[]{ e(":method", "GET"), e(":scheme", "http"), e(":path", "/"), e(":authority", "www.example.com")}, + new Entry[]{ e(":authority", "www.example.com")}), + Arguments.of( + requestRaw, + new byte[] { b(0x82),b(0x86),b(0x84),b(0xbe),b(0x58),b(0x08),b(0x6e),b(0x6f),b(0x2d),b(0x63),b(0x61),b(0x63),b(0x68),b(0x65)}, + new Entry[]{ e(":method", "GET"), e(":scheme", "http"), e(":path", "/"), e(":authority", "www.example.com"),e("cache-control", "no-cache")}, + new Entry[]{ e("cache-control", "no-cache"), e(":authority", "www.example.com")}), + Arguments.of( + requestRaw, + new byte[] { b(0x82),b(0x87),b(0x85),b(0xbf),b(0x40),b(0x0a),b(0x63),b(0x75),b(0x73),b(0x74),b(0x6f),b(0x6d),b(0x2d),b(0x6b),b(0x65),b(0x79),b(0x0c),b(0x63),b(0x75),b(0x73),b(0x74),b(0x6f),b(0x6d),b(0x2d),b(0x76),b(0x61),b(0x6c),b(0x75),b(0x65)}, + new Entry[]{ e(":method", "GET"), e(":scheme", "https"), e(":path", "/index.html"), e(":authority", "www.example.com"),e("custom-key", "custom-value")}, + new Entry[]{ e("custom-key", "custom-value"),e("cache-control", "no-cache"),e(":authority", "www.example.com")}), + + // request chain with huffman coding + Arguments.of( + requestHm, + new byte[] { b(0x82),b(0x86),b(0x84),b(0x41),b(0x8c),b(0xf1),b(0xe3),b(0xc2),b(0xe5),b(0xf2),b(0x3a),b(0x6b),b(0xa0),b(0xab),b(0x90),b(0xf4),b(0xff)}, + new Entry[]{ e(":method", "GET"), e(":scheme", "http"), e(":path", "/"), e(":authority", "www.example.com")}, + new Entry[]{ e(":authority", "www.example.com")}), + Arguments.of( + requestHm, + new byte[] { b(0x82),b(0x86),b(0x84),b(0xbe),b(0x58),b(0x86),b(0xa8),b(0xeb),b(0x10),b(0x64),b(0x9c),b(0xbf)}, + new Entry[]{ e(":method", "GET"), e(":scheme", "http"), e(":path", "/"), e(":authority", "www.example.com"),e("cache-control", "no-cache")}, + new Entry[]{ e("cache-control", "no-cache"), e(":authority", "www.example.com")}), + Arguments.of( + requestHm, + new byte[] { b(0x82),b(0x87),b(0x85),b(0xbf),b(0x40),b(0x88),b(0x25),b(0xa8),b(0x49),b(0xe9),b(0x5b),b(0xa9),b(0x7d),b(0x7f),b(0x89),b(0x25),b(0xa8),b(0x49),b(0xe9),b(0x5b),b(0xb8),b(0xe8),b(0xb4),b(0xbf)}, + new Entry[]{ e(":method", "GET"), e(":scheme", "https"), e(":path", "/index.html"), e(":authority", "www.example.com"),e("custom-key", "custom-value")}, + new Entry[]{ e("custom-key", "custom-value"),e("cache-control", "no-cache"),e(":authority", "www.example.com")}), + + // response chain without huffman coding + Arguments.of( + responseRaw, + new byte[] { b(0x48),b(0x03),b(0x33),b(0x30),b(0x32),b(0x58),b(0x07),b(0x70),b(0x72),b(0x69),b(0x76),b(0x61),b(0x74),b(0x65),b(0x61),b(0x1d),b(0x4d),b(0x6f),b(0x6e),b(0x2c),b(0x20),b(0x32),b(0x31),b(0x20),b(0x4f),b(0x63),b(0x74),b(0x20),b(0x32),b(0x30),b(0x31),b(0x33),b(0x20),b(0x32),b(0x30),b(0x3a),b(0x31),b(0x33),b(0x3a),b(0x32),b(0x31),b(0x20),b(0x47),b(0x4d),b(0x54),b(0x6e),b(0x17),b(0x68),b(0x74),b(0x74),b(0x70),b(0x73),b(0x3a),b(0x2f),b(0x2f),b(0x77),b(0x77),b(0x77),b(0x2e),b(0x65),b(0x78),b(0x61),b(0x6d),b(0x70),b(0x6c),b(0x65),b(0x2e),b(0x63),b(0x6f),b(0x6d)}, + new Entry[]{ e(":status", "302"), e("cache-control", "private"), e("date", "Mon, 21 Oct 2013 20:13:21 GMT"), e("location", "https://www.example.com")}, + new Entry[]{ e("location", "https://www.example.com"), e("date", "Mon, 21 Oct 2013 20:13:21 GMT"), e("cache-control", "private"),e(":status", "302")}), + Arguments.of( + responseRaw, + new byte[] { b(0x48),b(0x03),b(0x33),b(0x30),b(0x37),b(0xc1),b(0xc0),b(0xbf)}, + new Entry[]{ e(":status", "307"), e("cache-control", "private"), e("date", "Mon, 21 Oct 2013 20:13:21 GMT"), e("location", "https://www.example.com")}, + new Entry[]{ e(":status", "307"), e("location", "https://www.example.com"), e("date", "Mon, 21 Oct 2013 20:13:21 GMT"), e("cache-control", "private")}), + Arguments.of( + responseRaw, + new byte[] { b(0x88),b(0xc1),b(0x61),b(0x1d),b(0x4d),b(0x6f),b(0x6e),b(0x2c),b(0x20),b(0x32),b(0x31),b(0x20),b(0x4f),b(0x63),b(0x74),b(0x20),b(0x32),b(0x30),b(0x31),b(0x33),b(0x20),b(0x32),b(0x30),b(0x3a),b(0x31),b(0x33),b(0x3a),b(0x32),b(0x32),b(0x20),b(0x47),b(0x4d),b(0x54),b(0xc0),b(0x5a),b(0x04),b(0x67),b(0x7a),b(0x69),b(0x70),b(0x77),b(0x38),b(0x66),b(0x6f),b(0x6f),b(0x3d),b(0x41),b(0x53),b(0x44),b(0x4a),b(0x4b),b(0x48),b(0x51),b(0x4b),b(0x42),b(0x5a),b(0x58),b(0x4f),b(0x51),b(0x57),b(0x45),b(0x4f),b(0x50),b(0x49),b(0x55),b(0x41),b(0x58),b(0x51),b(0x57),b(0x45),b(0x4f),b(0x49),b(0x55),b(0x3b),b(0x20),b(0x6d),b(0x61),b(0x78),b(0x2d),b(0x61),b(0x67),b(0x65),b(0x3d),b(0x33),b(0x36),b(0x30),b(0x30),b(0x3b),b(0x20),b(0x76),b(0x65),b(0x72),b(0x73),b(0x69),b(0x6f),b(0x6e),b(0x3d),b(0x31)}, + new Entry[]{ e(":status", "200"), e("cache-control", "private"), e("date", "Mon, 21 Oct 2013 20:13:22 GMT"), e("location", "https://www.example.com"), e("content-encoding", "gzip"), e("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1")}, + new Entry[]{ e("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), e("content-encoding", "gzip"), e("date", "Mon, 21 Oct 2013 20:13:22 GMT")}), + + // response chain with huffman coding + Arguments.of( + responseHm, + new byte[] { b(0x48),b(0x82),b(0x64),b(0x02),b(0x58),b(0x85),b(0xae),b(0xc3),b(0x77),b(0x1a),b(0x4b),b(0x61),b(0x96),b(0xd0),b(0x7a),b(0xbe),b(0x94),b(0x10),b(0x54),b(0xd4),b(0x44),b(0xa8),b(0x20),b(0x05),b(0x95),b(0x04),b(0x0b),b(0x81),b(0x66),b(0xe0),b(0x82),b(0xa6),b(0x2d),b(0x1b),b(0xff),b(0x6e),b(0x91),b(0x9d),b(0x29),b(0xad),b(0x17),b(0x18),b(0x63),b(0xc7),b(0x8f),b(0x0b),b(0x97),b(0xc8),b(0xe9),b(0xae),b(0x82),b(0xae),b(0x43),b(0xd3)}, + new Entry[]{ e(":status", "302"), e("cache-control", "private"), e("date", "Mon, 21 Oct 2013 20:13:21 GMT"), e("location", "https://www.example.com")}, + new Entry[]{ e("location", "https://www.example.com"), e("date", "Mon, 21 Oct 2013 20:13:21 GMT"), e("cache-control", "private"),e(":status", "302")}), + Arguments.of( + responseHm, + new byte[] { b(0x48),b(0x83),b(0x64),b(0x0e),b(0xff),b(0xc1),b(0xc0),b(0xbf)}, + new Entry[]{ e(":status", "307"), e("cache-control", "private"), e("date", "Mon, 21 Oct 2013 20:13:21 GMT"), e("location", "https://www.example.com")}, + new Entry[]{ e(":status", "307"), e("location", "https://www.example.com"), e("date", "Mon, 21 Oct 2013 20:13:21 GMT"), e("cache-control", "private")}), + Arguments.of( + responseHm, + new byte[] { b(0x88),b(0xc1),b(0x61),b(0x1d),b(0x4d),b(0x6f),b(0x6e),b(0x2c),b(0x20),b(0x32),b(0x31),b(0x20),b(0x4f),b(0x63),b(0x74),b(0x20),b(0x32),b(0x30),b(0x31),b(0x33),b(0x20),b(0x32),b(0x30),b(0x3a),b(0x31),b(0x33),b(0x3a),b(0x32),b(0x32),b(0x20),b(0x47),b(0x4d),b(0x54),b(0xc0),b(0x5a),b(0x04),b(0x67),b(0x7a),b(0x69),b(0x70),b(0x77),b(0x38),b(0x66),b(0x6f),b(0x6f),b(0x3d),b(0x41),b(0x53),b(0x44),b(0x4a),b(0x4b),b(0x48),b(0x51),b(0x4b),b(0x42),b(0x5a),b(0x58),b(0x4f),b(0x51),b(0x57),b(0x45),b(0x4f),b(0x50),b(0x49),b(0x55),b(0x41),b(0x58),b(0x51),b(0x57),b(0x45),b(0x4f),b(0x49),b(0x55),b(0x3b),b(0x20),b(0x6d),b(0x61),b(0x78),b(0x2d),b(0x61),b(0x67),b(0x65),b(0x3d),b(0x33),b(0x36),b(0x30),b(0x30),b(0x3b),b(0x20),b(0x76),b(0x65),b(0x72),b(0x73),b(0x69),b(0x6f),b(0x6e),b(0x3d),b(0x31)}, + new Entry[]{ e(":status", "200"), e("cache-control", "private"), e("date", "Mon, 21 Oct 2013 20:13:22 GMT"), e("location", "https://www.example.com"), e("content-encoding", "gzip"), e("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1")}, + new Entry[]{ e("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"), e("content-encoding", "gzip"), e("date", "Mon, 21 Oct 2013 20:13:22 GMT")}) + ); //@formatter:on + } + + @ParameterizedTest + @MethodSource + public void readHeaders(Http2Headers h, byte[] data, Entry[] headers, Entry[] table) throws InterruptedException, IOException { + Buffers b = new Buffers(); + b.write(data); + + Map r = new HashMap<>(); + while (b.length() > 0) + h.readHeaders(b, r::put); + + assertEquals(table.length, h.dynamic.size()); + int i = 0; + for (Entry e : h.dynamic) + assertEquals(table[i++], e); + + assertEquals(headers.length, r.size()); + for (Entry e : headers) + assertEquals(e.value(), r.get(e.name())); + } + + private static final byte b(int i) { + return (byte) (i & 0xFF); + } + + private static final Entry e(String n, String v) { +// return new Entry(new EntryData(n, n.length()), new EntryData(v, v.length())); + return new Entry(n, v); + } +} diff --git a/unknow-server-http/src/test/java/unknow/server/http/utils/F.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/F.java similarity index 94% rename from unknow-server-http/src/test/java/unknow/server/http/utils/F.java rename to unknow-server-servlet/src/test/java/unknow/server/servlet/utils/F.java index cce754ba..c9d28060 100644 --- a/unknow-server-http/src/test/java/unknow/server/http/utils/F.java +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/F.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.utils; +package unknow.server.servlet.utils; import java.io.IOException; diff --git a/unknow-server-http/src/test/java/unknow/server/http/utils/FC.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/FC.java similarity index 82% rename from unknow-server-http/src/test/java/unknow/server/http/utils/FC.java rename to unknow-server-servlet/src/test/java/unknow/server/servlet/utils/FC.java index 79659bf2..13f40eed 100644 --- a/unknow-server-http/src/test/java/unknow/server/http/utils/FC.java +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/FC.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.utils; +package unknow.server.servlet.utils; import java.io.IOException; @@ -9,8 +9,8 @@ import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; -import unknow.server.http.utils.PathTree.Node; -import unknow.server.http.utils.PathTree.PartNode; +import unknow.server.servlet.utils.PathTree.Node; +import unknow.server.servlet.utils.PathTree.PartNode; public class FC implements FilterChain { private final String name; diff --git a/unknow-server-http/src/test/java/unknow/server/http/utils/PathTreeBuilderTest.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeBuilderTest.java similarity index 90% rename from unknow-server-http/src/test/java/unknow/server/http/utils/PathTreeBuilderTest.java rename to unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeBuilderTest.java index ce98e2d4..0adc2faf 100644 --- a/unknow-server-http/src/test/java/unknow/server/http/utils/PathTreeBuilderTest.java +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeBuilderTest.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.utils; +package unknow.server.servlet.utils; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -15,9 +15,11 @@ import jakarta.servlet.DispatcherType; import jakarta.servlet.ServletException; -import unknow.server.http.servlet.FilterConfigImpl; -import unknow.server.http.servlet.ServletConfigImpl; -import unknow.server.http.utils.PathTree.PartNode; +import unknow.server.servlet.impl.FilterConfigImpl; +import unknow.server.servlet.impl.ServletConfigImpl; +import unknow.server.servlet.utils.PathTree; +import unknow.server.servlet.utils.PathTreeBuilder; +import unknow.server.servlet.utils.PathTree.PartNode; /** * @author unknow diff --git a/unknow-server-http/src/test/java/unknow/server/http/utils/PathTreeTest.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeTest.java similarity index 87% rename from unknow-server-http/src/test/java/unknow/server/http/utils/PathTreeTest.java rename to unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeTest.java index cd7bcd06..7cb764e4 100644 --- a/unknow-server-http/src/test/java/unknow/server/http/utils/PathTreeTest.java +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeTest.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.utils; +package unknow.server.servlet.utils; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; @@ -18,11 +18,12 @@ import jakarta.servlet.DispatcherType; import jakarta.servlet.FilterChain; -import unknow.server.http.HttpConnection; -import unknow.server.http.servlet.ServletContextImpl; -import unknow.server.http.servlet.ServletRequestImpl; -import unknow.server.http.utils.PathTree.Node; -import unknow.server.http.utils.PathTree.PartNode; +import unknow.server.servlet.HttpConnection; +import unknow.server.servlet.impl.ServletContextImpl; +import unknow.server.servlet.impl.ServletRequestImpl; +import unknow.server.servlet.utils.PathTree; +import unknow.server.servlet.utils.PathTree.Node; +import unknow.server.servlet.utils.PathTree.PartNode; /** * @author unknow diff --git a/unknow-server-http/src/test/java/unknow/server/http/utils/S.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/S.java similarity index 89% rename from unknow-server-http/src/test/java/unknow/server/http/utils/S.java rename to unknow-server-servlet/src/test/java/unknow/server/servlet/utils/S.java index c212a58d..9ac22969 100644 --- a/unknow-server-http/src/test/java/unknow/server/http/utils/S.java +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/S.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.http.utils; +package unknow.server.servlet.utils; import jakarta.servlet.http.HttpServlet; diff --git a/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java b/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java index 7d1f32bb..e0346817 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java +++ b/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java @@ -64,10 +64,6 @@ public T set(int key, T value) { values[i] = value; return null; } - if (i < len) { - System.arraycopy(keys, i, keys, i + 1, len - i); - System.arraycopy(values, i, values, i + 1, len - i); - } ensure(++len); i = -i - 1; T old = values[i]; diff --git a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java index 75795b8d..86890e36 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java +++ b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java @@ -3,6 +3,8 @@ */ package unknow.server.util.io; +import java.io.IOException; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; @@ -11,7 +13,7 @@ * @author unknow */ public class Buffers { - private static final int BUF_LEN = 4096; + public static final int BUF_LEN = 4096; private final ReentrantLock lock = new ReentrantLock(); private final Condition cond = lock.newCondition(); @@ -47,7 +49,7 @@ public void write(int b) throws InterruptedException { tail.next = new Chunk(); tail = tail.next; } - tail.b[tail.o + tail.l] = (byte) b; + tail.b[tail.o + tail.l] = (byte) (b & 0xFF); tail.l++; len++; cond.signalAll(); @@ -191,7 +193,7 @@ public int read(boolean wait) throws InterruptedException { awaitContent(); if (len == 0) return -1; - int r = head.b[head.o++]; + int r = head.b[head.o++] & 0xFF; if (--len == 0) tail = null; if (--head.l == 0) @@ -315,8 +317,8 @@ private void append(Chunk c) throws InterruptedException { * read a number amount of byte into the buffers * * @param buf where to read - * @param wait if true wait for data * @param l number of byte to read + * @param wait if true wait for data * @throws InterruptedException on interrupt */ public void read(Buffers buf, int l, boolean wait) throws InterruptedException { @@ -370,6 +372,53 @@ public void read(Buffers buf, int l, boolean wait) throws InterruptedException { } } + /** + * read a number amount of byte into the buffers + * + * @param out where to read + * @param l number of byte to read + * @param wait if true wait for data + * @throws InterruptedException on interrupt + * @throws IOException + */ + public void read(OutputStream out, int l, boolean wait) throws InterruptedException, IOException { + if (l == 0) + return; + lock.lockInterruptibly(); + try { + if (wait) + awaitContent(); + if (len == 0 || head == null) + return; + + Chunk last = null; + Chunk c = head; + // read whole chunk + while (c != null && l >= c.l) { + out.write(c.b, c.o, c.l); + l -= c.l; + len -= c.l; + last = c; + c = c.next; + } + if (last != null) + last.next = null; + + if (c != null && l > 0) { + out.write(c.b, c.o, l); + c.o += l; + c.l -= l; + len -= l; + head = c; + } + head = c; + if (c == null) + tail = null; + } finally { + lock.unlock(); + } + } + /** * skip l bytes * @@ -485,7 +534,7 @@ public int get(int off) throws InterruptedException { if (b == null) return -1; } - return b.b[b.o + off]; + return b.b[b.o + off] & 0xFF; } finally { lock.unlock(); } From 508cb72b7cd647e89071aa957dbf954ea111f34f Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 24 Mar 2024 12:56:59 +0100 Subject: [PATCH 03/40] gh-30 fix dependencies --- unknow-server-bom/pom.xml | 2 +- unknow-server-maven/pom.xml | 2 +- .../unknow/server/maven/jsp/JspGeneratorMojo.java | 2 +- .../unknow/server/maven/servlet/ServletGenMojo.java | 12 ++++++------ .../server/maven/servlet/builder/CreateContext.java | 2 +- .../maven/servlet/builder/CreateEventManager.java | 2 +- .../server/maven/servlet/builder/CreateFilters.java | 2 +- .../maven/servlet/builder/CreateServletManager.java | 2 +- .../server/maven/servlet/builder/CreateServlets.java | 4 ++-- .../server/maven/servlet/descriptor/Descriptor.java | 4 ++-- .../unknow/server/servlet/utils/PathTreeTest.java | 10 +++++----- unknow-server-test/unknow-server-test-jar/pom.xml | 2 +- 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/unknow-server-bom/pom.xml b/unknow-server-bom/pom.xml index 8daf6aa2..1b47073e 100644 --- a/unknow-server-bom/pom.xml +++ b/unknow-server-bom/pom.xml @@ -27,7 +27,7 @@ ${project.groupId} - unknow-server-http + unknow-server-servlet ${project.version} diff --git a/unknow-server-maven/pom.xml b/unknow-server-maven/pom.xml index 9d78b75d..c89a23bc 100644 --- a/unknow-server-maven/pom.xml +++ b/unknow-server-maven/pom.xml @@ -41,7 +41,7 @@ ${project.groupId} - unknow-server-http + unknow-server-servlet ${project.version} diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/jsp/JspGeneratorMojo.java b/unknow-server-maven/src/main/java/unknow/server/maven/jsp/JspGeneratorMojo.java index 0c0ce763..b5ac71be 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/jsp/JspGeneratorMojo.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/jsp/JspGeneratorMojo.java @@ -21,9 +21,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import unknow.server.http.servlet.ServletJsp; import unknow.server.maven.TypeCache; import unknow.server.maven.Utils; +import unknow.server.servlet.impl.ServletJsp; /** * @author unknow diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/ServletGenMojo.java b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/ServletGenMojo.java index f66211d6..9cd65969 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/ServletGenMojo.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/ServletGenMojo.java @@ -33,11 +33,6 @@ import jakarta.servlet.DispatcherType; import jakarta.servlet.ServletContainerInitializer; import unknow.sax.SaxParser; -import unknow.server.http.AbstractHttpServer; -import unknow.server.http.AccessLogFilter; -import unknow.server.http.servlet.ServletResource; -import unknow.server.http.servlet.ServletResourceStatic; -import unknow.server.http.utils.Resource; import unknow.server.maven.AbstractGeneratorMojo; import unknow.server.maven.TypeCache; import unknow.server.maven.model.ModelLoader; @@ -52,6 +47,11 @@ import unknow.server.maven.servlet.descriptor.Descriptor; import unknow.server.maven.servlet.descriptor.SD; import unknow.server.maven.servlet.sax.Context; +import unknow.server.servlet.AbstractHttpServer; +import unknow.server.servlet.AccessLogFilter; +import unknow.server.servlet.utils.Resource; +import unknow.server.servlet.utils.ServletResource; +import unknow.server.servlet.utils.ServletResourceStatic; /** * @author unknow @@ -88,7 +88,7 @@ public class ServletGenMojo extends AbstractGeneratorMojo implements BuilderCont @Parameter(defaultValue = "4096") private int staticResourceSize; - @Parameter(defaultValue = "unknow.server.http.servlet.session.NoSessionFactory") + @Parameter(defaultValue = "unknow.server.servlet.impl.session.NoSessionFactory") private String sessionFactory; @Parameter(defaultValue = "false") diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateContext.java b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateContext.java index 42ddafaf..9bfe6d0f 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateContext.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateContext.java @@ -8,11 +8,11 @@ import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.stmt.ReturnStmt; -import unknow.server.http.servlet.ServletContextImpl; import unknow.server.maven.TypeCache; import unknow.server.maven.Utils; import unknow.server.maven.servlet.Builder; import unknow.server.maven.servlet.Names; +import unknow.server.servlet.impl.ServletContextImpl; /** * @author unknow diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateEventManager.java b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateEventManager.java index 107cd72b..e43db7dd 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateEventManager.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateEventManager.java @@ -19,12 +19,12 @@ import com.github.javaparser.ast.stmt.BlockStmt; import com.github.javaparser.ast.stmt.ReturnStmt; -import unknow.server.http.utils.EventManager; import unknow.server.maven.TypeCache; import unknow.server.maven.Utils; import unknow.server.maven.servlet.Builder; import unknow.server.maven.servlet.descriptor.Descriptor; import unknow.server.maven.servlet.descriptor.LD; +import unknow.server.servlet.utils.EventManager; /** * @author unknow diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateFilters.java b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateFilters.java index 377a00f2..a7e782fc 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateFilters.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateFilters.java @@ -15,13 +15,13 @@ import com.github.javaparser.ast.type.ClassOrInterfaceType; import jakarta.servlet.DispatcherType; -import unknow.server.http.servlet.FilterConfigImpl; import unknow.server.maven.TypeCache; import unknow.server.maven.Utils; import unknow.server.maven.servlet.Builder; import unknow.server.maven.servlet.Names; import unknow.server.maven.servlet.descriptor.Descriptor; import unknow.server.maven.servlet.descriptor.SD; +import unknow.server.servlet.impl.FilterConfigImpl; import unknow.server.util.data.ArraySet; /** diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateServletManager.java b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateServletManager.java index 83e92568..ff8c21a6 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateServletManager.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateServletManager.java @@ -19,13 +19,13 @@ import com.github.javaparser.ast.stmt.ReturnStmt; import com.github.javaparser.ast.type.PrimitiveType; -import unknow.server.http.utils.ServletManager; import unknow.server.maven.TypeCache; import unknow.server.maven.Utils; import unknow.server.maven.servlet.Builder; import unknow.server.maven.servlet.Names; import unknow.server.maven.servlet.descriptor.Descriptor; import unknow.server.maven.servlet.descriptor.SD; +import unknow.server.servlet.utils.ServletManager; import unknow.server.util.data.IntArrayMap; import unknow.server.util.data.ObjectArrayMap; diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateServlets.java b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateServlets.java index 2b021e63..4094d333 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateServlets.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateServlets.java @@ -16,14 +16,14 @@ import com.github.javaparser.ast.stmt.ReturnStmt; import com.github.javaparser.ast.type.ClassOrInterfaceType; -import unknow.server.http.servlet.ServletConfigImpl; -import unknow.server.http.utils.Resource; import unknow.server.maven.TypeCache; import unknow.server.maven.Utils; import unknow.server.maven.servlet.Builder; import unknow.server.maven.servlet.Names; import unknow.server.maven.servlet.descriptor.Descriptor; import unknow.server.maven.servlet.descriptor.SD; +import unknow.server.servlet.impl.ServletConfigImpl; +import unknow.server.servlet.utils.Resource; /** * @author unknow diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/descriptor/Descriptor.java b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/descriptor/Descriptor.java index 2edddf50..4ad16182 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/descriptor/Descriptor.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/descriptor/Descriptor.java @@ -23,11 +23,11 @@ import jakarta.servlet.http.HttpSessionAttributeListener; import jakarta.servlet.http.HttpSessionIdListener; import jakarta.servlet.http.HttpSessionListener; -import unknow.server.http.servlet.ServletCookieConfigImpl; -import unknow.server.http.utils.Resource; import unknow.server.maven.AbstractGeneratorMojo.TypeConsumer; import unknow.server.maven.model.AnnotationModel; import unknow.server.maven.model.TypeModel; +import unknow.server.servlet.impl.ServletCookieConfigImpl; +import unknow.server.servlet.utils.Resource; /** * @author unknow diff --git a/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeTest.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeTest.java index 7cb764e4..c178ed1d 100644 --- a/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeTest.java +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeTest.java @@ -7,7 +7,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -18,10 +17,9 @@ import jakarta.servlet.DispatcherType; import jakarta.servlet.FilterChain; -import unknow.server.servlet.HttpConnection; +import unknow.server.servlet.HttpAdapter; import unknow.server.servlet.impl.ServletContextImpl; import unknow.server.servlet.impl.ServletRequestImpl; -import unknow.server.servlet.utils.PathTree; import unknow.server.servlet.utils.PathTree.Node; import unknow.server.servlet.utils.PathTree.PartNode; @@ -36,9 +34,11 @@ public class PathTreeTest { public void init() { path = new ArrayList<>(); ServletContextImpl ctx = new ServletContextImpl("", "", null, null, null, null, null, null); - HttpConnection p = mock(HttpConnection.class, Mockito.withSettings().useConstructor(null, ctx, 0)); + HttpAdapter p = mock(HttpAdapter.class); + when(p.ctx()).thenReturn(ctx); + mock = mock(ServletRequestImpl.class, Mockito.withSettings().useConstructor(p, DispatcherType.REQUEST)); - Mockito.when(mock.getPaths()).thenReturn(path); + when(mock.getPaths()).thenReturn(path); } @Test diff --git a/unknow-server-test/unknow-server-test-jar/pom.xml b/unknow-server-test/unknow-server-test-jar/pom.xml index 78acda3e..c15c4592 100644 --- a/unknow-server-test/unknow-server-test-jar/pom.xml +++ b/unknow-server-test/unknow-server-test-jar/pom.xml @@ -101,7 +101,7 @@ ${project.groupId} - unknow-server-http + unknow-server-servlet ${project.version} From 341c3093c52f0457095ebd8fd5a94fcccb1bf4e4 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 24 Mar 2024 13:33:28 +0100 Subject: [PATCH 04/40] gh-30 some fix --- .../server/servlet/http11/Http11Processor.java | 13 +++---------- .../unknow/server/servlet/http11/Http11Worker.java | 2 ++ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java index ff6f3c76..8066971c 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java @@ -5,7 +5,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import unknow.server.servlet.Decode; import unknow.server.servlet.HttpConnection; import unknow.server.servlet.HttpProcessor; import unknow.server.util.io.BuffersUtils; @@ -18,18 +17,11 @@ public class Http11Processor implements HttpProcessor { private static final int MAX_START_SIZE = 8192; private final HttpConnection co; - private final int keepAliveIdle; - private final StringBuilder sb; - private final Decode decode; private volatile Future exec; public Http11Processor(HttpConnection co) { this.co = co; - this.keepAliveIdle = co.getkeepAlive(); - - sb = new StringBuilder(); - decode = new Decode(sb); } @Override @@ -40,12 +32,13 @@ public final void process() { // nothing to do @Override public final boolean isClosed() { - return exec.isDone(); + return exec == null || exec.isDone(); } @Override public final void close() { - exec.cancel(true); + if (exec != null) + exec.cancel(true); } public static final HttpProcessorFactory Factory = co -> { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java index fa716dc0..efce1d3c 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -188,6 +188,8 @@ public final void doStart() throws IOException, InterruptedException { @Override protected void doDone() { + if ("close".equalsIgnoreCase(res.getHeader("connection"))) + co.getOut().close(); } private boolean fillRequest(ServletRequestImpl req) throws InterruptedException, IOException { From aa3438c7d40753d9ef8b9168980f34f916f568af Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 24 Mar 2024 20:44:08 +0100 Subject: [PATCH 05/40] gh-30 more fixes --- .../unknow/server/servlet/HttpAdapter.java | 3 - .../servlet/http11/Http11Processor.java | 2 +- .../server/servlet/http11/Http11Worker.java | 41 +- .../servlet/http11/LengthOutputStream.java | 1 + .../servlet/http2/Http2FlowControl.java | 13 +- .../server/servlet/http2/Http2Processor.java | 852 +++++++++--------- .../server/servlet/http2/Http2Stream.java | 65 +- .../servlet/impl/ServletRequestImpl.java | 3 +- .../servlet/impl/ServletResponseImpl.java | 10 +- .../java/unknow/server/util/io/Buffers.java | 4 + 10 files changed, 528 insertions(+), 466 deletions(-) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java index c86d3ba0..f43546aa 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java @@ -19,7 +19,4 @@ public interface HttpAdapter { AbstractServletOutput createOutput(); void commit() throws IOException; - - void sendError(HttpError e, Throwable t, String msg) throws IOException; - } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java index 8066971c..6911208a 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java @@ -26,7 +26,7 @@ public Http11Processor(HttpConnection co) { @Override public final void process() { // nothing to do - if (exec != null) + if (exec == null) exec = co.submit(new Http11Worker(co)); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java index efce1d3c..16e72ad3 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -144,27 +144,26 @@ public void commit() throws IOException { out.write(CRLF); } - @SuppressWarnings("resource") - @Override - public void sendError(HttpError e, Throwable t, String msg) throws IOException { - Out out = co.getOut(); - if (msg == null) { - out.write(e.empty()); - return; - } - - out.write(e.encoded); - byte[] bytes = msg.getBytes(StandardCharsets.UTF_8); - - out.write(CONTENT_HTML); - out.write(CONTENT_LENGTH); - out.write(Integer.toString(bytes.length + ERROR_START.length + ERROR_END.length).getBytes(StandardCharsets.US_ASCII)); - out.write(CRLF); - out.write(CRLF); - out.write(ERROR_START); - out.write(bytes); - out.write(ERROR_END); - } +// @SuppressWarnings("resource") +// public void sendError(HttpError e, Throwable t, String msg) throws IOException { +// Out out = co.getOut(); +// if (msg == null) { +// out.write(e.empty()); +// return; +// } +// +// out.write(e.encoded); +// byte[] bytes = msg.getBytes(StandardCharsets.UTF_8); +// +// out.write(CONTENT_HTML); +// out.write(CONTENT_LENGTH); +// out.write(Integer.toString(bytes.length + ERROR_START.length + ERROR_END.length).getBytes(StandardCharsets.US_ASCII)); +// out.write(CRLF); +// out.write(CRLF); +// out.write(ERROR_START); +// out.write(bytes); +// out.write(ERROR_END); +// } @SuppressWarnings("resource") @Override diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java index 23fb2dd3..a8c8e61f 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java @@ -65,6 +65,7 @@ public void write(byte[] b, int off, int len) throws IOException { @Override public void flush() throws IOException { + res.commit(); try { buffer.read(out, -1, false); } catch (@SuppressWarnings("unused") InterruptedException e) { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2FlowControl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2FlowControl.java index a8da7ea2..87f80702 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2FlowControl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2FlowControl.java @@ -1,15 +1,6 @@ package unknow.server.servlet.http2; -public abstract class Http2FlowControl { - private int window; +public interface Http2FlowControl { - protected Http2FlowControl(int window) { - this.window = window; - } - - public void add(int v) { - window += v; - if (window < 0) - window = Integer.MAX_VALUE; - } + void add(int v); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index a9f0580c..bdbddd95 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -1,422 +1,430 @@ -//package unknow.server.servlet.http2; -// -//import java.io.IOException; -//import java.nio.charset.StandardCharsets; -// -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import unknow.server.servlet.HttpProcessor; -//import unknow.server.util.data.IntArrayMap; -//import unknow.server.util.io.Buffers; -//import unknow.server.util.io.BuffersUtils; -// -//public class Http2Processor extends Http2FlowControl implements HttpProcessor { -// private static final Logger logger = LoggerFactory.getLogger(Http2Processor.class); -// -// private static final byte[] PRI = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.US_ASCII); -// -// private static final int NO_ERROR = 0; -// private static final int PROTOCOL_ERROR = 1; -// private static final int INTERNAL_ERROR = 2; -// private static final int FLOW_CONTROL_ERROR = 3; -// private static final int SETTINGS_TIMEOUT = 4; -// private static final int STREAM_CLOSED = 5; -// private static final int FRAME_SIZE_ERROR = 6; -// private static final int REFUSED_STREAM = 7; -// private static final int CANCEL = 8; -// private static final int COMPRESSION_ERROR = 9; -// private static final int CONNECT_ERROR = 10; -// private static final int ENHANCE_YOUR_CALM = 12; -// private static final int INADEQUATE_SECURITY = 13; -// private static final int HTTP_1_1_REQUIRED = 14; -// -// private final HttpConnection co; -// private final IntArrayMap streams; -// private final Http2Headers headers; -// -// private boolean allowPush; -// private int concurrent; -// private int initialWindow; -// private int frame; -// private int headerList; -// private FrameReader r; -// -// private int lastId; -// -// public Http2Processor(HttpConnection co) throws InterruptedException { -// super(65535); -// this.co = co; -// this.streams = new IntArrayMap<>(); -// this.headers = new Http2Headers(4096); -// -// this.allowPush = true; -// this.concurrent = Integer.MAX_VALUE; -// this.initialWindow = 65535; -// this.frame = 16384; -// this.headerList = Integer.MAX_VALUE; -// byte[] b = new byte[9]; -// format(b, 0, 4, 0, 0); -// co.pendingWrite.write(b); -// co.flush(); -// } -// -// @Override -// public void process() throws InterruptedException { -// if (r != null) { -// r = r.process(co.pendingRead); -// if (r != null) -// return; -// } -// -// while (co.pendingRead.length() > 9 && r == null) -// readFrame(co.pendingRead); -// } -// -// @Override -// public boolean isClosed() { -// return false; -// } -// -// @Override -// public void close() { -// } -// -// private static final byte[] b = new byte[9]; -// -// /** -// * return true if the frame is done -// * @param buf -// * @return -// * @throws InterruptedException -// */ -// private void readFrame(Buffers buf) throws InterruptedException { -// buf.read(b, 0, 9, false); -// int size = (b[0] & 0xff) << 16 | (b[1] & 0xff) << 8 | (b[2] & 0xff); -// int type = b[3]; -// int flags = b[4]; -// int id = (b[5] & 0x7f) << 24 | (b[6] & 0xff) << 16 | (b[7] & 0xff) << 8 | (b[8] & 0xff); -// logger.debug("{} {} {} {}", size, type, flags, id); -// -// switch (type) { -// case 0: // data -// break; -// case 1: // header -// if (id == 0) { -// goaway(PROTOCOL_ERROR); -// return; -// } -// -// Http2Stream s = new Http2Stream(initialWindow, co); -// streams.set(id, s); -// -// try { -// Buffers b = new Buffers(); -// b.read(co.pendingRead, size, false); -// headers.readHeaders(co.pendingRead, (k, v) -> logger.debug(" {}: {}", k, v)); -// } catch (Exception e) { -// logger.error("Failed to parse headers", e); -// error(PROTOCOL_ERROR); -// } -// -// // TODO -// break; -// case 2: // priority -// break; -// case 3: // rst_stream -// co.pendingRead.read(b, 0, 4, false); -// int err = (b[0] & 0xff) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); -// logger.debug("close stream {}, err: {}", id, err); -// // TODO close stream id -// return; -// case 4: // settings -// if ((flags & 0x1) == 1) { -// if (size != 0) -// goaway(FRAME_SIZE_ERROR); -// logger.info("SETTINGS ACK"); -// return; -// } -// if (id != 0) { -// goaway(PROTOCOL_ERROR); -// } -// if (size % 6 != 0) { -// goaway(FRAME_SIZE_ERROR); -// return; -// } -// r = new FrameSettings(size, flags, id).process(buf); -// return; -// case 5: // push promise -// break; -// case 6: // ping -// if (id != 0) { -// goaway(PROTOCOL_ERROR); -// return; -// } -// if (size != 8) { -// goaway(FRAME_SIZE_ERROR); -// return; -// } -// if ((flags & 0x1) == 1) -// break; -// r = new FramePing(size, flags, id).process(buf); -// return; -// case 7: // go away -// if (id != 0) { -// goaway(PROTOCOL_ERROR); -// return; -// } -// // TODO -// return; -// case 8: // window update -// if (size != 4) { -// goaway(FRAME_SIZE_ERROR); -// return; -// } -// r = new FrameWindowUpdate(size, flags, id).process(buf); -// return; -// case 9: // continuation -// // id == 0 send PROTOCOL_ERROR -// // if previous frame != HEADERS, PUSH_PROMISE or CONTINUATION send PROTOCOL_ERROR -// default: -// } -// -// co.pendingRead.skip(size); -// } -// -// private void goaway(int err) throws InterruptedException { -// byte[] b = new byte[17]; -// format(b, 8, 7, 0, 0); -// b[9] = (byte) ((lastId >> 24) & 0x7f); -// b[10] = (byte) ((lastId >> 16) & 0xff); -// b[11] = (byte) ((lastId >> 8) & 0xff); -// b[12] = (byte) (lastId & 0xff); -// b[13] = (byte) ((err >> 24) & 0x7f); -// b[14] = (byte) ((err >> 16) & 0xff); -// b[15] = (byte) ((err >> 8) & 0xff); -// b[16] = (byte) (err & 0xff); -// co.pendingWrite.write(b); -// co.flush(); -// } -// -// private void format(byte[] b, int size, int type, int flags, int id) { -// b[0] = (byte) ((size >> 16) & 0xff); -// b[1] = (byte) ((size >> 8) & 0xff); -// b[2] = (byte) (size & 0xff); -// b[3] = (byte) (type & 0xff); -// b[4] = (byte) (flags & 0xff); -// b[5] = (byte) ((id >> 24) & 0x7f); -// b[6] = (byte) ((id >> 16) & 0xff); -// b[7] = (byte) ((id >> 8) & 0xff); -// b[8] = (byte) (id & 0xff); -// } -// -// private static String error(int err) { -// switch (err) { -// case 0: -// return "NO_ERROR"; -// case 1: -// return "PROTOCOL_ERROR"; -// case 2: -// return "INTERNAL_ERROR"; -// case 3: -// return "FLOW_CONTROL_ERROR"; -// case 4: -// return "SETTINGS_TIMEOUT"; -// case 5: -// return "STREAM_CLOSED"; -// case 6: -// return "FRAME_SIZE_ERROR"; -// case 7: -// return "REFUSED_STREAM"; -// case 8: -// return "CANCEL"; -// case 9: -// return "COMPRESSION_ERROR"; -// case 10: -// return "CONNECT_ERROR"; -// case 11: -// return "ENHANCE_YOUR_CALM"; -// case 12: -// return "INADEQUATE_SECURITY"; -// case 13: -// return "HTTP_1_1_REQUIRED"; -// default: -// return "unknown " + err; -// } -// } -// -// public static final HttpProcessorFactory Factory = co -> { -// if (BuffersUtils.startsWith(co.pendingRead, PRI, 0, PRI.length)) { -// co.pendingRead.skip(PRI.length); -// return new Http2Processor(co); -// } -// return null; -// }; -// -// private static abstract class FrameReader { -// protected int size; -// protected int flags; -// protected int id; -// -// protected FrameReader(int size, int flags, int id) { -// this.size = size; -// this.flags = flags; -// this.id = id; -// } -// -// /** -// * @param buf -// * @return this or null -// * @throws InterruptedException -// */ -// public abstract FrameReader process(Buffers buf) throws InterruptedException; -// } -// -// private class FrameSettings extends FrameReader { -// -// private final byte[] b; -// -// protected FrameSettings(int size, int flags, int id) { -// super(size, flags, id); -// b = new byte[6]; -// } -// -// @Override -// public final FrameReader process(Buffers buf) throws InterruptedException { -// while (size > 0 && buf.length() > 6) { -// buf.read(b, 0, 6, false); -// size -= 6; -// -// int i = (b[0] & 0xff) << 8 | (b[1] & 0xff); -// int v = (b[2] & 0x7f) << 24 | (b[3] & 0xff) << 16 | (b[4] & 0xff) << 8 | (b[5] & 0xff); -// -// switch (i) { -// case 1: -// logger.debug(" SETTINGS_HEADER_TABLE_SIZE {}", v); -// headers.setMax(v); -// break; -// case 2: -// logger.debug(" SETTINGS_ENABLE_PUSH {}", v); -// if (v < 0 || v > 1) -// ; // send PROTOCOL_ERROR -// allowPush = v == 1; -// break; -// case 3: -// logger.debug(" SETTINGS_MAX_CONCURRENT_STREAMS {}", v); -// concurrent = v; -// break; -// case 4: -// logger.debug(" SETTINGS_INITIAL_WINDOW_SIZE {}", v); -// initialWindow = v; -// break; -// case 5: -// logger.debug(" SETTINGS_MAX_FRAME_SIZE {}", v); -// if (v < 16384 || v > 16777215) -// ; // send PROTOCOL_ERROR -// frame = v; -// break; -// case 6: -// logger.debug(" SETTINGS_MAX_HEADER_LIST_SIZE {}", v); -// headerList = v; -// break; -// default: -// // ignore -// } -// } -// -// if (size != 0) -// return this; -// -// byte[] b = new byte[9]; -// format(b, 0, 4, 1, 0); -// co.pendingWrite.write(b); -// co.flush(); -// return null; -// } -// } -// -// private class FramePing extends FrameReader { -// -// private final byte[] b; -// -// protected FramePing(int size, int flags, int id) { -// super(size, flags, id); -// b = new byte[9 + 8]; -// -// } -// -// @Override -// public final FrameReader process(Buffers buf) throws InterruptedException { -// if (buf.length() < 8) -// return this; -// -// buf.read(b, 9, 8, false); -// format(b, 8, 8, 1, 0); -// co.pendingWrite.write(b); -// co.flush(); -// return null; -// } -// } -// -// private class FrameWindowUpdate extends FrameReader { -// private final byte[] b; -// -// protected FrameWindowUpdate(int size, int flags, int id) { -// super(size, flags, id); -// b = new byte[4]; -// } -// -// @Override -// public final FrameReader process(Buffers buf) throws InterruptedException { -// if (buf.length() < 4) -// return this; -// buf.read(b, 0, 4, false); -// int v = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); -// if (v == 0) { -// goaway(PROTOCOL_ERROR); -// return null; -// } -// -// logger.debug(" window update {}", v); -// -// Http2FlowControl f = id == 0 ? Http2Processor.this : streams.get(id); -// if (f != null) -// f.add(v); -// return null; -// } -// } -// -// private class FrameHeader extends FrameReader { -// private final byte[] b; -// private int pad = 0; -// -// protected FrameHeader(int size, int flags, int id) { -// super(size, flags, id); -// b = new byte[4]; -// } -// -// @Override -// public final FrameReader process(Buffers buf) throws InterruptedException { -// if ((flags & 0x1) != 0) // END_STREAM -// ; // END_STREAM -// if ((flags & 0x4) != 0) // END_HEADERS -// ; -// if ((flags & 0x8) != 0) { // PADDED -// if (co.pendingRead.length() < 1) -// return this; -// pad = (co.pendingRead.read(false) & 0xff); -// flags ^= 0x8; -// } -// -// if ((flags & 0x20) != 0) { // PRIORITY -// if (co.pendingRead.length() < 5) -// return this; -// // TODO priority -// co.pendingRead.skip(5); -// flags ^= 0x20; -// } -// -// return null; -// } -// } -//} +package unknow.server.servlet.http2; + +import java.nio.charset.StandardCharsets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import unknow.server.servlet.HttpConnection; +import unknow.server.servlet.HttpProcessor; +import unknow.server.util.data.IntArrayMap; +import unknow.server.util.io.Buffers; +import unknow.server.util.io.BuffersUtils; + +public class Http2Processor implements HttpProcessor, Http2FlowControl { + private static final Logger logger = LoggerFactory.getLogger(Http2Processor.class); + + private static final byte[] PRI = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.US_ASCII); + + private static final int NO_ERROR = 0; + private static final int PROTOCOL_ERROR = 1; + private static final int INTERNAL_ERROR = 2; + private static final int FLOW_CONTROL_ERROR = 3; + private static final int SETTINGS_TIMEOUT = 4; + private static final int STREAM_CLOSED = 5; + private static final int FRAME_SIZE_ERROR = 6; + private static final int REFUSED_STREAM = 7; + private static final int CANCEL = 8; + private static final int COMPRESSION_ERROR = 9; + private static final int CONNECT_ERROR = 10; + private static final int ENHANCE_YOUR_CALM = 12; + private static final int INADEQUATE_SECURITY = 13; + private static final int HTTP_1_1_REQUIRED = 14; + + private final HttpConnection co; + private final IntArrayMap streams; + private final Http2Headers headers; + private int window; + + private boolean allowPush; + private int concurrent; + private int initialWindow; + private int frame; + private int headerList; + private FrameReader r; + + private int lastId; + + public Http2Processor(HttpConnection co) throws InterruptedException { + this.co = co; + this.streams = new IntArrayMap<>(); + this.headers = new Http2Headers(4096); + this.window = 65535; + + this.allowPush = true; + this.concurrent = Integer.MAX_VALUE; + this.initialWindow = 65535; + this.frame = 16384; + this.headerList = Integer.MAX_VALUE; + byte[] b = new byte[9]; + format(b, 0, 4, 0, 0); + co.pendingWrite.write(b); + co.flush(); + } + + @Override + public void process() throws InterruptedException { + if (r != null) { + r = r.process(co.pendingRead); + if (r != null) + return; + } + + while (co.pendingRead.length() > 9 && r == null) + readFrame(co.pendingRead); + } + + @Override + public void add(int v) { + window += v; + if (window < 0) + window = Integer.MAX_VALUE; + } + + @Override + public boolean isClosed() { + return false; + } + + @Override + public void close() { + } + + private static final byte[] b = new byte[9]; + + /** + * return true if the frame is done + * @param buf + * @return + * @throws InterruptedException + */ + private void readFrame(Buffers buf) throws InterruptedException { + buf.read(b, 0, 9, false); + int size = (b[0] & 0xff) << 16 | (b[1] & 0xff) << 8 | (b[2] & 0xff); + int type = b[3]; + int flags = b[4]; + int id = (b[5] & 0x7f) << 24 | (b[6] & 0xff) << 16 | (b[7] & 0xff) << 8 | (b[8] & 0xff); + logger.debug("{} {} {} {}", size, type, flags, id); + + switch (type) { + case 0: // data + break; + case 1: // header + if (id == 0) { + goaway(PROTOCOL_ERROR); + return; + } + + Http2Stream s = new Http2Stream(co, initialWindow); + streams.set(id, s); + + try { + Buffers b = new Buffers(); + b.read(co.pendingRead, size, false); + headers.readHeaders(co.pendingRead, (k, v) -> logger.debug(" {}: {}", k, v)); + } catch (Exception e) { + logger.error("Failed to parse headers", e); + error(PROTOCOL_ERROR); + } + + // TODO + break; + case 2: // priority + break; + case 3: // rst_stream + co.pendingRead.read(b, 0, 4, false); + int err = (b[0] & 0xff) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); + logger.debug("close stream {}, err: {}", id, err); + // TODO close stream id + return; + case 4: // settings + if ((flags & 0x1) == 1) { + if (size != 0) + goaway(FRAME_SIZE_ERROR); + logger.info("SETTINGS ACK"); + return; + } + if (id != 0) { + goaway(PROTOCOL_ERROR); + } + if (size % 6 != 0) { + goaway(FRAME_SIZE_ERROR); + return; + } + r = new FrameSettings(size, flags, id).process(buf); + return; + case 5: // push promise + break; + case 6: // ping + if (id != 0) { + goaway(PROTOCOL_ERROR); + return; + } + if (size != 8) { + goaway(FRAME_SIZE_ERROR); + return; + } + if ((flags & 0x1) == 1) + break; + r = new FramePing(size, flags, id).process(buf); + return; + case 7: // go away + if (id != 0) { + goaway(PROTOCOL_ERROR); + return; + } + // TODO + return; + case 8: // window update + if (size != 4) { + goaway(FRAME_SIZE_ERROR); + return; + } + r = new FrameWindowUpdate(size, flags, id).process(buf); + return; + case 9: // continuation + // id == 0 send PROTOCOL_ERROR + // if previous frame != HEADERS, PUSH_PROMISE or CONTINUATION send PROTOCOL_ERROR + default: + } + + co.pendingRead.skip(size); + } + + private void goaway(int err) throws InterruptedException { + byte[] b = new byte[17]; + format(b, 8, 7, 0, 0); + b[9] = (byte) ((lastId >> 24) & 0x7f); + b[10] = (byte) ((lastId >> 16) & 0xff); + b[11] = (byte) ((lastId >> 8) & 0xff); + b[12] = (byte) (lastId & 0xff); + b[13] = (byte) ((err >> 24) & 0x7f); + b[14] = (byte) ((err >> 16) & 0xff); + b[15] = (byte) ((err >> 8) & 0xff); + b[16] = (byte) (err & 0xff); + co.pendingWrite.write(b); + co.flush(); + } + + private void format(byte[] b, int size, int type, int flags, int id) { + b[0] = (byte) ((size >> 16) & 0xff); + b[1] = (byte) ((size >> 8) & 0xff); + b[2] = (byte) (size & 0xff); + b[3] = (byte) (type & 0xff); + b[4] = (byte) (flags & 0xff); + b[5] = (byte) ((id >> 24) & 0x7f); + b[6] = (byte) ((id >> 16) & 0xff); + b[7] = (byte) ((id >> 8) & 0xff); + b[8] = (byte) (id & 0xff); + } + + private static String error(int err) { + switch (err) { + case 0: + return "NO_ERROR"; + case 1: + return "PROTOCOL_ERROR"; + case 2: + return "INTERNAL_ERROR"; + case 3: + return "FLOW_CONTROL_ERROR"; + case 4: + return "SETTINGS_TIMEOUT"; + case 5: + return "STREAM_CLOSED"; + case 6: + return "FRAME_SIZE_ERROR"; + case 7: + return "REFUSED_STREAM"; + case 8: + return "CANCEL"; + case 9: + return "COMPRESSION_ERROR"; + case 10: + return "CONNECT_ERROR"; + case 11: + return "ENHANCE_YOUR_CALM"; + case 12: + return "INADEQUATE_SECURITY"; + case 13: + return "HTTP_1_1_REQUIRED"; + default: + return "unknown " + err; + } + } + + public static final HttpProcessorFactory Factory = co -> { + if (BuffersUtils.startsWith(co.pendingRead, PRI, 0, PRI.length)) { + co.pendingRead.skip(PRI.length); + return new Http2Processor(co); + } + return null; + }; + + private static abstract class FrameReader { + protected int size; + protected int flags; + protected int id; + + protected FrameReader(int size, int flags, int id) { + this.size = size; + this.flags = flags; + this.id = id; + } + + /** + * @param buf + * @return this or null + * @throws InterruptedException + */ + public abstract FrameReader process(Buffers buf) throws InterruptedException; + } + + private class FrameSettings extends FrameReader { + + private final byte[] b; + + protected FrameSettings(int size, int flags, int id) { + super(size, flags, id); + b = new byte[6]; + } + + @Override + public final FrameReader process(Buffers buf) throws InterruptedException { + while (size > 0 && buf.length() > 6) { + buf.read(b, 0, 6, false); + size -= 6; + + int i = (b[0] & 0xff) << 8 | (b[1] & 0xff); + int v = (b[2] & 0x7f) << 24 | (b[3] & 0xff) << 16 | (b[4] & 0xff) << 8 | (b[5] & 0xff); + + switch (i) { + case 1: + logger.debug(" SETTINGS_HEADER_TABLE_SIZE {}", v); + headers.setMax(v); + break; + case 2: + logger.debug(" SETTINGS_ENABLE_PUSH {}", v); + if (v < 0 || v > 1) + ; // send PROTOCOL_ERROR + allowPush = v == 1; + break; + case 3: + logger.debug(" SETTINGS_MAX_CONCURRENT_STREAMS {}", v); + concurrent = v; + break; + case 4: + logger.debug(" SETTINGS_INITIAL_WINDOW_SIZE {}", v); + initialWindow = v; + break; + case 5: + logger.debug(" SETTINGS_MAX_FRAME_SIZE {}", v); + if (v < 16384 || v > 16777215) + ; // send PROTOCOL_ERROR + frame = v; + break; + case 6: + logger.debug(" SETTINGS_MAX_HEADER_LIST_SIZE {}", v); + headerList = v; + break; + default: + // ignore + } + } + + if (size != 0) + return this; + + byte[] b = new byte[9]; + format(b, 0, 4, 1, 0); + co.pendingWrite.write(b); + co.flush(); + return null; + } + } + + private class FramePing extends FrameReader { + + private final byte[] b; + + protected FramePing(int size, int flags, int id) { + super(size, flags, id); + b = new byte[9 + 8]; + + } + + @Override + public final FrameReader process(Buffers buf) throws InterruptedException { + if (buf.length() < 8) + return this; + + buf.read(b, 9, 8, false); + format(b, 8, 8, 1, 0); + co.pendingWrite.write(b); + co.flush(); + return null; + } + } + + private class FrameWindowUpdate extends FrameReader { + private final byte[] b; + + protected FrameWindowUpdate(int size, int flags, int id) { + super(size, flags, id); + b = new byte[4]; + } + + @Override + public final FrameReader process(Buffers buf) throws InterruptedException { + if (buf.length() < 4) + return this; + buf.read(b, 0, 4, false); + int v = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); + if (v == 0) { + goaway(PROTOCOL_ERROR); + return null; + } + + logger.debug(" window update {}", v); + + Http2FlowControl f = id == 0 ? Http2Processor.this : streams.get(id); + if (f != null) + f.add(v); + return null; + } + } + + private class FrameHeader extends FrameReader { + private final byte[] b; + private int pad = 0; + + protected FrameHeader(int size, int flags, int id) { + super(size, flags, id); + b = new byte[4]; + } + + @Override + public final FrameReader process(Buffers buf) throws InterruptedException { + if ((flags & 0x1) != 0) // END_STREAM + ; // END_STREAM + if ((flags & 0x4) != 0) // END_HEADERS + ; + if ((flags & 0x8) != 0) { // PADDED + if (co.pendingRead.length() < 1) + return this; + pad = (co.pendingRead.read(false) & 0xff); + flags ^= 0x8; + } + + if ((flags & 0x20) != 0) { // PRIORITY + if (co.pendingRead.length() < 5) + return this; + // TODO priority + co.pendingRead.skip(5); + flags ^= 0x20; + } + + return null; + } + } +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java index 384fbd5a..5fd6a21b 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java @@ -1,9 +1,68 @@ package unknow.server.servlet.http2; -public class Http2Stream extends Http2FlowControl { +import java.io.IOException; +import java.net.InetSocketAddress; - protected Http2Stream(int window) { - super(window); +import jakarta.servlet.ServletInputStream; +import unknow.server.servlet.HttpConnection; +import unknow.server.servlet.HttpError; +import unknow.server.servlet.HttpWorker; +import unknow.server.servlet.impl.out.AbstractServletOutput; + +public class Http2Stream extends HttpWorker implements Http2FlowControl { + private int window; + + protected Http2Stream(HttpConnection co, int window) { + super(co); + this.window = window; + } + + @Override + public void add(int v) { + window += v; + if (window < 0) + window = Integer.MAX_VALUE; + } + + @Override + public ServletInputStream createInput() { + // TODO Auto-generated method stub + return null; + } + + @Override + public InetSocketAddress getRemote() { + // TODO Auto-generated method stub + return null; + } + + @Override + public InetSocketAddress getLocal() { + // TODO Auto-generated method stub + return null; } + @Override + public AbstractServletOutput createOutput() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void commit() throws IOException { + // TODO Auto-generated method stub + + } + + @Override + protected void doStart() throws IOException, InterruptedException { + // TODO Auto-generated method stub + + } + + @Override + protected void doDone() { + // TODO Auto-generated method stub + + } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java index a4bdacf9..19499fb5 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java @@ -165,7 +165,7 @@ private void parseParam() { * @throws IOException */ private void parseContentParam(Map> p) throws IOException { - try (BufferedReader r = getReader()) { + try (BufferedReader r = new BufferedReader(new InputStreamReader(co.createInput(), getCharacterEncoding()))) { int c; String key; StringBuilder sb = new StringBuilder(); @@ -180,6 +180,7 @@ private void parseContentParam(Map> p) throws IOException { sb.setLength(0); } while (c != -1); } + contentLength = 0; } private int readParam(StringBuilder sb, Reader r, boolean key) throws IOException { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java index ce84b78c..6aaf0e5e 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java @@ -83,7 +83,7 @@ public final void commit() throws IOException { } public void sendError(int sc, Throwable t, String msg) throws IOException { - checkCommited(); + reset(); status = sc; ServletManager manager = co.ctx().getServletManager(); FilterChain f = manager.getError(sc, t); @@ -98,7 +98,6 @@ public void sendError(int sc, Throwable t, String msg) throws IOException { } r.setAttribute("javax.servlet.error.request_uri", r.getRequestURI()); r.setAttribute("javax.servlet.error.servlet_name", ""); - reset(); try { f.doFilter(r, this); return; @@ -106,7 +105,9 @@ public void sendError(int sc, Throwable t, String msg) throws IOException { logger.error("failed to send error", e); } } - co.sendError(HttpError.fromStatus(sc), t, msg); + try (PrintWriter w = getWriter()) { + w.append("

Error ").append(Integer.toString(sc)).append(" ").append(msg).write("

"); + } } public void close() throws IOException { @@ -154,6 +155,8 @@ public void setContentType(String type) { @Override public String getContentType() { + if (type == null) + return null; if (!type.contains("charset=")) type += "; charset=" + getCharacterEncoding(); return type; @@ -168,7 +171,6 @@ public ServletOutputStream getOutputStream() throws IOException { return stream; } - @SuppressWarnings({ "rawtypes", "unchecked", "resource" }) @Override public PrintWriter getWriter() throws IOException { if (writer != null) diff --git a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java index 86890e36..28d657cd 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java +++ b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java @@ -330,6 +330,8 @@ public void read(Buffers buf, int l, boolean wait) throws InterruptedException { awaitContent(); if (len == 0 || head == null) return; + if (l == -1) + l = len; if (l >= len) { // move all content buf.append(head); @@ -390,6 +392,8 @@ public void read(OutputStream out, int l, boolean wait) throws InterruptedExcept awaitContent(); if (len == 0 || head == null) return; + if (l == -1) + l = len; Chunk last = null; Chunk c = head; From 08c70ba6954b6e086636c4477890dfba5305c7b4 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 24 Mar 2024 20:58:00 +0100 Subject: [PATCH 06/40] gh-30 update bensh workflow --- .github/workflows/bench.yaml | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/bench.yaml b/.github/workflows/bench.yaml index 74d5e9a7..93c338ef 100644 --- a/.github/workflows/bench.yaml +++ b/.github/workflows/bench.yaml @@ -20,9 +20,12 @@ jobs: - name: build native image run: native-image --install-exit-handlers --static --no-fallback -jar unknow-server-test/unknow-server-test-jar/target/server.jar server-native - - name: bench - run: java -jar unknow-server-bench/target/benchmark.jar + - uses: actions/upload-artifact@v4 + if: always() + with: + name: bench + path: unknow-server-bench/target/benchmark.jar - uses: actions/upload-artifact@v4 if: always() with: @@ -32,6 +35,27 @@ jobs: server-native unknow-server-test/*/target/*.war + bench: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/download-artifact@v4 + with: + name: bench + - uses: graalvm/setup-graalvm@v1 + with: + version: latest + java-version: 11 + components: native-image + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: bench + run: java -jar unknow-server-bench/target/benchmark.jar > bench.log + - uses: actions/upload-artifact@v4 + if: always() + with: + name: results-bench + path: bench.log + test: runs-on: ubuntu-latest strategy: @@ -69,7 +93,7 @@ jobs: result: runs-on: ubuntu-latest - needs: test + needs: [test, bench] steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 @@ -78,4 +102,6 @@ jobs: path: out/ merge-multiple: true - name: result - run: bash bench/result.sh out + run: | + cat bench.log + bash bench/result.sh out From 342c6c2f1e77450b7db37f2bd34469c834f282bc Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 24 Mar 2024 20:59:14 +0100 Subject: [PATCH 07/40] gh-30 update bensh workflow --- .github/workflows/bench.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bench.yaml b/.github/workflows/bench.yaml index 93c338ef..46b27c9c 100644 --- a/.github/workflows/bench.yaml +++ b/.github/workflows/bench.yaml @@ -103,5 +103,5 @@ jobs: merge-multiple: true - name: result run: | - cat bench.log - bash bench/result.sh out + cat bench.log + bash bench/result.sh out From e880a9a78298d5242be54bf24d1dd0e34f4721c6 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 24 Mar 2024 21:20:46 +0100 Subject: [PATCH 08/40] gh-30 fix some more --- bench/result.sh | 2 +- .../main/java/unknow/server/servlet/http11/Http11Worker.java | 4 ++-- .../java/unknow/server/servlet/impl/ServletResponseImpl.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bench/result.sh b/bench/result.sh index a8328719..eb514936 100644 --- a/bench/result.sh +++ b/bench/result.sh @@ -32,7 +32,7 @@ parse() done < <(sort -t , -r -k 3,1n "$1" | sort -t , -k 3 -u) } -for i in $1/*; do echo $i; time parse "$i"; done +for i in $1/*; do parse "$i"; done echo echo 'throughput:' diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java index 16e72ad3..0b597ce6 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -125,6 +125,7 @@ public void commit() throws IOException { if (res.getHeader("content-type") == null && res.getContentType() != null) { out.write(CONTENT_TYPE); writeString(out, res.getContentType()); + out.write(CRLF); } @SuppressWarnings("resource") @@ -133,8 +134,7 @@ public void commit() throws IOException { out.write(CONTENT_LENGTH); out.write(Long.toString(res.getContentLength()).getBytes(StandardCharsets.US_ASCII)); out.write(CRLF); - } - if (rawStream instanceof ChunckedOutputStream) + } else if (rawStream instanceof ChunckedOutputStream) out.write(CHUNKED); else out.write(CONTENT_LENGTH0); diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java index 6aaf0e5e..0dd34d95 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java @@ -84,7 +84,6 @@ public final void commit() throws IOException { public void sendError(int sc, Throwable t, String msg) throws IOException { reset(); - status = sc; ServletManager manager = co.ctx().getServletManager(); FilterChain f = manager.getError(sc, t); if (f != null) { @@ -105,6 +104,7 @@ public void sendError(int sc, Throwable t, String msg) throws IOException { logger.error("failed to send error", e); } } + status = sc; try (PrintWriter w = getWriter()) { w.append("

Error ").append(Integer.toString(sc)).append(" ").append(msg).write("

"); } From 1b5a65f8d5162aded04cb312ad680d37eaa2bfb3 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 24 Mar 2024 21:29:24 +0100 Subject: [PATCH 09/40] gh-30 fix bench --- .github/workflows/bench.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bench.yaml b/.github/workflows/bench.yaml index 46b27c9c..6cd61935 100644 --- a/.github/workflows/bench.yaml +++ b/.github/workflows/bench.yaml @@ -49,7 +49,7 @@ jobs: components: native-image github-token: ${{ secrets.GITHUB_TOKEN }} - name: bench - run: java -jar unknow-server-bench/target/benchmark.jar > bench.log + run: java -jar benchmark.jar > bench.log - uses: actions/upload-artifact@v4 if: always() with: From b65d80ae1b44b67296c17defdc4253f18e77c48f Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 24 Mar 2024 22:13:09 +0100 Subject: [PATCH 10/40] gh-30 fix workflow --- .github/workflows/bench.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bench.yaml b/.github/workflows/bench.yaml index 6cd61935..3e4f3169 100644 --- a/.github/workflows/bench.yaml +++ b/.github/workflows/bench.yaml @@ -103,5 +103,5 @@ jobs: merge-multiple: true - name: result run: | - cat bench.log + cat out/bench.log bash bench/result.sh out From 5c32bc0f459461fbfd37b3f1ab80c932b6a1a411 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 24 Mar 2024 22:51:57 +0100 Subject: [PATCH 11/40] gh-30 update workflow --- .github/workflows/bench.yaml | 2 +- bench/result.sh | 2 +- .../main/java/unknow/server/bench/Main.java | 20 ++++++++++++------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/bench.yaml b/.github/workflows/bench.yaml index 3e4f3169..83a78a98 100644 --- a/.github/workflows/bench.yaml +++ b/.github/workflows/bench.yaml @@ -49,7 +49,7 @@ jobs: components: native-image github-token: ${{ secrets.GITHUB_TOKEN }} - name: bench - run: java -jar benchmark.jar > bench.log + run: java -jar benchmark.jar - uses: actions/upload-artifact@v4 if: always() with: diff --git a/bench/result.sh b/bench/result.sh index eb514936..ffc79ca3 100644 --- a/bench/result.sh +++ b/bench/result.sh @@ -32,7 +32,7 @@ parse() done < <(sort -t , -r -k 3,1n "$1" | sort -t , -k 3 -u) } -for i in $1/*; do parse "$i"; done +for i in $1/*.csv; do parse "$i"; done echo echo 'throughput:' diff --git a/unknow-server-bench/src/main/java/unknow/server/bench/Main.java b/unknow-server-bench/src/main/java/unknow/server/bench/Main.java index 28b723b2..5d6d69a5 100644 --- a/unknow-server-bench/src/main/java/unknow/server/bench/Main.java +++ b/unknow-server-bench/src/main/java/unknow/server/bench/Main.java @@ -1,5 +1,9 @@ package unknow.server.bench; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Collection; @@ -13,14 +17,16 @@ public class Main { public static void main(String[] args) throws Exception { - Options o = new OptionsBuilder().forks(1).measurementIterations(10).verbosity(VerboseMode.SILENT).warmupIterations(5).build(); + Options o = new OptionsBuilder().forks(1).measurementIterations(10).verbosity(VerboseMode.NORMAL).warmupIterations(5).build(); - for (Class c : Arrays.asList(XmlBench.class)) { - System.out.println(); - System.out.println(c.getSimpleName()); - Collection result = new Runner(new OptionsBuilder().parent(o).include(c.getName()).build()).run(); - ResultFormatFactory.getInstance(ResultFormatType.TEXT, System.out).writeOut(result); - System.out.println(); + try (PrintStream w = new PrintStream(Files.newOutputStream(Paths.get("bench.log")), false, StandardCharsets.UTF_8)) { + for (Class c : Arrays.asList(XmlBench.class)) { + w.println(); + w.println(c.getSimpleName()); + Collection result = new Runner(new OptionsBuilder().parent(o).include(c.getName()).build()).run(); + ResultFormatFactory.getInstance(ResultFormatType.TEXT, w).writeOut(result); + w.println(); + } } } } From 398bcbcd81ccd04fc8d7bb7ede5951090012fad6 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Tue, 26 Mar 2024 21:01:34 +0100 Subject: [PATCH 12/40] gh-30 http1 fix keep-alive --- .../src/main/java/unknow/server/servlet/HttpConnection.java | 2 +- .../main/java/unknow/server/servlet/http11/Http11Processor.java | 2 +- .../main/java/unknow/server/servlet/http11/Http11Worker.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java index 31dc20d5..aee62bc2 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java @@ -58,7 +58,7 @@ public boolean closed(long now, boolean stop) { if (p != null && !p.isClosed()) return false; - if (keepAliveIdle > 0) { + if (pendingWrite.isEmpty() && keepAliveIdle > 0) { long e = now - keepAliveIdle; if (lastRead() <= e && lastWrite() <= e) return true; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java index 6911208a..dcffd792 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java @@ -26,7 +26,7 @@ public Http11Processor(HttpConnection co) { @Override public final void process() { // nothing to do - if (exec == null) + if (exec == null || exec.isDone()) exec = co.submit(new Http11Worker(co)); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java index 0b597ce6..b61dc318 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -181,7 +181,7 @@ public final void doStart() throws IOException, InterruptedException { out.flush(); } - close = keepAliveIdle == 0 || !"keep-alive".equalsIgnoreCase(req.getHeader("connection")); + close = keepAliveIdle == 0 || "close".equalsIgnoreCase(req.getHeader("connection")); res.setHeader("connection", close ? "close" : "keep-alive"); } From 39baa924ad3d88e70316b1a8f7b04d89d68db17b Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Tue, 26 Mar 2024 22:04:51 +0100 Subject: [PATCH 13/40] gh-30 cleanup --- .../java/unknow/server/servlet/HttpConnection.java | 10 +++++----- .../main/java/unknow/server/servlet/HttpWorker.java | 3 +++ .../unknow/server/servlet/http11/Http11Processor.java | 10 +++++----- .../server/servlet/utils/ServletResourceStatic.java | 2 ++ 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java index aee62bc2..aa7b8f20 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java @@ -47,14 +47,12 @@ public final void onRead() throws InterruptedException { @Override public boolean closed(long now, boolean stop) { - if (!pendingWrite.isEmpty()) - return false; - if (stop) return p == null || p.isClosed(); if (isClosed()) return true; + if (p != null && !p.isClosed()) return false; @@ -70,8 +68,10 @@ public boolean closed(long now, boolean stop) { @Override protected final void onFree() { - p.close(); - p = null; + if (p != null) { + p.close(); + p = null; + } pendingRead.clear(); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java index ecac9835..c18c760b 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java @@ -2,6 +2,8 @@ import java.io.IOException; +import javax.swing.plaf.metal.MetalBorders.Flush3DBorder; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,6 +68,7 @@ public final void run() { } } finally { doDone(); + co.flush(); } } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java index dcffd792..ee279356 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java @@ -1,5 +1,6 @@ package unknow.server.servlet.http11; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import org.slf4j.Logger; @@ -18,7 +19,7 @@ public class Http11Processor implements HttpProcessor { private final HttpConnection co; - private volatile Future exec; + private volatile Future exec = CompletableFuture.completedFuture(null); public Http11Processor(HttpConnection co) { this.co = co; @@ -26,19 +27,18 @@ public Http11Processor(HttpConnection co) { @Override public final void process() { // nothing to do - if (exec == null || exec.isDone()) + if (exec.isDone()) exec = co.submit(new Http11Worker(co)); } @Override public final boolean isClosed() { - return exec == null || exec.isDone(); + return exec.isDone(); } @Override public final void close() { - if (exec != null) - exec.cancel(true); + exec.cancel(true); } public static final HttpProcessorFactory Factory = co -> { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResourceStatic.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResourceStatic.java index 49fb3d26..a8ed08d7 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResourceStatic.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResourceStatic.java @@ -38,6 +38,8 @@ public void init() throws ServletException { this.mimeType = getServletContext().getMimeType(path); try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path)) { + if (is == null) + throw new ServletException("resource " + path + " not found"); int i = 0; int l; while ((l = is.read(data, i, data.length - i)) > 0) From e65c3e481552f476363cf71e2e9d5585adfbed19 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Tue, 26 Mar 2024 22:46:56 +0100 Subject: [PATCH 14/40] gh-30 ?? --- unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java index 28f99b0d..d2892a83 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java @@ -99,9 +99,8 @@ public final void run() { } private final void select(long timeout, boolean close) throws IOException, InterruptedException { - int l = selector.select(timeout); onSelect(close); - if (l == 0) + if (selector.select(timeout) == 0) return; Iterator it = selector.selectedKeys().iterator(); From edf039626c7af6ad6755c8fac30cb5b18ab5e39b Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 7 Apr 2024 13:59:55 +0200 Subject: [PATCH 15/40] gh-30 update ? --- .../src/main/java/unknow/server/nio/NIOConnection.java | 1 + .../src/main/java/unknow/server/servlet/HttpConnection.java | 4 +++- .../java/unknow/server/servlet/http11/Http11Processor.java | 2 +- .../main/java/unknow/server/servlet/http11/Http11Worker.java | 4 ++-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java index a42d43d8..7608204c 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java @@ -98,6 +98,7 @@ protected void onFree() throws IOException { // for override */ protected final void readFrom(SocketChannel channel, ByteBuffer buf) throws InterruptedException, IOException { int l; + lastRead = System.currentTimeMillis(); while (true) { l = channel.read(buf); if (l == -1) { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java index aa7b8f20..360582bc 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java @@ -58,8 +58,10 @@ public boolean closed(long now, boolean stop) { if (pendingWrite.isEmpty() && keepAliveIdle > 0) { long e = now - keepAliveIdle; - if (lastRead() <= e && lastWrite() <= e) + if (lastRead() <= e && lastWrite() <= e) { + logger.info(" keep alive idle reached"); return true; + } } // TODO check request timeout diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java index ee279356..345ce621 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java @@ -26,7 +26,7 @@ public Http11Processor(HttpConnection co) { } @Override - public final void process() { // nothing to do + public final void process() { if (exec.isDone()) exec = co.submit(new Http11Worker(co)); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java index b61dc318..d2c35a0a 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -181,13 +181,13 @@ public final void doStart() throws IOException, InterruptedException { out.flush(); } - close = keepAliveIdle == 0 || "close".equalsIgnoreCase(req.getHeader("connection")); + close = keepAliveIdle == 0 || !"keep-alive".equalsIgnoreCase(req.getHeader("connection")); res.setHeader("connection", close ? "close" : "keep-alive"); } @Override protected void doDone() { - if ("close".equalsIgnoreCase(res.getHeader("connection"))) + if (!"keep-alive".equalsIgnoreCase(res.getHeader("connection"))) co.getOut().close(); } From ee42041276cda689fe98658f22db33341459d472 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 14 Apr 2024 16:38:34 +0200 Subject: [PATCH 16/40] gh-30 update --- bench/result.sh | 6 +- bench/test.jmx | 215 +++++++++--------- .../server/protobuf/ProtoStuffConnection.java | 2 +- .../unknow/server/servlet/HttpWorker.java | 5 +- .../servlet/http11/ChunckedOutputStream.java | 1 - 5 files changed, 111 insertions(+), 118 deletions(-) diff --git a/bench/result.sh b/bench/result.sh index ffc79ca3..77a9ae0b 100644 --- a/bench/result.sh +++ b/bench/result.sh @@ -28,8 +28,10 @@ parse() while read t a n e do - end[$s:$n]=$t - done < <(sort -t , -r -k 3,1n "$1" | sort -t , -k 3 -u) + l=end[$s:$n] + e=$(($t+$a )) + [[ -z $l || $l -lt $n ]] && end[$s:$n]=$e + done < <(sort -t , -r -k 3,1n "$1") } for i in $1/*.csv; do parse "$i"; done diff --git a/bench/test.jmx b/bench/test.jmx index ba620b72..fea490fb 100644 --- a/bench/test.jmx +++ b/bench/test.jmx @@ -1,10 +1,9 @@ - + - - false + true - + host @@ -23,52 +22,41 @@ - false - - - - + + 1000 + 2000 ${host} ${port} + + + HttpClient4 - 1000 - 2000 - + + ${__P(c,10)} + 0 + ${__P(t,60)} + true + true startnextloop - + -1 false - ${__P(c,10)} - 0 - true - ${__P(t,60)} - - false - false - - false - - - + + 6 /missing GET - false - false true - false - false - false - false - 6 - false - 0 + false + + + @@ -93,57 +81,49 @@ - + + 10 + 0 + 60 + true + true startnextloop - + -1 false - 10 - 0 - true - 60 - - false - false - - false - - - + + 6 /test GET - false - false true - false - false - false - false - 6 - false - 0 + false + + + + 10 + 0 + 60 + true + true startnextloop - + -1 false - 10 - 0 - true - 60 - - false - false + 6 + /ws + POST + true true @@ -160,37 +140,27 @@ - /ws - POST - false - false - true - false - false - false - false - 6 - false - 0 - + + 10 + 0 + 60 + true + true startnextloop - + -1 false - 10 - 0 - true - 60 - - false - false + 6 + /rest/q + POST + true true @@ -201,18 +171,6 @@ - /rest/q - POST - false - false - true - false - false - false - false - 6 - false - 0 @@ -239,6 +197,37 @@ + + false + + saveConfig + + + false + true + true + + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + 0 + + + ${out} + + false @@ -276,35 +265,41 @@ - - false + + true saveConfig - false + true true true - false - false - false - false + true + true + true + true false - false - false + true + true false false false - false + true false false false - false + true 0 + true + true + true + true + true + true - ${out} + diff --git a/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java b/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java index c792253a..3a74e887 100644 --- a/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java +++ b/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java @@ -80,7 +80,7 @@ public final void onRead() { process(m); } catch (IOException e) { logger.warn("", e); - getOut().close(); + out.close(); } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java index c18c760b..d344702a 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java @@ -2,8 +2,6 @@ import java.io.IOException; -import javax.swing.plaf.metal.MetalBorders.Flush3DBorder; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,8 +35,6 @@ public ServletContextImpl ctx() { protected abstract void doStart() throws IOException, InterruptedException; -// protected abstract void doRun() throws IOException, InterruptedException; - protected abstract void doDone(); @Override @@ -69,6 +65,7 @@ public final void run() { } finally { doDone(); co.flush(); + co.pendingRead.clear(); } } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java index fcd0c6f8..6f45814a 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java @@ -33,7 +33,6 @@ public ChunckedOutputStream(OutputStream out, ServletResponseImpl res) { @Override protected void afterClose() throws IOException { out.write(END); - out.flush(); } @Override From 29b55942892368a4767b36d91086b0c89fba8bef Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Mon, 15 Apr 2024 22:59:56 +0200 Subject: [PATCH 17/40] gh-30 fix the errors ? --- bench/result.sh | 6 +- bench/test.jmx | 173 ++++++++++++------ .../java/unknow/server/nio/NIOConnection.java | 6 +- .../server/servlet/AccessLogFilter.java | 10 +- .../unknow/server/servlet/HttpConnection.java | 6 +- .../unknow/server/servlet/HttpWorker.java | 20 +- .../server/servlet/http11/Http11Worker.java | 34 +++- .../server/servlet/http2/Http2Stream.java | 4 +- .../servlet/impl/ServletResponseImpl.java | 16 +- 9 files changed, 191 insertions(+), 84 deletions(-) diff --git a/bench/result.sh b/bench/result.sh index 77a9ae0b..ffc79ca3 100644 --- a/bench/result.sh +++ b/bench/result.sh @@ -28,10 +28,8 @@ parse() while read t a n e do - l=end[$s:$n] - e=$(($t+$a )) - [[ -z $l || $l -lt $n ]] && end[$s:$n]=$e - done < <(sort -t , -r -k 3,1n "$1") + end[$s:$n]=$t + done < <(sort -t , -r -k 3,1n "$1" | sort -t , -k 3 -u) } for i in $1/*.csv; do parse "$i"; done diff --git a/bench/test.jmx b/bench/test.jmx index fea490fb..94ccb4f2 100644 --- a/bench/test.jmx +++ b/bench/test.jmx @@ -1,9 +1,9 @@ - + - + true - + host @@ -20,43 +20,80 @@ ${__P(out,result.csv)} = + + c + ${__P(c,1)} + = + + + t + = + ${__P(t,60)} + + false + false - - 1000 - 2000 + + + + + false + = + true + + + ${host} ${port} - - - HttpClient4 + 1000 + 2000 - - ${__P(c,10)} - 0 - ${__P(t,60)} + + + + id + ${__counter(FALSE)} + + + + + + ${c} + 0 + ${t} true true - startnextloop - + stoptestnow + -1 false + false + - - 6 - /missing - GET - true + false - + + /missing + GET + false + false + true + false + false + false + false + 6 + false + 0 @@ -81,49 +118,57 @@ - - 10 - 0 - 60 + + ${c} + 0 + ${t} true true - startnextloop - + stoptestnow + -1 false + false + - - 6 - /test - GET - true + false - + + /test + GET + false + false + true + false + false + false + false + 6 + false + 0 - 10 - 0 - 60 + ${c} + 0 + ${t} true true - startnextloop - + stoptestnow + -1 false + false + - 6 - /ws - POST - true true @@ -140,27 +185,37 @@ + /ws + POST + false + false + true + false + false + false + false + 6 + false + 0 - - 10 - 0 - 60 + + ${c} + 0 + ${t} true true - startnextloop - + stoptestnow + -1 false + false + - 6 - /rest/q - POST - true true @@ -171,6 +226,18 @@ + /rest/q + POST + false + false + true + false + false + false + false + 6 + false + 0 @@ -197,7 +264,7 @@ - + false saveConfig diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java index 7608204c..b61fa5b4 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java @@ -144,11 +144,11 @@ protected final void writeInto(SocketChannel channel, ByteBuffer buf) throws Int } channel.write(buf); - if (buf.hasRemaining()) + if (buf.hasRemaining()) { + pendingWrite.prepend(buf); break; + } } - if (buf.hasRemaining()) // we didn't write all - pendingWrite.prepend(buf); toggleKeyOps(); onWrite(); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/AccessLogFilter.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/AccessLogFilter.java index 5eec0486..67c4a111 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/AccessLogFilter.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/AccessLogFilter.java @@ -154,6 +154,8 @@ else if (t.equals("usec_frac")) }); } + private static final ThreadLocal SB = ThreadLocal.withInitial(() -> new StringBuilder()); + /** format used to log */ private Part[] parts; @@ -169,6 +171,10 @@ protected void setFormat(String format) throws ServletException { @Override public void init(FilterConfig filterConfig) throws ServletException { String f = filterConfig.getInitParameter("format"); + if (f == null) + f = System.getProperty("ACCESS_LOG_FMT"); + if (f == null) + f = System.getenv("ACCESS_LOG_FMT"); setFormat(f == null ? DEFAULT_FMT : f); } @@ -179,15 +185,17 @@ public final void doFilter(ServletRequest request, ServletResponse response, Fil return; } LocalDateTime start = LocalDateTime.now(); + try { chain.doFilter(request, response); } finally { LocalDateTime end = LocalDateTime.now(); - StringBuilder sb = new StringBuilder(); + StringBuilder sb = SB.get(); for (int i = 0; i < parts.length; i++) parts[i].append(sb, start, end, (HttpServletRequest) request, (HttpServletResponse) response); logger.info("{}", sb); + sb.setLength(0); } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java index 360582bc..c468e40d 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java @@ -56,6 +56,11 @@ public boolean closed(long now, boolean stop) { if (p != null && !p.isClosed()) return false; + if (p == null && lastRead() < now - 1000) { + logger.warn(" request timeout"); + return true; + } + if (pendingWrite.isEmpty() && keepAliveIdle > 0) { long e = now - keepAliveIdle; if (lastRead() <= e && lastWrite() <= e) { @@ -74,7 +79,6 @@ protected final void onFree() { p.close(); p = null; } - pendingRead.clear(); } public Future submit(Runnable r) { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java index d344702a..8f79e843 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java @@ -18,8 +18,8 @@ public abstract class HttpWorker implements Runnable, HttpAdapter { protected final HttpConnection co; protected final EventManager events; - protected final ServletRequestImpl req; - protected final ServletResponseImpl res; + protected ServletRequestImpl req; + protected ServletResponseImpl res; public HttpWorker(HttpConnection co) { this.co = co; @@ -33,18 +33,27 @@ public ServletContextImpl ctx() { return co.getCtx(); } - protected abstract void doStart() throws IOException, InterruptedException; + protected abstract boolean doStart() throws IOException, InterruptedException; protected abstract void doDone(); @Override - public final void run() { + public void run() { + doRun(); + } + + protected final void doRun() { try { - doStart(); + if (!doStart()) { + logger.warn("init req failed"); + co.getOut().close(); + return; + } events.fireRequestInitialized(req); FilterChain s = co.getCtx().getServletManager().find(req); try { s.doFilter(req, res); + co.pendingRead.clear(); } catch (UnavailableException e) { // TODO add page with retry-after res.sendError(503, e, null); @@ -65,7 +74,6 @@ public final void run() { } finally { doDone(); co.flush(); - co.pendingRead.clear(); } } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java index d2c35a0a..1127a964 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -12,6 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jakarta.servlet.DispatcherType; import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.Cookie; import unknow.server.nio.NIOConnection.Out; @@ -20,6 +21,7 @@ import unknow.server.servlet.HttpError; import unknow.server.servlet.HttpWorker; import unknow.server.servlet.impl.ServletRequestImpl; +import unknow.server.servlet.impl.ServletResponseImpl; import unknow.server.servlet.impl.in.ChunckedInputStream; import unknow.server.servlet.impl.in.EmptyInputStream; import unknow.server.servlet.impl.in.LengthInputStream; @@ -27,7 +29,7 @@ import unknow.server.util.io.Buffers; import unknow.server.util.io.BuffersUtils; -public class Http11Worker extends HttpWorker { +public final class Http11Worker extends HttpWorker { private static final Logger logger = LoggerFactory.getLogger(Http11Worker.class); private static final byte[] END = new byte[] { '\r', '\n', '\r', '\n' }; @@ -165,24 +167,38 @@ public void commit() throws IOException { // out.write(ERROR_END); // } - @SuppressWarnings("resource") @Override - public final void doStart() throws IOException, InterruptedException { - boolean close = false; + public void run() { + doRun(); + while (!co.pendingRead.isEmpty()) { + this.req = new ServletRequestImpl(this, DispatcherType.REQUEST); + this.res = new ServletResponseImpl(this); + doRun(); + } + } - Out out = co.getOut(); - if (!fillRequest(req)) - return; + @SuppressWarnings("resource") + @Override + public final boolean doStart() throws IOException, InterruptedException { + if (!fillRequest(req)) { + logger.warn("Failed to process request"); + return false; + } if ("100-continue".equals(req.getHeader("expect"))) { + Out out = co.getOut(); out.write(HttpError.CONTINUE.encoded); out.write('\r'); out.write('\n'); out.flush(); } - close = keepAliveIdle == 0 || !"keep-alive".equalsIgnoreCase(req.getHeader("connection")); - res.setHeader("connection", close ? "close" : "keep-alive"); + if (keepAliveIdle != 0 && "keep-alive".equalsIgnoreCase(req.getHeader("connection"))) { + res.setHeader("connection", "keep-alive"); + res.setHeader("keep-alive", "timeout=" + (keepAliveIdle / 1000)); + } else + res.setHeader("connection", "close"); + return true; } @Override diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java index 5fd6a21b..8c13078a 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java @@ -55,9 +55,9 @@ public void commit() throws IOException { } @Override - protected void doStart() throws IOException, InterruptedException { + protected boolean doStart() throws IOException, InterruptedException { // TODO Auto-generated method stub - + return true; } @Override diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java index 0dd34d95..8d6c78cd 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java @@ -83,7 +83,7 @@ public final void commit() throws IOException { } public void sendError(int sc, Throwable t, String msg) throws IOException { - reset(); + reset(false); ServletManager manager = co.ctx().getServletManager(); FilterChain f = manager.getError(sc, t); if (f != null) { @@ -224,12 +224,18 @@ public void resetBuffer() { @Override public void reset() { + reset(true); + } + + public void reset(boolean clearHeader) { resetBuffer(); status = 200; - List list = headers.get("connection"); - headers.clear(); - if (list != null) - headers.put("connection", list); + if (clearHeader) { + List list = headers.get("connection"); + headers.clear(); + if (list != null) + headers.put("connection", list); + } stream = null; writer = null; } From 99f156421cf3ed50533fd4663a2308269e60aaca Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Tue, 16 Apr 2024 18:54:31 +0200 Subject: [PATCH 18/40] gh-30 update & cleanup --- bench/run.sh | 2 +- .../maven/servlet/builder/CreateContext.java | 2 +- .../server/servlet/AbstractHttpServer.java | 10 +-- .../java/unknow/server/servlet/Decode.java | 10 +++ .../unknow/server/servlet/HttpAdapter.java | 5 ++ .../unknow/server/servlet/HttpConnection.java | 17 +++-- .../unknow/server/servlet/HttpProcessor.java | 5 +- .../unknow/server/servlet/HttpWorker.java | 24 ++++--- .../servlet/http11/Http11Processor.java | 8 +++ .../server/servlet/http11/Http11Worker.java | 56 ++++++++++++++-- .../server/servlet/http2/Http2Stream.java | 13 ++-- .../server/servlet/impl/FilterChainImpl.java | 6 ++ .../server/servlet/impl/FilterConfigImpl.java | 4 ++ .../servlet/impl/ServletConfigImpl.java | 1 - .../servlet/impl/ServletContextImpl.java | 35 ++++------ .../servlet/impl/ServletRequestImpl.java | 9 ++- .../servlet/impl/ServletResponseImpl.java | 47 ++++--------- .../servlet/impl/in/ChunckedInputStream.java | 20 ++++++ .../servlet/impl/in/EmptyInputStream.java | 9 ++- .../servlet/impl/in/LengthInputStream.java | 9 ++- .../impl/out/AbstractServletOutput.java | 4 ++ .../unknow/server/servlet/utils/Encoder.java | 5 ++ .../server/servlet/utils/EventManager.java | 66 +++++++++++-------- .../server/servlet/utils/PathTreeTest.java | 2 +- 24 files changed, 246 insertions(+), 123 deletions(-) diff --git a/bench/run.sh b/bench/run.sh index 2365e839..4f9caf7d 100644 --- a/bench/run.sh +++ b/bench/run.sh @@ -45,6 +45,6 @@ echo "Warming up" $JMETER -n -t bench/test.jmx -Jhost=127.0.0.1 -Jt=20 -Jport=8080 -Jout=/dev/null sleep 10 echo "Testing.." -$JMETER -n -t bench/test.jmx -Jhost=127.0.0.1 -Jt=60 -Jport=8080 -Jout=out/$1.csv +$JMETER -n -t bench/test.jmx -Jhost=127.0.0.1 -Jt=60 -Jc=10 -Jport=8080 -Jout=out/$1.csv ${1}_stop sleep 10 diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateContext.java b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateContext.java index 9bfe6d0f..3cc93c0e 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateContext.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/servlet/builder/CreateContext.java @@ -26,7 +26,7 @@ public void add(BuilderContext ctx) { ctx.self().addMethod("createContext", Modifier.Keyword.PROTECTED, Modifier.Keyword.FINAL).setType(t.getClass(ServletContextImpl.class)) .addParameter(String.class, "vhost").addMarkerAnnotation(Override.class).createBody() .addStatement(new ReturnStmt(new ObjectCreationExpr(null, t.getClass(ServletContextImpl.class), - Utils.list(Utils.text(ctx.descriptor().name), new NameExpr("vhost"), Utils.mapString(ctx.descriptor().param, t), Names.SERVLETS, Names.EVENTS, + Utils.list(Utils.text(ctx.descriptor().name), new NameExpr("vhost"), Utils.mapString(ctx.descriptor().param, t), Names.EVENTS, new ObjectCreationExpr(null, t.getClass(ctx.sessionFactory()), Utils.list()), Utils.mapString(ctx.descriptor().localeMapping, t), Utils.mapString(ctx.descriptor().mimeTypes, t))))); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/AbstractHttpServer.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/AbstractHttpServer.java index 3864c5ad..f220fe2c 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/AbstractHttpServer.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/AbstractHttpServer.java @@ -37,7 +37,7 @@ public abstract class AbstractHttpServer extends NIOServerBuilder { /** the servlet context */ protected ServletContextImpl ctx; /** the servlet manager */ - protected ServletManager servlets; + protected ServletManager manager; /** the events */ protected EventManager events; @@ -78,12 +78,12 @@ protected void process(NIOServer server, CommandLine cli) throws Exception { if (value == null) value = address.getHostString(); - servlets = createServletManager(); + manager = createServletManager(); events = createEventManager(); ctx = createContext(value); loadInitializer(); - servlets.initialize(ctx, createServlets(), createFilters()); + manager.initialize(ctx, createServlets(), createFilters()); events.fireContextInitialized(ctx); AtomicInteger i = new AtomicInteger(); @@ -94,7 +94,7 @@ protected void process(NIOServer server, CommandLine cli) throws Exception { return t; }); int keepAliveIdle = parseInt(cli, keepAlive, -1); - server.bind(address, () -> new HttpConnection(executor, ctx, keepAliveIdle)); + server.bind(address, () -> new HttpConnection(executor, ctx, manager, events, keepAliveIdle)); } /** @@ -120,7 +120,7 @@ public void process(String[] arg) throws Exception { } finally { nioServer.stop(); nioServer.await(); - events.fireContextDestroyed(ctx); + events.fireContextDestroyed(); } } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/Decode.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/Decode.java index 69af67c0..caa3bbb2 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/Decode.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/Decode.java @@ -9,6 +9,9 @@ import unknow.server.util.io.Buffers.Walker; +/** + * decode utf8 + */ public final class Decode implements Walker { private final CharsetDecoder d = StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE); private final char[] tmp = new char[2048]; @@ -16,6 +19,9 @@ public final class Decode implements Walker { private final ByteBuffer bbuf = ByteBuffer.allocate(4096); private final StringBuilder sb; + /** + * @param sb where to read data + */ public Decode(StringBuilder sb) { this.sb = sb; } @@ -58,6 +64,10 @@ private void decode() { bbuf.compact(); } + /** + * finish the decoding + * @return true if all data was decoded + */ public boolean done() { try { if (m != 0) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java index f43546aa..60759ed4 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java @@ -6,10 +6,13 @@ import jakarta.servlet.ServletInputStream; import unknow.server.servlet.impl.ServletContextImpl; import unknow.server.servlet.impl.out.AbstractServletOutput; +import unknow.server.servlet.utils.EventManager; public interface HttpAdapter { ServletContextImpl ctx(); + EventManager events(); + ServletInputStream createInput(); InetSocketAddress getRemote(); @@ -19,4 +22,6 @@ public interface HttpAdapter { AbstractServletOutput createOutput(); void commit() throws IOException; + + void sendError(int sc, Throwable t, String msg) throws IOException; } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java index c468e40d..5aa66a69 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java @@ -9,10 +9,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jakarta.servlet.ServletContext; import unknow.server.nio.NIOConnection; import unknow.server.servlet.HttpProcessor.HttpProcessorFactory; import unknow.server.servlet.http11.Http11Processor; import unknow.server.servlet.impl.ServletContextImpl; +import unknow.server.servlet.utils.EventManager; +import unknow.server.servlet.utils.ServletManager; public class HttpConnection extends NIOConnection { private static final Logger logger = LoggerFactory.getLogger(HttpConnection.class); @@ -20,7 +23,9 @@ public class HttpConnection extends NIOConnection { private static final HttpProcessorFactory[] VERSIONS = new HttpProcessorFactory[] { /*Http2Processor.Factory, */Http11Processor.Factory }; private final ExecutorService executor; - private final ServletContextImpl ctx; + protected final ServletContextImpl ctx; + protected final ServletManager manager; + protected final EventManager events; private final int keepAliveIdle; private HttpProcessor p; @@ -29,11 +34,15 @@ public class HttpConnection extends NIOConnection { * create new RequestBuilder * @param executor the executor * @param ctx the servlet context + * @param events + * @param manager */ - protected HttpConnection(ExecutorService executor, ServletContextImpl ctx, int keepAliveIdle) { + protected HttpConnection(ExecutorService executor, ServletContextImpl ctx, ServletManager manager, EventManager events, int keepAliveIdle) { this.executor = executor; - this.keepAliveIdle = keepAliveIdle; this.ctx = ctx; + this.manager = manager; + this.events = events; + this.keepAliveIdle = keepAliveIdle; } @Override @@ -85,7 +94,7 @@ public Future submit(Runnable r) { return executor.submit(r); } - public ServletContextImpl getCtx() { + public ServletContext getCtx() { return ctx; } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java index 6d7cb5fa..6576522b 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java @@ -2,7 +2,10 @@ public interface HttpProcessor { - /** called when we have more data */ + /** + * called when we have more data + * @throws InterruptedException on interrupt + */ void process() throws InterruptedException; /** @return true if the connection is closed */ diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java index 8f79e843..eeb8f58c 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java @@ -12,25 +12,31 @@ import unknow.server.servlet.impl.ServletRequestImpl; import unknow.server.servlet.impl.ServletResponseImpl; import unknow.server.servlet.utils.EventManager; +import unknow.server.servlet.utils.ServletManager; public abstract class HttpWorker implements Runnable, HttpAdapter { private static final Logger logger = LoggerFactory.getLogger(HttpWorker.class); protected final HttpConnection co; - protected final EventManager events; + protected final ServletManager manager; protected ServletRequestImpl req; protected ServletResponseImpl res; public HttpWorker(HttpConnection co) { this.co = co; - this.events = co.getCtx().getEvents(); + this.manager = co.manager; this.req = new ServletRequestImpl(this, DispatcherType.REQUEST); this.res = new ServletResponseImpl(this); } @Override - public ServletContextImpl ctx() { - return co.getCtx(); + public final ServletContextImpl ctx() { + return co.ctx; + } + + @Override + public final EventManager events() { + return co.events; } protected abstract boolean doStart() throws IOException, InterruptedException; @@ -49,20 +55,20 @@ protected final void doRun() { co.getOut().close(); return; } - events.fireRequestInitialized(req); - FilterChain s = co.getCtx().getServletManager().find(req); + co.events.fireRequestInitialized(req); + FilterChain s = manager.find(req); try { s.doFilter(req, res); - co.pendingRead.clear(); } catch (UnavailableException e) { // TODO add page with retry-after - res.sendError(503, e, null); + sendError(503, e, null); } catch (Exception e) { logger.error("failed to service '{}'", s, e); if (!res.isCommitted()) res.sendError(500); } - events.fireRequestDestroyed(req); + co.events.fireRequestDestroyed(req); + req.clearInput(); res.close(); } catch (Exception e) { logger.error("processor error", e); diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java index 345ce621..2e9090cc 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java @@ -10,6 +10,9 @@ import unknow.server.servlet.HttpProcessor; import unknow.server.util.io.BuffersUtils; +/** + * http/1.1 implementation + */ public class Http11Processor implements HttpProcessor { private static final Logger logger = LoggerFactory.getLogger(Http11Processor.class); @@ -21,6 +24,10 @@ public class Http11Processor implements HttpProcessor { private volatile Future exec = CompletableFuture.completedFuture(null); + /** + * new http11 processor + * @param co the connection + */ public Http11Processor(HttpConnection co) { this.co = co; } @@ -41,6 +48,7 @@ public final void close() { exec.cancel(true); } + /** the processor factory */ public static final HttpProcessorFactory Factory = co -> { if (BuffersUtils.indexOf(co.pendingRead, END, 0, MAX_START_SIZE) > 0) return new Http11Processor(co); diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java index 1127a964..933642a8 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -1,6 +1,7 @@ package unknow.server.servlet.http11; import java.io.IOException; +import java.io.PrintWriter; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -13,6 +14,8 @@ import org.slf4j.LoggerFactory; import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.Cookie; import unknow.server.nio.NIOConnection.Out; @@ -26,9 +29,12 @@ import unknow.server.servlet.impl.in.EmptyInputStream; import unknow.server.servlet.impl.in.LengthInputStream; import unknow.server.servlet.impl.out.AbstractServletOutput; +import unknow.server.servlet.utils.EventManager; +import unknow.server.servlet.utils.ServletManager; import unknow.server.util.io.Buffers; import unknow.server.util.io.BuffersUtils; +/** http/1.1 worker */ public final class Http11Worker extends HttpWorker { private static final Logger logger = LoggerFactory.getLogger(Http11Worker.class); @@ -73,6 +79,10 @@ public final class Http11Worker extends HttpWorker { private final StringBuilder sb; private final Decode decode; + /** + * new worker + * @param co the connection + */ public Http11Worker(HttpConnection co) { super(co); this.keepAliveIdle = co.getkeepAlive(); @@ -114,6 +124,38 @@ public AbstractServletOutput createOutput() { return new LengthOutputStream(co.getOut(), res, contentLength); } + @Override + public void sendError(int sc, Throwable t, String msg) throws IOException { + res.reset(false); + FilterChain f = manager.getError(sc, t); + if (f != null) { + ServletRequestImpl r = new ServletRequestImpl(this, DispatcherType.ERROR); + r.setMethod("GET"); + r.setAttribute("javax.servlet.error.status_code", sc); + if (t != null) { + r.setAttribute("javax.servlet.error.exception_type", t.getClass()); + r.setAttribute("javax.servlet.error.message", t.getMessage()); + r.setAttribute("javax.servlet.error.exception", t); + } + r.setAttribute("javax.servlet.error.request_uri", r.getRequestURI()); + r.setAttribute("javax.servlet.error.servlet_name", ""); + try { + f.doFilter(r, res); + return; + } catch (ServletException e) { + logger.error("failed to send error", e); + } + } + res.setStatus(sc); + if (msg == null) { + HttpError e = HttpError.fromStatus(sc); + msg = e == null ? "" : e.message; + } + try (PrintWriter w = res.getWriter()) { + w.append("

Error ").append(Integer.toString(sc)).append(" ").append(msg.replace("<", "<")).write("

"); + } + } + @Override public void commit() throws IOException { HttpError http = HttpError.fromStatus(res.getStatus()); @@ -126,7 +168,7 @@ public void commit() throws IOException { if (res.getHeader("content-type") == null && res.getContentType() != null) { out.write(CONTENT_TYPE); - writeString(out, res.getContentType()); + out.write(res.getContentType().getBytes(StandardCharsets.US_ASCII)); out.write(CRLF); } @@ -136,9 +178,9 @@ public void commit() throws IOException { out.write(CONTENT_LENGTH); out.write(Long.toString(res.getContentLength()).getBytes(StandardCharsets.US_ASCII)); out.write(CRLF); - } else if (rawStream instanceof ChunckedOutputStream) + } else if (rawStream instanceof ChunckedOutputStream) { out.write(CHUNKED); - else + } else out.write(CONTENT_LENGTH0); for (Cookie c : res.getCookies()) @@ -211,7 +253,7 @@ private boolean fillRequest(ServletRequestImpl req) throws InterruptedException, Buffers b = co.pendingRead; int i = BuffersUtils.indexOf(b, SPACE_SLASH, 0, MAX_METHOD_SIZE); if (i < 0) { - res.sendError(HttpError.BAD_REQUEST.code, null, null); + sendError(HttpError.BAD_REQUEST.code, null, null); return false; } BuffersUtils.toString(sb, b, 0, i); @@ -221,7 +263,7 @@ private boolean fillRequest(ServletRequestImpl req) throws InterruptedException, i = BuffersUtils.indexOf(b, SPACE, last, MAX_PATH_SIZE); if (i < 0) { - res.sendError(HttpError.URI_TOO_LONG.code, null, null); + sendError(HttpError.URI_TOO_LONG.code, null, null); return false; } int q = BuffersUtils.indexOf(b, QUESTION, last, i - last); @@ -265,7 +307,7 @@ private boolean fillRequest(ServletRequestImpl req) throws InterruptedException, i = BuffersUtils.indexOf(b, CRLF, last, MAX_VERSION_SIZE); if (i < 0) { - res.sendError(HttpError.BAD_REQUEST.code, null, null); + sendError(HttpError.BAD_REQUEST.code, null, null); return false; } BuffersUtils.toString(sb, b, last, i - last); @@ -277,7 +319,7 @@ private boolean fillRequest(ServletRequestImpl req) throws InterruptedException, while ((i = BuffersUtils.indexOf(b, CRLF, last, MAX_HEADER_SIZE)) > last) { int c = BuffersUtils.indexOf(b, COLON, last, i - last); if (c < 0) { - res.sendError(HttpError.BAD_REQUEST.code, null, null); + sendError(HttpError.BAD_REQUEST.code, null, null); return false; } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java index 8c13078a..a2cee423 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java @@ -5,7 +5,6 @@ import jakarta.servlet.ServletInputStream; import unknow.server.servlet.HttpConnection; -import unknow.server.servlet.HttpError; import unknow.server.servlet.HttpWorker; import unknow.server.servlet.impl.out.AbstractServletOutput; @@ -25,19 +24,19 @@ public void add(int v) { } @Override - public ServletInputStream createInput() { + public InetSocketAddress getRemote() { // TODO Auto-generated method stub return null; } @Override - public InetSocketAddress getRemote() { + public InetSocketAddress getLocal() { // TODO Auto-generated method stub return null; } @Override - public InetSocketAddress getLocal() { + public ServletInputStream createInput() { // TODO Auto-generated method stub return null; } @@ -48,6 +47,12 @@ public AbstractServletOutput createOutput() { return null; } + @Override + public void sendError(int sc, Throwable t, String msg) throws IOException { + // TODO Auto-generated method stub + + } + @Override public void commit() throws IOException { // TODO Auto-generated method stub diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterChainImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterChainImpl.java index 26509ffe..8108d2ff 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterChainImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterChainImpl.java @@ -72,10 +72,16 @@ public String toString() { } } + /** change the request path */ public static class ChangePath implements FilterChain { private final String path; private final FilterChain next; + /** + * change path filter + * @param path the new path + * @param next the next filter + */ public ChangePath(String path, FilterChain next) { this.path = path; this.next = next; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterConfigImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterConfigImpl.java index 2115f577..22e6bb5b 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterConfigImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterConfigImpl.java @@ -42,6 +42,7 @@ public class FilterConfigImpl implements FilterConfig, FilterRegistration { * @param parameters the init param * @param servletMappings the servlet name mappings * @param urlMappings the url mappings + * @param dispatcherTypes the allowed dispatcher types */ public FilterConfigImpl(String name, Filter filter, ServletContext context, ArrayMap parameters, Set servletMappings, Set urlMappings, Set dispatcherTypes) { @@ -126,6 +127,9 @@ public Collection getUrlPatternMappings() { return urlMappings; } + /** + * @return the allowed dispacher types + */ public Collection getDispatcherTypes() { return dispatcherTypes; } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletConfigImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletConfigImpl.java index 7f0306d2..231fbbba 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletConfigImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletConfigImpl.java @@ -33,7 +33,6 @@ public class ServletConfigImpl implements ServletConfig, ServletRegistration { * create new FilterConfigImpl * * @param name the name of this servlet - * @param servlet the servlet * @param context the context * @param parameters the init param * @param mappings the url mappings diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletContextImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletContextImpl.java index 46c055b4..1476318b 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletContextImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletContextImpl.java @@ -7,6 +7,7 @@ import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Enumeration; import java.util.EventListener; import java.util.Locale; @@ -29,7 +30,6 @@ import jakarta.servlet.descriptor.JspConfigDescriptor; import unknow.server.servlet.impl.session.SessionFactory; import unknow.server.servlet.utils.EventManager; -import unknow.server.servlet.utils.ServletManager; import unknow.server.util.data.ArrayMap; /** @@ -45,49 +45,38 @@ public class ServletContextImpl implements ServletContext { private final ArrayMap parameters; private final ArrayMap attributes; - private final ServletManager servlets; private final EventManager events; private final SessionFactory sessions; private final ArrayMap localeEncodings; private final ArrayMap mimeTypes; - private String requestEncoding = "UTF8"; - private String responseEncoding = "UTF8"; + private String requestEncoding = "utf-8"; + private String responseEncoding = "utf-8"; /** * create new ServletContextImpl * * @param name name for this context + * @param vhost the vhost * @param parameters initial parameter - * @param servlets the servlet manager * @param events the event manager + * @param sessions the session factory + * @param localeEncodings the encoding per locale + * @param mimeTypes the mime type per file extentions */ - public ServletContextImpl(String name, String vhost, ArrayMap parameters, ServletManager servlets, EventManager events, SessionFactory sessions, ArrayMap localeEncodings, ArrayMap mimeTypes) { + public ServletContextImpl(String name, String vhost, ArrayMap parameters, EventManager events, SessionFactory sessions, ArrayMap localeEncodings, + ArrayMap mimeTypes) { this.name = name; this.vhost = vhost; this.parameters = parameters; - this.attributes = new ArrayMap<>(); - - this.servlets = servlets; this.events = events; + this.sessions = sessions; this.localeEncodings = localeEncodings; this.mimeTypes = mimeTypes; - } - /** - * @return the events manager - */ - public EventManager getEvents() { - return events; - } - - /** - * @return the servlet manager - */ - public ServletManager getServletManager() { - return servlets; + this.attributes = new ArrayMap<>(); } /** @@ -164,7 +153,7 @@ public void removeAttribute(String name) { @Override public void setAttribute(String name, Object object) { Object old = attributes.put(name, object); - events.fireContextAttribute(this, name, object, old); + events.fireContextAttribute(name, object, old); } @Override diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java index 19499fb5..797761dc 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java @@ -204,6 +204,13 @@ public void setServletPath(String servletPath) { this.query = ""; } + public void clearInput() throws IOException { + if (input == null) + input = co.createInput(); + while (!input.isFinished()) + input.skip(Long.MAX_VALUE); + } + @Override public Object getAttribute(String name) { return attributes.get(name); @@ -222,7 +229,7 @@ public void removeAttribute(String name) { @Override public void setAttribute(String name, Object o) { Object old = attributes.put(name, o); - co.ctx().getEvents().fireRequestAttribute(this, name, o, old); + co.events().fireRequestAttribute(this, name, o, old); } @Override diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java index 8d6c78cd..be072804 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java @@ -82,34 +82,6 @@ public final void commit() throws IOException { co.commit(); } - public void sendError(int sc, Throwable t, String msg) throws IOException { - reset(false); - ServletManager manager = co.ctx().getServletManager(); - FilterChain f = manager.getError(sc, t); - if (f != null) { - ServletRequestImpl r = new ServletRequestImpl(co, DispatcherType.ERROR); - r.setMethod("GET"); - r.setAttribute("javax.servlet.error.status_code", sc); - if (t != null) { - r.setAttribute("javax.servlet.error.exception_type", t.getClass()); - r.setAttribute("javax.servlet.error.message", t.getMessage()); - r.setAttribute("javax.servlet.error.exception", t); - } - r.setAttribute("javax.servlet.error.request_uri", r.getRequestURI()); - r.setAttribute("javax.servlet.error.servlet_name", ""); - try { - f.doFilter(r, this); - return; - } catch (ServletException e) { - logger.error("failed to send error", e); - } - } - status = sc; - try (PrintWriter w = getWriter()) { - w.append("

Error ").append(Integer.toString(sc)).append(" ").append(msg).write("

"); - } - } - public void close() throws IOException { commit(); if (writer != null) @@ -158,7 +130,7 @@ public String getContentType() { if (type == null) return null; if (!type.contains("charset=")) - type += "; charset=" + getCharacterEncoding(); + type += ";charset=" + getCharacterEncoding(); return type; } @@ -227,17 +199,22 @@ public void reset() { reset(true); } - public void reset(boolean clearHeader) { + public void reset(boolean full) { resetBuffer(); status = 200; - if (clearHeader) { + stream = null; + writer = null; + if (full) { List list = headers.get("connection"); headers.clear(); if (list != null) headers.put("connection", list); + charset = null; + type = null; + cookies.clear(); + contentLength = -1; + locale = null; } - stream = null; - writer = null; } @Override @@ -278,12 +255,12 @@ public String encodeRedirectURL(String url) { @Override public void sendError(int sc, String msg) throws IOException { - sendError(sc, null, msg); + co.sendError(sc, null, msg); } @Override public void sendError(int sc) throws IOException { - sendError(sc, null, null); + co.sendError(sc, null, null); } @Override diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/ChunckedInputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/ChunckedInputStream.java index c3ce563a..7da13cab 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/ChunckedInputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/ChunckedInputStream.java @@ -33,6 +33,9 @@ public class ChunckedInputStream extends ServletInputStream { private final StringBuilder sb = new StringBuilder(); + /** + * @param in raw input + */ public ChunckedInputStream(InputStream in) { this.in = in; } @@ -47,6 +50,23 @@ public boolean isReady() { return true; } + @Override + public long skip(long n) throws IOException { + long s = 0; + + while (!isFinished() && n > l) { + s += l; + l = 0; + ensureData(); + } + if (l > n) { + s += n; + o += n; + l -= n; + } + return s; + } + @Override public void setReadListener(ReadListener listener) { this.listener = listener; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/EmptyInputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/EmptyInputStream.java index 9c7006ce..31e4f7f4 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/EmptyInputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/EmptyInputStream.java @@ -12,7 +12,9 @@ * @author unknow */ public class EmptyInputStream extends ServletInputStream { - /** the instance */ + /** + * the instance + */ public static final ServletInputStream INSTANCE = new EmptyInputStream(); private EmptyInputStream() { @@ -41,6 +43,11 @@ public int available() throws IOException { return 0; } + @Override + public long skip(long n) throws IOException { + return 0; + } + @Override public int readLine(byte[] b, int off, int len) throws IOException { return -1; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/LengthInputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/LengthInputStream.java index e9338937..c64b691c 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/LengthInputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/LengthInputStream.java @@ -27,7 +27,14 @@ public LengthInputStream(InputStream in, long length) { @Override public boolean isFinished() { - return length > 0; + return length == 0; + } + + @Override + public long skip(long n) throws IOException { + long skip = in.skip(Math.min(n, length)); + length -= skip; + return skip; } @Override diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java index 6eef2c30..cbddfab7 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java @@ -61,6 +61,10 @@ public final void setBufferSize(int bufferSize) { this.bufferSize = r == 0 ? bufferSize : bufferSize + Buffers.BUF_LEN - r; } + public boolean isClosed() { + return closed; + } + /** * @throws IOException if the stream is closed */ diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Encoder.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Encoder.java index 2a249487..22a2d6b0 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Encoder.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Encoder.java @@ -14,6 +14,11 @@ public class Encoder { private Encoder() { } + /** + * % encode + * @param s the string + * @return the encoded bytes + */ public static byte[] encodePart(String s) { byte[] bytes = s.getBytes(StandardCharsets.UTF_8); int l = bytes.length; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/EventManager.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/EventManager.java index ab028f29..21ec1ea3 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/EventManager.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/EventManager.java @@ -37,16 +37,18 @@ public class EventManager { private final List sessionAttributeListeners; private final List sessionIdListeners; + private ServletContext ctx; + /** * create new EventManager * - * @param contextListeners - * @param contextAttributeListeners - * @param requestListeners - * @param requestAttributeListeners - * @param sessionListeners - * @param sessionAttributeListeners - * @param sessionIdListeners + * @param contextListeners the context listeners + * @param contextAttributeListeners the attribute listeners + * @param requestListeners the request listeners + * @param requestAttributeListeners the request attribute listeners + * @param sessionListeners the session listener + * @param sessionAttributeListeners the session attribute listeners + * @param sessionIdListeners the sessionId listeners */ public EventManager(List contextListeners, List contextAttributeListeners, List requestListeners, List requestAttributeListeners, List sessionListeners, @@ -67,9 +69,12 @@ private static final void error(EventListener l, Exception e) { /** * notify of the context initialization * - * @param context + * @param context the context */ public void fireContextInitialized(ServletContext context) { + if (ctx != null) + throw new IllegalStateException("context already initialized"); + this.ctx = context; ServletContextEvent e = new ServletContextEvent(context); for (ServletContextListener l : contextListeners) { try { @@ -82,11 +87,12 @@ public void fireContextInitialized(ServletContext context) { /** * notify the context destruction - * - * @param context */ - public void fireContextDestroyed(ServletContext context) { - ServletContextEvent e = new ServletContextEvent(context); + public void fireContextDestroyed() { + if (ctx == null) + throw new IllegalStateException("context wasn't initialized"); + + ServletContextEvent e = new ServletContextEvent(ctx); for (ServletContextListener l : contextListeners) { try { l.contextDestroyed(e); @@ -99,20 +105,19 @@ public void fireContextDestroyed(ServletContext context) { /** * fire a change in the context attribute * - * @param context * @param key the key that changed * @param value the new value * @param old the old value */ - public void fireContextAttribute(ServletContext context, String key, Object value, Object old) { + public void fireContextAttribute(String key, Object value, Object old) { if (contextAttributeListeners.isEmpty()) return; if (value == null) - fireContextAttributeRemoved(new ServletContextAttributeEvent(context, key, old)); + fireContextAttributeRemoved(new ServletContextAttributeEvent(ctx, key, old)); else if (old == null) - fireContextAttributeAdded(new ServletContextAttributeEvent(context, key, value)); + fireContextAttributeAdded(new ServletContextAttributeEvent(ctx, key, value)); else - fireContextAttributeReplaced(new ServletContextAttributeEvent(context, key, old)); + fireContextAttributeReplaced(new ServletContextAttributeEvent(ctx, key, old)); } private void fireContextAttributeRemoved(ServletContextAttributeEvent e) { @@ -151,7 +156,7 @@ private void fireContextAttributeReplaced(ServletContextAttributeEvent e) { * @param req the request */ public void fireRequestInitialized(ServletRequest req) { - ServletRequestEvent e = new ServletRequestEvent(req.getServletContext(), req); + ServletRequestEvent e = new ServletRequestEvent(ctx, req); for (ServletRequestListener l : requestListeners) { try { l.requestInitialized(e); @@ -167,7 +172,7 @@ public void fireRequestInitialized(ServletRequest req) { * @param req the request */ public void fireRequestDestroyed(ServletRequest req) { - ServletRequestEvent e = new ServletRequestEvent(req.getServletContext(), req); + ServletRequestEvent e = new ServletRequestEvent(ctx, req); for (ServletRequestListener l : requestListeners) { try { l.requestDestroyed(e); @@ -189,11 +194,11 @@ public void fireRequestAttribute(ServletRequest req, String key, Object value, O if (contextAttributeListeners.isEmpty()) return; if (value == null) - fireRequestAttributeRemoved(new ServletRequestAttributeEvent(req.getServletContext(), req, key, old)); + fireRequestAttributeRemoved(new ServletRequestAttributeEvent(ctx, req, key, old)); else if (old == null) - fireRequestAttributeAdded(new ServletRequestAttributeEvent(req.getServletContext(), req, key, value)); + fireRequestAttributeAdded(new ServletRequestAttributeEvent(ctx, req, key, value)); else - fireRequestAttributeReplaced(new ServletRequestAttributeEvent(req.getServletContext(), req, key, old)); + fireRequestAttributeReplaced(new ServletRequestAttributeEvent(ctx, req, key, old)); } private void fireRequestAttributeRemoved(ServletRequestAttributeEvent e) { @@ -226,20 +231,25 @@ private void fireRequestAttributeReplaced(ServletRequestAttributeEvent e) { } } + /** + * add a listener + * @param listeners type + * @param t the listener + */ public void addListener(T t) { if (t instanceof ServletContextListener) contextListeners.add((ServletContextListener) t); - else if (t instanceof ServletContextAttributeListener) + if (t instanceof ServletContextAttributeListener) contextAttributeListeners.add((ServletContextAttributeListener) t); - else if (t instanceof ServletRequestListener) + if (t instanceof ServletRequestListener) requestListeners.add((ServletRequestListener) t); - else if (t instanceof ServletRequestAttributeListener) + if (t instanceof ServletRequestAttributeListener) requestAttributeListeners.add((ServletRequestAttributeListener) t); - else if (t instanceof HttpSessionListener) + if (t instanceof HttpSessionListener) sessionListeners.add((HttpSessionListener) t); - else if (t instanceof HttpSessionAttributeListener) + if (t instanceof HttpSessionAttributeListener) sessionAttributeListeners.add((HttpSessionAttributeListener) t); - else if (t instanceof HttpSessionIdListener) + if (t instanceof HttpSessionIdListener) sessionIdListeners.add((HttpSessionIdListener) t); } } diff --git a/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeTest.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeTest.java index c178ed1d..ecc3d53f 100644 --- a/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeTest.java +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeTest.java @@ -33,7 +33,7 @@ public class PathTreeTest { @BeforeEach public void init() { path = new ArrayList<>(); - ServletContextImpl ctx = new ServletContextImpl("", "", null, null, null, null, null, null); + ServletContextImpl ctx = new ServletContextImpl("", "", null, null, null, null, null); HttpAdapter p = mock(HttpAdapter.class); when(p.ctx()).thenReturn(ctx); From 20f0cbc542035ff83fef900e1684a45461e21464 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Tue, 16 Apr 2024 18:55:38 +0200 Subject: [PATCH 19/40] gh-30 update test --- bench/test.jmx | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/bench/test.jmx b/bench/test.jmx index 94ccb4f2..8b051abd 100644 --- a/bench/test.jmx +++ b/bench/test.jmx @@ -68,7 +68,7 @@ ${t} true true - stoptestnow + continue -1 false @@ -106,15 +106,11 @@ 8 - - - <title>Not found</title> - - - Assertion.response_data - true - 2 - + + SizeAssertion.response_data + 132 + 1 + @@ -124,7 +120,7 @@ ${t} true true - stoptestnow + continue -1 false @@ -159,7 +155,7 @@ ${t} true true - stoptestnow + continue -1 false @@ -206,7 +202,7 @@ ${t} true true - stoptestnow + continue -1 false From 0b5c05afeba6021e286328143a9be53ad9e02724 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 19 May 2024 13:39:17 +0200 Subject: [PATCH 20/40] gh-30 fix last error ? --- .../java/unknow/server/servlet/http11/Http11Processor.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java index 2e9090cc..9c22a789 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java @@ -40,7 +40,12 @@ public final void process() { @Override public final boolean isClosed() { - return exec.isDone(); + if (!exec.isDone()) + return false; + if (co.pendingRead.isEmpty()) + return true; + exec = co.submit(new Http11Worker(co)); + return false; } @Override From 2870a0c5254ff2455481847b4969e82843067f4c Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Fri, 14 Jun 2024 20:59:05 +0200 Subject: [PATCH 21/40] gh-30 update support for http2 --- .../unknow/server/servlet/HttpConnection.java | 3 +- .../unknow/server/servlet/HttpWorker.java | 47 +++- .../server/servlet/ProcessDoneException.java | 5 - .../server/servlet/http11/Http11Worker.java | 78 +----- .../server/servlet/http2/Http2Headers.java | 195 +++++++++---- .../server/servlet/http2/Http2Huffman.java | 40 +-- .../server/servlet/http2/Http2Processor.java | 256 ++++++++++++++---- .../servlet/http2/Http2ServletInput.java | 83 ++++++ .../servlet/http2/Http2ServletOutput.java | 33 +++ .../server/servlet/http2/Http2Stream.java | 90 ++++-- .../servlet/impl/ServletRequestImpl.java | 80 ++---- .../servlet/impl/ServletResponseImpl.java | 2 +- .../impl/out/AbstractServletOutput.java | 2 +- .../unknow/server/servlet/utils/Encoder.java | 44 --- .../server/servlet/utils/PathUtils.java | 77 ++++++ .../servlet/http2/Http2HeadersTest.java | 37 ++- .../unknow/server/util/data/IntArrayMap.java | 98 +++++++ .../java/unknow/server/util/io/Buffers.java | 48 +++- .../server/util/io/BuffersInputStream.java | 50 +++- 19 files changed, 910 insertions(+), 358 deletions(-) delete mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/ProcessDoneException.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletOutput.java delete mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Encoder.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java index 5aa66a69..7908af7c 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java @@ -13,6 +13,7 @@ import unknow.server.nio.NIOConnection; import unknow.server.servlet.HttpProcessor.HttpProcessorFactory; import unknow.server.servlet.http11.Http11Processor; +import unknow.server.servlet.http2.Http2Processor; import unknow.server.servlet.impl.ServletContextImpl; import unknow.server.servlet.utils.EventManager; import unknow.server.servlet.utils.ServletManager; @@ -20,7 +21,7 @@ public class HttpConnection extends NIOConnection { private static final Logger logger = LoggerFactory.getLogger(HttpConnection.class); - private static final HttpProcessorFactory[] VERSIONS = new HttpProcessorFactory[] { /*Http2Processor.Factory, */Http11Processor.Factory }; + private static final HttpProcessorFactory[] VERSIONS = new HttpProcessorFactory[] { Http2Processor.Factory, Http11Processor.Factory }; private final ExecutorService executor; protected final ServletContextImpl ctx; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java index eeb8f58c..0b6e8305 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java @@ -1,12 +1,15 @@ package unknow.server.servlet; import java.io.IOException; +import java.io.PrintWriter; +import java.net.InetSocketAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jakarta.servlet.DispatcherType; import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; import jakarta.servlet.UnavailableException; import unknow.server.servlet.impl.ServletContextImpl; import unknow.server.servlet.impl.ServletRequestImpl; @@ -39,6 +42,16 @@ public final EventManager events() { return co.events; } + @Override + public InetSocketAddress getRemote() { + return co.getRemote(); + } + + @Override + public InetSocketAddress getLocal() { + return co.getLocal(); + } + protected abstract boolean doStart() throws IOException, InterruptedException; protected abstract void doDone(); @@ -48,6 +61,38 @@ public void run() { doRun(); } + @Override + public void sendError(int sc, Throwable t, String msg) throws IOException { + res.reset(false); + FilterChain f = manager.getError(sc, t); + if (f != null) { + ServletRequestImpl r = new ServletRequestImpl(this, DispatcherType.ERROR); + r.setMethod("GET"); + r.setAttribute("javax.servlet.error.status_code", sc); + if (t != null) { + r.setAttribute("javax.servlet.error.exception_type", t.getClass()); + r.setAttribute("javax.servlet.error.message", t.getMessage()); + r.setAttribute("javax.servlet.error.exception", t); + } + r.setAttribute("javax.servlet.error.request_uri", r.getRequestURI()); + r.setAttribute("javax.servlet.error.servlet_name", ""); + try { + f.doFilter(r, res); + return; + } catch (ServletException e) { + logger.error("failed to send error", e); + } + } + res.setStatus(sc); + if (msg == null) { + HttpError e = HttpError.fromStatus(sc); + msg = e == null ? "" : e.message; + } + try (PrintWriter w = res.getWriter()) { + w.append("

Error ").append(Integer.toString(sc)).append(" ").append(msg.replace("<", "<")).write("

"); + } + } + protected final void doRun() { try { if (!doStart()) { @@ -65,7 +110,7 @@ protected final void doRun() { } catch (Exception e) { logger.error("failed to service '{}'", s, e); if (!res.isCommitted()) - res.sendError(500); + sendError(500, e, null); } co.events.fireRequestDestroyed(req); req.clearInput(); diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/ProcessDoneException.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/ProcessDoneException.java deleted file mode 100644 index 018adc7c..00000000 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/ProcessDoneException.java +++ /dev/null @@ -1,5 +0,0 @@ -//package unknow.server.servlet; -// -//public class ProcessDoneException extends RuntimeException { -// private static final long serialVersionUID = 1L; -//} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java index 933642a8..7163e8e3 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -1,12 +1,9 @@ package unknow.server.servlet.http11; import java.io.IOException; -import java.io.PrintWriter; -import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -14,8 +11,6 @@ import org.slf4j.LoggerFactory; import jakarta.servlet.DispatcherType; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.Cookie; import unknow.server.nio.NIOConnection.Out; @@ -29,8 +24,6 @@ import unknow.server.servlet.impl.in.EmptyInputStream; import unknow.server.servlet.impl.in.LengthInputStream; import unknow.server.servlet.impl.out.AbstractServletOutput; -import unknow.server.servlet.utils.EventManager; -import unknow.server.servlet.utils.ServletManager; import unknow.server.util.io.Buffers; import unknow.server.util.io.BuffersUtils; @@ -91,16 +84,6 @@ public Http11Worker(HttpConnection co) { decode = new Decode(sb); } - @Override - public InetSocketAddress getRemote() { - return co.getRemote(); - } - - @Override - public InetSocketAddress getLocal() { - return co.getLocal(); - } - @SuppressWarnings("resource") @Override public ServletInputStream createInput() { @@ -124,38 +107,6 @@ public AbstractServletOutput createOutput() { return new LengthOutputStream(co.getOut(), res, contentLength); } - @Override - public void sendError(int sc, Throwable t, String msg) throws IOException { - res.reset(false); - FilterChain f = manager.getError(sc, t); - if (f != null) { - ServletRequestImpl r = new ServletRequestImpl(this, DispatcherType.ERROR); - r.setMethod("GET"); - r.setAttribute("javax.servlet.error.status_code", sc); - if (t != null) { - r.setAttribute("javax.servlet.error.exception_type", t.getClass()); - r.setAttribute("javax.servlet.error.message", t.getMessage()); - r.setAttribute("javax.servlet.error.exception", t); - } - r.setAttribute("javax.servlet.error.request_uri", r.getRequestURI()); - r.setAttribute("javax.servlet.error.servlet_name", ""); - try { - f.doFilter(r, res); - return; - } catch (ServletException e) { - logger.error("failed to send error", e); - } - } - res.setStatus(sc); - if (msg == null) { - HttpError e = HttpError.fromStatus(sc); - msg = e == null ? "" : e.message; - } - try (PrintWriter w = res.getWriter()) { - w.append("

Error ").append(Integer.toString(sc)).append(" ").append(msg.replace("<", "<")).write("

"); - } - } - @Override public void commit() throws IOException { HttpError http = HttpError.fromStatus(res.getStatus()); @@ -276,23 +227,6 @@ private boolean fillRequest(ServletRequestImpl req) throws InterruptedException, req.setRequestUri(sb.toString()); sb.setLength(0); - int s; - while ((s = BuffersUtils.indexOf(b, SLASH, last + 1, q - last - 1)) > 0) { - int c = BuffersUtils.indexOf(b, SEMICOLON, last + 1, s - last - 1); - b.walk(decode, last + 1, (c < 0 ? s : c) - last - 1); - if (!decode.done()) - return false; - req.addPath(sb.toString()); - sb.setLength(0); - last = s; - } - if (s == -2 && last + 1 < q) { - int c = BuffersUtils.indexOf(b, SEMICOLON, last + 1, q - last - 1); - BuffersUtils.toString(sb, b, last + 1, c < 0 ? q - last - 1 : c); - req.addPath(sb.toString()); - sb.setLength(0); - } - if (q < i) { BuffersUtils.toString(sb, b, q + 1, i - q - 1); req.setQuery(sb.toString()); @@ -300,9 +234,7 @@ private boolean fillRequest(ServletRequestImpl req) throws InterruptedException, } else req.setQuery(""); - Map> map = new HashMap<>(); - parseParam(map, b, q + 1, i); - req.setQueryParam(map); + parseParam(req.getQueryParam(), b, q + 1, i); last = i + 1; i = BuffersUtils.indexOf(b, CRLF, last, MAX_VERSION_SIZE); @@ -315,7 +247,6 @@ private boolean fillRequest(ServletRequestImpl req) throws InterruptedException, sb.setLength(0); last = i + 2; - map = new HashMap<>(); while ((i = BuffersUtils.indexOf(b, CRLF, last, MAX_HEADER_SIZE)) > last) { int c = BuffersUtils.indexOf(b, COLON, last, i - last); if (c < 0) { @@ -331,14 +262,9 @@ private boolean fillRequest(ServletRequestImpl req) throws InterruptedException, String v = sb.toString().trim(); sb.setLength(0); - List list = map.get(k); - if (list == null) - map.put(k, list = new ArrayList<>(1)); - list.add(v); - + req.addHeader(k, v); last = i + 2; } - req.setHeaders(map); b.skip(last + 2); return true; } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java index 1e530924..f9703c17 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java @@ -1,18 +1,23 @@ package unknow.server.servlet.http2; +import java.io.EOFException; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.LinkedList; import java.util.Objects; import java.util.function.BiConsumer; import unknow.server.util.io.Buffers; -import unknow.server.util.io.BuffersUtils; /** * Manage header encoded in HPACK * implements https://httpwg.org/specs/rfc7541.html */ public class Http2Headers { + private static final int[] MASK = { 0b00000000, 0b00000001, 0b00000011, 0b00000111, 0b00001111, 0b00011111, 0b00111111, 0b01111111, 0b11111111 }; + private static final int[] MAX = { 0, 1, 3, 7, 15, 31, 63, 127, 255 }; + protected static final Entry[] TABLE = new Entry[] { //@formatter:off new Entry(":authority", null), new Entry(":method", "GET"), @@ -90,44 +95,132 @@ public Http2Headers(int maxSize) { this.size = 0; } - public void readHeaders(Buffers b, BiConsumer h) throws InterruptedException, IOException { - while (!b.isEmpty()) - readHeader(b, h); + /** + * read an int + * @param in the data + * @param i the first byte + * @param n the prefix + * @return + * @throws IOException + */ + public static int readInt(InputStream in, int i, int n) throws IOException { + i = i & MASK[n]; + if (i == MAX[n]) { + int m = 0; + int j; + do { + j = in.read(); + if (j == -1) + throw new EOFException(); + i += (j & 0x7F) << m; + m += 7; + } while ((j & 0x10) == 0x10); + } + return i; + } + + /** + * write an int + * @param out the data + * @param p the prefix value + * @param n the prefix length + * @param v the value + * @throws InterruptedException + */ + public static void writeInt(Buffers out, int p, int n, int v) throws InterruptedException { + int m = MAX[n]; + if (v < m) { + out.write(v | p); + return; + } + out.write(p | m); + v -= m; + while (v >= 0x80) { + out.write((v & 0x7F) | 0x80); + v >>= 7; + } + out.write(v); + } + + /** + * write a header to the output + * @param out the output + * @param name the header name + * @param value the header value + * @throws InterruptedException + */ + public void writeHeader(Buffers out, String name, String value) throws InterruptedException { + int o = 0; + for (int i = 0; i < TABLE.length; i++) { + Entry e = TABLE[i]; + if (e.name.equals(name)) { + if (e.value != null && e.value.equals(value)) { + writeInt(out, 0b10000000, 7, i + 1); + return; + } + if (o == 0) + o = i; + } + } + int i = TABLE.length; + for (Entry e : dynamic) { + i++; + if (e.name.equals(name)) { + if (e.value.equals(value)) { + writeInt(out, 0b10000000, 7, i + 1); + return; + } + if (o == 0) + o = i; + } + } + + writeInt(out, 0b01000000, 6, o < 0 ? 0 : o); + if (o < 0) + writeData(out, name); + writeData(out, value); + add(new Entry(name, value)); } - private void readHeader(Buffers b, BiConsumer h) throws InterruptedException, IOException { - int i = b.read(false); + /** + * read one header + * @param in the input + * @param h the header key/value + * @throws IOException in case of error + */ + public void readHeader(InputStream in, BiConsumer h) throws IOException { + int i = in.read(); if (i == -1) - throw new IOException("EOF"); + throw new EOFException(); if ((i & 0b10000000) != 0) { // indexed - Entry e = get(readInt(b, i, 7)); + Entry e = get(readInt(in, i, 7)); h.accept(e.name(), e.value()); } else if ((i & 0b01000000) != 0) { // literal indexed - i = readInt(b, i, 6); + i = readInt(in, i, 6); String n; if (i == 0) { // new name - n = readData(b); + n = readData(in); } else n = get(i).name; - String v = readData(b); + String v = readData(in); h.accept(n, v); add(new Entry(n, v)); } else if ((i & 0b00100000) != 0) { // update size - i = readInt(b, i, 5); + i = readInt(in, i, 5); if (i > settingsSize) ; // TODO error max = i; ensureMax(); } else { // literal non indexed - i = readInt(b, i, 4); + i = readInt(in, i, 4); String n; if (i == 0) { // new name - n = readData(b); + n = readData(in); } else n = get(i).name; - String v = readData(b); + String v = readData(in); h.accept(n, v); } } @@ -137,23 +230,40 @@ public void setMax(int size) { this.max = Math.min(size, max); } - private String readData(Buffers b) throws InterruptedException, IOException { - int i = b.read(false); + private static String readData(InputStream in) throws IOException { + int i = in.read(); if (i == -1) - throw new IOException("EOF"); + throw new EOFException(); boolean huffman = (i & 0x80) == 0x80; - i = readInt(b, i, 7); + i = readInt(in, i, 7); StringBuilder sb = new StringBuilder(); - if (!huffman) { - BuffersUtils.toString(sb, b, 0, i); - b.skip(i); - } else - Http2Huffman.decode(b, i, sb); -// return new EntryData(sb.toString(), i); + if (!huffman) + toString(sb, in, i); + else + Http2Huffman.decode(in, i, sb); return sb.toString(); } + private static void writeData(Buffers out, String value) throws InterruptedException { + byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); + writeInt(out, 0, 7, bytes.length); + out.write(bytes); + +// int i = in.read(); +// if (i == -1) +// throw new EOFException(); +// boolean huffman = (i & 0x80) == 0x80; +// i = readInt(in, i, 7); +// +// StringBuilder sb = new StringBuilder(); +// if (!huffman) +// toString(sb, in, i); +// else +// Http2Huffman.decode(in, i, sb); +// return sb.toString(); + } + protected Entry get(int i) { if (--i < TABLE.length) return TABLE[i]; @@ -172,34 +282,15 @@ protected void ensureMax() { size -= e.size(); } - /** - * read an int - * @param b the data - * @param i the first byte - * @param n the prefix - * @return - * @throws InterruptedException - * @throws IOException - */ - public static int readInt(Buffers b, int i, int n) throws InterruptedException, IOException { - i = i & MASK[n]; - if (i == MAX[n]) { - int m = 0; - int j; - do { - j = b.read(false); - if (j == -1) - throw new IOException("EOF"); - i += (j & 0x7F) << m; - m += 7; - } while ((j & 0x10) == 0x10); + public static void toString(StringBuilder sb, InputStream in, int l) throws IOException { + while (l-- > 0) { + int i = in.read(); + if (i < 0) + throw new EOFException(); + sb.append((char) i); } - return i; } - private static final int[] MASK = { 0b00000000, 0b00000001, 0b00000011, 0b00000111, 0b00001111, 0b00011111, 0b00111111, 0b01111111, 0b11111111 }; - private static final int[] MAX = { 0, 1, 3, 7, 15, 31, 63, 127, 255 }; - public static class Entry { final String name; final String value; @@ -209,10 +300,6 @@ public Entry(String name, String value) { this.value = value; } -// public Entry(String name, String value) { -// this(new EntryData(name, name.length()), value == null ? null : new EntryData(value, value.length())); -// } - public int size() { return (value == null ? name.length() : name.length() + value.length()) + 32; } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java index bd368165..646f7041 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java @@ -1,6 +1,8 @@ package unknow.server.servlet.http2; +import java.io.EOFException; import java.io.IOException; +import java.io.InputStream; import unknow.server.util.io.Buffers; @@ -37,17 +39,17 @@ public class Http2Huffman { 10, 13, 22, 256 // 29, 30 }; - private static int bits(S s, int need) throws InterruptedException, IOException { + private static int bits(S s, int need) throws IOException { int val = s.bit; while (s.cnt < need) { if (--s.max < 0) { if (s.bit != (1 << s.cnt) - 1) - throw new IOException("EOF"); + throw new EOFException(); return -1; } val <<= 8; - val |= s.b.read(false); /* load eight bits */ + val |= s.b.read(); /* load eight bits */ s.cnt += 8; } @@ -60,7 +62,7 @@ private static int bits(S s, int need) throws InterruptedException, IOException return (val & m) >> s.cnt; } - public static String decode(Buffers b, int max, StringBuilder sb) throws InterruptedException, IOException { + public static String decode(InputStream b, int max, StringBuilder sb) throws IOException { S s = new S(b, max); char c; while ((s.max > 0 || s.cnt > 0) && (c = read(s)) != 256) @@ -68,7 +70,11 @@ public static String decode(Buffers b, int max, StringBuilder sb) throws Interru return sb.toString(); } - private static final char read(S s) throws InterruptedException, IOException { +// public static void encode(Buffers b, String value) { +// +// } + + private static final char read(S s) throws IOException { int first = 0; /* first code of length len */ int index = 0; /* index of first code of length len in symbol table */ @@ -95,35 +101,15 @@ private static final char read(S s) throws InterruptedException, IOException { } static final class S { - final Buffers b; + final InputStream b; int max; int bit; int cnt; - public S(Buffers b, int max) { + public S(InputStream b, int max) { this.b = b; this.max = max; } } - - public static void main(String[] arg) throws Exception { -// System.out.println( - Buffers b = new Buffers(); - b.write(0xae); - b.write(0xc3); - b.write(0x77); - b.write(0x1a); - b.write(0x4b); - - b.walk((d, o, l) -> { - for (int i = 0; i < l; i++) - System.out.println(" " + Integer.toString(d[i + o] & 0xFF, 2)); - return true; - }, 0, -1); - - StringBuilder sb = new StringBuilder(); - decode(b, b.length(), sb); - System.out.println(sb); - } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index bdbddd95..a7bc5dc4 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -1,5 +1,7 @@ package unknow.server.servlet.http2; +import java.io.EOFException; +import java.io.IOException; import java.nio.charset.StandardCharsets; import org.slf4j.Logger; @@ -7,8 +9,10 @@ import unknow.server.servlet.HttpConnection; import unknow.server.servlet.HttpProcessor; +import unknow.server.servlet.impl.ServletResponseImpl; import unknow.server.util.data.IntArrayMap; import unknow.server.util.io.Buffers; +import unknow.server.util.io.BuffersInputStream; import unknow.server.util.io.BuffersUtils; public class Http2Processor implements HttpProcessor, Http2FlowControl { @@ -34,6 +38,7 @@ public class Http2Processor implements HttpProcessor, Http2FlowControl { private final HttpConnection co; private final IntArrayMap streams; private final Http2Headers headers; + private final byte[] b; private int window; private boolean allowPush; @@ -45,10 +50,13 @@ public class Http2Processor implements HttpProcessor, Http2FlowControl { private int lastId; + private boolean wantContinuation; + public Http2Processor(HttpConnection co) throws InterruptedException { this.co = co; this.streams = new IntArrayMap<>(); this.headers = new Http2Headers(4096); + this.b = new byte[9]; this.window = 65535; this.allowPush = true; @@ -57,7 +65,7 @@ public Http2Processor(HttpConnection co) throws InterruptedException { this.frame = 16384; this.headerList = Integer.MAX_VALUE; byte[] b = new byte[9]; - format(b, 0, 4, 0, 0); + formatFrame(b, 0, 4, 0, 0); co.pendingWrite.write(b); co.flush(); } @@ -88,10 +96,10 @@ public boolean isClosed() { @Override public void close() { + for (Http2Stream s : streams.values()) + s.close(true); } - private static final byte[] b = new byte[9]; - /** * return true if the frame is done * @param buf @@ -106,36 +114,75 @@ private void readFrame(Buffers buf) throws InterruptedException { int id = (b[5] & 0x7f) << 24 | (b[6] & 0xff) << 16 | (b[7] & 0xff) << 8 | (b[8] & 0xff); logger.debug("{} {} {} {}", size, type, flags, id); + if (wantContinuation && type != 9 || !wantContinuation && type == 9) { + goaway(PROTOCOL_ERROR); + return; + } + wantContinuation = false; + + Http2Stream s; + int pad = 0; switch (type) { case 0: // data + s = streams.get(id); + if (s == null) { + goaway(PROTOCOL_ERROR); + return; + } + + if ((flags & 0x8) == 1) { + pad = buf.read(false); + if (pad >= size) { + goaway(PROTOCOL_ERROR); + return; + } + } + + r = new FrameData(size, flags, id, pad).process(buf); + break; case 1: // header - if (id == 0) { + if (id == 0 || streams.contains(id)) { goaway(PROTOCOL_ERROR); return; } - Http2Stream s = new Http2Stream(co, initialWindow); - streams.set(id, s); + streams.set(id, s = new Http2Stream(co, id, this, initialWindow)); - try { - Buffers b = new Buffers(); - b.read(co.pendingRead, size, false); - headers.readHeaders(co.pendingRead, (k, v) -> logger.debug(" {}: {}", k, v)); - } catch (Exception e) { - logger.error("Failed to parse headers", e); - error(PROTOCOL_ERROR); + if ((flags & 0x1) == 1) // END_STREAM + s.close(false); + if ((flags & 0x8) == 1) { + pad = buf.read(false); + if (pad >= size) { + goaway(PROTOCOL_ERROR); + return; + } + } + if ((flags & 0x20) == 1) { // PRIORITY + // TODO priority + co.pendingRead.skip(5); } - // TODO + r = new FrameHeader(size, flags, id, pad).process(co.pendingRead); + break; case 2: // priority break; case 3: // rst_stream + s = streams.remove(id); + if (s == null) { + goaway(PROTOCOL_ERROR); + return; + } + if (size != 4) { + goaway(FRAME_SIZE_ERROR); + return; + } + co.pendingRead.read(b, 0, 4, false); int err = (b[0] & 0xff) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); - logger.debug("close stream {}, err: {}", id, err); - // TODO close stream id + logger.info("close stream {}, err: {}", id, err); + s.close(true); return; case 4: // settings if ((flags & 0x1) == 1) { @@ -146,6 +193,7 @@ private void readFrame(Buffers buf) throws InterruptedException { } if (id != 0) { goaway(PROTOCOL_ERROR); + return; } if (size % 6 != 0) { goaway(FRAME_SIZE_ERROR); @@ -154,6 +202,7 @@ private void readFrame(Buffers buf) throws InterruptedException { r = new FrameSettings(size, flags, id).process(buf); return; case 5: // push promise + // XXX break; case 6: // ping if (id != 0) { @@ -183,8 +232,18 @@ private void readFrame(Buffers buf) throws InterruptedException { r = new FrameWindowUpdate(size, flags, id).process(buf); return; case 9: // continuation - // id == 0 send PROTOCOL_ERROR - // if previous frame != HEADERS, PUSH_PROMISE or CONTINUATION send PROTOCOL_ERROR + s = streams.get(id); + if (s == null) { + goaway(PROTOCOL_ERROR); + return; + } + + r = new FrameHeader(size, flags, id, pad).process(co.pendingRead); + + wantContinuation = (flags & 0x4) == 0; + if (!wantContinuation) + s.start(); + default: } @@ -193,7 +252,7 @@ private void readFrame(Buffers buf) throws InterruptedException { private void goaway(int err) throws InterruptedException { byte[] b = new byte[17]; - format(b, 8, 7, 0, 0); + formatFrame(b, 8, 7, 0, 0); b[9] = (byte) ((lastId >> 24) & 0x7f); b[10] = (byte) ((lastId >> 16) & 0xff); b[11] = (byte) ((lastId >> 8) & 0xff); @@ -202,11 +261,59 @@ private void goaway(int err) throws InterruptedException { b[14] = (byte) ((err >> 16) & 0xff); b[15] = (byte) ((err >> 8) & 0xff); b[16] = (byte) (err & 0xff); - co.pendingWrite.write(b); - co.flush(); + synchronized (co) { + co.pendingWrite.write(b); + co.flush(); + } } - private void format(byte[] b, int size, int type, int flags, int id) { + @SuppressWarnings("resource") + public void sendHeaders(int id, ServletResponseImpl res) throws InterruptedException { + + Buffers out = new Buffers(); + int type = 1; + synchronized (headers) { + headers.writeHeader(out, ":status", Integer.toString(res.getStatus())); + for (String n : res.getHeaderNames()) { + for (String v : res.getHeaders(n)) { + headers.writeHeader(out, n, v); + + if (out.length() >= frame) { + formatFrame(b, out.length(), type, 0, id); + type = 9; + synchronized (co) { + co.pendingWrite.write(b); + out.read(co.pendingWrite, frame, false); + co.flush(); + } + } + } + } + } + int f = 0x4; + + Http2ServletOutput sout = (Http2ServletOutput) res.getRawStream(); + if (sout == null || sout.isDone()) + f |= 0x1; + formatFrame(b, out.length(), type, f, id); + synchronized (co) { + co.pendingWrite.write(b); + out.read(co.pendingWrite, -1, false); + co.flush(); + } + } + + public void sendData(int id, Buffers data, boolean done) throws InterruptedException { + int l = Math.min(data.length(), frame); + formatFrame(b, l, 0, done ? 0x1 : 0, id); + synchronized (co) { + co.pendingWrite.write(b); + data.read(co.pendingWrite, l, false); + co.flush(); + } + } + + public static void formatFrame(byte[] b, int size, int type, int flags, int id) { b[0] = (byte) ((size >> 16) & 0xff); b[1] = (byte) ((size >> 8) & 0xff); b[2] = (byte) (size & 0xff); @@ -276,6 +383,7 @@ protected FrameReader(int size, int flags, int id) { * @param buf * @return this or null * @throws InterruptedException + * @throws IOException */ public abstract FrameReader process(Buffers buf) throws InterruptedException; } @@ -301,7 +409,9 @@ public final FrameReader process(Buffers buf) throws InterruptedException { switch (i) { case 1: logger.debug(" SETTINGS_HEADER_TABLE_SIZE {}", v); - headers.setMax(v); + synchronized (headers) { + headers.setMax(v); + } break; case 2: logger.debug(" SETTINGS_ENABLE_PUSH {}", v); @@ -336,9 +446,11 @@ public final FrameReader process(Buffers buf) throws InterruptedException { return this; byte[] b = new byte[9]; - format(b, 0, 4, 1, 0); - co.pendingWrite.write(b); - co.flush(); + formatFrame(b, 0, 4, 1, 0); + synchronized (co) { + co.pendingWrite.write(b); + co.flush(); + } return null; } } @@ -359,9 +471,11 @@ public final FrameReader process(Buffers buf) throws InterruptedException { return this; buf.read(b, 9, 8, false); - format(b, 8, 8, 1, 0); - co.pendingWrite.write(b); - co.flush(); + formatFrame(b, 8, 8, 1, 0); + synchronized (co) { + co.pendingWrite.write(b); + co.flush(); + } return null; } } @@ -395,36 +509,86 @@ public final FrameReader process(Buffers buf) throws InterruptedException { } private class FrameHeader extends FrameReader { - private final byte[] b; - private int pad = 0; + private final Http2Stream s; + private final Buffers remain; + private int pad; - protected FrameHeader(int size, int flags, int id) { + protected FrameHeader(int size, int flags, int id, int pad) { super(size, flags, id); - b = new byte[4]; + this.s = streams.get(id); + this.remain = new Buffers(); + this.pad = pad; } + @SuppressWarnings("resource") @Override public final FrameReader process(Buffers buf) throws InterruptedException { - if ((flags & 0x1) != 0) // END_STREAM - ; // END_STREAM - if ((flags & 0x4) != 0) // END_HEADERS - ; - if ((flags & 0x8) != 0) { // PADDED - if (co.pendingRead.length() < 1) + try { + buf.prepend(remain); + BuffersInputStream in = new BuffersInputStream(buf); + try { + synchronized (headers) { + while (in.readCount() < size - pad) { + in.mark(4096); + headers.readHeader(in, s::addHeader); + } + } + } catch (@SuppressWarnings("unused") EOFException e) { + in.writeMark(remain); return this; - pad = (co.pendingRead.read(false) & 0xff); - flags ^= 0x8; + } finally { + size -= in.readCount(); + } + + if (pad > 0) { + pad -= in.skip(pad); + if (pad > 0) + return this; + } + + wantContinuation = (flags & 0x4) == 0; + if (!wantContinuation) + s.start(); + + return null; + } catch (IOException e) { + logger.error("Failed to parse headers", e); + error(PROTOCOL_ERROR); + return null; } + } + } + + private class FrameData extends FrameReader { + private final Http2Stream s; + private int pad; - if ((flags & 0x20) != 0) { // PRIORITY - if (co.pendingRead.length() < 5) + protected FrameData(int size, int flags, int id, int pad) { + super(size - pad, flags, id); + this.s = streams.get(id); + this.pad = pad; + } + + @Override + public FrameReader process(Buffers buf) throws InterruptedException { + int l = Math.min(buf.length(), size); + s.in.read(buf, l); + size -= l; + if (size > 0) + return this; + + if (pad > 0) { + pad -= buf.skip(pad); + if (pad > 0) return this; - // TODO priority - co.pendingRead.skip(5); - flags ^= 0x20; } + if ((flags & 0x1) == 1) { + streams.remove(id); + s.close(false); + } return null; } + } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java new file mode 100644 index 00000000..d6aecca4 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java @@ -0,0 +1,83 @@ +package unknow.server.servlet.http2; + +import java.io.IOException; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import unknow.server.util.io.Buffers; + +public class Http2ServletInput extends ServletInputStream { + private final Buffers in; + private boolean closed; + + public Http2ServletInput() { + this.in = new Buffers(); + } + + public void read(Buffers b, int size) throws InterruptedException { + b.read(in, size, false); + } + + @Override + public void close() { + closed = true; + } + + @Override + public boolean isFinished() { + return closed && in.isEmpty(); + } + + @Override + public boolean isReady() { + return in.length() > 0; + } + + @Override + public void setReadListener(ReadListener readListener) { + ; + } + + @Override + public int read() throws IOException { + try { + while (!closed && in.isEmpty()) + in.wait(); + if (closed && in.isEmpty()) + return -1; + return in.read(false); + } catch (InterruptedException e) { + throw new IOException(e); + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + try { + while (!closed && in.isEmpty()) + in.awaitContent(); + if (closed && in.isEmpty()) + return -1; + return in.read(b, off, len, false); + } catch (InterruptedException e) { + throw new IOException(e); + } + } + + @Override + public int available() throws IOException { + return in.length(); + } + + @Override + public long skip(long n) throws IOException { + try { + while (!closed && in.isEmpty()) + in.awaitContent(); + return in.skip(n); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException(e); + } + } +} \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletOutput.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletOutput.java new file mode 100644 index 00000000..4a1e8579 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletOutput.java @@ -0,0 +1,33 @@ +package unknow.server.servlet.http2; + +import java.io.IOException; + +import unknow.server.servlet.impl.ServletResponseImpl; +import unknow.server.servlet.impl.out.AbstractServletOutput; + +public class Http2ServletOutput extends AbstractServletOutput { + private final Http2Processor p; + private final int id; + + public Http2ServletOutput(ServletResponseImpl res, Http2Processor p, int id) { + super(res); + this.p = p; + this.id = id; + } + + public boolean isDone() { + return isClosed() && buffer.isEmpty(); + } + + @Override + public void flush() throws IOException { + res.commit(); + try { + p.sendData(id, buffer, isClosed()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException(e); + } + } + +} \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java index a2cee423..be52fe7e 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java @@ -1,62 +1,97 @@ package unknow.server.servlet.http2; import java.io.IOException; -import java.net.InetSocketAddress; +import java.io.Reader; +import java.io.StringReader; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; import jakarta.servlet.ServletInputStream; import unknow.server.servlet.HttpConnection; import unknow.server.servlet.HttpWorker; import unknow.server.servlet.impl.out.AbstractServletOutput; +import unknow.server.servlet.utils.PathUtils; public class Http2Stream extends HttpWorker implements Http2FlowControl { + + private final int id; + private final Http2Processor p; + final Http2ServletInput in; + final Http2ServletOutput out; + private int window; - protected Http2Stream(HttpConnection co, int window) { + private volatile Future exec; + + protected Http2Stream(HttpConnection co, int id, Http2Processor p, int window) { super(co); + this.id = id; + this.p = p; + this.in = new Http2ServletInput(); + this.out = new Http2ServletOutput(res, p, id); + this.window = window; + + this.exec = CompletableFuture.completedFuture(null); + + req.setProtocol("HTTP/2"); } - @Override - public void add(int v) { - window += v; - if (window < 0) - window = Integer.MAX_VALUE; + public final void addHeader(String name, String value) { + if (name.charAt(0) != ':') { + req.addHeader(name, value); + return; + } + + if (":method".equals(name)) + req.setMethod(value); + else if (":path".equals(name)) + parsePath(value); } - @Override - public InetSocketAddress getRemote() { - // TODO Auto-generated method stub - return null; + private void parsePath(String path) { + int q = path.indexOf('?'); + if (q > 0) { + req.setQuery(path.substring(q + 1)); + try (Reader r = new StringReader(req.getQueryString())) { + PathUtils.pathQuery(r, req.getQueryParam()); + } catch (@SuppressWarnings("unused") IOException e) { // ok + } + path = path.substring(0, q); + } + req.setRequestUri(path); } - @Override - public InetSocketAddress getLocal() { - // TODO Auto-generated method stub - return null; + public void start() { + exec = co.submit(this); } @Override - public ServletInputStream createInput() { - // TODO Auto-generated method stub - return null; + public void add(int v) { + window += v; + if (window < 0) + window = Integer.MAX_VALUE; } @Override - public AbstractServletOutput createOutput() { - // TODO Auto-generated method stub - return null; + public ServletInputStream createInput() { + return in; } @Override - public void sendError(int sc, Throwable t, String msg) throws IOException { - // TODO Auto-generated method stub - + public AbstractServletOutput createOutput() { + return out; } @Override public void commit() throws IOException { - // TODO Auto-generated method stub + // write header + try { + p.sendHeaders(id, res); + } catch (InterruptedException e) { + throw new IOException(e); + } } @Override @@ -68,6 +103,11 @@ protected boolean doStart() throws IOException, InterruptedException { @Override protected void doDone() { // TODO Auto-generated method stub + } + public final void close(boolean stop) { + in.close(); + if (stop) + exec.cancel(true); } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java index 797761dc..b7ed0749 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java @@ -6,7 +6,6 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.io.Reader; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -45,6 +44,7 @@ import jakarta.servlet.http.Part; import unknow.server.servlet.HttpAdapter; import unknow.server.servlet.impl.session.SessionFactory; +import unknow.server.servlet.utils.PathUtils; import unknow.server.util.data.ArrayMap; /** @@ -68,8 +68,8 @@ public final class ServletRequestImpl implements HttpServletRequest { private String method = null; private String servletPath = null; private String pathInfo = null; - private String query = null; - private Map> queryParam = null; + private String query = ""; + private Map> queryParam = new HashMap<>(); private String encoding = null; private long contentLength = -2; @@ -114,30 +114,38 @@ public void setQuery(String query) { this.query = query; } - public void setQueryParam(Map> query) { - this.queryParam = query; + public Map> getQueryParam() { + return queryParam; } public void setProtocol(String protocol) { this.protocol = protocol; } + public void addHeader(String name, String value) { + headers.computeIfAbsent(name, k -> new ArrayList<>(1)).add(value); + } + public void setHeaders(Map> headers) { this.headers = headers; } public void setRequestUri(String path) { this.requestUri = path; + int j, i = 1; + do { + j = path.indexOf('/', i); + if (j < 0) + j = path.length(); + this.path.add(path.substring(i, j)); + i = j; + } while (j < path.length()); } public List getPaths() { return path; } - public void addPath(String path) { - this.path.add(path); - } - public void setPathInfo(int index) { pathInfoIndex = index; } @@ -166,35 +174,11 @@ private void parseParam() { */ private void parseContentParam(Map> p) throws IOException { try (BufferedReader r = new BufferedReader(new InputStreamReader(co.createInput(), getCharacterEncoding()))) { - int c; - String key; - StringBuilder sb = new StringBuilder(); - do { - c = readParam(sb, r, true); - key = sb.toString(); - sb.setLength(0); - if (c == '=') - c = readParam(sb, r, false); - - p.computeIfAbsent(key, k -> new ArrayList<>(1)).add(sb.toString()); - sb.setLength(0); - } while (c != -1); + PathUtils.pathQuery(r, p); } contentLength = 0; } - private int readParam(StringBuilder sb, Reader r, boolean key) throws IOException { - int c; - while ((c = r.read()) != -1) { - if (c == '&' || key && c == '=') - return c; - if (c == '%') // TODO decode - ; - sb.append((char) c); - } - return c; - } - /** * @param servletPath the servletPath to set */ @@ -674,25 +658,6 @@ public ServletContext getServletContext() { return co.ctx(); } -// private ServletInputStream createInput() { -// String tr = getHeader("transfer-encoding"); -// if ("chunked".equalsIgnoreCase(tr)) -// return new ChunckedInputStream(co.getIn()); -// long l = getContentLengthLong(); -// if (l > 0) -// return new LengthInputStream(co.getIn(), l); -// return EmptyInputStream.INSTANCE; -// } - - private static String getAddr(InetSocketAddress a) { - if (a == null) - return "127.0.0.1"; - InetAddress address = a.getAddress(); - if (address == null) - return "127.0.0.1"; - return address.getHostAddress(); - } - @Override public String getRequestId() { // TODO add value for keep-alive connection return getServletConnection().getConnectionId(); @@ -708,4 +673,13 @@ public ServletConnection getServletConnection() { // TODO Auto-generated method stub return null; } + + private static String getAddr(InetSocketAddress a) { + if (a == null) + return "127.0.0.1"; + InetAddress address = a.getAddress(); + if (address == null) + return "127.0.0.1"; + return address.getHostAddress(); + } } \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java index be072804..86d27fe3 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java @@ -83,11 +83,11 @@ public final void commit() throws IOException { } public void close() throws IOException { - commit(); if (writer != null) writer.close(); if (stream != null) stream.close(); + commit(); } public void checkCommited() { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java index cbddfab7..2f8a3f5d 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java @@ -13,7 +13,7 @@ public abstract class AbstractServletOutput extends ServletOutputStream { /** the buffer */ protected final Buffers buffer; - /** response taht created this stream */ + /** response that created this stream */ protected final ServletResponseImpl res; private int bufferSize; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Encoder.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Encoder.java deleted file mode 100644 index 22a2d6b0..00000000 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/Encoder.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * - */ -package unknow.server.servlet.utils; - -import java.nio.charset.StandardCharsets; - -/** - * @author unknow - */ -public class Encoder { - private static final byte[] HEX = new byte[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - - private Encoder() { - } - - /** - * % encode - * @param s the string - * @return the encoded bytes - */ - public static byte[] encodePart(String s) { - byte[] bytes = s.getBytes(StandardCharsets.UTF_8); - int l = bytes.length; - for (int i = 0; i < bytes.length; i++) { - if (bytes[i] < 0 || bytes[i] == 20) - l += 2; - } - if (l == bytes.length) - return bytes; - byte[] b = new byte[l]; - int j = 0; - for (int i = 0; i < bytes.length; i++) { - byte c = bytes[i]; - if (c < 0 || c == 20) { - b[j++] = '%'; - b[j++] = HEX[c / 16]; - b[j++] = HEX[c % 16]; - } else - b[j++] = c; - } - return b; - } -} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java new file mode 100644 index 00000000..45756417 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java @@ -0,0 +1,77 @@ +/** + * + */ +package unknow.server.servlet.utils; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author unknow + */ +public class PathUtils { + private static final byte[] HEX = new byte[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + private PathUtils() { + } + + /** + * % encode + * @param s the string + * @return the encoded bytes + */ + public static byte[] encodePart(String s) { + byte[] bytes = s.getBytes(StandardCharsets.UTF_8); + int l = bytes.length; + for (int i = 0; i < bytes.length; i++) { + if (bytes[i] < 0 || bytes[i] == 20) + l += 2; + } + if (l == bytes.length) + return bytes; + byte[] b = new byte[l]; + int j = 0; + for (int i = 0; i < bytes.length; i++) { + byte c = bytes[i]; + if (c < 0 || c == 20) { + b[j++] = '%'; + b[j++] = HEX[c / 16]; + b[j++] = HEX[c % 16]; + } else + b[j++] = c; + } + return b; + } + + public static void pathQuery(Reader r, Map> p) throws IOException { + int c; + String key; + StringBuilder sb = new StringBuilder(); + do { + c = readParam(sb, r, true); + key = sb.toString(); + sb.setLength(0); + if (c == '=') + c = readParam(sb, r, false); + + p.computeIfAbsent(key, k -> new ArrayList<>(1)).add(sb.toString()); + sb.setLength(0); + } while (c != -1); + } + + private static int readParam(StringBuilder sb, Reader r, boolean key) throws IOException { + int c; + while ((c = r.read()) != -1) { + if (c == '&' || key && c == '=') + return c; + if (c == '%') // TODO decode + ; + sb.append((char) c); + } + return c; + } +} diff --git a/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java index d0027d42..1d3a5f0f 100644 --- a/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; @@ -13,6 +14,7 @@ import unknow.server.servlet.http2.Http2Headers.Entry; import unknow.server.util.io.Buffers; +import unknow.server.util.io.BuffersInputStream; public class Http2HeadersTest { @@ -20,7 +22,7 @@ public void staticTable() { assertEquals(61, Http2Headers.TABLE.length); } - public static Stream readInt() { + public static Stream integer() { //@formatter:off return Stream.of( Arguments.of(new byte[] { b(0b00001010) }, 5, 10), @@ -29,13 +31,30 @@ public static Stream readInt() { } @ParameterizedTest - @MethodSource + @MethodSource("integer") public void readInt(byte[] data, int prefix, int expected) throws InterruptedException, IOException { Buffers b = new Buffers(); b.write(data); - int value = Http2Headers.readInt(b, b.read(false), prefix); - assertEquals(expected, value); + try (InputStream in = new BuffersInputStream(b)) { + int value = Http2Headers.readInt(in, b.read(false), prefix); + assertEquals(expected, value); + } + } + + @ParameterizedTest + @MethodSource("integer") + public void writeInt(byte[] expected, int prefix, int value) throws InterruptedException { + Buffers b = new Buffers(); + Http2Headers.writeInt(b, 0, prefix, value); + + assertEquals(expected.length, b.length()); + for (int i = 0; i < expected.length; i++) + assertEquals(expected[i]&0xFF, b.get(i)); + } + + public static final byte b(int i) { + return (byte) (i & 0xFF); } public static Stream readHeaders() { @@ -145,8 +164,10 @@ public void readHeaders(Http2Headers h, byte[] data, Entry[] headers, Entry[] ta b.write(data); Map r = new HashMap<>(); - while (b.length() > 0) - h.readHeaders(b, r::put); + try (InputStream in = new BuffersInputStream(b)) { + while (b.length() > 0) + h.readHeader(in, r::put); + } assertEquals(table.length, h.dynamic.size()); int i = 0; @@ -158,10 +179,6 @@ public void readHeaders(Http2Headers h, byte[] data, Entry[] headers, Entry[] ta assertEquals(e.value(), r.get(e.name())); } - private static final byte b(int i) { - return (byte) (i & 0xFF); - } - private static final Entry e(String n, String v) { // return new Entry(new EntryData(n, n.length()), new EntryData(v, v.length())); return new Entry(n, v); diff --git a/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java b/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java index e0346817..236320d1 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java +++ b/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java @@ -42,6 +42,14 @@ public IntArrayMap(int[] key, T[] value) { this.len = key.length; } + /** + * @param key the key + * @return true if the key exists + */ + public boolean contains(int key) { + return Arrays.binarySearch(keys, 0, len, key) >= 0; + } + /** * @param key the key * @return the associated value @@ -128,6 +136,10 @@ public Set keySet() { return new KeySet(); } + public Collection values() { + return new Values(); + } + private class KeySet implements Set { @Override @@ -212,4 +224,90 @@ public Integer next() { } } + + private class Values implements Collection { + + @Override + public int size() { + return len; + } + + @Override + public boolean isEmpty() { + return len == 0; + } + + @Override + public boolean contains(Object o) { + // TODO Auto-generated method stub + return false; + } + + @Override + public Iterator iterator() { + return new ValuesIt(); + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public E[] toArray(E[] a) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean add(T e) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + } + + private class ValuesIt implements Iterator { + private int i = 0; + + @Override + public boolean hasNext() { + return i < len; + } + + @Override + public T next() { + if (i == len) + throw new NoSuchElementException(); + return values[i++]; + } + + } } diff --git a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java index 28d657cd..0c97a3de 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java +++ b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java @@ -179,6 +179,30 @@ public void prepend(ByteBuffer buf) throws InterruptedException { } } + /** + * add data in front of this buffers + * + * @param buf data to append + * @throws InterruptedException on interrupt + */ + public void prepend(Buffers buf) throws InterruptedException { + lock.lockInterruptibly(); + buf.lock.lockInterruptibly(); + try { + if (buf.isEmpty()) + return; + len += buf.len; + buf.tail.next = head; + head = buf.head; + + buf.len = 0; + buf.head = buf.tail = null; + cond.signalAll(); + } finally { + lock.unlock(); + } + } + /** * read one byte * @@ -285,9 +309,18 @@ public boolean read(ByteBuffer bb, boolean wait) throws InterruptedException { } } - private void awaitContent() throws InterruptedException { - while (len == 0) - cond.await(); + /** + * wait for more content to be written + * @throws InterruptedException + */ + public void awaitContent() throws InterruptedException { + lock.lockInterruptibly(); + try { + while (len == 0) + cond.await(); + } finally { + lock.unlock(); + } } /** @@ -427,26 +460,31 @@ public void read(OutputStream out, int l, boolean wait) throws InterruptedExcept * skip l bytes * * @param l number of byte to skip + * @return actual number of byte skiped * @throws InterruptedException on interrupt */ - public void skip(int l) throws InterruptedException { + public long skip(long l) throws InterruptedException { if (l < 0) throw new IllegalArgumentException("length < 0"); if (l == 0) - return; + return 0; + long s = 0; lock.lockInterruptibly(); try { while (head != null && l >= head.l) { l -= head.l; len -= head.l; + s += head.l; head = head.next; } if (head != null) { head.o += l; head.l -= l; len -= l; + s += l; } else tail = null; + return s; } finally { lock.unlock(); } diff --git a/unknow-server-util/src/main/java/unknow/server/util/io/BuffersInputStream.java b/unknow-server-util/src/main/java/unknow/server/util/io/BuffersInputStream.java index 7da27911..8601eeb6 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/io/BuffersInputStream.java +++ b/unknow-server-util/src/main/java/unknow/server/util/io/BuffersInputStream.java @@ -12,24 +12,49 @@ */ public class BuffersInputStream extends InputStream { private final Buffers buffers; + private final boolean wait; + + private long read; private byte[] mark; - private int l = 0; + private int l; /** * create a new input stream * @param buffers the data to read */ public BuffersInputStream(Buffers buffers) { + this(buffers, true); + } + + /** + * create a new input stream + * @param buffers the data to read + * @param wait if false read won't wait for more data + */ + public BuffersInputStream(Buffers buffers, boolean wait) { this.buffers = buffers; + this.wait = wait; + this.read = 0; + this.l = 0; + } + + /** + * @return number of bytes read + */ + public long readCount() { + return read; } @Override public int read() throws IOException { try { - int b = buffers.read(true); - if (mark != null && b > 0) - mark[l++] = (byte) b; + int b = buffers.read(wait); + if (b > 0) { + read++; + if (mark != null) + mark[l++] = (byte) b; + } return b; } catch (InterruptedException e) { Thread.currentThread().interrupt(); @@ -45,15 +70,18 @@ public int read(byte[] b) throws IOException { @Override public int read(byte[] b, int off, int len) throws IOException { try { - len = buffers.read(b, off, len, true); + len = buffers.read(b, off, len, wait); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException(e); } - if (mark != null && len >= 0) { - ensureMark(l + len); - System.arraycopy(b, off, mark, l, len); - l += len; + if (len >= 0) { + read += len; + if (mark != null) { + ensureMark(l + len); + System.arraycopy(b, off, mark, l, len); + l += len; + } } return len; } @@ -83,6 +111,10 @@ public synchronized void reset() throws IOException { mark = null; } + public void writeMark(Buffers b) throws InterruptedException { + b.write(mark, 0, l); + } + private void ensureMark(int len) { if (mark != null && mark.length < len) mark = Arrays.copyOf(mark, len); From 2050f2bcbdfa15d3d8333a1cad41a022cd9df696 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Fri, 14 Jun 2024 22:23:15 +0200 Subject: [PATCH 22/40] gh-30 enable http2 on tomcat & update bench --- bench/prepare.sh | 4 + bench/run.sh | 3 + bench/server.xml | 53 +++++++++++++ .../server/servlet/http2/Http2Processor.java | 78 ++++++++++++++----- .../server/servlet/http2/Http2Stream.java | 5 ++ 5 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 bench/server.xml diff --git a/bench/prepare.sh b/bench/prepare.sh index 1b8fa1fa..b5e19251 100644 --- a/bench/prepare.sh +++ b/bench/prepare.sh @@ -9,6 +9,8 @@ VER=$(wget -q -O - "https://dlcdn.apache.org/tomcat/tomcat-$TOMCAT/" | grep "apache-jmeter-).*(?=.tgz)' | sort -rV | head -1) mkdir -p jmeter wget -O - https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-$VER.tgz | tar xzf - --strip-components=1 -C jmeter @@ -17,3 +19,5 @@ mkdir -p logs # avoid going out of ephemeral port sudo sysctl net.ipv4.tcp_tw_reuse=1 + +sudo apt install nghttp2-client \ No newline at end of file diff --git a/bench/run.sh b/bench/run.sh index 4f9caf7d..6a22c45b 100644 --- a/bench/run.sh +++ b/bench/run.sh @@ -46,5 +46,8 @@ $JMETER -n -t bench/test.jmx -Jhost=127.0.0.1 -Jt=20 -Jport=8080 -Jout=/dev/null sleep 10 echo "Testing.." $JMETER -n -t bench/test.jmx -Jhost=127.0.0.1 -Jt=60 -Jc=10 -Jport=8080 -Jout=out/$1.csv + +h2load -c 10 -t 10 -m 10 -D 60 --warm-up-time=10 http://127.0.0.1:8080/test + ${1}_stop sleep 10 diff --git a/bench/server.xml b/bench/server.xml new file mode 100644 index 00000000..8ff270f8 --- /dev/null +++ b/bench/server.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index a7bc5dc4..7746e59c 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -139,8 +139,7 @@ private void readFrame(Buffers buf) throws InterruptedException { } r = new FrameData(size, flags, id, pad).process(buf); - - break; + return; case 1: // header if (id == 0 || streams.contains(id)) { goaway(PROTOCOL_ERROR); @@ -160,14 +159,14 @@ private void readFrame(Buffers buf) throws InterruptedException { } if ((flags & 0x20) == 1) { // PRIORITY // TODO priority - co.pendingRead.skip(5); + buf.skip(5); } - r = new FrameHeader(size, flags, id, pad).process(co.pendingRead); - - break; + r = new FrameHeader(size, flags, id, pad).process(buf); + return; case 2: // priority - break; + r = new FrameReader(size, flags, id).process(buf); + return; case 3: // rst_stream s = streams.remove(id); if (s == null) { @@ -179,7 +178,7 @@ private void readFrame(Buffers buf) throws InterruptedException { return; } - co.pendingRead.read(b, 0, 4, false); + buf.read(b, 0, 4, false); int err = (b[0] & 0xff) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); logger.info("close stream {}, err: {}", id, err); s.close(true); @@ -203,7 +202,8 @@ private void readFrame(Buffers buf) throws InterruptedException { return; case 5: // push promise // XXX - break; + r = new FrameReader(size, flags, id).process(buf); + return; case 6: // ping if (id != 0) { goaway(PROTOCOL_ERROR); @@ -222,7 +222,7 @@ private void readFrame(Buffers buf) throws InterruptedException { goaway(PROTOCOL_ERROR); return; } - // TODO + r = new FrameGoAway(size, flags, id).process(buf); return; case 8: // window update if (size != 4) { @@ -238,16 +238,15 @@ private void readFrame(Buffers buf) throws InterruptedException { return; } - r = new FrameHeader(size, flags, id, pad).process(co.pendingRead); + r = new FrameHeader(size, flags, id, pad).process(buf); wantContinuation = (flags & 0x4) == 0; if (!wantContinuation) s.start(); - + return; default: + goaway(PROTOCOL_ERROR); } - - co.pendingRead.skip(size); } private void goaway(int err) throws InterruptedException { @@ -269,7 +268,7 @@ private void goaway(int err) throws InterruptedException { @SuppressWarnings("resource") public void sendHeaders(int id, ServletResponseImpl res) throws InterruptedException { - + byte[] b = new byte[9]; Buffers out = new Buffers(); int type = 1; synchronized (headers) { @@ -304,8 +303,17 @@ public void sendHeaders(int id, ServletResponseImpl res) throws InterruptedExcep } public void sendData(int id, Buffers data, boolean done) throws InterruptedException { - int l = Math.min(data.length(), frame); - formatFrame(b, l, 0, done ? 0x1 : 0, id); + byte[] b = new byte[9]; + int l = data.length(); + while (l > frame) { + formatFrame(b, frame, 0, done ? 0x1 : 0, id); + synchronized (co) { + co.pendingWrite.write(b); + data.read(co.pendingWrite, frame, false); + co.flush(); + } + } + formatFrame(b, data.length(), 0, done ? 0x1 : 0, id); synchronized (co) { co.pendingWrite.write(b); data.read(co.pendingWrite, l, false); @@ -368,7 +376,7 @@ private static String error(int err) { return null; }; - private static abstract class FrameReader { + private static class FrameReader { protected int size; protected int flags; protected int id; @@ -385,7 +393,10 @@ protected FrameReader(int size, int flags, int id) { * @throws InterruptedException * @throws IOException */ - public abstract FrameReader process(Buffers buf) throws InterruptedException; + public FrameReader process(Buffers buf) throws InterruptedException { + size -= buf.skip(size); + return size == 0 ? null : this; + } } private class FrameSettings extends FrameReader { @@ -480,6 +491,34 @@ public final FrameReader process(Buffers buf) throws InterruptedException { } } + private class FrameGoAway extends FrameReader { + + private final byte[] b; + + private int lastId = -1; + + protected FrameGoAway(int size, int flags, int id) { + super(size, flags, id); + b = new byte[4]; + } + + public FrameReader process(Buffers buf) throws InterruptedException { + if (lastId >= 0) + return super.process(buf); + if (buf.length() < 8) + return this; + + buf.read(b, 0, 4, false); + lastId = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); + + buf.read(b, 0, 4, false); + int err = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); + logger.info("goaway last: {} err: {}", lastId, error(err)); + size -= 8; + return super.process(buf); + } + } + private class FrameWindowUpdate extends FrameReader { private final byte[] b; @@ -537,6 +576,7 @@ public final FrameReader process(Buffers buf) throws InterruptedException { in.writeMark(remain); return this; } finally { + logger.debug(" header read: {}", in.readCount()); size -= in.readCount(); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java index be52fe7e..ae396e3a 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java @@ -6,6 +6,9 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import jakarta.servlet.ServletInputStream; import unknow.server.servlet.HttpConnection; import unknow.server.servlet.HttpWorker; @@ -13,6 +16,7 @@ import unknow.server.servlet.utils.PathUtils; public class Http2Stream extends HttpWorker implements Http2FlowControl { + private static final Logger logger = LoggerFactory.getLogger(Http2Stream.class); private final int id; private final Http2Processor p; @@ -38,6 +42,7 @@ protected Http2Stream(HttpConnection co, int id, Http2Processor p, int window) { } public final void addHeader(String name, String value) { + logger.debug("addHeader {}: {}", name, value); if (name.charAt(0) != ':') { req.addHeader(name, value); return; From d6b6a1520138ccc7b6e40dd5dff01bca423b171e Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sat, 15 Jun 2024 10:09:51 +0200 Subject: [PATCH 23/40] gh-30 fix memory leak --- .../server/servlet/http2/Http2Processor.java | 18 ++++----- .../unknow/server/util/data/IntArrayMap.java | 37 +++++++++++++++---- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index 7746e59c..03de8986 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -98,6 +98,7 @@ public boolean isClosed() { public void close() { for (Http2Stream s : streams.values()) s.close(true); + streams.clear(); } /** @@ -146,10 +147,12 @@ private void readFrame(Buffers buf) throws InterruptedException { return; } - streams.set(id, s = new Http2Stream(co, id, this, initialWindow)); + s = new Http2Stream(co, id, this, initialWindow); if ((flags & 0x1) == 1) // END_STREAM s.close(false); + else + streams.set(id, s); if ((flags & 0x8) == 1) { pad = buf.read(false); if (pad >= size) { @@ -162,7 +165,7 @@ private void readFrame(Buffers buf) throws InterruptedException { buf.skip(5); } - r = new FrameHeader(size, flags, id, pad).process(buf); + r = new FrameHeader(size, flags, id, pad, s).process(buf); return; case 2: // priority r = new FrameReader(size, flags, id).process(buf); @@ -238,11 +241,7 @@ private void readFrame(Buffers buf) throws InterruptedException { return; } - r = new FrameHeader(size, flags, id, pad).process(buf); - - wantContinuation = (flags & 0x4) == 0; - if (!wantContinuation) - s.start(); + r = new FrameHeader(size, flags, id, pad, s).process(buf); return; default: goaway(PROTOCOL_ERROR); @@ -502,6 +501,7 @@ protected FrameGoAway(int size, int flags, int id) { b = new byte[4]; } + @Override public FrameReader process(Buffers buf) throws InterruptedException { if (lastId >= 0) return super.process(buf); @@ -552,9 +552,9 @@ private class FrameHeader extends FrameReader { private final Buffers remain; private int pad; - protected FrameHeader(int size, int flags, int id, int pad) { + protected FrameHeader(int size, int flags, int id, int pad, Http2Stream s) { super(size, flags, id); - this.s = streams.get(id); + this.s = s; this.remain = new Buffers(); this.pad = pad; } diff --git a/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java b/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java index 236320d1..b8e601f0 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java +++ b/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java @@ -69,15 +69,19 @@ public T get(int key) { public T set(int key, T value) { int i = Arrays.binarySearch(keys, 0, len, key); if (i >= 0) { + T old = values[i]; values[i] = value; - return null; + return old; } ensure(++len); i = -i - 1; - T old = values[i]; + if (i < len - 1) { + System.arraycopy(keys, i, keys, i + 1, len - i - 1); + System.arraycopy(values, i, values, i + 1, len - i - 1); + } keys[i] = key; values[i] = value; - return old; + return null; } /** @@ -91,12 +95,12 @@ public boolean setOnce(int key, T value) { int i = Arrays.binarySearch(keys, 0, len, key); if (i >= 0) return false; - if (i < len) { - System.arraycopy(keys, i, keys, i + 1, len - i); - System.arraycopy(values, i, values, i + 1, len - i); - } ensure(++len); i = -i - 1; + if (i < len - 1) { + System.arraycopy(keys, i, keys, i + 1, len - i - 1); + System.arraycopy(values, i, values, i + 1, len - i - 1); + } keys[i] = key; values[i] = value; return true; @@ -107,7 +111,24 @@ public boolean setOnce(int key, T value) { * @return the removed value */ public T remove(int key) { - return set(key, null); + int i = Arrays.binarySearch(keys, 0, len, key); + if (i < 0) + return null; + T old = values[i]; + len--; + System.arraycopy(keys, i + 1, keys, i, len - i); + System.arraycopy(values, i + 1, values, i, len - i); + values[len] = null; + return old; + } + + /** + * empty the map + */ + public void clear() { + for (int i = 0; i < len; i++) + values[i] = null; + len = 0; } /** From 9ff95b39deeda6cf4127d3a8fd0301c457a1fc28 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sat, 15 Jun 2024 12:31:44 +0200 Subject: [PATCH 24/40] gh-30 remove native image test --- .github/workflows/bench.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bench.yaml b/.github/workflows/bench.yaml index 83a78a98..0ac941c4 100644 --- a/.github/workflows/bench.yaml +++ b/.github/workflows/bench.yaml @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - t: ['cxf', 'tomcat', 'unknow', 'native'] + t: ['cxf', 'tomcat', 'unknow'] needs: build steps: - uses: actions/checkout@v4 From 0d491160878581c576d6658c316b1dbc105d3469 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sat, 15 Jun 2024 18:25:10 +0200 Subject: [PATCH 25/40] gh-30 fix some infinite loop --- .github/workflows/bench.yaml | 2 +- .../java/unknow/server/servlet/impl/ServletRequestImpl.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bench.yaml b/.github/workflows/bench.yaml index 0ac941c4..83a78a98 100644 --- a/.github/workflows/bench.yaml +++ b/.github/workflows/bench.yaml @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - t: ['cxf', 'tomcat', 'unknow'] + t: ['cxf', 'tomcat', 'unknow', 'native'] needs: build steps: - uses: actions/checkout@v4 diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java index b7ed0749..bca9d051 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java @@ -138,8 +138,8 @@ public void setRequestUri(String path) { if (j < 0) j = path.length(); this.path.add(path.substring(i, j)); - i = j; - } while (j < path.length()); + i = j + 1; + } while (i < path.length()); } public List getPaths() { From 9ee8d12143d4f33e717d6ea0c39803ffcd35d31d Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 16 Jun 2024 10:58:41 +0200 Subject: [PATCH 26/40] gh-30 various sonar issue fix --- .../server/maven/jaxb/JaxbGeneratorMojo.java | 4 +- .../maven/jaxws/JaxwsServletBuilder.java | 5 +- .../main/java/unknow/server/nio/NIOLoop.java | 2 +- .../unknow/server/servlet/HttpConnection.java | 14 +- .../unknow/server/servlet/HttpProcessor.java | 8 +- .../unknow/server/servlet/HttpWorker.java | 46 ++--- .../servlet/http11/Http11Processor.java | 7 +- .../server/servlet/http11/Http11Worker.java | 41 ++--- .../server/servlet/http2/Http2Headers.java | 25 +-- .../server/servlet/http2/Http2Huffman.java | 9 +- .../server/servlet/http2/Http2Processor.java | 165 +++++++++--------- .../servlet/http2/Http2ServletInput.java | 7 +- .../server/servlet/http2/Http2Stream.java | 5 +- .../servlet/impl/ServletContextImpl.java | 12 +- .../servlet/impl/ServletCookieConfigImpl.java | 2 + .../servlet/impl/ServletRequestImpl.java | 6 +- .../servlet/impl/ServletResponseImpl.java | 9 +- .../server/servlet/utils/PathUtils.java | 14 +- .../servlet/http2/Http2HeadersTest.java | 3 +- .../unknow/server/util/data/IntArrayMap.java | 7 + 20 files changed, 185 insertions(+), 206 deletions(-) diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/jaxb/JaxbGeneratorMojo.java b/unknow-server-maven/src/main/java/unknow/server/maven/jaxb/JaxbGeneratorMojo.java index eea12a4f..11af8362 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/jaxb/JaxbGeneratorMojo.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/jaxb/JaxbGeneratorMojo.java @@ -26,8 +26,6 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.ResolutionScope; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Modifier; @@ -81,7 +79,7 @@ */ @Mojo(defaultPhase = LifecyclePhase.GENERATE_SOURCES, name = "jaxb-generator", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME) public class JaxbGeneratorMojo extends AbstractGeneratorMojo { - private static final Logger logger = LoggerFactory.getLogger(JaxbGeneratorMojo.class); +// private static final Logger logger = LoggerFactory.getLogger(JaxbGeneratorMojo.class); private static final HandlerBuilder HANDLER = new HandlerBuilder(); diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/jaxws/JaxwsServletBuilder.java b/unknow-server-maven/src/main/java/unknow/server/maven/jaxws/JaxwsServletBuilder.java index 7ce36604..1cfdde53 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/jaxws/JaxwsServletBuilder.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/jaxws/JaxwsServletBuilder.java @@ -14,9 +14,6 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Modifier; import com.github.javaparser.ast.NodeList; @@ -70,7 +67,7 @@ * @author unknow */ public class JaxwsServletBuilder { - private static final Logger logger = LoggerFactory.getLogger(JaxwsServletBuilder.class); +// private static final Logger logger = LoggerFactory.getLogger(JaxwsServletBuilder.class); private final ClassModel serviceClass; diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java index d2892a83..f0b18037 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOLoop.java @@ -86,7 +86,7 @@ public final void run() { try { logger.info("wait {} connection before close", selector.keys().size()); select(500, true); - } catch (Throwable e) { + } catch (Exception e) { logger.error("failed to execute", e); } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java index 7908af7c..0378fd0f 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java @@ -58,28 +58,27 @@ public final void onRead() throws InterruptedException { @Override public boolean closed(long now, boolean stop) { if (stop) - return p == null || p.isClosed(); + return p == null || p.isClosable(stop); if (isClosed()) return true; - if (p != null && !p.isClosed()) + if (p != null && !p.isClosable(stop)) return false; if (p == null && lastRead() < now - 1000) { - logger.warn(" request timeout"); + logger.warn("request timeout {}", this); return true; } if (pendingWrite.isEmpty() && keepAliveIdle > 0) { long e = now - keepAliveIdle; if (lastRead() <= e && lastWrite() <= e) { - logger.info(" keep alive idle reached"); + logger.info("keep alive idle reached {}", this); return true; } } - // TODO check request timeout return false; } @@ -91,8 +90,9 @@ protected final void onFree() { } } - public Future submit(Runnable r) { - return executor.submit(r); + @SuppressWarnings("unchecked") + public Future submit(Runnable r) { + return (Future) executor.submit(r); } public ServletContext getCtx() { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java index 6576522b..2f7fb4bc 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpProcessor.java @@ -8,8 +8,12 @@ public interface HttpProcessor { */ void process() throws InterruptedException; - /** @return true if the connection is closed */ - boolean isClosed(); + /** + * check if the connection can be closed + * @param stop if true the server want to stop + * @return true if the connection is closed + */ + boolean isClosable(boolean stop); /** close the process */ void close(); diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java index 0b6e8305..19a46d59 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpWorker.java @@ -25,7 +25,7 @@ public abstract class HttpWorker implements Runnable, HttpAdapter { protected ServletRequestImpl req; protected ServletResponseImpl res; - public HttpWorker(HttpConnection co) { + protected HttpWorker(HttpConnection co) { this.co = co; this.manager = co.manager; this.req = new ServletRequestImpl(this, DispatcherType.REQUEST); @@ -56,11 +56,6 @@ public InetSocketAddress getLocal() { protected abstract void doDone(); - @Override - public void run() { - doRun(); - } - @Override public void sendError(int sc, Throwable t, String msg) throws IOException { res.reset(false); @@ -93,28 +88,15 @@ public void sendError(int sc, Throwable t, String msg) throws IOException { } } - protected final void doRun() { + @Override + public void run() { try { if (!doStart()) { logger.warn("init req failed"); co.getOut().close(); return; } - co.events.fireRequestInitialized(req); - FilterChain s = manager.find(req); - try { - s.doFilter(req, res); - } catch (UnavailableException e) { - // TODO add page with retry-after - sendError(503, e, null); - } catch (Exception e) { - logger.error("failed to service '{}'", s, e); - if (!res.isCommitted()) - sendError(500, e, null); - } - co.events.fireRequestDestroyed(req); - req.clearInput(); - res.close(); + doRun(); } catch (Exception e) { logger.error("processor error", e); try { @@ -122,9 +104,29 @@ protected final void doRun() { res.sendError(500); } catch (@SuppressWarnings("unused") IOException e1) { //ok } + if (e instanceof InterruptedException) + Thread.currentThread().interrupt(); } finally { doDone(); co.flush(); } } + + private final void doRun() throws IOException { + co.events.fireRequestInitialized(req); + FilterChain s = manager.find(req); + try { + s.doFilter(req, res); + } catch (UnavailableException e) { + // TODO add page with retry-after + sendError(503, e, null); + } catch (Exception e) { + logger.error("failed to service '{}'", s, e); + if (!res.isCommitted()) + sendError(500, e, null); + } + co.events.fireRequestDestroyed(req); + req.clearInput(); + res.close(); + } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java index 9c22a789..de1ec856 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java @@ -3,9 +3,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import unknow.server.servlet.HttpConnection; import unknow.server.servlet.HttpProcessor; import unknow.server.util.io.BuffersUtils; @@ -14,7 +11,7 @@ * http/1.1 implementation */ public class Http11Processor implements HttpProcessor { - private static final Logger logger = LoggerFactory.getLogger(Http11Processor.class); +// private static final Logger logger = LoggerFactory.getLogger(Http11Processor.class); private static final byte[] END = new byte[] { '\r', '\n', '\r', '\n' }; @@ -39,7 +36,7 @@ public final void process() { } @Override - public final boolean isClosed() { + public final boolean isClosable(boolean stop) { if (!exec.isDone()) return false; if (co.pendingRead.isEmpty()) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java index 7163e8e3..41f75e7b 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -31,7 +31,7 @@ public final class Http11Worker extends HttpWorker { private static final Logger logger = LoggerFactory.getLogger(Http11Worker.class); - private static final byte[] END = new byte[] { '\r', '\n', '\r', '\n' }; +// private static final byte[] END = new byte[] { '\r', '\n', '\r', '\n' }; private static final byte[] CRLF = { '\r', '\n' }; private static final byte[] PARAM_SEP = { '&', '=' }; @@ -42,22 +42,22 @@ public final class Http11Worker extends HttpWorker { private static final byte[] CONTENT_LENGTH = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h', ':', ' ' }; private static final byte[] CONTENT_LENGTH0 = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h', ':', ' ', '0', '\r', '\n' }; private static final byte[] CONTENT_TYPE = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 't', 'y', 'p', 'e', ':', ' ' }; - private static final byte[] CONTENT_HTML = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 't', 'y', 'p', 'e', ':', ' ', 't', 'e', 'x', 't', '/', 'h', 't', 'm', 'l', - ';', 'c', 'h', 'a', 'r', 's', 'e', 't', '=', 'u', 't', 'f', '8', '\r', '\n' }; +// private static final byte[] CONTENT_HTML = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 't', 'y', 'p', 'e', ':', ' ', 't', 'e', 'x', 't', '/', 'h', 't', 'm', 'l', +// ';', 'c', 'h', 'a', 'r', 's', 'e', 't', '=', 'u', 't', 'f', '8', '\r', '\n' }; private static final byte[] COOKIE = new byte[] { 's', 'e', 't', '-', 'c', 'o', 'o', 'k', 'i', 'e', ':', ' ' }; private static final byte[] PATH = new byte[] { ';', 'p', 'a', 't', 'h', '=' }; private static final byte[] DOMAIN = new byte[] { ';', 'd', 'o', 'm', 'a', 'i', 'n', '=' }; private static final byte[] MAX_AGE = new byte[] { ';', 'm', 'a', 'x', '-', 'a', 'g', 'e' }; private static final byte[] SECURE = new byte[] { ';', 's', 'e', 'c', 'u', 'r', 'e' }; private static final byte[] HTTP_ONLY = new byte[] { ';', 'h', 't', 't', 'p', 'o', 'n', 'l', 'y' }; - private static final byte[] ERROR_START = new byte[] { '<', 'h', 't', 'm', 'l', '>', '<', 'b', 'o', 'd', 'y', '>', '<', 'h', '1', '>' }; - private static final byte[] ERROR_END = new byte[] { '<', '/', 'h', '1', '>', '<', '/', 'b', 'o', 'd', 'y', '>', '<', '/', 'h', 't', 'm', 'l', '>' }; +// private static final byte[] ERROR_START = new byte[] { '<', 'h', 't', 'm', 'l', '>', '<', 'b', 'o', 'd', 'y', '>', '<', 'h', '1', '>' }; +// private static final byte[] ERROR_END = new byte[] { '<', '/', 'h', '1', '>', '<', '/', 'b', 'o', 'd', 'y', '>', '<', '/', 'h', 't', 'm', 'l', '>' }; private static final byte SPACE = ' '; private static final byte QUESTION = '?'; private static final byte COLON = ':'; - private static final byte SEMICOLON = ';'; - private static final byte SLASH = '/'; +// private static final byte SEMICOLON = ';'; +// private static final byte SLASH = '/'; private static final byte AMPERSAMP = '&'; private static final byte EQUAL = '='; @@ -139,34 +139,13 @@ public void commit() throws IOException { out.write(CRLF); } -// @SuppressWarnings("resource") -// public void sendError(HttpError e, Throwable t, String msg) throws IOException { -// Out out = co.getOut(); -// if (msg == null) { -// out.write(e.empty()); -// return; -// } -// -// out.write(e.encoded); -// byte[] bytes = msg.getBytes(StandardCharsets.UTF_8); -// -// out.write(CONTENT_HTML); -// out.write(CONTENT_LENGTH); -// out.write(Integer.toString(bytes.length + ERROR_START.length + ERROR_END.length).getBytes(StandardCharsets.US_ASCII)); -// out.write(CRLF); -// out.write(CRLF); -// out.write(ERROR_START); -// out.write(bytes); -// out.write(ERROR_END); -// } - @Override public void run() { - doRun(); + super.run(); while (!co.pendingRead.isEmpty()) { this.req = new ServletRequestImpl(this, DispatcherType.REQUEST); this.res = new ServletResponseImpl(this); - doRun(); + super.run(); } } @@ -265,7 +244,7 @@ private boolean fillRequest(ServletRequestImpl req) throws InterruptedException, req.addHeader(k, v); last = i + 2; } - b.skip(last + 2); + b.skip(last + 2L); return true; } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java index f9703c17..8b192809 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java @@ -85,11 +85,11 @@ public class Http2Headers { private int settingsSize; private int size; - private int max; + private int maxSize; public Http2Headers(int maxSize) { this.settingsSize = maxSize; - this.max = settingsSize; + this.maxSize = settingsSize; this.dynamic = new LinkedList<>(); this.size = 0; @@ -210,8 +210,8 @@ public void readHeader(InputStream in, BiConsumer h) throws IOEx } else if ((i & 0b00100000) != 0) { // update size i = readInt(in, i, 5); if (i > settingsSize) - ; // TODO error - max = i; + throw new IOException("Update size > max size"); + maxSize = i; ensureMax(); } else { // literal non indexed i = readInt(in, i, 4); @@ -227,7 +227,7 @@ public void readHeader(InputStream in, BiConsumer h) throws IOEx public void setMax(int size) { this.settingsSize = size; - this.max = Math.min(size, max); + this.maxSize = Math.min(size, maxSize); } private static String readData(InputStream in) throws IOException { @@ -249,19 +249,6 @@ private static void writeData(Buffers out, String value) throws InterruptedExcep byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); writeInt(out, 0, 7, bytes.length); out.write(bytes); - -// int i = in.read(); -// if (i == -1) -// throw new EOFException(); -// boolean huffman = (i & 0x80) == 0x80; -// i = readInt(in, i, 7); -// -// StringBuilder sb = new StringBuilder(); -// if (!huffman) -// toString(sb, in, i); -// else -// Http2Huffman.decode(in, i, sb); -// return sb.toString(); } protected Entry get(int i) { @@ -278,7 +265,7 @@ protected void add(Entry e) { protected void ensureMax() { Entry e; - while (size > max && (e = dynamic.pollLast()) != null) + while (size > maxSize && (e = dynamic.pollLast()) != null) size -= e.size(); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java index 646f7041..7c6e144a 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java @@ -4,8 +4,6 @@ import java.io.IOException; import java.io.InputStream; -import unknow.server.util.io.Buffers; - /** * HPACK static huffman table * https://httpwg.org/specs/rfc7541.html#huffman.code @@ -39,6 +37,9 @@ public class Http2Huffman { 10, 13, 22, 256 // 29, 30 }; + private Http2Huffman() { + } + private static int bits(S s, int need) throws IOException { int val = s.bit; while (s.cnt < need) { @@ -70,10 +71,6 @@ public static String decode(InputStream b, int max, StringBuilder sb) throws IOE return sb.toString(); } -// public static void encode(Buffers b, String value) { -// -// } - private static final char read(S s) throws IOException { int first = 0; /* first code of length len */ int index = 0; /* index of first code of length len in symbol table */ diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index 03de8986..04f13808 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -22,30 +22,31 @@ public class Http2Processor implements HttpProcessor, Http2FlowControl { private static final int NO_ERROR = 0; private static final int PROTOCOL_ERROR = 1; - private static final int INTERNAL_ERROR = 2; - private static final int FLOW_CONTROL_ERROR = 3; - private static final int SETTINGS_TIMEOUT = 4; - private static final int STREAM_CLOSED = 5; +// private static final int INTERNAL_ERROR = 2; +// private static final int FLOW_CONTROL_ERROR = 3; +// private static final int SETTINGS_TIMEOUT = 4; +// private static final int STREAM_CLOSED = 5; private static final int FRAME_SIZE_ERROR = 6; - private static final int REFUSED_STREAM = 7; - private static final int CANCEL = 8; - private static final int COMPRESSION_ERROR = 9; - private static final int CONNECT_ERROR = 10; - private static final int ENHANCE_YOUR_CALM = 12; - private static final int INADEQUATE_SECURITY = 13; - private static final int HTTP_1_1_REQUIRED = 14; +// private static final int REFUSED_STREAM = 7; +// private static final int CANCEL = 8; +// private static final int COMPRESSION_ERROR = 9; +// private static final int CONNECT_ERROR = 10; +// private static final int ENHANCE_YOUR_CALM = 12; +// private static final int INADEQUATE_SECURITY = 13; +// private static final int HTTP_1_1_REQUIRED = 14; private final HttpConnection co; private final IntArrayMap streams; private final Http2Headers headers; private final byte[] b; private int window; + private boolean closing; - private boolean allowPush; - private int concurrent; +// private boolean allowPush; +// private int concurrent; private int initialWindow; private int frame; - private int headerList; +// private int headerList; private FrameReader r; private int lastId; @@ -57,16 +58,18 @@ public Http2Processor(HttpConnection co) throws InterruptedException { this.streams = new IntArrayMap<>(); this.headers = new Http2Headers(4096); this.b = new byte[9]; + this.closing = false; + this.window = 65535; - this.allowPush = true; - this.concurrent = Integer.MAX_VALUE; +// this.allowPush = true; +// this.concurrent = Integer.MAX_VALUE; this.initialWindow = 65535; this.frame = 16384; - this.headerList = Integer.MAX_VALUE; - byte[] b = new byte[9]; +// this.headerList = Integer.MAX_VALUE; + formatFrame(b, 0, 4, 0, 0); - co.pendingWrite.write(b); + co.pendingWrite.write(b, 0, 9); co.flush(); } @@ -90,8 +93,15 @@ public void add(int v) { } @Override - public boolean isClosed() { - return false; + public boolean isClosable(boolean stop) { + if (stop && !closing) { + try { + goaway(NO_ERROR); + closing = true; + } catch (@SuppressWarnings("unused") InterruptedException e) { // ok + } + } + return streams.isEmpty(); } @Override @@ -161,7 +171,6 @@ private void readFrame(Buffers buf) throws InterruptedException { } } if ((flags & 0x20) == 1) { // PRIORITY - // TODO priority buf.skip(5); } @@ -190,7 +199,6 @@ private void readFrame(Buffers buf) throws InterruptedException { if ((flags & 0x1) == 1) { if (size != 0) goaway(FRAME_SIZE_ERROR); - logger.info("SETTINGS ACK"); return; } if (id != 0) { @@ -204,8 +212,7 @@ private void readFrame(Buffers buf) throws InterruptedException { r = new FrameSettings(size, flags, id).process(buf); return; case 5: // push promise - // XXX - r = new FrameReader(size, flags, id).process(buf); + goaway(PROTOCOL_ERROR); return; case 6: // ping if (id != 0) { @@ -249,25 +256,25 @@ private void readFrame(Buffers buf) throws InterruptedException { } private void goaway(int err) throws InterruptedException { - byte[] b = new byte[17]; - formatFrame(b, 8, 7, 0, 0); - b[9] = (byte) ((lastId >> 24) & 0x7f); - b[10] = (byte) ((lastId >> 16) & 0xff); - b[11] = (byte) ((lastId >> 8) & 0xff); - b[12] = (byte) (lastId & 0xff); - b[13] = (byte) ((err >> 24) & 0x7f); - b[14] = (byte) ((err >> 16) & 0xff); - b[15] = (byte) ((err >> 8) & 0xff); - b[16] = (byte) (err & 0xff); + byte[] f = new byte[17]; + formatFrame(f, 8, 7, 0, 0); + f[9] = (byte) ((lastId >> 24) & 0x7f); + f[10] = (byte) ((lastId >> 16) & 0xff); + f[11] = (byte) ((lastId >> 8) & 0xff); + f[12] = (byte) (lastId & 0xff); + f[13] = (byte) ((err >> 24) & 0x7f); + f[14] = (byte) ((err >> 16) & 0xff); + f[15] = (byte) ((err >> 8) & 0xff); + f[16] = (byte) (err & 0xff); synchronized (co) { - co.pendingWrite.write(b); + co.pendingWrite.write(f); co.flush(); } } @SuppressWarnings("resource") public void sendHeaders(int id, ServletResponseImpl res) throws InterruptedException { - byte[] b = new byte[9]; + byte[] f = new byte[9]; Buffers out = new Buffers(); int type = 1; synchronized (headers) { @@ -277,10 +284,10 @@ public void sendHeaders(int id, ServletResponseImpl res) throws InterruptedExcep headers.writeHeader(out, n, v); if (out.length() >= frame) { - formatFrame(b, out.length(), type, 0, id); + formatFrame(f, out.length(), type, 0, id); type = 9; synchronized (co) { - co.pendingWrite.write(b); + co.pendingWrite.write(f); out.read(co.pendingWrite, frame, false); co.flush(); } @@ -288,33 +295,31 @@ public void sendHeaders(int id, ServletResponseImpl res) throws InterruptedExcep } } } - int f = 0x4; - + int flag = 0x4; Http2ServletOutput sout = (Http2ServletOutput) res.getRawStream(); if (sout == null || sout.isDone()) - f |= 0x1; - formatFrame(b, out.length(), type, f, id); + flag |= 0x1; + formatFrame(f, out.length(), type, flag, id); synchronized (co) { - co.pendingWrite.write(b); + co.pendingWrite.write(f); out.read(co.pendingWrite, -1, false); co.flush(); } } public void sendData(int id, Buffers data, boolean done) throws InterruptedException { - byte[] b = new byte[9]; + byte[] f = new byte[9]; int l = data.length(); while (l > frame) { - formatFrame(b, frame, 0, done ? 0x1 : 0, id); + formatFrame(f, frame, 0, 0, id); synchronized (co) { - co.pendingWrite.write(b); + co.pendingWrite.write(f); data.read(co.pendingWrite, frame, false); - co.flush(); } } - formatFrame(b, data.length(), 0, done ? 0x1 : 0, id); + formatFrame(f, data.length(), 0, done ? 0x1 : 0, id); synchronized (co) { - co.pendingWrite.write(b); + co.pendingWrite.write(f); data.read(co.pendingWrite, l, false); co.flush(); } @@ -404,7 +409,7 @@ private class FrameSettings extends FrameReader { protected FrameSettings(int size, int flags, int id) { super(size, flags, id); - b = new byte[6]; + b = new byte[9]; } @Override @@ -418,34 +423,32 @@ public final FrameReader process(Buffers buf) throws InterruptedException { switch (i) { case 1: - logger.debug(" SETTINGS_HEADER_TABLE_SIZE {}", v); synchronized (headers) { headers.setMax(v); } break; case 2: - logger.debug(" SETTINGS_ENABLE_PUSH {}", v); - if (v < 0 || v > 1) - ; // send PROTOCOL_ERROR - allowPush = v == 1; + if (v < 0 || v > 1) { + goaway(PROTOCOL_ERROR); + return null; + } +// allowPush = v == 1; break; case 3: - logger.debug(" SETTINGS_MAX_CONCURRENT_STREAMS {}", v); - concurrent = v; +// concurrent = v; break; case 4: - logger.debug(" SETTINGS_INITIAL_WINDOW_SIZE {}", v); initialWindow = v; break; case 5: - logger.debug(" SETTINGS_MAX_FRAME_SIZE {}", v); - if (v < 16384 || v > 16777215) - ; // send PROTOCOL_ERROR + if (v < 16384 || v > 16777215) { + goaway(PROTOCOL_ERROR); + return null; + } frame = v; break; case 6: - logger.debug(" SETTINGS_MAX_HEADER_LIST_SIZE {}", v); - headerList = v; +// headerList = v; break; default: // ignore @@ -455,7 +458,6 @@ public final FrameReader process(Buffers buf) throws InterruptedException { if (size != 0) return this; - byte[] b = new byte[9]; formatFrame(b, 0, 4, 1, 0); synchronized (co) { co.pendingWrite.write(b); @@ -565,20 +567,9 @@ public final FrameReader process(Buffers buf) throws InterruptedException { try { buf.prepend(remain); BuffersInputStream in = new BuffersInputStream(buf); - try { - synchronized (headers) { - while (in.readCount() < size - pad) { - in.mark(4096); - headers.readHeader(in, s::addHeader); - } - } - } catch (@SuppressWarnings("unused") EOFException e) { - in.writeMark(remain); + readHeaders(in); + if (size > pad) return this; - } finally { - logger.debug(" header read: {}", in.readCount()); - size -= in.readCount(); - } if (pad > 0) { pad -= in.skip(pad); @@ -593,10 +584,26 @@ public final FrameReader process(Buffers buf) throws InterruptedException { return null; } catch (IOException e) { logger.error("Failed to parse headers", e); - error(PROTOCOL_ERROR); + goaway(PROTOCOL_ERROR); return null; } } + + private void readHeaders(BuffersInputStream in) throws InterruptedException, IOException { + try { + synchronized (headers) { + while (in.readCount() < size - pad) { + in.mark(4096); + headers.readHeader(in, s::addHeader); + } + } + } catch (@SuppressWarnings("unused") EOFException e) { + in.writeMark(remain); + size += remain.length(); + } finally { + size -= in.readCount(); + } + } } private class FrameData extends FrameReader { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java index d6aecca4..723b7b5b 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java @@ -34,19 +34,19 @@ public boolean isReady() { } @Override - public void setReadListener(ReadListener readListener) { - ; + public void setReadListener(ReadListener readListener) { // ok } @Override public int read() throws IOException { try { while (!closed && in.isEmpty()) - in.wait(); + in.awaitContent(); if (closed && in.isEmpty()) return -1; return in.read(false); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); throw new IOException(e); } } @@ -60,6 +60,7 @@ public int read(byte[] b, int off, int len) throws IOException { return -1; return in.read(b, off, len, false); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); throw new IOException(e); } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java index ae396e3a..8a292966 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java @@ -95,19 +95,18 @@ public void commit() throws IOException { try { p.sendHeaders(id, res); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); throw new IOException(e); } } @Override protected boolean doStart() throws IOException, InterruptedException { - // TODO Auto-generated method stub return true; } @Override - protected void doDone() { - // TODO Auto-generated method stub + protected void doDone() { // ok } public final void close(boolean stop) { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletContextImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletContextImpl.java index 1476318b..92c67c93 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletContextImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletContextImpl.java @@ -7,7 +7,7 @@ import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; -import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.Enumeration; import java.util.EventListener; import java.util.Locale; @@ -165,7 +165,7 @@ public String getMimeType(String file) { @Override public Set getResourcePaths(String path) { // TODO Auto-generated method stub - return null; + return Collections.emptySet(); } @Override @@ -269,7 +269,7 @@ public ServletRegistration getServletRegistration(String servletName) { @Override public Map getServletRegistrations() { // TODO Auto-generated method stub - return null; + return Collections.emptyMap(); } @Override @@ -305,7 +305,7 @@ public FilterRegistration getFilterRegistration(String filterName) { @Override public Map getFilterRegistrations() { // TODO Auto-generated method stub - return null; + return Collections.emptyMap(); } @Override @@ -323,13 +323,13 @@ public void setSessionTrackingModes(Set sessionTrackingMode @Override public Set getDefaultSessionTrackingModes() { // TODO Auto-generated method stub - return null; + return Collections.emptySet(); } @Override public Set getEffectiveSessionTrackingModes() { // TODO Auto-generated method stub - return null; + return Collections.emptySet(); } @SuppressWarnings("unchecked") diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletCookieConfigImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletCookieConfigImpl.java index f27f2e15..c2738a82 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletCookieConfigImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletCookieConfigImpl.java @@ -54,12 +54,14 @@ public void setPath(String path) { @Deprecated(since = "Servlet 6.0", forRemoval = true) @Override + /** @deprectated */ public String getComment() { return null; } @Deprecated(since = "Servlet 6.0", forRemoval = true) @Override + /** @deprectated */ public void setComment(String comment) { // OK } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java index bca9d051..af503fcf 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletRequestImpl.java @@ -132,9 +132,9 @@ public void setHeaders(Map> headers) { public void setRequestUri(String path) { this.requestUri = path; - int j, i = 1; + int i = 1; do { - j = path.indexOf('/', i); + int j = path.indexOf('/', i); if (j < 0) j = path.length(); this.path.add(path.substring(i, j)); @@ -636,7 +636,7 @@ public Collection getParts() throws IOException, ServletException { if (!getContentType().startsWith("multipart/form-data")) throw new ServletException("not a multipart/form-data"); // TODO Auto-generated method stub - return null; + return Collections.emptyList(); } @Override diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java index 86d27fe3..b64f4f47 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java @@ -17,25 +17,18 @@ import java.util.Locale; import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jakarta.servlet.DispatcherType; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import unknow.server.servlet.HttpAdapter; import unknow.server.servlet.HttpError; import unknow.server.servlet.impl.out.AbstractServletOutput; -import unknow.server.servlet.utils.ServletManager; /** * @author unknow */ public class ServletResponseImpl implements HttpServletResponse { - private static final Logger logger = LoggerFactory.getLogger(ServletResponseImpl.class); +// private static final Logger logger = LoggerFactory.getLogger(ServletResponseImpl.class); private static final DateTimeFormatter RFC1123 = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC); diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java index 45756417..8f54c104 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java @@ -68,10 +68,20 @@ private static int readParam(StringBuilder sb, Reader r, boolean key) throws IOE while ((c = r.read()) != -1) { if (c == '&' || key && c == '=') return c; - if (c == '%') // TODO decode - ; + if (c == '%') + c = decodeHex(r.read()) << 8 | decodeHex(r.read()); sb.append((char) c); } return c; } + + private static int decodeHex(int c) throws IOException { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return 10 + c - 'A'; + if (c >= 'a' && c <= 'f') + return 10 + c - 'A'; + return 0; + } } diff --git a/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java index 1d3a5f0f..77c965fe 100644 --- a/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HeadersTest.java @@ -50,7 +50,7 @@ public void writeInt(byte[] expected, int prefix, int value) throws InterruptedE assertEquals(expected.length, b.length()); for (int i = 0; i < expected.length; i++) - assertEquals(expected[i]&0xFF, b.get(i)); + assertEquals(expected[i] & 0xFF, b.get(i)); } public static final byte b(int i) { @@ -180,7 +180,6 @@ public void readHeaders(Http2Headers h, byte[] data, Entry[] headers, Entry[] ta } private static final Entry e(String n, String v) { -// return new Entry(new EntryData(n, n.length()), new EntryData(v, v.length())); return new Entry(n, v); } } diff --git a/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java b/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java index b8e601f0..3d9e0d3d 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java +++ b/unknow-server-util/src/main/java/unknow/server/util/data/IntArrayMap.java @@ -150,6 +150,13 @@ public int size() { return len; } + /** + * @return true if map is empty + */ + public boolean isEmpty() { + return len == 0; + } + /** * @return set of all the keys */ From 0712f54c7d55cd91d74d9b1f71f88f6b910ca3fe Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Fri, 21 Jun 2024 22:10:51 +0200 Subject: [PATCH 27/40] gh-30 cleanup & reorder --- .../server/maven/jaxb/JaxbGeneratorMojo.java | 1 - .../maven/jaxws/JaxwsServletBuilder.java | 2 - .../unknow/server/servlet/HttpAdapter.java | 2 +- .../in => http11}/ChunckedInputStream.java | 7 +- .../servlet/http11/ChunckedOutputStream.java | 2 +- .../{impl/in => http11}/EmptyInputStream.java | 2 +- .../server/servlet/http11/EmptyStream.java | 2 +- .../servlet/http11/Http11Processor.java | 2 - .../server/servlet/http11/Http11Worker.java | 13 +- .../in => http11}/LengthInputStream.java | 8 +- .../servlet/http11/LengthOutputStream.java | 2 +- .../server/servlet/http2/Http2Processor.java | 477 +++--------------- .../servlet/http2/Http2ServletOutput.java | 2 +- .../server/servlet/http2/Http2Stream.java | 13 +- .../server/servlet/http2/frame/FrameData.java | 56 ++ .../servlet/http2/frame/FrameGoAway.java | 46 ++ .../servlet/http2/frame/FrameHeader.java | 109 ++++ .../server/servlet/http2/frame/FramePing.java | 44 ++ .../servlet/http2/frame/FrameReader.java | 53 ++ .../servlet/http2/frame/FrameRstStream.java | 46 ++ .../servlet/http2/frame/FrameSettings.java | 87 ++++ .../http2/frame/FrameWindowUpdate.java | 39 ++ .../impl/{out => }/AbstractServletOutput.java | 3 +- .../servlet/impl/ServletCookieConfigImpl.java | 4 +- .../servlet/impl/ServletResponseImpl.java | 3 - .../server/servlet/utils/PathUtils.java | 2 +- 26 files changed, 571 insertions(+), 456 deletions(-) rename unknow-server-servlet/src/main/java/unknow/server/servlet/{impl/in => http11}/ChunckedInputStream.java (93%) rename unknow-server-servlet/src/main/java/unknow/server/servlet/{impl/in => http11}/EmptyInputStream.java (96%) rename unknow-server-servlet/src/main/java/unknow/server/servlet/{impl/in => http11}/LengthInputStream.java (85%) create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameData.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameHeader.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FramePing.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameReader.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameRstStream.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameWindowUpdate.java rename unknow-server-servlet/src/main/java/unknow/server/servlet/impl/{out => }/AbstractServletOutput.java (92%) diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/jaxb/JaxbGeneratorMojo.java b/unknow-server-maven/src/main/java/unknow/server/maven/jaxb/JaxbGeneratorMojo.java index 11af8362..3db489dc 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/jaxb/JaxbGeneratorMojo.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/jaxb/JaxbGeneratorMojo.java @@ -79,7 +79,6 @@ */ @Mojo(defaultPhase = LifecyclePhase.GENERATE_SOURCES, name = "jaxb-generator", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME) public class JaxbGeneratorMojo extends AbstractGeneratorMojo { -// private static final Logger logger = LoggerFactory.getLogger(JaxbGeneratorMojo.class); private static final HandlerBuilder HANDLER = new HandlerBuilder(); diff --git a/unknow-server-maven/src/main/java/unknow/server/maven/jaxws/JaxwsServletBuilder.java b/unknow-server-maven/src/main/java/unknow/server/maven/jaxws/JaxwsServletBuilder.java index 1cfdde53..17922dd8 100644 --- a/unknow-server-maven/src/main/java/unknow/server/maven/jaxws/JaxwsServletBuilder.java +++ b/unknow-server-maven/src/main/java/unknow/server/maven/jaxws/JaxwsServletBuilder.java @@ -67,8 +67,6 @@ * @author unknow */ public class JaxwsServletBuilder { -// private static final Logger logger = LoggerFactory.getLogger(JaxwsServletBuilder.class); - private final ClassModel serviceClass; private final Service service; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java index 60759ed4..886d9745 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpAdapter.java @@ -4,8 +4,8 @@ import java.net.InetSocketAddress; import jakarta.servlet.ServletInputStream; +import unknow.server.servlet.impl.AbstractServletOutput; import unknow.server.servlet.impl.ServletContextImpl; -import unknow.server.servlet.impl.out.AbstractServletOutput; import unknow.server.servlet.utils.EventManager; public interface HttpAdapter { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/ChunckedInputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedInputStream.java similarity index 93% rename from unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/ChunckedInputStream.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedInputStream.java index 7da13cab..00b092dd 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/ChunckedInputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedInputStream.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.servlet.impl.in; +package unknow.server.servlet.http11; import java.io.IOException; import java.io.InputStream; @@ -23,8 +23,6 @@ public class ChunckedInputStream extends ServletInputStream { private final InputStream in; - private ReadListener listener; - private int step = 0; private int chunkLen; private final byte[] b = new byte[4096]; @@ -68,8 +66,7 @@ public long skip(long n) throws IOException { } @Override - public void setReadListener(ReadListener listener) { - this.listener = listener; + public void setReadListener(ReadListener listener) { // ok } private void readData() throws IOException { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java index 6f45814a..868108e9 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/ChunckedOutputStream.java @@ -6,8 +6,8 @@ import java.io.IOException; import java.io.OutputStream; +import unknow.server.servlet.impl.AbstractServletOutput; import unknow.server.servlet.impl.ServletResponseImpl; -import unknow.server.servlet.impl.out.AbstractServletOutput; /** * Http chuncked entity diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/EmptyInputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/EmptyInputStream.java similarity index 96% rename from unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/EmptyInputStream.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/http11/EmptyInputStream.java index 31e4f7f4..dae9eb30 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/EmptyInputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/EmptyInputStream.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.servlet.impl.in; +package unknow.server.servlet.http11; import java.io.IOException; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/EmptyStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/EmptyStream.java index d4c618e3..fa7420ab 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/EmptyStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/EmptyStream.java @@ -5,7 +5,7 @@ import java.io.IOException; -import unknow.server.servlet.impl.out.AbstractServletOutput; +import unknow.server.servlet.impl.AbstractServletOutput; /** * @author unknow diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java index de1ec856..1e140cdd 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Processor.java @@ -11,8 +11,6 @@ * http/1.1 implementation */ public class Http11Processor implements HttpProcessor { -// private static final Logger logger = LoggerFactory.getLogger(Http11Processor.class); - private static final byte[] END = new byte[] { '\r', '\n', '\r', '\n' }; private static final int MAX_START_SIZE = 8192; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java index 41f75e7b..f2448981 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/Http11Worker.java @@ -18,12 +18,9 @@ import unknow.server.servlet.HttpConnection; import unknow.server.servlet.HttpError; import unknow.server.servlet.HttpWorker; +import unknow.server.servlet.impl.AbstractServletOutput; import unknow.server.servlet.impl.ServletRequestImpl; import unknow.server.servlet.impl.ServletResponseImpl; -import unknow.server.servlet.impl.in.ChunckedInputStream; -import unknow.server.servlet.impl.in.EmptyInputStream; -import unknow.server.servlet.impl.in.LengthInputStream; -import unknow.server.servlet.impl.out.AbstractServletOutput; import unknow.server.util.io.Buffers; import unknow.server.util.io.BuffersUtils; @@ -31,8 +28,6 @@ public final class Http11Worker extends HttpWorker { private static final Logger logger = LoggerFactory.getLogger(Http11Worker.class); -// private static final byte[] END = new byte[] { '\r', '\n', '\r', '\n' }; - private static final byte[] CRLF = { '\r', '\n' }; private static final byte[] PARAM_SEP = { '&', '=' }; private static final byte[] SPACE_SLASH = { ' ', '/' }; @@ -42,22 +37,16 @@ public final class Http11Worker extends HttpWorker { private static final byte[] CONTENT_LENGTH = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h', ':', ' ' }; private static final byte[] CONTENT_LENGTH0 = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h', ':', ' ', '0', '\r', '\n' }; private static final byte[] CONTENT_TYPE = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 't', 'y', 'p', 'e', ':', ' ' }; -// private static final byte[] CONTENT_HTML = new byte[] { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 't', 'y', 'p', 'e', ':', ' ', 't', 'e', 'x', 't', '/', 'h', 't', 'm', 'l', -// ';', 'c', 'h', 'a', 'r', 's', 'e', 't', '=', 'u', 't', 'f', '8', '\r', '\n' }; private static final byte[] COOKIE = new byte[] { 's', 'e', 't', '-', 'c', 'o', 'o', 'k', 'i', 'e', ':', ' ' }; private static final byte[] PATH = new byte[] { ';', 'p', 'a', 't', 'h', '=' }; private static final byte[] DOMAIN = new byte[] { ';', 'd', 'o', 'm', 'a', 'i', 'n', '=' }; private static final byte[] MAX_AGE = new byte[] { ';', 'm', 'a', 'x', '-', 'a', 'g', 'e' }; private static final byte[] SECURE = new byte[] { ';', 's', 'e', 'c', 'u', 'r', 'e' }; private static final byte[] HTTP_ONLY = new byte[] { ';', 'h', 't', 't', 'p', 'o', 'n', 'l', 'y' }; -// private static final byte[] ERROR_START = new byte[] { '<', 'h', 't', 'm', 'l', '>', '<', 'b', 'o', 'd', 'y', '>', '<', 'h', '1', '>' }; -// private static final byte[] ERROR_END = new byte[] { '<', '/', 'h', '1', '>', '<', '/', 'b', 'o', 'd', 'y', '>', '<', '/', 'h', 't', 'm', 'l', '>' }; private static final byte SPACE = ' '; private static final byte QUESTION = '?'; private static final byte COLON = ':'; -// private static final byte SEMICOLON = ';'; -// private static final byte SLASH = '/'; private static final byte AMPERSAMP = '&'; private static final byte EQUAL = '='; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/LengthInputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthInputStream.java similarity index 85% rename from unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/LengthInputStream.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthInputStream.java index c64b691c..5fa8f0d6 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/in/LengthInputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthInputStream.java @@ -1,7 +1,7 @@ /** * */ -package unknow.server.servlet.impl.in; +package unknow.server.servlet.http11; import java.io.IOException; import java.io.InputStream; @@ -15,7 +15,6 @@ public class LengthInputStream extends ServletInputStream { private final InputStream in; private long length; - private ReadListener listener; /** * create new ServletInputLength @@ -41,14 +40,13 @@ public long skip(long n) throws IOException { public boolean isReady() { try { return in.available() > 0; - } catch (IOException e) { + } catch (@SuppressWarnings("unused") IOException e) { return true; } } @Override - public void setReadListener(ReadListener readListener) { - this.listener = readListener; + public void setReadListener(ReadListener readListener) { // ok } @Override diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java index a8c8e61f..97603e81 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http11/LengthOutputStream.java @@ -6,8 +6,8 @@ import java.io.IOException; import java.io.OutputStream; +import unknow.server.servlet.impl.AbstractServletOutput; import unknow.server.servlet.impl.ServletResponseImpl; -import unknow.server.servlet.impl.out.AbstractServletOutput; /** * http with a Content-Length (can be -1) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index 04f13808..b4f9ee00 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -1,61 +1,86 @@ package unknow.server.servlet.http2; -import java.io.EOFException; -import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import unknow.server.servlet.HttpConnection; import unknow.server.servlet.HttpProcessor; +import unknow.server.servlet.http2.frame.FrameData; +import unknow.server.servlet.http2.frame.FrameGoAway; +import unknow.server.servlet.http2.frame.FrameHeader; +import unknow.server.servlet.http2.frame.FramePing; +import unknow.server.servlet.http2.frame.FrameReader; +import unknow.server.servlet.http2.frame.FrameReader.FrameBuilder; +import unknow.server.servlet.http2.frame.FrameRstStream; +import unknow.server.servlet.http2.frame.FrameSettings; +import unknow.server.servlet.http2.frame.FrameWindowUpdate; import unknow.server.servlet.impl.ServletResponseImpl; import unknow.server.util.data.IntArrayMap; import unknow.server.util.io.Buffers; -import unknow.server.util.io.BuffersInputStream; import unknow.server.util.io.BuffersUtils; public class Http2Processor implements HttpProcessor, Http2FlowControl { - private static final Logger logger = LoggerFactory.getLogger(Http2Processor.class); + static final Logger logger = LoggerFactory.getLogger(Http2Processor.class); private static final byte[] PRI = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.US_ASCII); - private static final int NO_ERROR = 0; - private static final int PROTOCOL_ERROR = 1; -// private static final int INTERNAL_ERROR = 2; -// private static final int FLOW_CONTROL_ERROR = 3; -// private static final int SETTINGS_TIMEOUT = 4; -// private static final int STREAM_CLOSED = 5; - private static final int FRAME_SIZE_ERROR = 6; -// private static final int REFUSED_STREAM = 7; -// private static final int CANCEL = 8; -// private static final int COMPRESSION_ERROR = 9; -// private static final int CONNECT_ERROR = 10; -// private static final int ENHANCE_YOUR_CALM = 12; -// private static final int INADEQUATE_SECURITY = 13; -// private static final int HTTP_1_1_REQUIRED = 14; - - private final HttpConnection co; - private final IntArrayMap streams; - private final Http2Headers headers; + public static final int NO_ERROR = 0; + public static final int PROTOCOL_ERROR = 1; + public static final int INTERNAL_ERROR = 2; + public static final int FLOW_CONTROL_ERROR = 3; + public static final int SETTINGS_TIMEOUT = 4; + public static final int STREAM_CLOSED = 5; + public static final int FRAME_SIZE_ERROR = 6; + public static final int REFUSED_STREAM = 7; + public static final int CANCEL = 8; + public static final int COMPRESSION_ERROR = 9; + public static final int CONNECT_ERROR = 10; + public static final int ENHANCE_YOUR_CALM = 12; + public static final int INADEQUATE_SECURITY = 13; + public static final int HTTP_1_1_REQUIRED = 14; + + private static final IntArrayMap BUILDERS = new IntArrayMap<>(); + + static { + BUILDERS.set(0, FrameData.BUILDER); + BUILDERS.set(1, FrameHeader.BUILDER); + BUILDERS.set(2, FrameReader.BUILDER); + BUILDERS.set(3, FrameRstStream.BUILDER); + BUILDERS.set(4, FrameSettings.BUILDER); + BUILDERS.set(6, FramePing.BUILDER); + BUILDERS.set(7, FrameGoAway.BUILDER); + BUILDERS.set(8, FrameWindowUpdate.BUILDER); + BUILDERS.set(9, FrameHeader.CONTINUATION); + } + + public final HttpConnection co; + public final IntArrayMap streams; + public final List pending; + public final Http2Headers headers; private final byte[] b; private int window; - private boolean closing; + public boolean closing; // private boolean allowPush; // private int concurrent; - private int initialWindow; - private int frame; + public int initialWindow; + public int frame; // private int headerList; private FrameReader r; private int lastId; - private boolean wantContinuation; + public boolean wantContinuation; public Http2Processor(HttpConnection co) throws InterruptedException { this.co = co; this.streams = new IntArrayMap<>(); + this.pending = new LinkedList<>(); this.headers = new Http2Headers(4096); this.b = new byte[9]; this.closing = false; @@ -98,10 +123,16 @@ public boolean isClosable(boolean stop) { try { goaway(NO_ERROR); closing = true; - } catch (@SuppressWarnings("unused") InterruptedException e) { // ok + } catch (@SuppressWarnings("unused") InterruptedException e) { + Thread.currentThread().interrupt(); } } - return streams.isEmpty(); + Iterator it = pending.iterator(); + while (it.hasNext()) { + if (it.next().isClosed()) + it.remove(); + } + return pending.isEmpty() && streams.isEmpty(); } @Override @@ -131,131 +162,16 @@ private void readFrame(Buffers buf) throws InterruptedException { } wantContinuation = false; - Http2Stream s; - int pad = 0; - switch (type) { - case 0: // data - s = streams.get(id); - if (s == null) { - goaway(PROTOCOL_ERROR); - return; - } - - if ((flags & 0x8) == 1) { - pad = buf.read(false); - if (pad >= size) { - goaway(PROTOCOL_ERROR); - return; - } - } - - r = new FrameData(size, flags, id, pad).process(buf); - return; - case 1: // header - if (id == 0 || streams.contains(id)) { - goaway(PROTOCOL_ERROR); - return; - } - - s = new Http2Stream(co, id, this, initialWindow); - - if ((flags & 0x1) == 1) // END_STREAM - s.close(false); - else - streams.set(id, s); - if ((flags & 0x8) == 1) { - pad = buf.read(false); - if (pad >= size) { - goaway(PROTOCOL_ERROR); - return; - } - } - if ((flags & 0x20) == 1) { // PRIORITY - buf.skip(5); - } - - r = new FrameHeader(size, flags, id, pad, s).process(buf); - return; - case 2: // priority - r = new FrameReader(size, flags, id).process(buf); - return; - case 3: // rst_stream - s = streams.remove(id); - if (s == null) { - goaway(PROTOCOL_ERROR); - return; - } - if (size != 4) { - goaway(FRAME_SIZE_ERROR); - return; - } - - buf.read(b, 0, 4, false); - int err = (b[0] & 0xff) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); - logger.info("close stream {}, err: {}", id, err); - s.close(true); - return; - case 4: // settings - if ((flags & 0x1) == 1) { - if (size != 0) - goaway(FRAME_SIZE_ERROR); - return; - } - if (id != 0) { - goaway(PROTOCOL_ERROR); - return; - } - if (size % 6 != 0) { - goaway(FRAME_SIZE_ERROR); - return; - } - r = new FrameSettings(size, flags, id).process(buf); - return; - case 5: // push promise - goaway(PROTOCOL_ERROR); - return; - case 6: // ping - if (id != 0) { - goaway(PROTOCOL_ERROR); - return; - } - if (size != 8) { - goaway(FRAME_SIZE_ERROR); - return; - } - if ((flags & 0x1) == 1) - break; - r = new FramePing(size, flags, id).process(buf); - return; - case 7: // go away - if (id != 0) { - goaway(PROTOCOL_ERROR); - return; - } - r = new FrameGoAway(size, flags, id).process(buf); - return; - case 8: // window update - if (size != 4) { - goaway(FRAME_SIZE_ERROR); - return; - } - r = new FrameWindowUpdate(size, flags, id).process(buf); - return; - case 9: // continuation - s = streams.get(id); - if (s == null) { - goaway(PROTOCOL_ERROR); - return; - } - - r = new FrameHeader(size, flags, id, pad, s).process(buf); - return; - default: - goaway(PROTOCOL_ERROR); + FrameBuilder b = BUILDERS.get(type); + if (b == null) { + goaway(PROTOCOL_ERROR); + return; } + + r = b.build(this, size, flags, id, buf); } - private void goaway(int err) throws InterruptedException { + public void goaway(int err) throws InterruptedException { byte[] f = new byte[17]; formatFrame(f, 8, 7, 0, 0); f[9] = (byte) ((lastId >> 24) & 0x7f); @@ -337,7 +253,7 @@ public static void formatFrame(byte[] b, int size, int type, int flags, int id) b[8] = (byte) (id & 0xff); } - private static String error(int err) { + public static String error(int err) { switch (err) { case 0: return "NO_ERROR"; @@ -379,263 +295,4 @@ private static String error(int err) { } return null; }; - - private static class FrameReader { - protected int size; - protected int flags; - protected int id; - - protected FrameReader(int size, int flags, int id) { - this.size = size; - this.flags = flags; - this.id = id; - } - - /** - * @param buf - * @return this or null - * @throws InterruptedException - * @throws IOException - */ - public FrameReader process(Buffers buf) throws InterruptedException { - size -= buf.skip(size); - return size == 0 ? null : this; - } - } - - private class FrameSettings extends FrameReader { - - private final byte[] b; - - protected FrameSettings(int size, int flags, int id) { - super(size, flags, id); - b = new byte[9]; - } - - @Override - public final FrameReader process(Buffers buf) throws InterruptedException { - while (size > 0 && buf.length() > 6) { - buf.read(b, 0, 6, false); - size -= 6; - - int i = (b[0] & 0xff) << 8 | (b[1] & 0xff); - int v = (b[2] & 0x7f) << 24 | (b[3] & 0xff) << 16 | (b[4] & 0xff) << 8 | (b[5] & 0xff); - - switch (i) { - case 1: - synchronized (headers) { - headers.setMax(v); - } - break; - case 2: - if (v < 0 || v > 1) { - goaway(PROTOCOL_ERROR); - return null; - } -// allowPush = v == 1; - break; - case 3: -// concurrent = v; - break; - case 4: - initialWindow = v; - break; - case 5: - if (v < 16384 || v > 16777215) { - goaway(PROTOCOL_ERROR); - return null; - } - frame = v; - break; - case 6: -// headerList = v; - break; - default: - // ignore - } - } - - if (size != 0) - return this; - - formatFrame(b, 0, 4, 1, 0); - synchronized (co) { - co.pendingWrite.write(b); - co.flush(); - } - return null; - } - } - - private class FramePing extends FrameReader { - - private final byte[] b; - - protected FramePing(int size, int flags, int id) { - super(size, flags, id); - b = new byte[9 + 8]; - - } - - @Override - public final FrameReader process(Buffers buf) throws InterruptedException { - if (buf.length() < 8) - return this; - - buf.read(b, 9, 8, false); - formatFrame(b, 8, 8, 1, 0); - synchronized (co) { - co.pendingWrite.write(b); - co.flush(); - } - return null; - } - } - - private class FrameGoAway extends FrameReader { - - private final byte[] b; - - private int lastId = -1; - - protected FrameGoAway(int size, int flags, int id) { - super(size, flags, id); - b = new byte[4]; - } - - @Override - public FrameReader process(Buffers buf) throws InterruptedException { - if (lastId >= 0) - return super.process(buf); - if (buf.length() < 8) - return this; - - buf.read(b, 0, 4, false); - lastId = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); - - buf.read(b, 0, 4, false); - int err = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); - logger.info("goaway last: {} err: {}", lastId, error(err)); - size -= 8; - return super.process(buf); - } - } - - private class FrameWindowUpdate extends FrameReader { - private final byte[] b; - - protected FrameWindowUpdate(int size, int flags, int id) { - super(size, flags, id); - b = new byte[4]; - } - - @Override - public final FrameReader process(Buffers buf) throws InterruptedException { - if (buf.length() < 4) - return this; - buf.read(b, 0, 4, false); - int v = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); - if (v == 0) { - goaway(PROTOCOL_ERROR); - return null; - } - - logger.debug(" window update {}", v); - - Http2FlowControl f = id == 0 ? Http2Processor.this : streams.get(id); - if (f != null) - f.add(v); - return null; - } - } - - private class FrameHeader extends FrameReader { - private final Http2Stream s; - private final Buffers remain; - private int pad; - - protected FrameHeader(int size, int flags, int id, int pad, Http2Stream s) { - super(size, flags, id); - this.s = s; - this.remain = new Buffers(); - this.pad = pad; - } - - @SuppressWarnings("resource") - @Override - public final FrameReader process(Buffers buf) throws InterruptedException { - try { - buf.prepend(remain); - BuffersInputStream in = new BuffersInputStream(buf); - readHeaders(in); - if (size > pad) - return this; - - if (pad > 0) { - pad -= in.skip(pad); - if (pad > 0) - return this; - } - - wantContinuation = (flags & 0x4) == 0; - if (!wantContinuation) - s.start(); - - return null; - } catch (IOException e) { - logger.error("Failed to parse headers", e); - goaway(PROTOCOL_ERROR); - return null; - } - } - - private void readHeaders(BuffersInputStream in) throws InterruptedException, IOException { - try { - synchronized (headers) { - while (in.readCount() < size - pad) { - in.mark(4096); - headers.readHeader(in, s::addHeader); - } - } - } catch (@SuppressWarnings("unused") EOFException e) { - in.writeMark(remain); - size += remain.length(); - } finally { - size -= in.readCount(); - } - } - } - - private class FrameData extends FrameReader { - private final Http2Stream s; - private int pad; - - protected FrameData(int size, int flags, int id, int pad) { - super(size - pad, flags, id); - this.s = streams.get(id); - this.pad = pad; - } - - @Override - public FrameReader process(Buffers buf) throws InterruptedException { - int l = Math.min(buf.length(), size); - s.in.read(buf, l); - size -= l; - if (size > 0) - return this; - - if (pad > 0) { - pad -= buf.skip(pad); - if (pad > 0) - return this; - } - - if ((flags & 0x1) == 1) { - streams.remove(id); - s.close(false); - } - return null; - } - - } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletOutput.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletOutput.java index 4a1e8579..f5c45948 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletOutput.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletOutput.java @@ -2,8 +2,8 @@ import java.io.IOException; +import unknow.server.servlet.impl.AbstractServletOutput; import unknow.server.servlet.impl.ServletResponseImpl; -import unknow.server.servlet.impl.out.AbstractServletOutput; public class Http2ServletOutput extends AbstractServletOutput { private final Http2Processor p; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java index 8a292966..7fff6d44 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java @@ -10,9 +10,8 @@ import org.slf4j.LoggerFactory; import jakarta.servlet.ServletInputStream; -import unknow.server.servlet.HttpConnection; import unknow.server.servlet.HttpWorker; -import unknow.server.servlet.impl.out.AbstractServletOutput; +import unknow.server.servlet.impl.AbstractServletOutput; import unknow.server.servlet.utils.PathUtils; public class Http2Stream extends HttpWorker implements Http2FlowControl { @@ -20,15 +19,15 @@ public class Http2Stream extends HttpWorker implements Http2FlowControl { private final int id; private final Http2Processor p; - final Http2ServletInput in; + public final Http2ServletInput in; final Http2ServletOutput out; private int window; private volatile Future exec; - protected Http2Stream(HttpConnection co, int id, Http2Processor p, int window) { - super(co); + public Http2Stream(Http2Processor p, int id, int window) { + super(p.co); this.id = id; this.p = p; this.in = new Http2ServletInput(); @@ -114,4 +113,8 @@ public final void close(boolean stop) { if (stop) exec.cancel(true); } + + public boolean isClosed() { + return exec.isDone(); + } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameData.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameData.java new file mode 100644 index 00000000..a0a218e7 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameData.java @@ -0,0 +1,56 @@ +package unknow.server.servlet.http2.frame; + +import unknow.server.servlet.http2.Http2Processor; +import unknow.server.servlet.http2.Http2Stream; +import unknow.server.util.io.Buffers; + +public class FrameData extends FrameReader { + public static final FrameBuilder BUILDER = (p, size, flags, id, buf) -> { + Http2Stream s = p.streams.get(id); + if (s == null) { + p.goaway(Http2Processor.PROTOCOL_ERROR); + return null; + } + + return new FrameData(p, size, flags, id).process(buf); + }; + + private final Http2Stream s; + private int pad; + + protected FrameData(Http2Processor p, int size, int flags, int id) { + super(p, size, flags, id); + this.s = p.streams.get(id); + this.pad = -1; + } + + @Override + public FrameReader process(Buffers buf) throws InterruptedException { + if (pad < 0) { + pad = readPad(buf); + if (pad < 0) + return null; + size -= pad; + } + + int l = Math.min(buf.length(), size); + s.in.read(buf, l); + size -= l; + if (size > 0) + return this; + + if (pad > 0) { + pad -= buf.skip(pad); + if (pad > 0) + return this; + } + + if ((flags & 0x1) == 1) { + p.streams.remove(id); + p.pending.add(s); + s.close(false); + } + return null; + } + +} \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java new file mode 100644 index 00000000..88f41d15 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java @@ -0,0 +1,46 @@ +package unknow.server.servlet.http2.frame; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import unknow.server.servlet.http2.Http2Processor; +import unknow.server.util.io.Buffers; + +public class FrameGoAway extends FrameReader { + private static final Logger logger = LoggerFactory.getLogger(FrameGoAway.class); + + public static final FrameBuilder BUILDER = (p, size, flags, id, buf) -> { + if (id != 0) { + p.goaway(Http2Processor.PROTOCOL_ERROR); + return null; + } + return new FrameGoAway(p, size, flags, id).process(buf); + }; + + private final byte[] b; + + private int lastId = -1; + + protected FrameGoAway(Http2Processor p, int size, int flags, int id) { + super(p, size, flags, id); + b = new byte[4]; + } + + @Override + public FrameReader process(Buffers buf) throws InterruptedException { + if (lastId >= 0) + return super.process(buf); + if (buf.length() < 8) + return this; + + buf.read(b, 0, 4, false); + lastId = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); + + buf.read(b, 0, 4, false); + int err = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); + logger.info("goaway last: {} err: {}", lastId, Http2Processor.error(err)); + size -= 8; + p.closing = true; + return super.process(buf); + } +} \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameHeader.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameHeader.java new file mode 100644 index 00000000..552ff887 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameHeader.java @@ -0,0 +1,109 @@ +package unknow.server.servlet.http2.frame; + +import java.io.EOFException; +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import unknow.server.servlet.http2.Http2Processor; +import unknow.server.servlet.http2.Http2Stream; +import unknow.server.util.io.Buffers; +import unknow.server.util.io.BuffersInputStream; + +public class FrameHeader extends FrameReader { + private static final Logger logger = LoggerFactory.getLogger(FrameHeader.class); + + public static final FrameBuilder BUILDER = (p, size, flags, id, buf) -> { + if (id == 0 || p.streams.contains(id)) { + p.goaway(Http2Processor.PROTOCOL_ERROR); + return null; + } + + Http2Stream s = new Http2Stream(p, id, p.initialWindow); + p.streams.set(id, s); + + return new FrameHeader(p, size, flags, id, s).process(buf); + }; + + public static final FrameBuilder CONTINUATION = (p, size, flags, id, buf) -> { + Http2Stream s = p.streams.get(id); + if (s == null) { + p.goaway(Http2Processor.PROTOCOL_ERROR); + return null; + } + + return new FrameHeader(p, size, flags, id, s).process(buf); + }; + + private final Http2Stream s; + private final Buffers remain; + private int pad; + + protected FrameHeader(Http2Processor p, int size, int flags, int id, Http2Stream s) { + super(p, size, flags, id); + this.s = s; + this.remain = new Buffers(); + this.pad = -1; + } + + @SuppressWarnings("resource") + @Override + public final FrameReader process(Buffers buf) throws InterruptedException { + if (pad < 0) { + pad = readPad(buf); + if (pad < 0) + return null; + } + + if ((flags & 0x20) == 1) { // PRIORITY + buf.skip(5); + } + + try { + buf.prepend(remain); + BuffersInputStream in = new BuffersInputStream(buf); + readHeaders(in); + if (size > pad) + return this; + + if (pad > 0) { + pad -= in.skip(pad); + if (pad > 0) + return this; + } + + p.wantContinuation = (flags & 0x4) == 0; + if (!p.wantContinuation) + s.start(); + + if ((flags & 0x1) == 1) { + p.streams.remove(id); + p.pending.add(s); + s.close(false); + } + + return null; + } catch (IOException e) { + logger.error("Failed to parse headers", e); + p.goaway(Http2Processor.PROTOCOL_ERROR); + return null; + } + } + + private void readHeaders(BuffersInputStream in) throws InterruptedException, IOException { + try { + synchronized (p.headers) { + while (in.readCount() < size - pad) { + in.mark(4096); + p.headers.readHeader(in, s::addHeader); + } + } + } catch (@SuppressWarnings("unused") EOFException e) { + in.writeMark(remain); + size += remain.length(); + } finally { + size -= in.readCount(); + } + } +} \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FramePing.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FramePing.java new file mode 100644 index 00000000..d262645a --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FramePing.java @@ -0,0 +1,44 @@ +package unknow.server.servlet.http2.frame; + +import unknow.server.servlet.HttpConnection; +import unknow.server.servlet.http2.Http2Processor; +import unknow.server.util.io.Buffers; + +public class FramePing extends FrameReader { + public static final FrameBuilder BUILDER = (p, size, flags, id, buf) -> { + if (id != 0) { + p.goaway(Http2Processor.PROTOCOL_ERROR); + return null; + } + if (size != 8) { + p.goaway(Http2Processor.FRAME_SIZE_ERROR); + return null; + } + if ((flags & 0x1) == 1) + return null; + return new FramePing(p, size, flags, id).process(buf); + }; + + private final byte[] b; + + protected FramePing(Http2Processor p, int size, int flags, int id) { + super(p, size, flags, id); + b = new byte[9 + 8]; + + } + + @Override + public final FrameReader process(Buffers buf) throws InterruptedException { + if (buf.length() < 8) + return this; + + buf.read(b, 9, 8, false); + Http2Processor.formatFrame(b, 8, 8, 1, 0); + HttpConnection co = p.co; + synchronized (co) { + co.pendingWrite.write(b); + co.flush(); + } + return null; + } +} \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameReader.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameReader.java new file mode 100644 index 00000000..b2b8f922 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameReader.java @@ -0,0 +1,53 @@ +package unknow.server.servlet.http2.frame; + +import unknow.server.servlet.http2.Http2Processor; +import unknow.server.util.io.Buffers; + +public class FrameReader { + public static final FrameBuilder BUILDER = (p, size, flags, id, buf) -> new FrameReader(p, size, flags, id); + + protected final Http2Processor p; + protected int size; + protected int flags; + protected int id; + + protected FrameReader(Http2Processor p, int size, int flags, int id) { + this.p = p; + this.size = size; + this.flags = flags; + this.id = id; + } + + /** + * read the size of the padding field if the flags is set (and unset it) + * @param buf where to read + * @return the pad length or -1 is case of error + * @throws InterruptedException + */ + protected int readPad(Buffers buf) throws InterruptedException { + if ((flags & 0x8) == 0) + return 0; + flags &= ~0x8; + + int pad = buf.read(false); + if (pad >= size) { + p.goaway(Http2Processor.PROTOCOL_ERROR); + return -1; + } + return pad; + } + + /** + * @param buf + * @return this or null + * @throws InterruptedException + */ + public FrameReader process(Buffers buf) throws InterruptedException { + size -= buf.skip(size); + return size == 0 ? null : this; + } + + public interface FrameBuilder { + FrameReader build(Http2Processor p, int size, int flags, int id, Buffers buf) throws InterruptedException; + } +} \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameRstStream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameRstStream.java new file mode 100644 index 00000000..264db9e2 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameRstStream.java @@ -0,0 +1,46 @@ +package unknow.server.servlet.http2.frame; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import unknow.server.servlet.http2.Http2Processor; +import unknow.server.servlet.http2.Http2Stream; +import unknow.server.util.io.Buffers; + +public class FrameRstStream extends FrameReader { + private static final Logger logger = LoggerFactory.getLogger(FrameRstStream.class); + + public static final FrameBuilder BUILDER = (p, size, flags, id, buf) -> { + Http2Stream s = p.streams.remove(id); + if (s == null) { + p.goaway(Http2Processor.PROTOCOL_ERROR); + return null; + } + if (size != 4) { + p.goaway(Http2Processor.FRAME_SIZE_ERROR); + return null; + } + + return new FrameRstStream(p, size, flags, id, s).process(buf); + }; + + private final Http2Stream s; + private final byte[] b; + + protected FrameRstStream(Http2Processor p, int size, int flags, int id, Http2Stream s) { + super(p, size, flags, id); + this.s = s; + this.b = new byte[4]; + } + + @Override + public final FrameReader process(Buffers buf) throws InterruptedException { + if (buf.length() < 4) + return null; + buf.read(b, 0, 4, false); + int err = (b[0] & 0xff) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); + logger.info("closing stream {} err: {}", id, Http2Processor.error(err)); + s.close(true); + return null; + } +} \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java new file mode 100644 index 00000000..0c577583 --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java @@ -0,0 +1,87 @@ +package unknow.server.servlet.http2.frame; + +import unknow.server.servlet.HttpConnection; +import unknow.server.servlet.http2.Http2Processor; +import unknow.server.util.io.Buffers; + +public class FrameSettings extends FrameReader { + public static final FrameBuilder BUILDER = (p, size, flags, id, buf) -> { + if ((flags & 0x1) == 1) { + if (size != 0) + p.goaway(Http2Processor.FRAME_SIZE_ERROR); + return null; + } + if (id != 0) { + p.goaway(Http2Processor.PROTOCOL_ERROR); + return null; + } + if (size % 6 != 0) { + p.goaway(Http2Processor.FRAME_SIZE_ERROR); + return null; + } + return new FrameSettings(p, size, flags, id).process(buf); + }; + + private final byte[] b; + + protected FrameSettings(Http2Processor p, int size, int flags, int id) { + super(p, size, flags, id); + b = new byte[9]; + } + + @Override + public final FrameReader process(Buffers buf) throws InterruptedException { + while (size > 0 && buf.length() > 6) { + buf.read(b, 0, 6, false); + size -= 6; + + int i = (b[0] & 0xff) << 8 | (b[1] & 0xff); + int v = (b[2] & 0x7f) << 24 | (b[3] & 0xff) << 16 | (b[4] & 0xff) << 8 | (b[5] & 0xff); + + switch (i) { + case 1: + synchronized (p.headers) { + p.headers.setMax(v); + } + break; + case 2: + if (v < 0 || v > 1) { + p.goaway(Http2Processor.PROTOCOL_ERROR); + return null; + } +// allowPush = v == 1; + break; + case 3: +// concurrent = v; + break; + case 4: + p.initialWindow = v; + break; + case 5: + if (v < 16384 || v > 16777215) { + p.goaway(Http2Processor.PROTOCOL_ERROR); + return null; + } + p.frame = v; + break; + case 6: +// headerList = v; + break; + default: + // ignore + } + } + + if (size != 0) + return this; + + Http2Processor.formatFrame(b, 0, 4, 1, 0); + + HttpConnection co = p.co; + synchronized (co) { + co.pendingWrite.write(b); + co.flush(); + } + return null; + } +} \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameWindowUpdate.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameWindowUpdate.java new file mode 100644 index 00000000..8acdf18c --- /dev/null +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameWindowUpdate.java @@ -0,0 +1,39 @@ +package unknow.server.servlet.http2.frame; + +import unknow.server.servlet.http2.Http2FlowControl; +import unknow.server.servlet.http2.Http2Processor; +import unknow.server.util.io.Buffers; + +public class FrameWindowUpdate extends FrameReader { + public static final FrameBuilder BUILDER = (p, size, flags, id, buf) -> { + if (size != 4) { + p.goaway(Http2Processor.FRAME_SIZE_ERROR); + return null; + } + return new FrameWindowUpdate(p, size, flags, id).process(buf); + }; + + private final byte[] b; + + protected FrameWindowUpdate(Http2Processor p, int size, int flags, int id) { + super(p, size, flags, id); + b = new byte[4]; + } + + @Override + public final FrameReader process(Buffers buf) throws InterruptedException { + if (buf.length() < 4) + return this; + buf.read(b, 0, 4, false); + int v = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); + if (v == 0) { + p.goaway(Http2Processor.PROTOCOL_ERROR); + return null; + } + + Http2FlowControl f = id == 0 ? p : p.streams.get(id); + if (f != null) + f.add(v); + return null; + } +} \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/AbstractServletOutput.java similarity index 92% rename from unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java rename to unknow-server-servlet/src/main/java/unknow/server/servlet/impl/AbstractServletOutput.java index 2f8a3f5d..4e70110a 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/out/AbstractServletOutput.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/AbstractServletOutput.java @@ -1,10 +1,9 @@ -package unknow.server.servlet.impl.out; +package unknow.server.servlet.impl; import java.io.IOException; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.WriteListener; -import unknow.server.servlet.impl.ServletResponseImpl; import unknow.server.util.io.Buffers; /** diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletCookieConfigImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletCookieConfigImpl.java index c2738a82..2d851d98 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletCookieConfigImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletCookieConfigImpl.java @@ -52,16 +52,16 @@ public void setPath(String path) { setAttribute(PATH, path); } + /** {@inheritDoc} */ @Deprecated(since = "Servlet 6.0", forRemoval = true) @Override - /** @deprectated */ public String getComment() { return null; } + /** {@inheritDoc} */ @Deprecated(since = "Servlet 6.0", forRemoval = true) @Override - /** @deprectated */ public void setComment(String comment) { // OK } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java index b64f4f47..2ba64c4a 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletResponseImpl.java @@ -22,14 +22,11 @@ import jakarta.servlet.http.HttpServletResponse; import unknow.server.servlet.HttpAdapter; import unknow.server.servlet.HttpError; -import unknow.server.servlet.impl.out.AbstractServletOutput; /** * @author unknow */ public class ServletResponseImpl implements HttpServletResponse { -// private static final Logger logger = LoggerFactory.getLogger(ServletResponseImpl.class); - private static final DateTimeFormatter RFC1123 = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC); private final HttpAdapter co; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java index 8f54c104..5579e54d 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathUtils.java @@ -75,7 +75,7 @@ private static int readParam(StringBuilder sb, Reader r, boolean key) throws IOE return c; } - private static int decodeHex(int c) throws IOException { + private static int decodeHex(int c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') From 2410cdea357903e39bf62663e0ee028ab5b0341e Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sat, 22 Jun 2024 14:03:34 +0200 Subject: [PATCH 28/40] gh-30 fix race condition & update htt2 load test --- bench/result.sh | 9 +++++++ bench/run.sh | 7 +++--- .../servlet/http2/Http2ServletInput.java | 11 +++++--- .../java/unknow/server/util/io/Buffers.java | 25 +++++++++++++++++++ 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/bench/result.sh b/bench/result.sh index ffc79ca3..82ba385c 100644 --- a/bench/result.sh +++ b/bench/result.sh @@ -60,3 +60,12 @@ do for n in "${!tests[@]}"; do printf ' %10s' "${error[$s:$n]:-0}"; done echo done + +echo +echo "Http2 result" + +for i in "${!servers[@]}" +do + echo "$s" + tail -n 9 "$1/$i.log" +done diff --git a/bench/run.sh b/bench/run.sh index 6a22c45b..f72e8f4a 100644 --- a/bench/run.sh +++ b/bench/run.sh @@ -41,13 +41,14 @@ trap '[[ "$pid" ]] && kill -9 $pid' EXIT ${1}_start sleep 10 -echo "Warming up" +echo -e "\nWarming up" $JMETER -n -t bench/test.jmx -Jhost=127.0.0.1 -Jt=20 -Jport=8080 -Jout=/dev/null sleep 10 -echo "Testing.." +echo -e "\nTesting.." $JMETER -n -t bench/test.jmx -Jhost=127.0.0.1 -Jt=60 -Jc=10 -Jport=8080 -Jout=out/$1.csv -h2load -c 10 -t 10 -m 10 -D 60 --warm-up-time=10 http://127.0.0.1:8080/test +echo -e "\n launch http2 bench" +h2load -v -c 10 -t 10 -m 10 -D 60 --warm-up-time=10 http://127.0.0.1:8080/test > out/$1.log ${1}_stop sleep 10 diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java index 723b7b5b..dfdcd9c7 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java @@ -21,6 +21,11 @@ public void read(Buffers b, int size) throws InterruptedException { @Override public void close() { closed = true; + try { + in.signal(); + } catch (@SuppressWarnings("unused") InterruptedException e) { + Thread.currentThread().interrupt(); + } } @Override @@ -41,7 +46,7 @@ public void setReadListener(ReadListener readListener) { // ok public int read() throws IOException { try { while (!closed && in.isEmpty()) - in.awaitContent(); + in.await(); if (closed && in.isEmpty()) return -1; return in.read(false); @@ -55,7 +60,7 @@ public int read() throws IOException { public int read(byte[] b, int off, int len) throws IOException { try { while (!closed && in.isEmpty()) - in.awaitContent(); + in.await(); if (closed && in.isEmpty()) return -1; return in.read(b, off, len, false); @@ -74,7 +79,7 @@ public int available() throws IOException { public long skip(long n) throws IOException { try { while (!closed && in.isEmpty()) - in.awaitContent(); + in.await(); return in.skip(n); } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java index 0c97a3de..c243482a 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java +++ b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java @@ -323,6 +323,30 @@ public void awaitContent() throws InterruptedException { } } + /** + * wait for the signal (data added or signal is called) + */ + public void await() throws InterruptedException { + lock.lockInterruptibly(); + try { + cond.await(); + } finally { + lock.unlock(); + } + } + + /** + * wake up all thread in await() + */ + public void signal() throws InterruptedException { + lock.lockInterruptibly(); + try { + cond.signalAll(); + } finally { + lock.unlock(); + } + } + /** * append chunk * @param c chunk to add @@ -678,4 +702,5 @@ private Chunk() { o = l = 0; } } + } \ No newline at end of file From 49342c4cb75974a20b674c9f0759f4bda0ebc278 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sat, 22 Jun 2024 14:21:03 +0200 Subject: [PATCH 29/40] gh-30 fix report --- bench/result.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bench/result.sh b/bench/result.sh index 82ba385c..df576ad3 100644 --- a/bench/result.sh +++ b/bench/result.sh @@ -64,8 +64,9 @@ done echo echo "Http2 result" -for i in "${!servers[@]}" +for s in "${!servers[@]}" do + echo echo "$s" - tail -n 9 "$1/$i.log" + tail -n 9 "$1/$s.log" done From dbf4890bc1a3b803b35b9997a013c53e6d4478d3 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 23 Jun 2024 13:38:49 +0200 Subject: [PATCH 30/40] gh-30 fix header encoding --- bench/run.sh | 2 +- .../server/servlet/http2/Http2Headers.java | 12 ++--- .../server/servlet/http2/Http2Processor.java | 44 +++++++++---------- .../server/servlet/http2/Http2Stream.java | 5 --- .../servlet/http2/frame/FrameGoAway.java | 2 +- .../servlet/http2/frame/FrameSettings.java | 11 +++++ 6 files changed, 42 insertions(+), 34 deletions(-) diff --git a/bench/run.sh b/bench/run.sh index f72e8f4a..c21dbfa7 100644 --- a/bench/run.sh +++ b/bench/run.sh @@ -48,7 +48,7 @@ echo -e "\nTesting.." $JMETER -n -t bench/test.jmx -Jhost=127.0.0.1 -Jt=60 -Jc=10 -Jport=8080 -Jout=out/$1.csv echo -e "\n launch http2 bench" -h2load -v -c 10 -t 10 -m 10 -D 60 --warm-up-time=10 http://127.0.0.1:8080/test > out/$1.log +h2load -c 10 -t 10 -m 10 -D 60 --warm-up-time=10 http://127.0.0.1:8080/test > out/$1.log ${1}_stop sleep 10 diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java index 8b192809..1a9e2d78 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java @@ -150,7 +150,7 @@ public static void writeInt(Buffers out, int p, int n, int v) throws Interrupted * @throws InterruptedException */ public void writeHeader(Buffers out, String name, String value) throws InterruptedException { - int o = 0; + int o = -1; for (int i = 0; i < TABLE.length; i++) { Entry e = TABLE[i]; if (e.name.equals(name)) { @@ -158,24 +158,24 @@ public void writeHeader(Buffers out, String name, String value) throws Interrupt writeInt(out, 0b10000000, 7, i + 1); return; } - if (o == 0) + if (o == -1) o = i; } } int i = TABLE.length; for (Entry e : dynamic) { - i++; if (e.name.equals(name)) { if (e.value.equals(value)) { writeInt(out, 0b10000000, 7, i + 1); return; } - if (o == 0) + if (o == -1) o = i; } + i++; } - writeInt(out, 0b01000000, 6, o < 0 ? 0 : o); + writeInt(out, 0b01000000, 6, o < 0 ? 0 : o + 1); if (o < 0) writeData(out, name); writeData(out, value); @@ -247,6 +247,8 @@ private static String readData(InputStream in) throws IOException { private static void writeData(Buffers out, String value) throws InterruptedException { byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); + // TODO huffman + writeInt(out, 0, 7, bytes.length); out.write(bytes); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index b4f9ee00..d84e13c6 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -154,7 +154,6 @@ private void readFrame(Buffers buf) throws InterruptedException { int type = b[3]; int flags = b[4]; int id = (b[5] & 0x7f) << 24 | (b[6] & 0xff) << 16 | (b[7] & 0xff) << 8 | (b[8] & 0xff); - logger.debug("{} {} {} {}", size, type, flags, id); if (wantContinuation && type != 9 || !wantContinuation && type == 9) { goaway(PROTOCOL_ERROR); @@ -168,6 +167,7 @@ private void readFrame(Buffers buf) throws InterruptedException { return; } + logger.debug("{}: read {}", this, b.getClass().getSimpleName()); r = b.build(this, size, flags, id, buf); } @@ -186,37 +186,37 @@ public void goaway(int err) throws InterruptedException { co.pendingWrite.write(f); co.flush(); } + logger.debug("{}: send GOAWAY {}", this, error(err)); } - @SuppressWarnings("resource") public void sendHeaders(int id, ServletResponseImpl res) throws InterruptedException { byte[] f = new byte[9]; Buffers out = new Buffers(); int type = 1; - synchronized (headers) { - headers.writeHeader(out, ":status", Integer.toString(res.getStatus())); - for (String n : res.getHeaderNames()) { - for (String v : res.getHeaders(n)) { - headers.writeHeader(out, n, v); - - if (out.length() >= frame) { - formatFrame(f, out.length(), type, 0, id); - type = 9; - synchronized (co) { - co.pendingWrite.write(f); - out.read(co.pendingWrite, frame, false); - co.flush(); + synchronized (co) { + synchronized (headers) { + headers.writeHeader(out, ":status", Integer.toString(res.getStatus())); + for (String n : res.getHeaderNames()) { + for (String v : res.getHeaders(n)) { + headers.writeHeader(out, n, v); + + if (out.length() >= frame) { + formatFrame(f, out.length(), type, 0, id); + type = 9; + synchronized (co) { + co.pendingWrite.write(f); + out.read(co.pendingWrite, frame, false); + co.flush(); + } } } } } - } - int flag = 0x4; - Http2ServletOutput sout = (Http2ServletOutput) res.getRawStream(); - if (sout == null || sout.isDone()) - flag |= 0x1; - formatFrame(f, out.length(), type, flag, id); - synchronized (co) { + int flag = 0x4; + Http2ServletOutput sout = (Http2ServletOutput) res.getRawStream(); + if (sout == null || sout.isDone()) + flag |= 0x1; + formatFrame(f, out.length(), type, flag, id); co.pendingWrite.write(f); out.read(co.pendingWrite, -1, false); co.flush(); diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java index 7fff6d44..bfafae16 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java @@ -6,16 +6,12 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import jakarta.servlet.ServletInputStream; import unknow.server.servlet.HttpWorker; import unknow.server.servlet.impl.AbstractServletOutput; import unknow.server.servlet.utils.PathUtils; public class Http2Stream extends HttpWorker implements Http2FlowControl { - private static final Logger logger = LoggerFactory.getLogger(Http2Stream.class); private final int id; private final Http2Processor p; @@ -41,7 +37,6 @@ public Http2Stream(Http2Processor p, int id, int window) { } public final void addHeader(String name, String value) { - logger.debug("addHeader {}: {}", name, value); if (name.charAt(0) != ':') { req.addHeader(name, value); return; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java index 88f41d15..1bdb7661 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java @@ -37,7 +37,7 @@ public FrameReader process(Buffers buf) throws InterruptedException { lastId = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); buf.read(b, 0, 4, false); - int err = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); + int err = (b[0] & 0xff) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); logger.info("goaway last: {} err: {}", lastId, Http2Processor.error(err)); size -= 8; p.closing = true; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java index 0c577583..aaf34f67 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java @@ -1,14 +1,19 @@ package unknow.server.servlet.http2.frame; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import unknow.server.servlet.HttpConnection; import unknow.server.servlet.http2.Http2Processor; import unknow.server.util.io.Buffers; public class FrameSettings extends FrameReader { + private static final Logger logger = LoggerFactory.getLogger(FrameSettings.class); public static final FrameBuilder BUILDER = (p, size, flags, id, buf) -> { if ((flags & 0x1) == 1) { if (size != 0) p.goaway(Http2Processor.FRAME_SIZE_ERROR); + logger.trace("{}: settings ack", p); return null; } if (id != 0) { @@ -40,11 +45,13 @@ public final FrameReader process(Buffers buf) throws InterruptedException { switch (i) { case 1: + logger.trace("{}: SETTINGS_HEADER_TABLE_SIZE {}", p, v); synchronized (p.headers) { p.headers.setMax(v); } break; case 2: + logger.trace("{}: SETTINGS_ENABLE_PUSH {}", p, v); if (v < 0 || v > 1) { p.goaway(Http2Processor.PROTOCOL_ERROR); return null; @@ -52,12 +59,15 @@ public final FrameReader process(Buffers buf) throws InterruptedException { // allowPush = v == 1; break; case 3: + logger.trace("{}: SETTINGS_MAX_CONCURRENT_STREAMS {}", p, v); // concurrent = v; break; case 4: + logger.trace("{}: SETTINGS_INITIAL_WINDOW_SIZE {}", p, v); p.initialWindow = v; break; case 5: + logger.trace("{}: SETTINGS_MAX_FRAME_SIZE {}", p, v); if (v < 16384 || v > 16777215) { p.goaway(Http2Processor.PROTOCOL_ERROR); return null; @@ -65,6 +75,7 @@ public final FrameReader process(Buffers buf) throws InterruptedException { p.frame = v; break; case 6: + logger.trace("{}: SETTINGS_MAX_HEADER_LIST_SIZE {}", p, v); // headerList = v; break; default: From 047761a46f42b34d4269f7e8ad52c37d087980eb Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sun, 23 Jun 2024 17:35:33 +0200 Subject: [PATCH 31/40] gh-30 add huffman coding for header --- .../server/servlet/http2/Http2Headers.java | 12 +++- .../server/servlet/http2/Http2Huffman.java | 62 +++++++++++++++++-- .../servlet/http2/Http2HuffmanTest.java | 53 ++++++++++++++++ 3 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HuffmanTest.java diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java index 1a9e2d78..6ad69195 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java @@ -247,10 +247,16 @@ private static String readData(InputStream in) throws IOException { private static void writeData(Buffers out, String value) throws InterruptedException { byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); - // TODO huffman - writeInt(out, 0, 7, bytes.length); - out.write(bytes); + Buffers b = new Buffers(); + Http2Huffman.encode(b, bytes); + if (b.length() < bytes.length) { + writeInt(out, 0x80, 7, b.length()); + b.read(out, -1, false); + } else { + writeInt(out, 0, 7, bytes.length); + out.write(bytes); + } } protected Entry get(int i) { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java index 7c6e144a..fd8441eb 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java @@ -4,6 +4,8 @@ import java.io.IOException; import java.io.InputStream; +import unknow.server.util.io.Buffers; + /** * HPACK static huffman table * https://httpwg.org/specs/rfc7541.html#huffman.code @@ -37,6 +39,29 @@ public class Http2Huffman { 10, 13, 22, 256 // 29, 30 }; + private static final int[] codes = { 0x1ff8, 0x7fffd8, 0xfffffe2, 0xfffffe3, 0xfffffe4, 0xfffffe5, 0xfffffe6, 0xfffffe7, 0xfffffe8, 0xffffea, 0x3ffffffc, 0xfffffe9, + 0xfffffea, 0x3ffffffd, 0xfffffeb, 0xfffffec, 0xfffffed, 0xfffffee, 0xfffffef, 0xffffff0, 0xffffff1, 0xffffff2, 0x3ffffffe, 0xffffff3, 0xffffff4, 0xffffff5, + 0xffffff6, 0xffffff7, 0xffffff8, 0xffffff9, 0xffffffa, 0xffffffb, 0x14, 0x3f8, 0x3f9, 0xffa, 0x1ff9, 0x15, 0xf8, 0x7fa, 0x3fa, 0x3fb, 0xf9, 0x7fb, 0xfa, 0x16, + 0x17, 0x18, 0x0, 0x1, 0x2, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x5c, 0xfb, 0x7ffc, 0x20, 0xffb, 0x3fc, 0x1ffa, 0x21, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xfc, 0x73, 0xfd, 0x1ffb, 0x7fff0, 0x1ffc, 0x3ffc, 0x22, 0x7ffd, + 0x3, 0x23, 0x4, 0x24, 0x5, 0x25, 0x26, 0x27, 0x6, 0x74, 0x75, 0x28, 0x29, 0x2a, 0x7, 0x2b, 0x76, 0x2c, 0x8, 0x9, 0x2d, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7ffe, 0x7fc, + 0x3ffd, 0x1ffd, 0xffffffc, 0xfffe6, 0x3fffd2, 0xfffe7, 0xfffe8, 0x3fffd3, 0x3fffd4, 0x3fffd5, 0x7fffd9, 0x3fffd6, 0x7fffda, 0x7fffdb, 0x7fffdc, 0x7fffdd, 0x7fffde, + 0xffffeb, 0x7fffdf, 0xffffec, 0xffffed, 0x3fffd7, 0x7fffe0, 0xffffee, 0x7fffe1, 0x7fffe2, 0x7fffe3, 0x7fffe4, 0x1fffdc, 0x3fffd8, 0x7fffe5, 0x3fffd9, 0x7fffe6, + 0x7fffe7, 0xffffef, 0x3fffda, 0x1fffdd, 0xfffe9, 0x3fffdb, 0x3fffdc, 0x7fffe8, 0x7fffe9, 0x1fffde, 0x7fffea, 0x3fffdd, 0x3fffde, 0xfffff0, 0x1fffdf, 0x3fffdf, + 0x7fffeb, 0x7fffec, 0x1fffe0, 0x1fffe1, 0x3fffe0, 0x1fffe2, 0x7fffed, 0x3fffe1, 0x7fffee, 0x7fffef, 0xfffea, 0x3fffe2, 0x3fffe3, 0x3fffe4, 0x7ffff0, 0x3fffe5, + 0x3fffe6, 0x7ffff1, 0x3ffffe0, 0x3ffffe1, 0xfffeb, 0x7fff1, 0x3fffe7, 0x7ffff2, 0x3fffe8, 0x1ffffec, 0x3ffffe2, 0x3ffffe3, 0x3ffffe4, 0x7ffffde, 0x7ffffdf, + 0x3ffffe5, 0xfffff1, 0x1ffffed, 0x7fff2, 0x1fffe3, 0x3ffffe6, 0x7ffffe0, 0x7ffffe1, 0x3ffffe7, 0x7ffffe2, 0xfffff2, 0x1fffe4, 0x1fffe5, 0x3ffffe8, 0x3ffffe9, + 0xffffffd, 0x7ffffe3, 0x7ffffe4, 0x7ffffe5, 0xfffec, 0xfffff3, 0xfffed, 0x1fffe6, 0x3fffe9, 0x1fffe7, 0x1fffe8, 0x7ffff3, 0x3fffea, 0x3fffeb, 0x1ffffee, 0x1ffffef, + 0xfffff4, 0xfffff5, 0x3ffffea, 0x7ffff4, 0x3ffffeb, 0x7ffffe6, 0x3ffffec, 0x3ffffed, 0x7ffffe7, 0x7ffffe8, 0x7ffffe9, 0x7ffffea, 0x7ffffeb, 0xffffffe, 0x7ffffec, + 0x7ffffed, 0x7ffffee, 0x7ffffef, 0x7fffff0, 0x3ffffee }; + + private static final int[] sizes = { 13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 28, 6, 10, + 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10, 13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6, 15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6, 6, 6, 5, 6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 15, 11, 14, 13, 28, 20, 22, 20, 20, 22, 22, + 22, 23, 22, 23, 23, 23, 23, 23, 24, 23, 24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23, 24, 22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, + 23, 21, 21, 22, 21, 23, 22, 23, 23, 20, 22, 22, 22, 23, 22, 22, 23, 26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27, 26, 24, 25, 19, 21, 26, 27, 27, 26, 27, 24, + 21, 21, 26, 26, 28, 27, 27, 27, 20, 24, 20, 21, 22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23, 26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27, 27, 27, 27, 27, 26 }; + private Http2Huffman() { } @@ -71,6 +96,33 @@ public static String decode(InputStream b, int max, StringBuilder sb) throws IOE return sb.toString(); } + public static void encode(Buffers b, byte[] data) throws InterruptedException { + C c = new C(); + for (int i = 0; i < data.length; i++) + encode(c, b, data[i]); + if (c.cnt != 0) + b.write(c.bit | (0xff >> c.cnt)); + } + + private static void encode(C c, Buffers buf, byte b) throws InterruptedException { + int code = codes[b]; + int size = sizes[b]; + + while (size > 0) { + int r = 8 - c.cnt; + if (size <= r) { + c.cnt += size; + c.bit |= (code << (r - size)) & 0xFF; + return; + } + + size -= r; + buf.write(c.bit | (code >> size) & 0xFF); + c.bit = 0; + c.cnt = 0; + } + } + private static final char read(S s) throws IOException { int first = 0; /* first code of length len */ int index = 0; /* index of first code of length len in symbol table */ @@ -97,12 +149,14 @@ private static final char read(S s) throws IOException { } } - static final class S { - final InputStream b; - int max; - + static class C { int bit; int cnt; + } + + static class S extends C { + final InputStream b; + int max; public S(InputStream b, int max) { this.b = b; diff --git a/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HuffmanTest.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HuffmanTest.java new file mode 100644 index 00000000..060f942b --- /dev/null +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HuffmanTest.java @@ -0,0 +1,53 @@ +package unknow.server.servlet.http2; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import unknow.server.util.io.Buffers; +import unknow.server.util.io.BuffersUtils; + +public class Http2HuffmanTest { + public static Stream input() { + //@formatter:off + return Stream.of( + Arguments.of("www.example.com", new byte[] { b(0xf1), b(0xe3), b(0xc2), b(0xe5), b(0xf2), b(0x3a), b(0x6b), b(0xa0), b(0xab), b(0x90), b(0xf4), b(0xff) }), + Arguments.of("no-cache", new byte[]{b(0xa8),b(0xeb),b(0x10),b(0x64),b(0x9c),b(0xbf)}), + Arguments.of("custom-key", new byte[]{b(0x25),b(0xa8),b(0x49),b(0xe9),b(0x5b),b(0xa9),b(0x7d),b(0x7f)}), + Arguments.of("custom-value", new byte[]{b(0x25),b(0xa8),b(0x49),b(0xe9),b(0x5b),b(0xb8),b(0xe8),b(0xb4),b(0xbf)}) + ); //@formatter:on + } + + @ParameterizedTest + @MethodSource("input") + public void decode(String decoded, byte[] encoded) throws IOException { + StringBuilder sb = new StringBuilder(); + try (InputStream b = new ByteArrayInputStream(encoded)) { + Http2Huffman.decode(b, encoded.length, sb); + } + assertEquals(decoded, sb.toString()); + } + + @ParameterizedTest + @MethodSource("input") + public void encode(String decoded, byte[] encoded) throws InterruptedException { + byte[] bytes = decoded.getBytes(StandardCharsets.US_ASCII); + Buffers b = new Buffers(); + Http2Huffman.encode(b, bytes); + byte[] array = BuffersUtils.toArray(b, 0, -1); + assertArrayEquals(encoded, array); + } + + public static final byte b(int i) { + return (byte) (i & 0xFF); + } +} From 2a1f78ed34613899ab526f00f35ba547c50bcbba Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Mon, 24 Jun 2024 21:12:26 +0200 Subject: [PATCH 32/40] gh-30 add log --- bench/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/run.sh b/bench/run.sh index c21dbfa7..cfc9668b 100644 --- a/bench/run.sh +++ b/bench/run.sh @@ -1,7 +1,7 @@ #!/bin/bash unknow_start() { - java -jar unknow-server-test/unknow-server-test-jar/target/server.jar > logs/unknow.log 2>&1 & + java -Dorg.slf4j.simpleLogger.defaultLogLevel=debug -jar unknow-server-test/unknow-server-test-jar/target/server.jar --listener=LOG> logs/unknow.log 2>&1 & pid=$! } unknow_stop() { From 267093821b6d40ec1b974a305a189e705eb5231d Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Mon, 24 Jun 2024 21:52:57 +0200 Subject: [PATCH 33/40] gh-30 update --- bench/run.sh | 2 +- .../java/unknow/server/nio/NIOConnection.java | 50 ++++++++++--------- .../java/unknow/server/nio/NIOServer.java | 8 +-- .../unknow/server/nio/NIOServerBuilder.java | 2 +- .../java/unknow/server/nio/NIOWorker.java | 10 ++-- .../java/unknow/server/nio/NIOWorkers.java | 11 ++-- .../unknow/server/nio/ShutdownConnection.java | 10 +++- .../server/protobuf/ProtoStuffConnection.java | 6 +-- .../server/servlet/AbstractHttpServer.java | 2 +- .../unknow/server/servlet/HttpConnection.java | 4 +- 10 files changed, 59 insertions(+), 46 deletions(-) diff --git a/bench/run.sh b/bench/run.sh index cfc9668b..20b3f33f 100644 --- a/bench/run.sh +++ b/bench/run.sh @@ -1,7 +1,7 @@ #!/bin/bash unknow_start() { - java -Dorg.slf4j.simpleLogger.defaultLogLevel=debug -jar unknow-server-test/unknow-server-test-jar/target/server.jar --listener=LOG> logs/unknow.log 2>&1 & + java -jar unknow-server-test/unknow-server-test-jar/target/server.jar --listener=LOG> logs/unknow.log 2>&1 & pid=$! } unknow_stop() { diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java index b61fa5b4..993ca0f3 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java @@ -36,27 +36,40 @@ public class NIOConnection { private final InputStream in = new BuffersInputStream(pendingRead); /** Output stream */ - private Out out; + protected final Out out; /** selection key */ - private SelectionKey key; + private final SelectionKey key; + + protected final InetSocketAddress local; + protected final InetSocketAddress remote; private long lastRead; private long lastWrite; - /** create new connection */ - protected NIOConnection() { - } - /** - * bind this co on a selection key - * @param key the key + * create new connection + * @param key the selectionKey */ - final void init(SelectionKey key) { + @SuppressWarnings("resource") + protected NIOConnection(SelectionKey key) { this.key = key; this.out = new Out(this); lastRead = lastWrite = System.currentTimeMillis(); - onInit(); + SocketChannel channel = (SocketChannel) key.channel(); + InetSocketAddress a; + try { + a = (InetSocketAddress) channel.getLocalAddress(); + } catch (@SuppressWarnings("unused") Exception e) { + a = DISCONECTED; + } + local = a; + try { + a = (InetSocketAddress) channel.getRemoteAddress(); + } catch (@SuppressWarnings("unused") Exception e) { + a = DISCONECTED; + } + remote = a; } /** @@ -202,13 +215,8 @@ public final Out getOut() { * * @return the remote address */ - @SuppressWarnings("resource") public final InetSocketAddress getRemote() { - try { - return (InetSocketAddress) ((SocketChannel) key.channel()).getRemoteAddress(); - } catch (@SuppressWarnings("unused") Exception e) { - return DISCONECTED; - } + return remote; } /** @@ -216,13 +224,8 @@ public final InetSocketAddress getRemote() { * * @return the local address */ - @SuppressWarnings("resource") public final InetSocketAddress getLocal() { - try { - return (InetSocketAddress) ((SocketChannel) key.channel()).getLocalAddress(); - } catch (@SuppressWarnings("unused") Exception e) { - return DISCONECTED; - } + return local; } /** @@ -256,10 +259,9 @@ public final void free() throws IOException { onFree(); } - @SuppressWarnings("resource") @Override public String toString() { - return key.channel().toString() + " closed: " + isClosed() + " pending: " + pendingWrite.length(); + return getClass() + "[local=" + local + " remote=" + remote + "]"; } /** output stream for this connection */ diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOServer.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOServer.java index eb648baf..745d7332 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOServer.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOServer.java @@ -8,7 +8,7 @@ import java.nio.channels.SelectionKey; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; -import java.util.function.Supplier; +import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,7 +48,7 @@ public NIOServer(NIOWorkers workers, NIOServerListener listener) throws IOExcept * @throws IOException on ioException */ @SuppressWarnings("resource") - public void bind(SocketAddress a, Supplier s) throws IOException { + public void bind(SocketAddress a, Function s) throws IOException { logger.info("Server bind to {}", a); ServerSocketChannel open = ServerSocketChannel.open(); open.configureBlocking(false); @@ -67,9 +67,9 @@ protected void onStartup() { protected void selected(SelectionKey key) throws IOException, InterruptedException { try { @SuppressWarnings("unchecked") - Supplier pool = (Supplier) key.attachment(); + Function factory = (Function) key.attachment(); SocketChannel socket = ((ServerSocketChannel) key.channel()).accept(); - workers.register(socket, pool); + workers.register(socket, factory); } catch (IOException e) { logger.warn("Failed to accept", e); } diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOServerBuilder.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOServerBuilder.java index 2e61d7dd..e0dafe9a 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOServerBuilder.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOServerBuilder.java @@ -153,7 +153,7 @@ public final NIOServer build(String... arg) throws Exception { process(server, cli); InetSocketAddress addr = parseAddr(cli, shutdown, "127.0.0.1"); if (addr != null) - server.bind(addr, () -> new ShutdownConnection(server)); + server.bind(addr, key -> new ShutdownConnection(key, server)); return server; } diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorker.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorker.java index d56f2d97..4ab1212c 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorker.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorker.java @@ -12,7 +12,7 @@ import java.util.Queue; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Supplier; +import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,12 +65,14 @@ public NIOWorker(int id, NIOServerListener listener, long timeout) throws IOExce */ @SuppressWarnings("resource") @Override - public final void register(SocketChannel socket, Supplier pool) throws IOException, InterruptedException { + public final void register(SocketChannel socket, Function pool) throws IOException, InterruptedException { socket.setOption(StandardSocketOptions.SO_KEEPALIVE, Boolean.TRUE).configureBlocking(false); mutex.lockInterruptibly(); try { selector.wakeup(); - init.add(socket.register(selector, SelectionKey.OP_READ, pool.get())); + SelectionKey key = socket.register(selector, SelectionKey.OP_READ); + init.add(key); + key.attach(pool.apply(key)); } finally { mutex.unlock(); } @@ -119,7 +121,7 @@ protected void onSelect(boolean close) throws InterruptedException { SelectionKey k; while ((k = init.poll()) != null) { NIOConnection co = (NIOConnection) k.attachment(); - co.init(k); + co.onInit(); listener.accepted(id, co); } diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorkers.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorkers.java index 839b1009..494cd748 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorkers.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOWorkers.java @@ -4,8 +4,9 @@ package unknow.server.nio; import java.io.IOException; +import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; -import java.util.function.Supplier; +import java.util.function.Function; /** * @author unknow @@ -15,11 +16,11 @@ public interface NIOWorkers { * register a socket to an IOWorker * * @param socket the socket to register - * @param pool the connection factory + * @param factory the connection factory * @throws IOException on ioexception * @throws InterruptedException on interrupt */ - void register(SocketChannel socket, Supplier pool) throws IOException, InterruptedException; + void register(SocketChannel socket, Function factory) throws IOException, InterruptedException; /** * start the IOWorker @@ -52,8 +53,8 @@ public RoundRobin(NIOWorker[] workers) { } @Override - public synchronized void register(SocketChannel socket, Supplier pool) throws IOException, InterruptedException { - w[o++].register(socket, pool); + public synchronized void register(SocketChannel socket, Function factory) throws IOException, InterruptedException { + w[o++].register(socket, factory); if (o == w.length) o = 0; } diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/ShutdownConnection.java b/unknow-server-nio/src/main/java/unknow/server/nio/ShutdownConnection.java index 097a7981..2cc6e2db 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/ShutdownConnection.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/ShutdownConnection.java @@ -3,14 +3,20 @@ */ package unknow.server.nio; +import java.nio.channels.SelectionKey; + /** * a connection that shutdown the server on creation */ public class ShutdownConnection extends NIOConnection { private final NIOServer server; - /** @param server the server */ - public ShutdownConnection(NIOServer server) { + /** + * @param key the selection key + * @param server the server + */ + public ShutdownConnection(SelectionKey key, NIOServer server) { + super(key); this.server = server; } diff --git a/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java b/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java index 3a74e887..3090cce7 100644 --- a/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java +++ b/unknow-server-protostuff/src/main/java/unknow/server/protobuf/ProtoStuffConnection.java @@ -5,6 +5,7 @@ */ import java.io.IOException; +import java.nio.channels.SelectionKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,18 +30,17 @@ public abstract class ProtoStuffConnection extends NIOConnection { private final Schema schema; private final boolean protostuff; - protected Out out; private LimitedInputStream in; private CodedInput input; - protected ProtoStuffConnection(Schema schema, boolean protostuff) { + protected ProtoStuffConnection(SelectionKey key, Schema schema, boolean protostuff) { + super(key); this.schema = schema; this.protostuff = protostuff; } @Override protected final void onInit() { - out = getOut(); in = new LimitedInputStream(getIn(), Integer.MAX_VALUE); input = new CodedInput(in, protostuff); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/AbstractHttpServer.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/AbstractHttpServer.java index f220fe2c..e22b126e 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/AbstractHttpServer.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/AbstractHttpServer.java @@ -94,7 +94,7 @@ protected void process(NIOServer server, CommandLine cli) throws Exception { return t; }); int keepAliveIdle = parseInt(cli, keepAlive, -1); - server.bind(address, () -> new HttpConnection(executor, ctx, manager, events, keepAliveIdle)); + server.bind(address, key -> new HttpConnection(key,executor, ctx, manager, events, keepAliveIdle)); } /** diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java index 0378fd0f..50ce0153 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/HttpConnection.java @@ -3,6 +3,7 @@ */ package unknow.server.servlet; +import java.nio.channels.SelectionKey; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @@ -38,7 +39,8 @@ public class HttpConnection extends NIOConnection { * @param events * @param manager */ - protected HttpConnection(ExecutorService executor, ServletContextImpl ctx, ServletManager manager, EventManager events, int keepAliveIdle) { + protected HttpConnection(SelectionKey key, ExecutorService executor, ServletContextImpl ctx, ServletManager manager, EventManager events, int keepAliveIdle) { + super(key); this.executor = executor; this.ctx = ctx; this.manager = manager; From 08807f48a83742911419678e004d88c6996da43e Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Mon, 24 Jun 2024 22:24:57 +0200 Subject: [PATCH 34/40] gh-30 put back debug log --- bench/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/run.sh b/bench/run.sh index 20b3f33f..35f4d065 100644 --- a/bench/run.sh +++ b/bench/run.sh @@ -1,7 +1,7 @@ #!/bin/bash unknow_start() { - java -jar unknow-server-test/unknow-server-test-jar/target/server.jar --listener=LOG> logs/unknow.log 2>&1 & + java -Dorg.slf4j.simpleLogger.defaultLogLevel=debug -jar unknow-server-test/unknow-server-test-jar/target/server.jar > logs/unknow.log 2>&1 & pid=$! } unknow_stop() { From bb1e47de9d533c65b2aceefff33e89284a540ae3 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Mon, 24 Jun 2024 22:54:00 +0200 Subject: [PATCH 35/40] gh-30 put log in info --- bench/run.sh | 2 +- .../main/java/unknow/server/servlet/http2/Http2Processor.java | 3 ++- .../java/unknow/server/servlet/utils/PathTreeBuilderTest.java | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bench/run.sh b/bench/run.sh index 35f4d065..c21dbfa7 100644 --- a/bench/run.sh +++ b/bench/run.sh @@ -1,7 +1,7 @@ #!/bin/bash unknow_start() { - java -Dorg.slf4j.simpleLogger.defaultLogLevel=debug -jar unknow-server-test/unknow-server-test-jar/target/server.jar > logs/unknow.log 2>&1 & + java -jar unknow-server-test/unknow-server-test-jar/target/server.jar > logs/unknow.log 2>&1 & pid=$! } unknow_stop() { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index d84e13c6..e25d0976 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -167,7 +167,8 @@ private void readFrame(Buffers buf) throws InterruptedException { return; } - logger.debug("{}: read {}", this, b.getClass().getSimpleName()); + logger.info("{}: readFrame", this); +// logger.debug("{}: read {}", this, b.getClass().getSimpleName()); r = b.build(this, size, flags, id, buf); } diff --git a/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeBuilderTest.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeBuilderTest.java index 0adc2faf..a8002034 100644 --- a/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeBuilderTest.java +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/utils/PathTreeBuilderTest.java @@ -17,8 +17,6 @@ import jakarta.servlet.ServletException; import unknow.server.servlet.impl.FilterConfigImpl; import unknow.server.servlet.impl.ServletConfigImpl; -import unknow.server.servlet.utils.PathTree; -import unknow.server.servlet.utils.PathTreeBuilder; import unknow.server.servlet.utils.PathTree.PartNode; /** From cf60a79520013e57d5051eace3be8505f7ee3a51 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Mon, 24 Jun 2024 23:09:26 +0200 Subject: [PATCH 36/40] gh-30 remove logs --- .../main/java/unknow/server/servlet/http2/Http2Processor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index e25d0976..bf84620e 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -167,7 +167,6 @@ private void readFrame(Buffers buf) throws InterruptedException { return; } - logger.info("{}: readFrame", this); // logger.debug("{}: read {}", this, b.getClass().getSimpleName()); r = b.build(this, size, flags, id, buf); } From 091f6aaecd6bf9604e607e3af5077e5334723531 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Mon, 24 Jun 2024 23:28:29 +0200 Subject: [PATCH 37/40] gh-30 add log --- .../main/java/unknow/server/servlet/http2/Http2Processor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index bf84620e..8c25bc7b 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -99,7 +99,7 @@ public Http2Processor(HttpConnection co) throws InterruptedException { } @Override - public void process() throws InterruptedException { + public final void process() throws InterruptedException { if (r != null) { r = r.process(co.pendingRead); if (r != null) @@ -167,7 +167,7 @@ private void readFrame(Buffers buf) throws InterruptedException { return; } -// logger.debug("{}: read {}", this, b.getClass().getSimpleName()); + logger.info("readFrame"); r = b.build(this, size, flags, id, buf); } From 9b4aa47ec13c71ec4654c383d581f67ed0450a36 Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Fri, 28 Jun 2024 22:33:15 +0200 Subject: [PATCH 38/40] gh-30 fix some synchronization issues --- .../java/unknow/server/nio/NIOConnection.java | 2 +- .../server/servlet/http2/Http2Headers.java | 32 ---------- .../server/servlet/http2/Http2Processor.java | 63 ++++++++++--------- .../servlet/http2/Http2ServletInput.java | 24 ++++--- .../server/servlet/http2/Http2Stream.java | 4 +- .../servlet/http2/frame/FrameGoAway.java | 7 +-- .../server/servlet/http2/frame/FramePing.java | 8 ++- .../servlet/http2/frame/FrameSettings.java | 8 ++- .../java/unknow/server/util/io/Buffers.java | 8 +++ 9 files changed, 76 insertions(+), 80 deletions(-) diff --git a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java index 993ca0f3..877a6c90 100644 --- a/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java +++ b/unknow-server-nio/src/main/java/unknow/server/nio/NIOConnection.java @@ -166,7 +166,7 @@ protected final void writeInto(SocketChannel channel, ByteBuffer buf) throws Int onWrite(); } - private void toggleKeyOps() { + public void toggleKeyOps() { key.interestOps(pendingWrite.isEmpty() ? SelectionKey.OP_READ : SelectionKey.OP_READ | SelectionKey.OP_WRITE); } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java index 6ad69195..b4f319c4 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java @@ -329,36 +329,4 @@ public boolean equals(Object obj) { return Objects.equals(name, other.name) && Objects.equals(value, other.value); } } - - public static class EntryData { - final String v; - final int l; - - public EntryData(String text, int len) { - this.v = text; - this.l = len; - } - - @Override - public String toString() { - return v + "(" + l + ")"; - } - - @Override - public int hashCode() { - return Objects.hash(l, v); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - EntryData other = (EntryData) obj; - return l == other.l && Objects.equals(v, other.v); - } - } } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index 8c25bc7b..2bf25b8e 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -167,7 +167,6 @@ private void readFrame(Buffers buf) throws InterruptedException { return; } - logger.info("readFrame"); r = b.build(this, size, flags, id, buf); } @@ -182,32 +181,47 @@ public void goaway(int err) throws InterruptedException { f[14] = (byte) ((err >> 16) & 0xff); f[15] = (byte) ((err >> 8) & 0xff); f[16] = (byte) (err & 0xff); - synchronized (co) { - co.pendingWrite.write(f); - co.flush(); + Buffers b = co.pendingWrite; + b.lock(); + try { + b.write(f); + } finally { + b.unlock(); } + co.flush(); logger.debug("{}: send GOAWAY {}", this, error(err)); } + public void sendFrame(byte[] b, int size, int type, int flags, int id, Buffers data) throws InterruptedException { + size = Math.min(size, data.length()); + formatFrame(b, size, type, flags, id); + Buffers write = co.pendingWrite; + write.lock(); + try { + write.write(b); + data.read(write, size, false); + } finally { + write.unlock(); + } + co.toggleKeyOps(); + } + public void sendHeaders(int id, ServletResponseImpl res) throws InterruptedException { byte[] f = new byte[9]; Buffers out = new Buffers(); int type = 1; - synchronized (co) { + Buffers write = co.pendingWrite; + write.lock(); + try { // all headers frame should be together synchronized (headers) { headers.writeHeader(out, ":status", Integer.toString(res.getStatus())); for (String n : res.getHeaderNames()) { for (String v : res.getHeaders(n)) { headers.writeHeader(out, n, v); - if (out.length() >= frame) { - formatFrame(f, out.length(), type, 0, id); + while (out.length() >= frame) { + sendFrame(f, frame, type, 0, id, out); type = 9; - synchronized (co) { - co.pendingWrite.write(f); - out.read(co.pendingWrite, frame, false); - co.flush(); - } } } } @@ -216,29 +230,20 @@ public void sendHeaders(int id, ServletResponseImpl res) throws InterruptedExcep Http2ServletOutput sout = (Http2ServletOutput) res.getRawStream(); if (sout == null || sout.isDone()) flag |= 0x1; - formatFrame(f, out.length(), type, flag, id); - co.pendingWrite.write(f); - out.read(co.pendingWrite, -1, false); - co.flush(); + + sendFrame(f, out.length(), type, flag, id, out); + } finally { + write.unlock(); } } public void sendData(int id, Buffers data, boolean done) throws InterruptedException { byte[] f = new byte[9]; int l = data.length(); - while (l > frame) { - formatFrame(f, frame, 0, 0, id); - synchronized (co) { - co.pendingWrite.write(f); - data.read(co.pendingWrite, frame, false); - } - } - formatFrame(f, data.length(), 0, done ? 0x1 : 0, id); - synchronized (co) { - co.pendingWrite.write(f); - data.read(co.pendingWrite, l, false); - co.flush(); - } + while (l > frame) + sendFrame(f, frame, 0, 0, id, data); + sendFrame(f, data.length(), 0, done ? 0x1 : 0, id, data); + co.flush(); } public static void formatFrame(byte[] b, int size, int type, int flags, int id) { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java index dfdcd9c7..f69c13ae 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2ServletInput.java @@ -20,11 +20,14 @@ public void read(Buffers b, int size) throws InterruptedException { @Override public void close() { - closed = true; try { + in.lock(); + closed = true; in.signal(); } catch (@SuppressWarnings("unused") InterruptedException e) { Thread.currentThread().interrupt(); + } finally { + in.unlock(); } } @@ -45,8 +48,7 @@ public void setReadListener(ReadListener readListener) { // ok @Override public int read() throws IOException { try { - while (!closed && in.isEmpty()) - in.await(); + await(); if (closed && in.isEmpty()) return -1; return in.read(false); @@ -59,8 +61,7 @@ public int read() throws IOException { @Override public int read(byte[] b, int off, int len) throws IOException { try { - while (!closed && in.isEmpty()) - in.await(); + await(); if (closed && in.isEmpty()) return -1; return in.read(b, off, len, false); @@ -78,12 +79,21 @@ public int available() throws IOException { @Override public long skip(long n) throws IOException { try { - while (!closed && in.isEmpty()) - in.await(); + await(); return in.skip(n); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException(e); } } + + private void await() throws InterruptedException { + in.lock(); + try { + while (!closed && in.isEmpty()) + in.await(); + } finally { + in.unlock(); + } + } } \ No newline at end of file diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java index bfafae16..642600a3 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Stream.java @@ -16,7 +16,7 @@ public class Http2Stream extends HttpWorker implements Http2FlowControl { private final int id; private final Http2Processor p; public final Http2ServletInput in; - final Http2ServletOutput out; + private final Http2ServletOutput out; private int window; @@ -84,8 +84,6 @@ public AbstractServletOutput createOutput() { @Override public void commit() throws IOException { - // write header - try { p.sendHeaders(id, res); } catch (InterruptedException e) { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java index 1bdb7661..4c91411d 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameGoAway.java @@ -23,7 +23,7 @@ public class FrameGoAway extends FrameReader { protected FrameGoAway(Http2Processor p, int size, int flags, int id) { super(p, size, flags, id); - b = new byte[4]; + b = new byte[8]; } @Override @@ -33,11 +33,10 @@ public FrameReader process(Buffers buf) throws InterruptedException { if (buf.length() < 8) return this; - buf.read(b, 0, 4, false); + buf.read(b, 0, 8, false); lastId = (b[0] & 0x7f) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); - buf.read(b, 0, 4, false); - int err = (b[0] & 0xff) << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); + int err = (b[4] & 0xff) << 24 | (b[5] & 0xff) << 16 | (b[6] & 0xff) << 8 | (b[7] & 0xff); logger.info("goaway last: {} err: {}", lastId, Http2Processor.error(err)); size -= 8; p.closing = true; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FramePing.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FramePing.java index d262645a..62e1ed5b 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FramePing.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FramePing.java @@ -35,9 +35,13 @@ public final FrameReader process(Buffers buf) throws InterruptedException { buf.read(b, 9, 8, false); Http2Processor.formatFrame(b, 8, 8, 1, 0); HttpConnection co = p.co; - synchronized (co) { - co.pendingWrite.write(b); + Buffers write = co.pendingWrite; + write.lock(); + try { + write.write(b); co.flush(); + } finally { + write.unlock(); } return null; } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java index aaf34f67..05086083 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/frame/FrameSettings.java @@ -89,9 +89,13 @@ public final FrameReader process(Buffers buf) throws InterruptedException { Http2Processor.formatFrame(b, 0, 4, 1, 0); HttpConnection co = p.co; - synchronized (co) { - co.pendingWrite.write(b); + Buffers write = co.pendingWrite; + write.lock(); + try { + write.write(b); co.flush(); + } finally { + write.unlock(); } return null; } diff --git a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java index c243482a..94fbe61b 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java +++ b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java @@ -323,6 +323,14 @@ public void awaitContent() throws InterruptedException { } } + public void lock() throws InterruptedException { + lock.lockInterruptibly(); + } + + public void unlock() { + lock.unlock(); + } + /** * wait for the signal (data added or signal is called) */ From bd92dbeee25c10d011cf17fe6eb9dff272bf916b Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sat, 29 Jun 2024 09:44:55 +0200 Subject: [PATCH 39/40] gh-30 fix some code duplication --- .../server/servlet/AccessLogFilter.java | 26 ++++----- .../server/servlet/http2/Http2Processor.java | 11 ++++ .../server/servlet/http2/frame/FramePing.java | 11 +--- .../servlet/http2/frame/FrameSettings.java | 15 +---- .../server/servlet/impl/AbstractConfig.java | 47 ++++++++++++++++ .../server/servlet/impl/FilterConfigImpl.java | 41 +------------- .../servlet/impl/ServletConfigImpl.java | 41 +------------- .../unknow/server/servlet/utils/PathTree.java | 32 ----------- .../server/servlet/utils/ServletResource.java | 17 +++--- .../servlet/utils/ServletResourceStatic.java | 55 ++----------------- .../java/unknow/server/util/io/Buffers.java | 1 + 11 files changed, 90 insertions(+), 207 deletions(-) create mode 100644 unknow-server-servlet/src/main/java/unknow/server/servlet/impl/AbstractConfig.java diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/AccessLogFilter.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/AccessLogFilter.java index 67c4a111..c76b218f 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/AccessLogFilter.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/AccessLogFilter.java @@ -71,25 +71,11 @@ public class AccessLogFilter implements Filter { throw new ServletException("unknow remote type '" + t + "'"); }); builders.put("start", param -> { - String type = param.isEmpty() ? "iso" : param.get(0); - DateTimeFormatter f; - if (type.equals("iso")) - f = DateTimeFormatter.ISO_LOCAL_DATE_TIME; - else if (type.equals("rfc")) - f = DateTimeFormatter.RFC_1123_DATE_TIME; - else - f = DateTimeFormatter.ofPattern(type); + DateTimeFormatter f = getFormater(param.isEmpty() ? "iso" : param.get(0)); return (sb, start, end, req, res) -> f.formatTo(start, sb); }); builders.put("end", param -> { - String type = param.isEmpty() ? "iso" : param.get(0); - DateTimeFormatter f; - if (type.equals("iso")) - f = DateTimeFormatter.ISO_LOCAL_DATE_TIME; - else if (type.equals("rfc")) - f = DateTimeFormatter.RFC_1123_DATE_TIME; - else - f = DateTimeFormatter.ofPattern(type); + DateTimeFormatter f = getFormater(param.isEmpty() ? "iso" : param.get(0)); return (sb, start, end, req, res) -> f.formatTo(end, sb); }); builders.put("duration", param -> { @@ -265,6 +251,14 @@ private static final String parseFormat(String template, int o, int e, List parameters; + + protected AbstractConfig(String name, ArrayMap parameters) { + this.name = name; + this.parameters = parameters; + } + + public String getName() { + return name; + } + + public String getInitParameter(String name) { + return parameters.get(name); + } + + public Enumeration getInitParameterNames() { + return parameters.names(); + } + + public Map getInitParameters() { + return Collections.unmodifiableMap(parameters); + } + + @SuppressWarnings("unused") + public boolean setInitParameter(String name, String value) { + throw new IllegalStateException(ALREADY_INITIALIZED); + } + + @SuppressWarnings("unused") + public Set setInitParameters(Map initParameters) { + throw new IllegalStateException(ALREADY_INITIALIZED); + } + +} diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterConfigImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterConfigImpl.java index 22e6bb5b..4f41b61a 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterConfigImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/FilterConfigImpl.java @@ -4,10 +4,7 @@ package unknow.server.servlet.impl; import java.util.Collection; -import java.util.Collections; import java.util.EnumSet; -import java.util.Enumeration; -import java.util.Map; import java.util.Set; import jakarta.servlet.DispatcherType; @@ -22,13 +19,10 @@ * * @author unknow */ -public class FilterConfigImpl implements FilterConfig, FilterRegistration { - private static final String ALREADY_INITIALIZED = "already initialized"; +public class FilterConfigImpl extends AbstractConfig implements FilterConfig, FilterRegistration { - private final String name; private final Filter filter; private final ServletContext context; - private final ArrayMap parameters; private final Set servletMappings; private final Set urlMappings; private final Set dispatcherTypes; @@ -46,10 +40,9 @@ public class FilterConfigImpl implements FilterConfig, FilterRegistration { */ public FilterConfigImpl(String name, Filter filter, ServletContext context, ArrayMap parameters, Set servletMappings, Set urlMappings, Set dispatcherTypes) { - this.name = name; + super(name, parameters); this.filter = filter; this.context = context; - this.parameters = parameters; this.servletMappings = servletMappings; this.urlMappings = urlMappings; this.dispatcherTypes = dispatcherTypes; @@ -60,11 +53,6 @@ public String getFilterName() { return getName(); } - @Override - public String getName() { - return name; - } - /** * @return the filter */ @@ -82,31 +70,6 @@ public ServletContext getServletContext() { return context; } - @Override - public String getInitParameter(String name) { - return parameters.get(name); - } - - @Override - public Enumeration getInitParameterNames() { - return parameters.names(); - } - - @Override - public Map getInitParameters() { - return Collections.unmodifiableMap(parameters); - } - - @Override - public boolean setInitParameter(String name, String value) { - throw new IllegalStateException(ALREADY_INITIALIZED); - } - - @Override - public Set setInitParameters(Map initParameters) { - throw new IllegalStateException(ALREADY_INITIALIZED); - } - @Override public void addMappingForServletNames(EnumSet dispatcherTypes, boolean isMatchAfter, String... servletNames) { throw new IllegalStateException(ALREADY_INITIALIZED); diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletConfigImpl.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletConfigImpl.java index 231fbbba..c3035946 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletConfigImpl.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/impl/ServletConfigImpl.java @@ -4,9 +4,6 @@ package unknow.server.servlet.impl; import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.Map; import java.util.Set; import jakarta.servlet.Servlet; @@ -20,13 +17,10 @@ * * @author unknow */ -public class ServletConfigImpl implements ServletConfig, ServletRegistration { - private static final String ALREADY_INITIALIZED = "already initialized"; +public class ServletConfigImpl extends AbstractConfig implements ServletConfig, ServletRegistration { - private final String name; private final Servlet servlet; private final ServletContext context; - private final ArrayMap parameters; private final Set mappings; /** @@ -38,10 +32,9 @@ public class ServletConfigImpl implements ServletConfig, ServletRegistration { * @param mappings the url mappings */ public ServletConfigImpl(String name, Servlet servlet, ServletContext context, ArrayMap parameters, Set mappings) { - this.name = name; + super(name, parameters); this.servlet = servlet; this.context = context; - this.parameters = parameters; this.mappings = mappings; } @@ -62,36 +55,6 @@ public ServletContext getServletContext() { return context; } - @Override - public String getInitParameter(String name) { - return parameters.get(name); - } - - @Override - public Enumeration getInitParameterNames() { - return parameters.names(); - } - - @Override - public Map getInitParameters() { - return Collections.unmodifiableMap(parameters); - } - - @Override - public boolean setInitParameter(String name, String value) { - throw new IllegalStateException(ALREADY_INITIALIZED); - } - - @Override - public Set setInitParameters(Map initParameters) { - throw new IllegalStateException(ALREADY_INITIALIZED); - } - - @Override - public String getName() { - return name; - } - @Override public String getClassName() { return servlet.getClass().getName(); diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathTree.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathTree.java index f2fa03f4..1f1cc0ed 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathTree.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/PathTree.java @@ -3,7 +3,6 @@ */ package unknow.server.servlet.utils; -import java.nio.charset.StandardCharsets; import java.util.List; import jakarta.servlet.FilterChain; @@ -242,35 +241,4 @@ public void toString(StringBuilder sb, StringBuilder name) { name.setLength(l); } } - - /** - * url encode a path part - * - * @param s part to encode - * @return encoded part - */ - public static byte[] encodePart(String s) { - byte[] bytes = s.getBytes(StandardCharsets.UTF_8); - int l = bytes.length; - for (int i = 0; i < bytes.length; i++) { - if (bytes[i] < 0 || bytes[i] == 20) - l += 2; - } - if (l == bytes.length) - return bytes; - byte[] b = new byte[l]; - int j = 0; - for (int i = 0; i < bytes.length; i++) { - byte c = bytes[i]; - if (c < 0 || c == 20) { - b[j++] = '%'; - b[j++] = HEX[c / 16]; - b[j++] = HEX[c % 16]; - } else - b[j++] = c; - } - return b; - } - - private static final byte[] HEX = new byte[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResource.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResource.java index 648b3d75..8581e0de 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResource.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResource.java @@ -15,10 +15,10 @@ /** * @author unknow */ -public final class ServletResource extends HttpServlet { +public class ServletResource extends HttpServlet { private static final long serialVersionUID = 1L; - private final String path; + protected final String path; private final long lastModified; private final long size; private String mimeType; @@ -42,7 +42,7 @@ public void init() throws ServletException { * @param content if true file content will be sent * @throws IOException */ - private void process(HttpServletRequest req, HttpServletResponse resp, boolean content) throws IOException { + private final void process(HttpServletRequest req, HttpServletResponse resp, boolean content) throws IOException { Integer code = (Integer) req.getAttribute("javax.servlet.error.status_code"); if (code != null) resp.setStatus(code); @@ -51,6 +51,9 @@ private void process(HttpServletRequest req, HttpServletResponse resp, boolean c if (!content) return; + } + + protected void writeContent(HttpServletResponse resp) throws IOException { try (InputStream is = getServletContext().getResourceAsStream(path); ServletOutputStream os = resp.getOutputStream()) { byte[] b = new byte[4096]; int l; @@ -60,22 +63,22 @@ private void process(HttpServletRequest req, HttpServletResponse resp, boolean c } @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + protected final void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { process(req, resp, true); } @Override - protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + protected final void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { process(req, resp, false); } @Override - protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + protected final void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setHeader("Allow", "GET,HEAD,OPTIONS,TRACE"); } @Override - protected long getLastModified(HttpServletRequest req) { + protected final long getLastModified(HttpServletRequest req) { return lastModified; } diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResourceStatic.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResourceStatic.java index a8ed08d7..f6e88929 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResourceStatic.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResourceStatic.java @@ -8,34 +8,26 @@ import jakarta.servlet.ServletException; import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /** * @author unknow */ -public final class ServletResourceStatic extends HttpServlet { +public final class ServletResourceStatic extends ServletResource { private static final long serialVersionUID = 1L; - private final String path; private final byte[] data; - private final long lastModified; - private final long size; - private String mimeType; public ServletResourceStatic(String path, long lastModified, long size) { + super(path, lastModified, size); if (size > Integer.MAX_VALUE) throw new IllegalArgumentException("resource too big to load"); - this.path = path; this.data = new byte[(int) size]; - this.lastModified = lastModified; - this.size = size; } @Override public void init() throws ServletException { - this.mimeType = getServletContext().getMimeType(path); + super.init(); try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path)) { if (is == null) @@ -49,50 +41,13 @@ public void init() throws ServletException { } } - /** - * process a request - * - * @param req the request - * @param resp the response - * @param content if true file content will be sent - * @throws IOException - */ - private void process(HttpServletRequest req, HttpServletResponse resp, boolean content) throws IOException { - Integer code = (Integer) req.getAttribute("javax.servlet.error.status_code"); - if (code != null) - resp.setStatus(code); - resp.setContentLengthLong(size); - if (mimeType != null) - resp.setContentType(mimeType); - - if (!content) - return; - + @Override + protected void writeContent(HttpServletResponse resp) throws IOException { try (ServletOutputStream os = resp.getOutputStream()) { os.write(data); } } - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - process(req, resp, true); - } - - @Override - protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - process(req, resp, false); - } - - @Override - protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - resp.setHeader("Allow", "GET,HEAD,OPTIONS,TRACE"); - } - - @Override - protected long getLastModified(HttpServletRequest req) { - return lastModified; - } - @Override public String toString() { return "ResourceStatic:" + path; diff --git a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java index 94fbe61b..cf927ad6 100644 --- a/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java +++ b/unknow-server-util/src/main/java/unknow/server/util/io/Buffers.java @@ -199,6 +199,7 @@ public void prepend(Buffers buf) throws InterruptedException { buf.head = buf.tail = null; cond.signalAll(); } finally { + buf.lock.unlock(); lock.unlock(); } } From a47867e0c38a46b21cb84ca768b3493e8c1b888d Mon Sep 17 00:00:00 2001 From: Unknow0 Date: Sat, 29 Jun 2024 09:56:22 +0200 Subject: [PATCH 40/40] gh-30 clean code --- .../server/servlet/http2/Http2Processor.java | 15 ++++++++------- .../server/servlet/utils/ServletResource.java | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java index db44d4d8..e52aec05 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Processor.java @@ -161,13 +161,13 @@ private void readFrame(Buffers buf) throws InterruptedException { } wantContinuation = false; - FrameBuilder b = BUILDERS.get(type); - if (b == null) { + FrameBuilder builder = BUILDERS.get(type); + if (builder == null) { goaway(PROTOCOL_ERROR); return; } - r = b.build(this, size, flags, id, buf); + r = builder.build(this, size, flags, id, buf); } public void goaway(int err) throws InterruptedException { @@ -181,12 +181,12 @@ public void goaway(int err) throws InterruptedException { f[14] = (byte) ((err >> 16) & 0xff); f[15] = (byte) ((err >> 8) & 0xff); f[16] = (byte) (err & 0xff); - Buffers b = co.pendingWrite; - b.lock(); + Buffers buf = co.pendingWrite; + buf.lock(); try { - b.write(f); + buf.write(f); } finally { - b.unlock(); + buf.unlock(); } co.flush(); logger.debug("{}: send GOAWAY {}", this, error(err)); @@ -238,6 +238,7 @@ public void sendHeaders(int id, ServletResponseImpl res) throws InterruptedExcep } } int flag = 0x4; + @SuppressWarnings("resource") Http2ServletOutput sout = (Http2ServletOutput) res.getRawStream(); if (sout == null || sout.isDone()) flag |= 0x1; diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResource.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResource.java index 8581e0de..4b6ab2c3 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResource.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/utils/ServletResource.java @@ -49,8 +49,8 @@ private final void process(HttpServletRequest req, HttpServletResponse resp, boo resp.setContentLengthLong(size); resp.setContentType(mimeType); - if (!content) - return; + if (content) + writeContent(resp); } protected void writeContent(HttpServletResponse resp) throws IOException {