From 04acdb72f041a15267bb79c36d8fc4d517beaadb Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 3 May 2022 15:50:54 +0200 Subject: [PATCH] Jetty-12 Restructure Copied over non ee10 components from the hackathon branch Co-authored-by: Greg Wilkins Co-authored-by: Jan Bartel Co-authored-by: Joakim Erdfelt Co-authored-by: Lachlan Roberts Co-authored-by: Ludovic Orban Co-authored-by: Olivier Lamy Co-authored-by: Simone Bordet --- Jenkinsfile | 162 +- Jenkinsfile-autobahn | 40 +- SECURITY.md | 47 - VERSION.txt | 294 +- ...akarta.servlet.ServletContainerInitializer | 1 - .../services/org.apache.juli.logging.Log | 1 - .../jetty-codestyle-eclipse-ide.xml | 0 .../jetty-codestyle-intellij.xml | 0 .../build-resources}/pom.xml | 4 +- .../src/main/resources/jetty-checkstyle.xml | 0 {jetty-unixsocket => build}/pom.xml | 21 +- .../clirr-gen-master-index.output-foot.html | 0 .../clirr-gen-master-index.output-head.html | 0 .../clirr-gen-master-index.output-html.xslt | 0 .../scripts}/clirr-gen-master-index.sh | 0 {scripts => build/scripts}/git-log-csv.sh | 0 {scripts => build/scripts}/looptest.sh | 0 {scripts => build/scripts}/query-git-stats.sh | 0 {scripts => build/scripts}/release-jetty.sh | 0 demos/demo-async-rest/pom.xml | 19 - .../test/resources/jetty-logging.properties | 3 - .../resources/META-INF/javaxmail.providers | 1 - ...akarta.servlet.ServletContainerInitializer | 1 - .../jetty-asciidoctor-extensions/pom.xml | 4 +- documentation/jetty-documentation/pom.xml | 43 +- .../annotations/quick-annotations-setup.adoc | 2 +- .../using-annotations-embedded.adoc | 8 +- .../main/asciidoc/old_docs/ant/jetty-ant.adoc | 2 +- .../architecture/jetty-classloading.adoc | 20 +- .../old_docs/contexts/custom-error-pages.adoc | 10 +- .../contexts/setting-context-path.adoc | 2 +- .../old_docs/contexts/setting-form-size.adoc | 2 +- .../contexts/temporary-directories.adoc | 12 +- ...onfiguring-specific-webapp-deployment.adoc | 12 +- .../deploying/deployment-architecture.adoc | 8 +- .../deployment-processing-webapps.adoc | 56 +- .../old_docs/deploying/quickstart-webapp.adoc | 6 +- .../asciidoc/old_docs/extras/cgi-servlet.adoc | 2 +- .../old_docs/extras/cross-origin-filter.adoc | 4 +- .../old_docs/extras/default-servlet.adoc | 2 +- .../asciidoc/old_docs/extras/dos-filter.adoc | 4 +- .../old_docs/extras/header-filter.adoc | 4 +- .../asciidoc/old_docs/extras/qos-filter.adoc | 4 +- .../old_docs/fastcgi/configuring-fastcgi.adoc | 10 +- .../asciidoc/old_docs/frameworks/cdi.adoc | 8 +- .../asciidoc/old_docs/frameworks/osgi.adoc | 14 +- .../configuring/what-to-configure.adoc | 4 +- .../getting-started/jetty-running.adoc | 6 +- .../old_docs/http2/configuring-push.adoc | 2 +- .../old_docs/jetty-xml/jetty-env-xml.adoc | 6 +- .../jetty-xml/jetty-web-xml-config.adoc | 4 +- .../old_docs/jetty-xml/override-web-xml.adoc | 4 +- .../old_docs/jetty-xml/webdefault-xml.adoc | 4 +- .../old_docs/jndi/jndi-configuration.adoc | 10 +- .../asciidoc/old_docs/jndi/jndi-embedded.adoc | 6 +- .../asciidoc/old_docs/jndi/using-jndi.adoc | 2 +- .../old_docs/jsp/configuring-jsp.adoc | 6 +- .../old_docs/runner/jetty-runner.adoc | 10 +- .../old_docs/security/authentication.adoc | 6 +- .../security/configuring-form-size.adoc | 2 +- .../old_docs/security/jaas-support.adoc | 2 +- .../troubleshooting-locked-files.adoc | 6 +- .../jetty/jetty-websocket-server-api.adoc | 2 +- .../operations-guide/annotations/chapter.adoc | 10 +- .../operations-guide/begin/deploy.adoc | 2 +- .../deploy/deploy-extract-war.adoc | 2 +- .../operations-guide/deploy/deploy-jetty.adoc | 4 +- .../operations-guide/deploy/deploy-jndi.adoc | 2 +- .../deploy/deploy-override-webxml.adoc | 2 +- .../deploy/deploy-virtual-hosts.adoc | 10 +- .../operations-guide/jaas/chapter.adoc | 2 +- .../operations-guide/jndi/chapter.adoc | 12 +- .../operations-guide/jsp/chapter.adoc | 2 +- .../sessions/session-xml.adoc | 2 +- .../operations-guide/xml/xml-syntax.adoc | 2 +- .../client/http/client-http-transport.adoc | 15 +- .../client/websocket/client-websocket.adoc | 4 +- .../maven/jetty-maven-plugin.adoc | 6 +- .../server/http/server-http-handler-use.adoc | 2 +- .../server/http/server-http.adoc | 10 +- .../sessions/session-sessionhandler.adoc | 12 +- .../websocket/server-websocket-filter.adoc | 2 +- .../websocket/server-websocket-jetty.adoc | 2 +- .../asciidoc/programming-guide/websocket.adoc | 10 +- .../jetty/docs/programming/WebSocketDocs.java | 24 +- .../client/http2/HTTP2ClientDocs.java | 2 +- .../client/websocket/WebSocketClientDocs.java | 8 +- .../server/http/HTTPServerDocs.java | 16 +- .../server/http2/HTTP2ServerDocs.java | 2 +- .../server/session/SessionDocs.java | 32 +- .../server/websocket/WebSocketServerDocs.java | 18 +- documentation/pom.xml | 4 +- javadoc/pom.xml | 20 +- .../org.eclipse.jetty.webapp.Configuration | 1 - .../jetty/client/AsyncContentProvider.java | 44 - .../jetty/client/api/ContentProvider.java | 94 - .../internal/RequestContentAdapter.java | 324 - .../util/ByteBufferContentProvider.java | 96 - .../client/util/BytesContentProvider.java | 89 - .../client/util/DeferredContentProvider.java | 343 - .../client/util/FormContentProvider.java | 50 - .../util/InputStreamContentProvider.java | 256 - .../client/util/MultiPartContentProvider.java | 408 - .../util/OutputStreamContentProvider.java | 158 - .../client/util/PathContentProvider.java | 188 - .../client/util/StringContentProvider.java | 51 - .../jetty/client/ssl/SslBytesServerTest.java | 1950 - .../jetty-alpn/jetty-alpn-client/pom.xml | 6 +- .../src/main/java/module-info.java | 0 .../jetty-alpn-conscrypt-client/pom.xml | 6 +- .../src/main/java/module-info.java | 0 ....eclipse.jetty.io.ssl.ALPNProcessor$Client | 0 .../test/resources/jetty-logging.properties | 0 .../jetty-alpn-conscrypt-server/pom.xml | 6 +- .../src/main/java/module-info.java | 0 ....eclipse.jetty.io.ssl.ALPNProcessor$Server | 0 .../server/ConscryptHTTP2ServerTest.java | 18 +- .../test/resources/jetty-logging.properties | 0 .../src/test/resources/keystore.p12 | Bin .../jetty-alpn/jetty-alpn-java-client/pom.xml | 6 +- .../src/main/java/module-info.java | 0 ....eclipse.jetty.io.ssl.ALPNProcessor$Client | 0 .../test/resources/jetty-logging.properties | 0 .../jetty-alpn/jetty-alpn-java-server/pom.xml | 6 +- .../src/main/java/module-info.java | 0 ....eclipse.jetty.io.ssl.ALPNProcessor$Server | 0 .../jetty/alpn/java/server/JDK9ALPNTest.java | 23 +- .../test/resources/jetty-logging.properties | 0 .../src/test/resources/keystore.p12 | Bin .../jetty-alpn/jetty-alpn-server/pom.xml | 6 +- .../src/main/java/module-info.java | 0 jetty-core/jetty-alpn/pom.xml | 8 +- jetty-core/{jetty-bom => jetty-bom/pom.xml} | 260 +- jetty-core/jetty-client/pom.xml | 22 +- .../src/main/java/module-info.java | 0 .../org/eclipse/jetty/client/HttpRequest.java | 24 - .../org/eclipse/jetty/client/api/Request.java | 24 - .../jetty/client/api/package-info.java | 0 .../dynamic/HttpClientTransportDynamic.java | 4 +- .../eclipse/jetty/client/package-info.java | 0 .../jetty/client/util/package-info.java | 0 .../client/ClientConnectionCloseTest.java | 64 +- .../jetty/client/ConnectionPoolTest.java | 97 +- .../jetty/client/ContentResponseTest.java | 44 +- .../jetty/client/EmptyServerHandler.java | 36 +- .../client/HostnameVerificationTest.java | 18 +- .../client/HttpAuthenticationStoreTest.java | 4 +- .../client/HttpClientAsyncContentTest.java | 64 +- .../client/HttpClientAuthenticationTest.java | 69 +- .../client/HttpClientChunkedContentTest.java | 35 +- .../client/HttpClientCorrelationDataTest.java | 9 +- .../client/HttpClientCustomProxyTest.java | 21 +- .../jetty/client/HttpClientGZIPTest.java | 61 +- .../client/HttpClientIdleTimeoutTest.java | 16 +- .../client/HttpClientProxyProtocolTest.java | 33 +- .../jetty/client/HttpClientProxyTest.java | 74 +- .../jetty/client/HttpClientRedirectTest.java | 146 +- .../jetty/client/HttpClientTLSTest.java | 2 + .../eclipse/jetty/client/HttpClientTest.java | 319 +- .../jetty/client/HttpClientURITest.java | 149 +- ...pClientUploadDuringServerShutdownTest.java | 48 +- .../client/HttpConnectionLifecycleTest.java | 17 +- .../eclipse/jetty/client/HttpCookieTest.java | 301 +- .../jetty/client/HttpRequestAbortTest.java | 96 +- .../jetty/client/HttpResponseAbortTest.java | 29 +- .../HttpResponseConcurrentAbortTest.java | 53 +- .../client/NetworkTrafficListenerTest.java | 101 +- .../jetty/client/ProxyConfigurationTest.java | 10 +- .../client/ServerConnectionCloseTest.java | 3 +- .../client/TLSServerConnectionCloseTest.java | 3 +- .../client/ValidatingConnectionPoolTest.java | 37 +- .../jetty/client/ssl/SslBytesTest.java | 2 +- .../client/util/InputStreamContentTest.java | 21 +- .../client/util/MultiPartContentTest.java | 50 +- .../client/util/SPNEGOAuthenticationTest.java | 55 +- .../client/util/TypedContentProviderTest.java | 49 +- jetty-core/jetty-deploy/pom.xml | 10 +- .../src/main/config/etc/jetty-decorate.xml | 2 +- .../src/main/config/etc/jetty-deploy.xml | 2 +- .../main/config/etc/jetty-web-decorate.xml | 6 +- .../src/main/config/modules/decorate.mod | 4 +- .../global-webapp-common.xml | 2 +- .../global-webapp-common.d/webapp-common.xml | 2 +- .../src/main/java/module-info.java | 3 +- .../java/org/eclipse/jetty/deploy/App.java | 45 +- .../jetty/deploy/DeploymentManager.java | 22 +- .../jetty/deploy/bindings/package-info.java | 0 .../jetty/deploy/graph/package-info.java | 0 .../jetty/deploy/jmx/package-info.java | 0 .../eclipse/jetty/deploy/package-info.java | 0 .../deploy/providers/ScanningAppProvider.java | 5 +- .../deploy/providers/WebAppProvider.java | 131 +- .../jetty/deploy/providers/package-info.java | 0 .../jetty/deploy/util/package-info.java | 0 .../jetty/deploy/DeploymentManagerTest.java | 2 + .../eclipse/jetty/deploy/MockAppProvider.java | 11 +- ...ScanningAppProviderRuntimeUpdatesTest.java | 12 +- .../ScanningAppProviderStartupTest.java | 4 +- .../jetty/deploy/test/XmlConfiguredJetty.java | 30 +- .../resources/binding-test-contexts-1.xml | 8 +- .../test/resources/context-binding-test-1.xml | 2 +- .../src/test/resources/etc/realm.properties | 2 +- .../src/test/resources/etc/webdefault.xml | 12 +- .../src/test/resources/jetty-deploy-wars.xml | 2 +- .../resources/jetty-deploymgr-contexts.xml | 2 +- .../test/resources/jetty-logging.properties | 2 +- .../test/resources/webapps/badapp/badapp.xml | 2 +- .../src/test/resources/webapps/foo.xml | 2 +- jetty-core/jetty-fcgi/fcgi-client/pom.xml | 6 +- .../src/main/java/module-info.java | 5 +- .../java/org/eclipse/jetty/fcgi/FCGI.java | 53 +- .../fcgi/client/http/HttpChannelOverFCGI.java | 500 +- .../http/HttpClientTransportOverFCGI.java | 7 +- .../client/http/HttpConnectionOverFCGI.java | 18 +- .../eclipse/jetty/fcgi/generator/Flusher.java | 4 +- .../jetty/fcgi/generator/Generator.java | 2 +- .../jetty/fcgi/parser/ClientParser.java | 10 +- .../fcgi/parser/ResponseContentParser.java | 2 +- .../test/resources/jetty-logging.properties | 0 jetty-core/jetty-fcgi/fcgi-server/pom.xml | 25 +- .../src/main/java/module-info.java | 6 +- .../server/ServerFCGIConnectionFactory.java | 1 + .../server/internal/HttpStreamOverFCGI.java | 379 + .../server/internal/ServerFCGIConnection.java | 208 +- .../server/AbstractHttpClientServerTest.java | 3 +- .../jetty/fcgi/server/HttpClientTest.java | 237 +- .../test/resources/jetty-logging.properties | 0 jetty-core/jetty-fcgi/pom.xml | 8 +- jetty-core/jetty-http-spi/pom.xml | 23 +- .../src/main/java/module-info.java | 0 .../jetty/http/spi/HttpSpiContextHandler.java | 122 +- .../jetty/http/spi/JettyHttpContext.java | 13 +- .../jetty/http/spi/JettyHttpExchange.java | 8 +- .../http/spi/JettyHttpExchangeDelegate.java | 105 +- .../jetty/http/spi/JettyHttpServer.java | 22 +- .../jetty/http/spi/JettyHttpsExchange.java | 8 +- .../eclipse/jetty/http/spi/TestSPIServer.java | 27 +- .../jetty-http-tools/pom.xml | 8 +- .../matchers/HttpFieldsContainsHeaderKey.java | 0 .../HttpFieldsContainsHeaderValue.java | 0 .../tools/matchers/HttpFieldsHeaderValue.java | 0 .../tools/matchers/HttpFieldsMatchers.java | 0 .../matchers/HttpFieldsMatchersTest.java | 0 jetty-core/jetty-http/pom.xml | 6 +- .../src/main/java/module-info.java | 3 +- .../jetty/http/BadMessageException.java | 9 +- .../jetty/http/CachingContentFactory.java | 407 + .../org/eclipse/jetty/http/CookieCache.java | 118 + .../org/eclipse/jetty/http/CookieCutter.java | 23 +- .../jetty/http/GZIPContentDecoder.java | 33 +- .../jetty/http/Http10FieldPreEncoder.java | 26 + .../jetty/http/Http11FieldPreEncoder.java | 26 + .../jetty/http/Http1FieldPreEncoder.java | 9 +- .../org/eclipse/jetty/http/HttpContent.java | 27 +- .../org/eclipse/jetty/http/HttpCookie.java | 60 +- .../org/eclipse/jetty/http/HttpField.java | 24 +- .../org/eclipse/jetty/http/HttpFields.java | 1112 +- .../org/eclipse/jetty/http/HttpGenerator.java | 10 +- .../org/eclipse/jetty/http/HttpParser.java | 40 +- .../org/eclipse/jetty/http/HttpTester.java | 2 +- .../java/org/eclipse/jetty/http/HttpURI.java | 95 +- .../org/eclipse/jetty/http/HttpVersion.java | 8 + .../java/org/eclipse/jetty/http/MetaData.java | 11 +- .../org/eclipse/jetty/http/MimeTypes.java | 47 +- .../jetty/http/PreEncodedHttpField.java | 74 +- .../jetty/http/PrecompressedHttpContent.java | 64 +- .../jetty/http/ResourceHttpContent.java | 61 +- .../org/eclipse/jetty/http/package-info.java | 0 ...org.eclipse.jetty.http.HttpFieldPreEncoder | 3 +- .../jetty/http/GZIPContentDecoderTest.java | 58 +- .../eclipse/jetty/http/HttpFieldsTest.java | 261 +- .../eclipse/jetty/http/HttpParserTest.java | 78 +- .../org/eclipse/jetty/http/HttpURITest.java | 402 +- jetty-core/jetty-http2/http2-client/pom.xml | 42 +- .../src/main/java/module-info.java | 0 .../client/HTTP2ClientConnectionFactory.java | 7 +- .../client/internal/HTTP2ClientSession.java | 8 +- jetty-core/jetty-http2/http2-common/pom.xml | 6 +- .../src/main/java/module-info.java | 6 +- .../http2/AbstractFlowControlStrategy.java | 2 +- .../http2/BufferingFlowControlStrategy.java | 4 + .../java/org/eclipse/jetty/http2/IStream.java | 24 + .../org/eclipse/jetty/http2/RateControl.java | 2 +- .../http2/SimpleFlowControlStrategy.java | 4 + .../jetty/http2/WindowRateControl.java | 2 +- .../jetty/http2/frames/GoAwayFrame.java | 2 +- .../jetty/http2/frames/ResetFrame.java | 2 +- .../jetty/http2/internal/ErrorCode.java | 2 +- .../eclipse/jetty/http2/internal/Flags.java | 2 +- .../jetty/http2/internal/HTTP2Channel.java | 9 +- .../jetty/http2/internal/HTTP2Connection.java | 5 +- .../jetty/http2/internal/HTTP2Flusher.java | 4 +- .../jetty/http2/internal/HTTP2Session.java | 10 +- .../jetty/http2/internal/HTTP2Stream.java | 21 +- .../http2/internal/HTTP2StreamEndPoint.java | 3 +- .../internal/generator/DataGenerator.java | 4 +- .../internal/generator/FrameGenerator.java | 2 +- .../http2/internal/generator/Generator.java | 2 +- .../internal/generator/GoAwayGenerator.java | 4 +- .../internal/generator/HeaderGenerator.java | 2 +- .../internal/generator/HeadersGenerator.java | 4 +- .../internal/generator/NoOpGenerator.java | 2 +- .../internal/generator/PingGenerator.java | 4 +- .../internal/generator/PrefaceGenerator.java | 2 +- .../internal/generator/PriorityGenerator.java | 4 +- .../generator/PushPromiseGenerator.java | 4 +- .../internal/generator/ResetGenerator.java | 4 +- .../internal/generator/SettingsGenerator.java | 4 +- .../generator/WindowUpdateGenerator.java | 4 +- .../http2/internal/parser/BodyParser.java | 6 +- .../parser/ContinuationBodyParser.java | 6 +- .../http2/internal/parser/DataBodyParser.java | 4 +- .../internal/parser/GoAwayBodyParser.java | 4 +- .../internal/parser/HeaderBlockFragments.java | 2 +- .../internal/parser/HeaderBlockParser.java | 4 +- .../http2/internal/parser/HeaderParser.java | 3 +- .../internal/parser/HeadersBodyParser.java | 6 +- .../jetty/http2/internal/parser/Parser.java | 7 +- .../http2/internal/parser/PingBodyParser.java | 6 +- .../http2/internal/parser/PrefaceParser.java | 4 +- .../internal/parser/PriorityBodyParser.java | 4 +- .../parser/PushPromiseBodyParser.java | 6 +- .../internal/parser/ResetBodyParser.java | 4 +- .../http2/internal/parser/ServerParser.java | 7 +- .../internal/parser/SettingsBodyParser.java | 7 +- .../internal/parser/UnknownBodyParser.java | 4 +- .../parser/WindowUpdateBodyParser.java | 4 +- .../http2/frames/ContinuationParseTest.java | 8 +- .../http2/frames/DataGenerateParseTest.java | 6 +- .../jetty/http2/frames/FrameFloodTest.java | 6 +- .../http2/frames/GoAwayGenerateParseTest.java | 6 +- .../frames/HeadersGenerateParseTest.java | 6 +- .../frames/HeadersTooLargeParseTest.java | 8 +- .../http2/frames/MaxFrameSizeParseTest.java | 4 +- .../http2/frames/PingGenerateParseTest.java | 6 +- .../frames/PriorityGenerateParseTest.java | 6 +- .../frames/PushPromiseGenerateParseTest.java | 6 +- .../http2/frames/ResetGenerateParseTest.java | 6 +- .../frames/SettingsGenerateParseTest.java | 8 +- .../jetty/http2/frames/UnknownParseTest.java | 4 +- .../frames/WindowUpdateGenerateParseTest.java | 6 +- .../test/resources/jetty-logging.properties | 0 jetty-core/jetty-http2/http2-hpack/pom.xml | 8 +- .../src/main/java/module-info.java | 1 + .../jetty/http2/hpack/HpackContext.java | 5 +- .../jetty/http2/hpack/HpackDecoder.java | 8 +- .../jetty/http2/hpack/HpackEncoder.java | 2 + .../jetty/http2/hpack/HpackException.java | 5 +- .../http2/hpack/HpackFieldPreEncoder.java | 2 + .../hpack/internal/AuthorityHttpField.java | 6 +- .../jetty/http2/hpack/internal/Huffman.java | 3 +- .../http2/hpack/internal/MetaDataBuilder.java | 10 +- .../http2/hpack/internal/NBitInteger.java | 2 +- .../hpack/internal/StaticTableHttpField.java | 2 +- .../jetty/http2/hpack/HpackContextTest.java | 2 + .../jetty/http2/hpack/HpackDecoderTest.java | 47 +- .../jetty/http2/hpack/HuffmanTest.java | 7 +- .../jetty/http2/hpack/NBitIntegerTest.java | 19 +- .../test/resources/jetty-logging.properties | 0 .../http2-http-client-transport/pom.xml | 37 +- .../src/main/java/module-info.java | 0 .../ClientConnectionFactoryOverHTTP2.java | 2 + .../http/HttpClientTransportOverHTTP2.java | 11 +- .../internal/ClientHTTP2StreamEndPoint.java | 6 +- .../internal/HTTPSessionListenerPromise.java | 10 +- .../http/internal}/HttpChannelOverHTTP2.java | 6 +- .../internal/HttpConnectionOverHTTP2.java | 6 +- .../http/internal/HttpReceiverOverHTTP2.java | 6 +- .../http/internal/HttpSenderOverHTTP2.java | 2 +- jetty-core/jetty-http2/http2-server/pom.xml | 87 +- .../src/main/config/etc/jetty-http2.xml | 2 +- .../src/main/config/etc/jetty-http2c.xml | 2 +- .../src/main/java/module-info.java | 0 .../AbstractHTTP2ServerConnectionFactory.java | 27 +- .../server/HTTP2CServerConnectionFactory.java | 6 +- .../server/HTTP2ServerConnectionFactory.java | 13 +- .../internal/HTTP2ServerConnection.java | 215 +- .../server/internal/HTTP2ServerSession.java | 10 +- .../server/internal/HttpChannelOverHTTP2.java | 760 + .../server/internal/HttpStreamOverHTTP2.java | 562 + .../internal/HttpTransportOverHTTP2.java | 1116 +- .../internal/ServerHTTP2StreamEndPoint.java | 6 +- .../jetty-http2/jetty-http2-tests/pom.xml | 114 + .../jetty/http2/tests/AbstractServerTest.java | 17 +- .../jetty/http2/tests/AbstractTest.java | 58 +- .../jetty/http2/tests/AsyncIOTest.java | 110 +- .../jetty/http2/tests/AsyncServletTest.java | 731 +- .../BlockedWritesWithSmallThreadPoolTest.java | 16 +- .../BufferingFlowControlStrategyTest.java | 2 +- .../eclipse/jetty/http2/tests/CloseTest.java | 11 +- .../tests/ConcurrentStreamCreationTest.java | 6 +- .../jetty/http2/tests/ConnectTimeoutTest.java | 9 +- .../jetty/http2/tests/ConnectTunnelTest.java | 6 +- .../jetty/http2/tests/ContentLengthTest.java | 82 +- .../jetty/http2/tests/DataDemandTest.java | 16 +- .../http2/tests/FlowControlStalledTest.java | 3 +- .../http2/tests/FlowControlStrategyTest.java | 9 +- .../http2/tests/FlowControlWindowsTest.java | 3 +- .../eclipse/jetty/http2/tests/GoAwayTest.java | 34 +- .../jetty/http2/tests/H2SpecServer.java | 3 +- .../jetty/http2/tests/HTTP2CServer.java | 41 +- .../jetty/http2/tests/HTTP2CServerTest.java | 23 +- .../jetty/http2/tests/HTTP2ServerTest.java | 87 +- .../eclipse/jetty/http2/tests/HTTP2Test.java | 192 +- .../HttpClientTransportOverHTTP2Test.java | 89 +- .../jetty/http2/tests/IdleTimeoutTest.java | 121 +- .../jetty/http2/tests/InterleavingTest.java | 4 +- .../http2/tests/MaxConcurrentStreamsTest.java | 162 +- .../http2/tests/MaxPushedStreamsTest.java | 10 +- .../tests/MultiplexedConnectionPoolTest.java | 84 +- .../eclipse/jetty/http2/tests/PingTest.java | 4 +- .../jetty/http2/tests/PrefaceTest.java | 32 +- .../tests/PriorKnowledgeHTTP2OverTLSTest.java | 57 +- .../jetty/http2/tests/PriorityTest.java | 10 +- .../jetty/http2/tests/ProxyProtocolTest.java | 65 +- .../eclipse/jetty/http2/tests/ProxyTest.java | 356 +- .../http2/tests/PushCacheFilterTest.java | 1939 +- .../http2/tests/PushedResourcesTest.java | 56 +- .../jetty/http2/tests/RawHTTP2ProxyTest.java | 62 +- .../http2/tests/RequestTrailersTest.java | 8 +- .../http2/tests/ResponseTrailerTest.java | 19 +- .../jetty/http2/tests/SessionFailureTest.java | 6 +- .../tests/SimpleFlowControlStrategyTest.java | 2 +- .../http2/tests/SmallThreadPoolLoadTest.java | 56 +- .../jetty/http2/tests/StreamCloseTest.java | 22 +- .../jetty/http2/tests/StreamCountTest.java | 10 +- .../jetty/http2/tests/StreamResetTest.java | 315 +- .../jetty/http2/tests/TrailersTest.java | 117 +- .../test/resources/jetty-logging.properties | 1 - jetty-core/jetty-http2/pom.xml | 9 +- jetty-core/jetty-http3/http3-client/pom.xml | 6 +- .../src/main/java/module-info.java | 0 jetty-core/jetty-http3/http3-common/pom.xml | 6 +- .../src/main/java/module-info.java | 0 .../http3/internal/HTTP3StreamConnection.java | 20 +- .../http3-http-client-transport/pom.xml | 6 +- .../src/main/java/module-info.java | 0 .../http/internal/HttpChannelOverHTTP3.java | 503 +- jetty-core/jetty-http3/http3-qpack/pom.xml | 8 +- .../src/main/java/module-info.java | 0 .../qpack/internal/metadata/Http3Fields.java | 10 +- .../test/resources/jetty-logging.properties | 0 jetty-core/jetty-http3/http3-server/pom.xml | 6 +- .../src/main/java/module-info.java | 0 .../server/HTTP3ServerConnectionFactory.java | 17 +- .../server/internal/HttpStreamOverHTTP3.java | 489 + .../internal/ServerHTTP3StreamConnection.java | 142 +- jetty-core/jetty-http3/http3-tests/pom.xml | 6 +- .../jetty/http3/tests/ClientServerTest.java | 8 +- .../jetty/http3/tests/DataDemandTest.java | 57 - .../http3/tests/HandlerClientServerTest.java | 22 +- .../HttpClientTransportOverHTTP3Test.java | 40 +- .../test/resources/jetty-logging.properties | 1 + jetty-core/jetty-http3/pom.xml | 8 +- jetty-core/jetty-io/pom.xml | 6 +- .../jetty-io}/src/main/java/module-info.java | 0 .../eclipse/jetty/io/AbstractConnection.java | 30 +- .../eclipse/jetty/io/AbstractEndPoint.java | 9 +- .../io/ArrayRetainableByteBufferPool.java | 30 + .../java/org/eclipse/jetty/io/Connection.java | 51 +- .../java/org/eclipse/jetty/io/EndPoint.java | 4 +- .../org/eclipse/jetty/io/IdleTimeout.java | 5 +- .../io/LogarithmicArrayByteBufferPool.java | 2 + .../jetty/io/MappedByteBufferPool.java | 2 + .../org/eclipse/jetty/io/QuietException.java | 22 + .../jetty/io/SocketChannelEndPoint.java | 3 +- .../org/eclipse/jetty/io/package-info.java | 0 .../eclipse/jetty/io/ssl/SslConnection.java | 24 +- .../eclipse/jetty/io/ssl/package-info.java | 0 .../io/ArrayRetainableByteBufferPoolTest.java | 2 +- jetty-core/jetty-jmx/pom.xml | 6 +- .../jetty-jmx}/src/main/java/module-info.java | 0 .../org/eclipse/jetty/jmx/package-info.java | 0 jetty-core/jetty-jndi/pom.xml | 23 +- .../src/main/java/module-info.java | 4 +- .../jndi/factories/MailSessionReference.java | 28 +- .../jetty/jndi/factories/package-info.java | 0 .../eclipse/jetty/jndi/java/package-info.java | 0 .../jetty/jndi/local/package-info.java | 0 .../org/eclipse/jetty/jndi/package-info.java | 0 .../org/eclipse/jetty/jndi/java/TestJNDI.java | 248 +- jetty-core/jetty-keystore/pom.xml | 6 +- jetty-core/jetty-quic/pom.xml | 8 +- jetty-core/jetty-quic/quic-client/pom.xml | 6 +- .../src/main/java/module-info.java | 0 .../jetty/quic/client/End2EndClientTest.java | 29 +- .../test/resources/jetty-logging.properties | 0 .../src/test/resources/keystore.p12 | Bin jetty-core/jetty-quic/quic-common/pom.xml | 6 +- .../src/main/java/module-info.java | 0 .../jetty/quic/common/package-info.java | 0 jetty-core/jetty-quic/quic-quiche/pom.xml | 6 +- .../quic-quiche/quic-quiche-common/pom.xml | 4 +- .../src/main/java/module-info.java | 0 .../quic-quiche-foreign-incubator/pom.xml | 4 +- .../src/main/java/module-info.java | 0 .../quiche/foreign/incubator/sockaddr.java | 0 ...rg.eclipse.jetty.quic.quiche.QuicheBinding | 0 .../foreign/incubator/LowLevelQuicheTest.java | 4 + .../test/resources/jetty-logging.properties | 0 .../src/test/resources/keystore.p12 | Bin .../quic-quiche/quic-quiche-jna/pom.xml | 4 +- .../src/main/java/module-info.java | 0 .../jetty/quic/quiche/jna/sockaddr.java | 0 ...rg.eclipse.jetty.quic.quiche.QuicheBinding | 0 .../quic/quiche/jna/LowLevelQuicheTest.java | 0 .../test/resources/jetty-logging.properties | 0 .../src/test/resources/keystore.p12 | Bin jetty-core/jetty-quic/quic-server/pom.xml | 6 +- .../src/main/java/module-info.java | 0 .../quic/server/ServerQuicConnectorTest.java | 36 +- .../test/resources/jetty-logging.properties | 0 .../src/test/resources/keystore.p12 | Bin jetty-core/jetty-rewrite/pom.xml | 18 +- .../src/main/config/etc/jetty-rewrite.xml | 0 .../src/main/java/module-info.java | 4 +- .../jetty/rewrite/RewriteCustomizer.java | 9 +- .../rewrite/handler/CompactPathRule.java | 40 +- .../rewrite/handler/CookiePatternRule.java | 65 +- .../handler/ForceRequestHeaderValueRule.java | 54 +- .../handler/ForwardedSchemeHeaderRule.java | 21 +- .../rewrite/handler/HeaderPatternRule.java | 118 +- .../rewrite/handler/HeaderRegexRule.java | 115 +- .../jetty/rewrite/handler/HeaderRule.java | 55 +- .../jetty/rewrite/handler/InvalidURIRule.java | 130 + .../jetty/rewrite/handler/PatternRule.java | 47 +- .../rewrite/handler/RedirectPatternRule.java | 79 +- .../rewrite/handler/RedirectRegexRule.java | 88 +- .../jetty/rewrite/handler/RegexRule.java | 66 +- .../rewrite/handler/ResponsePatternRule.java | 88 +- .../jetty/rewrite/handler/RewriteHandler.java | 122 +- .../rewrite/handler/RewritePatternRule.java | 70 +- .../rewrite/handler/RewriteRegexRule.java | 95 +- .../eclipse/jetty/rewrite/handler/Rule.java | 64 +- .../jetty/rewrite/handler/RuleContainer.java | 178 +- .../handler/TerminatingPatternRule.java | 24 +- .../rewrite/handler/TerminatingRegexRule.java | 20 +- .../handler/VirtualHostRuleContainer.java | 99 +- .../jetty/rewrite/handler/package-info.java | 0 .../rewrite/handler/AbstractRuleTest.java | 41 + .../handler/CookiePatternRuleTest.java | 187 +- .../ForceRequestHeaderValueRuleTest.java | 159 +- .../ForwardedSchemeHeaderRuleTest.java | 133 +- .../handler/HeaderPatternRuleTest.java | 131 +- .../rewrite/handler/HeaderRegexRuleTest.java | 182 +- .../rewrite/handler/InvalidURIRuleTest.java | 254 + .../rewrite/handler/PatternRuleTest.java | 248 +- .../handler/RedirectPatternRuleTest.java | 68 +- .../handler/RedirectRegexRuleTest.java | 104 +- .../jetty/rewrite/handler/RegexRuleTest.java | 170 +- .../handler/ResponsePatternRuleTest.java | 79 +- .../rewrite/handler/RewriteHandlerTest.java | 216 +- .../handler/RewritePatternRuleTest.java | 163 +- .../rewrite/handler/RewriteRegexRuleTest.java | 186 +- .../handler/TerminatingPatternRuleTest.java | 91 +- .../handler/TerminatingRegexRuleTest.java | 91 +- .../handler/VirtualHostRuleContainerTest.java | 274 +- .../test/resources/jetty-logging.properties | 2 + .../jetty-rewrite.xml | 2 +- jetty-core/jetty-server/pom.xml | 38 +- .../main/config/etc/jetty-bytebufferpool.xml | 4 +- .../src/main/config/etc/jetty-requestlog.xml | 2 +- .../etc/sessions/file/session-store.xml | 0 .../etc/sessions/jdbc/session-store.xml | 0 .../etc/sessions/session-cache-hash.xml | 2 +- .../main/config/modules/bytebufferpool.mod | 12 +- .../src/main/config/modules/requestlog.mod | 25 +- .../src/main/config/modules/server.mod | 2 +- .../config/modules/session-cache-hash.mod | 2 +- .../src/main/config/modules/stats.mod | 2 +- .../modules/test-keystore/test-keystore.p12 | Bin 0 -> 2565 bytes .../src/main/java/module-info.java | 11 +- .../jetty/server/AbstractConnector.java | 45 +- .../server/AllowedResourceAliasChecker.java | 20 +- .../jetty/server/CachedContentFactory.java | 128 +- .../org/eclipse/jetty/server/Components.java | 47 + .../jetty/server/ConnectionFactory.java | 1 - .../jetty/server/ConnectionMetaData.java | 166 + .../org/eclipse/jetty/server/Content.java | 1020 + .../jetty/server/ContentProcessor.java | 225 + .../org/eclipse/jetty/server/Context.java | 59 + .../jetty/server/CustomRequestLog.java | 143 +- .../server/DetectorConnectionFactory.java | 18 +- .../server/ForwardedRequestCustomizer.java | 230 +- .../jetty/server/FutureFormFields.java | 240 + .../org/eclipse/jetty/server/Handler.java | 697 +- .../jetty/server/HandlerContainer.java | 35 +- .../jetty/server/HostHeaderCustomizer.java | 50 +- .../org/eclipse/jetty/server/HttpChannel.java | 1601 +- .../jetty/server/HttpChannelListeners.java | 18 +- .../jetty/server/HttpConfiguration.java | 26 +- .../jetty/server/HttpConnectionFactory.java | 1 + .../org/eclipse/jetty/server/HttpStream.java | 189 + .../eclipse/jetty/server/LocalConnector.java | 17 + .../jetty/server/ProxyConnectionFactory.java | 32 +- .../eclipse/jetty/server/ProxyCustomizer.java | 39 +- .../org/eclipse/jetty/server/Request.java | 2936 +- .../org/eclipse/jetty/server/RequestLog.java | 13 +- .../jetty/server/ResourceContentFactory.java | 1 + .../eclipse/jetty/server/ResourceService.java | 56 +- .../org/eclipse/jetty/server/Response.java | 1706 +- .../jetty/server/SecureRequestCustomizer.java | 317 +- .../java/org/eclipse/jetty/server/Server.java | 502 +- .../eclipse/jetty/server/ServerConnector.java | 37 +- .../eclipse/jetty/server/ShutdownMonitor.java | 8 +- .../jetty/server/SslConnectionFactory.java | 2 +- .../SymlinkAllowedResourceAliasChecker.java | 2 +- .../jetty/server/handler/AbstractHandler.java | 143 +- .../handler/AbstractHandlerContainer.java | 110 +- .../handler/BufferedResponseHandler.java | 299 +- .../jetty/server/handler/ContextHandler.java | 2983 +- .../handler/ContextHandlerCollection.java | 154 +- .../jetty/server/handler/ContextRequest.java | 134 + .../jetty/server/handler/ContextResponse.java | 51 + .../jetty/server/handler/DebugHandler.java | 18 +- .../jetty/server/handler/DefaultHandler.java | 77 +- .../jetty/server/handler/DelayedHandler.java | 260 + .../jetty/server/handler/ErrorProcessor.java | 569 + .../handler/FileBufferedResponseHandler.java | 16 +- .../jetty/server/handler/HandlerList.java | 30 +- .../jetty/server/handler/HandlerWrapper.java | 124 +- .../jetty/server/handler/HotSwapHandler.java | 103 +- .../server/handler/IdleTimeoutHandler.java | 61 +- .../server/handler/InetAccessHandler.java | 41 +- .../handler/ManagedAttributeListener.java | 9 +- .../server/handler/MovedContextHandler.java | 46 +- .../server/handler/ProxiedRequestHandler.java | 77 + .../jetty/server/handler/ResourceHandler.java | 1436 +- .../handler/SecuredRedirectHandler.java | 60 +- .../jetty/server/handler/ShutdownHandler.java | 20 +- .../server/handler/StatisticsHandler.java | 803 +- .../server/handler/ThreadLimitHandler.java | 140 +- .../server/handler/gzip/GzipHandler.java | 296 +- .../server/handler/gzip/GzipRequest.java | 203 + .../server/handler/gzip/GzipResponse.java | 171 +- .../handler/gzip/HeaderWrappingRequest.java | 24 +- .../server/handler/gzip/package-info.java | 0 .../handler/jmx/AbstractHandlerMBean.java | 97 + .../handler/jmx/ContextHandlerMBean.java | 19 +- .../server/handler/jmx/package-info.java | 0 .../jetty/server/handler/package-info.java | 0 .../server/internal/HttpChannelState.java | 2544 +- .../jetty/server/internal/HttpConnection.java | 1216 +- .../server/internal/MultiPartParser.java | 5 +- .../server/internal/ResponseHttpFields.java | 336 + .../server/jmx/AbstractHandlerMBean.java | 82 + .../eclipse/jetty/server/jmx/ServerMBean.java | 14 +- .../jetty/server/jmx/package-info.java | 2 +- .../eclipse/jetty/server/package-info.java | 0 .../org/eclipse/jetty/server/ssl/FixJPMS.java | 20 + .../jetty/server/AbstractHttpTest.java | 13 +- .../jetty/server/ConnectionOpenCloseTest.java | 26 +- .../jetty/server/ConnectorCloseTestBase.java | 6 +- .../jetty/server/ConnectorTimeoutTest.java | 95 +- .../org/eclipse/jetty/server/ContentTest.java | 910 + .../jetty/server/CustomRequestLogTest.java | 663 + .../server/CustomResourcesMonitorTest.java | 2 + .../jetty/server/DelayedServerTest.java | 45 +- .../jetty/server/DetectorConnectionTest.java | 26 +- .../jetty/server/ErrorProcessorTest.java | 699 + .../jetty/server/ExtendedServerTest.java | 41 +- .../ForwardedRequestCustomizerTest.java | 254 +- .../jetty/server/GracefulStopTest.java | 59 +- .../eclipse/jetty/server/HalfCloseTest.java | 22 +- .../server/HostHeaderCustomizerTest.java | 12 +- .../jetty/server/HttpChannelEventTest.java | 58 +- .../eclipse/jetty/server/HttpChannelTest.java | 1283 + ...ttpConfigurationAuthorityOverrideTest.java | 193 +- .../jetty/server/HttpConnectionTest.java | 716 +- .../jetty/server/HttpServerTestBase.java | 1201 +- .../jetty/server/HttpServerTestFixture.java | 253 +- .../eclipse/jetty/server/LargeHeaderTest.java | 18 +- .../jetty/server/LocalConnectorTest.java | 295 +- .../jetty/server/LowResourcesMonitorTest.java | 3 +- .../jetty/server/MockConnectionMetaData.java | 110 + .../eclipse/jetty/server/MockConnector.java | 35 +- .../eclipse/jetty/server/MockHttpStream.java | 267 + .../jetty/server/MultiPartCaptureTest.java | 28 +- .../server/MultiPartFormInputStreamTest.java | 44 +- .../jetty/server/MultiPartParserTest.java | 3 +- .../jetty/server/NotAcceptingTest.java | 33 +- .../server/OptionalSslConnectionTest.java | 10 +- .../jetty/server/PartialRFC2616Test.java | 6 +- .../jetty/server/ProxyConnectionTest.java | 17 +- .../jetty/server/ProxyCustomizerTest.java | 38 +- .../jetty/server/ProxyProtocolTest.java | 55 +- .../eclipse/jetty/server/RequestLogTest.java | 56 +- .../org/eclipse/jetty/server/RequestTest.java | 2276 +- .../jetty/server/ResourceCacheTest.java | 38 +- .../server/ServerConnectorAcceptTest.java | 19 +- .../server/ServerConnectorCloseTest.java | 2 +- .../server/ServerConnectorHttpServerTest.java | 104 +- .../jetty/server/ServerConnectorTest.java | 41 +- .../server/ServerConnectorTimeoutTest.java | 29 +- .../jetty/server/ShutdownMonitorTest.java | 1 + .../SlowClientWithPipelinedRequestTest.java | 25 +- .../org/eclipse/jetty/server/StopTest.java | 32 +- .../jetty/server/ThreadStarvationTest.java | 25 +- .../handler/AllowSymLinkAliasCheckerTest.java | 13 +- .../handler/BufferedResponseHandlerTest.java | 74 +- .../handler/ContextHandlerCollectionTest.java | 33 +- .../ContextHandlerGetResourceTest.java | 28 +- .../server/handler/ContextHandlerTest.java | 1366 +- .../server/handler/DebugHandlerTest.java | 9 +- .../server/handler/DefaultHandlerTest.java | 3 +- .../server/handler/DelayedHandlerTest.java | 460 + .../jetty/server/handler/DumpHandler.java | 300 +- .../jetty/server/handler/EchoHandler.java | 56 + .../FileBufferedResponseHandlerTest.java | 102 +- .../jetty/server/handler/HandlerTest.java | 155 +- .../jetty/server/handler/HelloHandler.java | 54 + .../server/handler/InetAccessHandlerTest.java | 9 +- .../server/handler/NcsaRequestLogTest.java | 35 +- .../handler/ResourceHandlerRangeTest.java | 4 +- .../server/handler/ResourceHandlerTest.java | 725 +- .../SecuredRedirectHandlerCodeTest.java | 15 +- .../handler/SecuredRedirectHandlerTest.java | 38 +- .../server/handler/ShutdownHandlerTest.java | 10 +- .../server/handler/StatisticsHandlerTest.java | 1477 +- .../handler/ThreadLimitHandlerTest.java | 110 +- .../server/handler/gzip}/GzipHandlerTest.java | 407 +- .../jetty/server/jmh/HandlerBenchmark.java | 165 + .../server/resource/RangeWriterTest.java | 2 + .../jetty/server/ssl/SSLCloseTest.java | 51 +- .../jetty/server/ssl/SSLEngineTest.java | 147 +- .../ssl/SSLReadEOFAfterResponseTest.java | 51 +- .../SSLSelectChannelConnectorLoadTest.java | 36 +- .../ssl/ServerConnectorSslServerTest.java | 84 +- .../jetty/server/ssl/SlowClientsTest.java | 39 +- .../ssl/SniSslConnectionFactoryTest.java | 52 +- .../server/ssl/SslConnectionFactoryTest.java | 18 +- .../ssl/SslContextFactoryReloadTest.java | 30 +- .../ssl/SslSelectChannelTimeoutTest.java | 4 +- .../jetty/server/ssl/SslUploadTest.java | 62 +- .../test/resources/jetty-logging.properties | 6 +- jetty-core/jetty-session/pom.xml | 61 + .../src/main/java/module-info.java | 25 + .../jetty}/session/AbstractSessionCache.java | 67 +- .../session/AbstractSessionCacheFactory.java | 14 +- .../session/AbstractSessionDataStore.java | 3 +- .../AbstractSessionDataStoreFactory.java | 2 +- .../jetty/session/AbstractSessionManager.java | 1252 + .../session/CachingSessionDataStore.java | 2 +- .../CachingSessionDataStoreFactory.java | 6 +- .../jetty}/session/DatabaseAdaptor.java | 2 +- .../jetty}/session/DefaultSessionCache.java | 16 +- .../session/DefaultSessionCacheFactory.java | 6 +- .../session/DefaultSessionIdManager.java | 96 +- .../jetty}/session/FileSessionDataStore.java | 3 +- .../session/FileSessionDataStoreFactory.java | 4 +- .../eclipse/jetty}/session/HouseKeeper.java | 21 +- .../jetty}/session/JDBCSessionDataStore.java | 2 +- .../session/JDBCSessionDataStoreFactory.java | 4 +- .../jetty}/session/NullSessionCache.java | 16 +- .../session/NullSessionCacheFactory.java | 10 +- .../jetty}/session/NullSessionDataStore.java | 2 +- .../session/NullSessionDataStoreFactory.java | 4 +- .../org/eclipse/jetty}/session/Session.java | 435 +- .../eclipse/jetty}/session/SessionCache.java | 26 +- .../jetty}/session/SessionCacheFactory.java | 4 +- .../eclipse/jetty/session/SessionConfig.java | 108 + .../jetty}/session/SessionContext.java | 51 +- .../eclipse/jetty}/session/SessionData.java | 2 +- .../jetty}/session/SessionDataMap.java | 2 +- .../jetty}/session/SessionDataMapFactory.java | 2 +- .../jetty}/session/SessionDataStore.java | 2 +- .../session/SessionDataStoreFactory.java | 4 +- .../jetty/session}/SessionIdManager.java | 26 +- .../jetty/session/SessionInactivityTimer.java | 121 + .../eclipse/jetty/session/SessionManager.java | 155 + .../UnreadableSessionDataException.java | 2 +- .../UnwriteableSessionDataException.java | 2 +- .../eclipse/jetty}/session/package-info.java | 2 +- .../session/AbstractSessionCacheTest.java | 409 +- .../session/AbstractSessionDataStoreTest.java | 503 +- .../session/AbstractSessionManagerTest.java | 345 + .../session/DefaultSessionCacheTest.java | 663 + .../session/DefaultSessionIdManagerTest.java | 120 + .../jetty/session/DirtyAttributeTest.java | 101 + .../session/FileSessionDataStoreTest.java | 2 +- .../jetty/session/FileSessionsTest.java | 70 +- .../jetty}/session/FileTestHelper.java | 3 +- .../jetty}/session/HouseKeeperTest.java | 6 +- .../jetty}/session/NullSessionCacheTest.java | 105 +- .../jetty/session/SessionListenerTest.java | 124 + .../jetty/session/SimpleSessionHandler.java | 187 + .../session/SimpleSessionHandlerTest.java | 322 + .../jetty/session/TestableRequest.java | 159 + .../session/TestableSessionDataStore.java | 119 + .../jetty/session/TestableSessionManager.java | 176 + .../src/test}/resources/Foo.clazz | Bin .../src/test}/resources/Foo.java | 0 .../src/test}/resources/Proxyable.clazz | Bin .../src/test}/resources/Proxyable.java | 0 .../test}/resources/ProxyableFactory.clazz | Bin .../src/test}/resources/ProxyableFactory.java | 0 .../ProxyableInvocationHandler.clazz | Bin .../resources/ProxyableInvocationHandler.java | 0 jetty-core/jetty-slf4j-impl/pom.xml | 6 +- .../src/main/java/module-info.java | 0 jetty-core/jetty-start/pom.xml | 6 +- .../org/eclipse/jetty/start/BaseBuilder.java | 4 +- .../org/eclipse/jetty/start/Classpath.java | 8 +- .../org/eclipse/jetty/start/Environment.java | 295 + .../java/org/eclipse/jetty/start/Main.java | 137 +- .../java/org/eclipse/jetty/start/Module.java | 24 +- .../jetty/start/ModuleGraphWriter.java | 18 +- .../java/org/eclipse/jetty/start/Modules.java | 45 +- .../org/eclipse/jetty/start/StartArgs.java | 915 +- .../org/eclipse/jetty/start/package-info.java | 0 .../jetty/start/IncludeJettyDirTest.java | 2 +- .../org/eclipse/jetty/start/MainTest.java | 57 +- .../jetty/start/ModuleGraphWriterTest.java | 52 +- .../org/eclipse/jetty/start/ModulesTest.java | 11 +- .../org/eclipse/jetty/start/PropertyDump.java | 2 + .../jetty/start/usecases/AbstractUseCase.java | 18 +- .../jetty/start/usecases/BasicTest.java | 6 +- .../start/usecases/EnvironmentsTest.java | 124 + .../test/resources/dist-home/modules/base.mod | 0 .../src/test/resources/dist-home/start.ini | 0 .../src/test/resources/empty.home/start.ini | 0 .../extra-jetty-dirs/logging/start.ini | 0 .../test/resources/hb.1/base/start.d/jmx.ini | 0 .../resources/hb.1/base/start.d/logging.ini | 0 .../src/test/resources/hb.1/base/start.ini | 0 .../test/resources/hb.1/home/start.d/jmx.ini | 0 .../resources/hb.1/home/start.d/logging.ini | 0 .../src/test/resources/hb.1/home/start.ini | 0 .../lib/example of a library with spaces.jar | 0 .../jetty home with spaces/modules/base.mod | 0 .../jetty home with spaces/start.ini | 0 .../usecases/minimal-start-ini/start.ini | 0 jetty-core/jetty-unixdomain-server/pom.xml | 6 +- .../src/main/java/module-info.java | 0 .../unixdomain/server/UnixDomainTest.java | 34 +- jetty-core/jetty-util-ajax/pom.xml | 11 +- .../src/main/java/module-info.java | 0 .../eclipse/jetty/util/ajax/package-info.java | 0 jetty-core/jetty-util/pom.xml | 8 +- .../src/main/java/module-info.java | 0 .../eclipse/jetty/util/ArrayTernaryTrie.java | 6 +- .../org/eclipse/jetty/util/ArrayTrie.java | 2 +- .../org/eclipse/jetty/util/Attributes.java | 411 +- .../org/eclipse/jetty/util/AttributesMap.java | 44 +- .../java/org/eclipse/jetty/util/Blocking.java | 377 + .../java/org/eclipse/jetty/util/Callback.java | 8 +- .../jetty/util/CharsetStringBuilder.java | 155 + .../java/org/eclipse/jetty/util/Fields.java | 128 +- .../eclipse/jetty/util/FutureCallback.java | 9 +- .../org/eclipse/jetty/util/FuturePromise.java | 9 +- .../main/java/org/eclipse/jetty/util/IO.java | 14 + .../jetty/util/IteratingCallback_State.puml | 35 + .../java/org/eclipse/jetty/util/MultiMap.java | 16 +- .../jetty/util/SharedBlockingCallback.java | 14 +- .../org/eclipse/jetty/util/StringUtil.java | 14 + .../java/org/eclipse/jetty/util/TreeTrie.java | 10 +- .../java/org/eclipse/jetty/util/TypeUtil.java | 58 + .../java/org/eclipse/jetty/util/URIUtil.java | 308 +- .../org/eclipse/jetty/util/UrlEncoded.java | 88 +- .../eclipse/jetty/util/Utf8Appendable.java | 8 +- .../eclipse/jetty/util/Utf8StringBuffer.java | 22 + .../eclipse/jetty/util/Utf8StringBuilder.java | 22 + .../jetty/util/annotation/package-info.java | 0 .../util/component/AbstractLifeCycle.java | 6 +- .../util/component/AttributeContainerMap.java | 17 +- .../jetty/util/component/Dumpable.java | 5 +- .../jetty/util/component/Environment.java | 49 + .../jetty/util/component/package-info.java | 0 .../org/eclipse/jetty/util/package-info.java | 0 .../jetty/util/preventers/package-info.java | 0 .../jetty/util/resource/PathCollators.java | 113 + .../eclipse/jetty/util/resource/Resource.java | 13 + .../util/resource/ResourceCollators.java | 1 + .../jetty/util/resource/ResourceFactory.java | 1 + .../jetty/util/resource/package-info.java | 0 .../eclipse/jetty/util/security/Password.java | 2 +- .../jetty/util/security/package-info.java | 0 .../eclipse/jetty/util/ssl/package-info.java | 0 .../jetty/util/statistic/package-info.java | 0 .../eclipse/jetty/util/thread/Invocable.java | 25 +- .../util/thread/ReservedThreadExecutor.java | 7 +- .../jetty/util/thread/SerializedExecutor.java | 109 +- .../jetty/util/thread/SerializedInvoker.java | 155 + .../jetty/util/thread/package-info.java | 0 .../strategy/AdaptiveExecutionStrategy.java | 2 +- .../eclipse/jetty/util/AttributesTest.java | 138 + .../org/eclipse/jetty/util/BlockingTest.java | 416 + .../jetty/util/CharsetStringBuilderTest.java | 59 + .../org/eclipse/jetty/util/FieldsTest.java | 85 + .../util/SharedBlockingCallbackTest.java | 1 + .../org/eclipse/jetty/util/URIUtilTest.java | 52 +- .../component/ContainerLifeCycleTest.java | 142 +- .../LifeCycleListenerNestedTest.java | 284 + .../util/thread/SerializedExecutorTest.java | 121 +- .../util/thread/SerializedInvokerTest.java | 150 + .../src/test/resources/TestData/test/alphabet | 0 .../src/test/resources/TestData/test/numbers | 0 .../resources/TestData/test/subdir/alphabet | 0 .../resources/TestData/test/subdir/numbers | 0 .../TestData/test/subdir/subsubdir/alphabet | 0 .../TestData/test/subdir/subsubdir/numbers | 0 .../org/eclipse/jetty/util/resource/four/four | 0 .../org/eclipse/jetty/util/resource/one/1.txt | 0 .../eclipse/jetty/util/resource/one/dir/1.txt | 0 .../eclipse/jetty/util/resource/resource.txt | 0 .../eclipse/jetty/util/resource/three/2.txt | 0 .../eclipse/jetty/util/resource/three/3.txt | 0 .../jetty/util/resource/three/dir/3.txt | 0 .../org/eclipse/jetty/util/resource/two/1.txt | 0 .../org/eclipse/jetty/util/resource/two/2.txt | 0 .../eclipse/jetty/util/resource/two/dir/2.txt | 0 .../src/test/resources/resource.txt | 0 jetty-core/jetty-websocket/pom.xml | 42 +- .../websocket-core-client/pom.xml | 6 +- .../src/main/java/module-info.java | 0 .../websocket-core-common/pom.xml | 6 +- .../src/main/java/module-info.java | 36 +- .../core/internal/NullAppendable.java | 6 + .../websocket-core-server/pom.xml | 10 +- .../src/main/java/module-info.java | 0 .../websocket/core/server/Handshaker.java | 7 +- .../core/server/ServerUpgradeRequest.java | 357 +- .../core/server/ServerUpgradeResponse.java | 144 +- .../core/server/WebSocketCreator.java | 6 +- .../core/server/WebSocketMappings.java | 55 +- .../core/server/WebSocketNegotiation.java | 41 +- .../server/WebSocketServerComponents.java | 36 +- .../core/server/WebSocketUpgradeHandler.java | 68 +- .../server/internal/AbstractHandshaker.java | 101 +- .../server/internal/CreatorNegotiator.java | 32 +- .../server/internal/HandshakerSelector.java | 11 +- .../server/internal/HttpFieldsWrapper.java | 113 + .../server/internal/RFC6455Handshaker.java | 38 +- .../server/internal/RFC6455Negotiation.java | 10 +- .../server/internal/RFC8441Handshaker.java | 34 +- .../server/internal/RFC8441Negotiation.java | 10 +- .../internal/WebSocketHttpFieldsWrapper.java | 88 + .../websocket-core-tests}/fuzzingclient.json | 0 .../websocket-core-tests}/fuzzingserver.json | 0 .../websocket-core-tests/pom.xml | 66 +- .../websocket/core/WebSocketEchoTest.java | 89 + .../core/WebSocketNegotiationTest.java | 4 +- .../core/WebSocketServerComponentsTest.java | 45 +- .../core/autobahn}/AutobahnFrameHandler.java | 3 +- .../core/autobahn/AutobahnTests.java | 113 +- .../core/autobahn}/CoreAutobahnClient.java | 60 +- .../core/autobahn}/CoreAutobahnServer.java | 40 +- .../core/chat/ChatWebSocketServer.java | 15 +- .../PermessageDeflateDemandTest.java | 1 - .../core/proxy/WebSocketProxyTest.java | 22 +- .../test/resources/jetty-logging.properties | 23 +- jetty-core/jetty-xml/pom.xml | 6 +- .../jetty-xml}/src/main/java/module-info.java | 0 .../eclipse/jetty/xml/XmlConfiguration.java | 2 +- .../org/eclipse/jetty/xml/package-info.java | 0 jetty-core/pom.xml | 58 + .../deploy/bindings/DebugListenerBinding.java | 48 - .../bindings/GlobalWebappConfigBinding.java | 113 - .../providers/jmx/WebAppProviderMBean.java | 39 - .../jetty/deploy/BadAppDeployTest.java | 167 - .../jetty/deploy/DeploymentTempDirTest.java | 235 - .../GlobalWebappConfigBindingTest.java | 105 - .../deploy/providers/WebAppProviderTest.java | 259 - .../jetty-ee9-annotations}/pom.xml | 22 +- .../src/main/java/module-info.java | 15 +- ...AbstractDiscoverableAnnotationHandler.java | 9 +- .../annotations/AnnotationConfiguration.java | 39 +- .../ee9/annotations/AnnotationDecorator.java | 4 +- .../annotations/AnnotationIntrospector.java | 10 +- .../ee9/annotations/AnnotationParser.java | 2 +- .../annotations/ClassInheritanceHandler.java | 8 +- ...ContainerInitializerAnnotationHandler.java | 24 +- .../DeclareRolesAnnotationHandler.java | 10 +- .../MultiPartConfigAnnotationHandler.java | 12 +- .../PostConstructAnnotationHandler.java | 14 +- .../PreDestroyAnnotationHandler.java | 14 +- .../ResourceAnnotationHandler.java | 29 +- .../ResourcesAnnotationHandler.java | 11 +- .../annotations/RunAsAnnotationHandler.java | 12 +- .../ServletContainerInitializersStarter.java | 8 +- .../ServletSecurityAnnotationHandler.java | 16 +- .../ee9/annotations/WebFilterAnnotation.java | 16 +- .../WebFilterAnnotationHandler.java | 13 +- .../annotations/WebListenerAnnotation.java | 14 +- .../WebListenerAnnotationHandler.java | 13 +- .../ee9/annotations/WebServletAnnotation.java | 16 +- .../WebServletAnnotationHandler.java | 13 +- .../jetty/ee9}/annotations/package-info.java | 2 +- ...org.eclipse.jetty.ee9.webapp.Configuration | 1 + .../eclipse/jetty/ee9/annotations/ClassA.java | 2 +- .../eclipse/jetty/ee9/annotations/ClassB.java | 2 +- .../jetty/ee9/annotations/FilterC.java | 2 +- .../jetty/ee9/annotations/InterfaceD.java | 2 +- .../jetty/ee9/annotations/ListenerC.java | 2 +- .../eclipse/jetty/ee9/annotations/Multi.java | 2 +- .../eclipse/jetty/ee9/annotations/Sample.java | 2 +- .../jetty/ee9/annotations/ServletC.java | 2 +- .../jetty/ee9/annotations/ServletD.java | 2 +- .../jetty/ee9/annotations/ServletE.java | 2 +- .../TestAnnotationConfiguration.java | 8 +- .../annotations/TestAnnotationDecorator.java | 14 +- .../TestAnnotationInheritance.java | 14 +- .../TestAnnotationIntrospector.java | 12 +- .../ee9/annotations/TestAnnotationParser.java | 34 +- ...eredServletContainerInitializerHolder.java | 9 +- .../ee9/annotations/TestRunAsAnnotation.java | 8 +- .../TestSecurityAnnotationConversions.java | 14 +- .../annotations/TestServletAnnotations.java | 12 +- .../ee9/annotations/resources/ResourceA.java | 2 +- .../ee9/annotations/resources/ResourceB.java | 2 +- .../resources/TestResourceAnnotations.java | 23 +- .../jetty-ee9-ant}/pom.xml | 36 +- .../ee9/ant/AntMetaInfConfiguration.java | 8 +- .../jetty/ee9/ant/AntWebAppContext.java | 30 +- .../jetty/ee9/ant/AntWebInfConfiguration.java | 12 +- .../jetty/ee9/ant/AntWebXmlConfiguration.java | 6 +- .../eclipse/jetty/ee9/ant/JettyRunTask.java | 18 +- .../eclipse/jetty/ee9/ant/JettyStopTask.java | 4 +- .../jetty/ee9/ant/ServerProxyImpl.java | 12 +- .../eclipse/jetty/ee9}/ant/package-info.java | 2 +- .../jetty/ee9/ant/types/Attribute.java | 2 +- .../jetty/ee9/ant/types/Attributes.java | 2 +- .../jetty/ee9/ant/types/Connector.java | 2 +- .../jetty/ee9/ant/types/Connectors.java | 2 +- .../jetty/ee9/ant/types/ContextHandlers.java | 2 +- .../ant/types/FileMatchingConfiguration.java | 2 +- .../jetty/ee9/ant/types/LoginServices.java | 4 +- .../jetty/ee9/ant/types/SystemProperties.java | 4 +- .../jetty/ee9}/ant/types/package-info.java | 2 +- .../jetty/ee9/ant/utils/ServerProxy.java | 4 +- .../eclipse/jetty/ee9/ant/utils/TaskLog.java | 2 +- .../jetty/ee9}/ant/utils/package-info.java | 2 +- .../org.eclipse.jetty.webapp.Configuration | 4 +- .../src/main/resources/tasks.properties | 4 +- .../org/eclipse/jetty/ee9/ant/AntBuild.java | 2 +- .../jetty/ee9/ant/JettyAntTaskTest.java | 2 +- .../src/test/resources/connector-test.xml | 2 +- .../src/test/resources/foo/index.html | 0 .../src/test/resources/foo/jsp/index.html | 0 .../src/test/resources/webapp-test.xml | 2 +- .../jetty-ee9-apache-jsp}/pom.xml | 26 +- .../src/main/config/modules/apache-jsp.mod | 0 .../src/main/java/module-info.java | 10 +- .../apache/jsp/JettyJasperInitializer.java | 2 +- .../ee9}/apache/jsp/JettyTldPreScanned.java | 2 +- .../jetty/ee9}/apache/jsp/JuliLog.java | 2 +- .../jetty/ee9}/jsp/JettyJspServlet.java | 2 +- ...akarta.servlet.ServletContainerInitializer | 1 + .../services/org.apache.juli.logging.Log | 1 + .../jetty/ee9}/jsp/TestJettyJspServlet.java | 6 +- .../ee9}/jsp/TestJettyTldPreScanned.java | 6 +- .../ee9}/jsp/TestJspFileNameToClass.java | 4 +- .../test/resources/META-INF/foo-taglib.tld | 0 .../src/test/resources/base/dir/empty.txt | 0 .../src/test/resources/base/foo.jsp | 0 .../src/test/resources/taglib.jar | Bin jetty-ee9/jetty-ee9-bom/pom.xml | 214 + .../jetty-ee9-cdi}/pom.xml | 23 +- .../src/main/config/etc/cdi/jetty-cdi.xml | 2 +- .../src/main/config/modules/cdi.mod | 6 +- .../src/main/java/module-info.java | 8 +- .../jetty/ee9/cdi/CdiConfiguration.java | 10 +- .../jetty/ee9/cdi/CdiDecoratingListener.java | 10 +- .../cdi/CdiServletContainerInitializer.java | 14 +- .../jetty/ee9/cdi/CdiSpiDecorator.java | 4 +- ...akarta.servlet.ServletContainerInitializer | 2 +- .../org.eclipse.jetty.webapp.Configuration | 2 +- .../demo-ee9-async-rest-jar}/pom.xml | 10 +- .../jetty/ee9}/demos/AbstractRestServlet.java | 2 +- .../jetty/ee9}/demos/AsyncRestServlet.java | 2 +- .../jetty/ee9}/demos/SerialRestServlet.java | 2 +- .../META-INF/resources/asyncrest.html | 0 .../META-INF/resources/asyncrest/green.png | Bin .../META-INF/resources/asyncrest/red.png | Bin .../main/resources/META-INF/web-fragment.xml | 0 .../demo-ee9-async-rest-server}/pom.xml | 14 +- .../jetty/ee9}/demos/AsyncRestServer.java | 4 +- .../demo-ee9-async-rest-webapp}/pom.xml | 14 +- .../main/config/modules/demo-async-rest.mod | 0 .../src/main/webapp/META-INF/MANIFEST.MF | 0 .../src/main/webapp/WEB-INF/jetty-web.xml | 4 +- .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/demo.css | 0 .../src/main/webapp/index.html | 0 .../src/main/webapp/small_powered_by.gif | Bin .../demo-ee9-async-rest/pom.xml | 19 + .../demo-ee9-embedded}/pom.xml | 64 +- .../demo-ee9-embedded}/prodDb.properties | 0 .../demo-ee9-embedded}/prodDb.script | 0 .../jetty/ee9}/demos/AsyncEchoServlet.java | 2 +- .../eclipse/jetty/ee9}/demos/DumpServlet.java | 2 +- .../jetty/ee9}/demos/ExampleServer.java | 4 +- .../jetty/ee9}/demos/ExampleServerXml.java | 2 +- .../eclipse/jetty/ee9}/demos/ExampleUtil.java | 2 +- .../jetty/ee9}/demos/FastFileServer.java | 4 +- .../eclipse/jetty/ee9}/demos/FileServer.java | 4 +- .../jetty/ee9}/demos/FileServerXml.java | 2 +- .../jetty/ee9}/demos/HelloHandler.java | 2 +- .../jetty/ee9}/demos/HelloServlet.java | 2 +- .../jetty/ee9}/demos/HelloSessionServlet.java | 2 +- .../eclipse/jetty/ee9}/demos/HelloWorld.java | 2 +- .../eclipse/jetty/ee9}/demos/Http2Server.java | 13 +- .../eclipse/jetty/ee9}/demos/JarServer.java | 8 +- .../eclipse/jetty/ee9}/demos/JettyDemos.java | 2 +- .../jetty/ee9}/demos/LikeJettyXml.java | 17 +- .../jetty/ee9}/demos/ManyConnectors.java | 2 +- .../jetty/ee9}/demos/ManyContexts.java | 2 +- .../jetty/ee9}/demos/ManyHandlers.java | 2 +- .../jetty/ee9}/demos/ManyServletContexts.java | 8 +- .../jetty/ee9}/demos/MinimalServlets.java | 4 +- .../jetty/ee9}/demos/OneConnector.java | 2 +- .../eclipse/jetty/ee9}/demos/OneContext.java | 2 +- .../eclipse/jetty/ee9}/demos/OneHandler.java | 2 +- .../jetty/ee9}/demos/OneServletContext.java | 10 +- .../ee9}/demos/OneServletContextJmxStats.java | 6 +- .../demos/OneServletContextWithSession.java | 12 +- .../eclipse/jetty/ee9}/demos/OneWebApp.java | 6 +- .../jetty/ee9}/demos/OneWebAppWithJsp.java | 8 +- .../eclipse/jetty/ee9}/demos/ProxyServer.java | 10 +- .../jetty/ee9}/demos/RewriteServer.java | 4 +- .../jetty/ee9}/demos/SecuredHelloHandler.java | 12 +- .../ee9}/demos/ServerWithAnnotations.java | 24 +- .../jetty/ee9}/demos/ServerWithJMX.java | 2 +- .../jetty/ee9}/demos/ServerWithJNDI.java | 20 +- .../jetty/ee9}/demos/SimplestServer.java | 2 +- .../jetty/ee9}/demos/SplitFileServer.java | 2 +- .../jetty/ee9}/demos/WebSocketServer.java | 18 +- .../src/main/other/content.jar | Bin .../main/resources/demo/demo-realm.properties | 2 +- .../src/main/resources/demo/webdefault.xml | 12 +- .../src/main/resources/docroot/push.html | 0 .../main/resources/docroot/pushed/tile00.jpg | Bin .../main/resources/docroot/pushed/tile01.jpg | Bin .../main/resources/docroot/pushed/tile02.jpg | Bin .../main/resources/docroot/pushed/tile03.jpg | Bin .../main/resources/docroot/pushed/tile04.jpg | Bin .../main/resources/docroot/pushed/tile05.jpg | Bin .../main/resources/docroot/pushed/tile06.jpg | Bin .../main/resources/docroot/pushed/tile07.jpg | Bin .../main/resources/docroot/pushed/tile08.jpg | Bin .../main/resources/docroot/pushed/tile09.jpg | Bin .../main/resources/docroot/pushed/tile10.jpg | Bin .../main/resources/docroot/pushed/tile11.jpg | Bin .../main/resources/docroot/pushed/tile12.jpg | Bin .../main/resources/docroot/pushed/tile13.jpg | Bin .../main/resources/docroot/pushed/tile14.jpg | Bin .../main/resources/docroot/pushed/tile15.jpg | Bin .../main/resources/docroot/pushed/tile16.jpg | Bin .../main/resources/docroot/pushed/tile17.jpg | Bin .../main/resources/docroot/pushed/tile18.jpg | Bin .../main/resources/docroot/pushed/tile19.jpg | Bin .../main/resources/docroot/pushed/tile20.jpg | Bin .../main/resources/docroot/pushed/tile21.jpg | Bin .../main/resources/docroot/pushed/tile22.jpg | Bin .../main/resources/docroot/pushed/tile23.jpg | Bin .../main/resources/docroot/pushed/tile24.jpg | Bin .../main/resources/docroot/pushed/tile25.jpg | Bin .../main/resources/docroot/pushed/tile26.jpg | Bin .../main/resources/docroot/pushed/tile27.jpg | Bin .../main/resources/docroot/pushed/tile28.jpg | Bin .../main/resources/docroot/pushed/tile29.jpg | Bin .../main/resources/docroot/pushed/tile30.jpg | Bin .../main/resources/docroot/pushed/tile31.jpg | Bin .../main/resources/docroot/pushed/tile32.jpg | Bin .../main/resources/docroot/pushed/tile33.jpg | Bin .../main/resources/docroot/pushed/tile34.jpg | Bin .../main/resources/docroot/pushed/tile35.jpg | Bin .../main/resources/docroot/pushed/tile36.jpg | Bin .../main/resources/docroot/pushed/tile37.jpg | Bin .../main/resources/docroot/pushed/tile38.jpg | Bin .../main/resources/docroot/pushed/tile39.jpg | Bin .../main/resources/docroot/pushed/tile40.jpg | Bin .../main/resources/docroot/pushed/tile41.jpg | Bin .../main/resources/docroot/pushed/tile42.jpg | Bin .../main/resources/docroot/pushed/tile43.jpg | Bin .../main/resources/docroot/pushed/tile44.jpg | Bin .../main/resources/docroot/pushed/tile45.jpg | Bin .../main/resources/docroot/pushed/tile46.jpg | Bin .../main/resources/docroot/pushed/tile47.jpg | Bin .../main/resources/docroot/pushed/tile48.jpg | Bin .../main/resources/docroot/pushed/tile49.jpg | Bin .../src/main/resources/docroot/readme.txt | 0 .../main/resources/docroot/tiles/tile00.jpg | Bin .../main/resources/docroot/tiles/tile01.jpg | Bin .../main/resources/docroot/tiles/tile02.jpg | Bin .../main/resources/docroot/tiles/tile03.jpg | Bin .../main/resources/docroot/tiles/tile04.jpg | Bin .../main/resources/docroot/tiles/tile05.jpg | Bin .../main/resources/docroot/tiles/tile06.jpg | Bin .../main/resources/docroot/tiles/tile07.jpg | Bin .../main/resources/docroot/tiles/tile08.jpg | Bin .../main/resources/docroot/tiles/tile09.jpg | Bin .../main/resources/docroot/tiles/tile10.jpg | Bin .../main/resources/docroot/tiles/tile11.jpg | Bin .../main/resources/docroot/tiles/tile12.jpg | Bin .../main/resources/docroot/tiles/tile13.jpg | Bin .../main/resources/docroot/tiles/tile14.jpg | Bin .../main/resources/docroot/tiles/tile15.jpg | Bin .../main/resources/docroot/tiles/tile16.jpg | Bin .../main/resources/docroot/tiles/tile17.jpg | Bin .../main/resources/docroot/tiles/tile18.jpg | Bin .../main/resources/docroot/tiles/tile19.jpg | Bin .../main/resources/docroot/tiles/tile20.jpg | Bin .../main/resources/docroot/tiles/tile21.jpg | Bin .../main/resources/docroot/tiles/tile22.jpg | Bin .../main/resources/docroot/tiles/tile23.jpg | Bin .../main/resources/docroot/tiles/tile24.jpg | Bin .../main/resources/docroot/tiles/tile25.jpg | Bin .../main/resources/docroot/tiles/tile26.jpg | Bin .../main/resources/docroot/tiles/tile27.jpg | Bin .../main/resources/docroot/tiles/tile28.jpg | Bin .../main/resources/docroot/tiles/tile29.jpg | Bin .../main/resources/docroot/tiles/tile30.jpg | Bin .../main/resources/docroot/tiles/tile31.jpg | Bin .../main/resources/docroot/tiles/tile32.jpg | Bin .../main/resources/docroot/tiles/tile33.jpg | Bin .../main/resources/docroot/tiles/tile34.jpg | Bin .../main/resources/docroot/tiles/tile35.jpg | Bin .../main/resources/docroot/tiles/tile36.jpg | Bin .../main/resources/docroot/tiles/tile37.jpg | Bin .../main/resources/docroot/tiles/tile38.jpg | Bin .../main/resources/docroot/tiles/tile39.jpg | Bin .../main/resources/docroot/tiles/tile40.jpg | Bin .../main/resources/docroot/tiles/tile41.jpg | Bin .../main/resources/docroot/tiles/tile42.jpg | Bin .../main/resources/docroot/tiles/tile43.jpg | Bin .../main/resources/docroot/tiles/tile44.jpg | Bin .../main/resources/docroot/tiles/tile45.jpg | Bin .../main/resources/docroot/tiles/tile46.jpg | Bin .../main/resources/docroot/tiles/tile47.jpg | Bin .../main/resources/docroot/tiles/tile48.jpg | Bin .../main/resources/docroot/tiles/tile49.jpg | Bin .../src/main/resources/etc/keystore.p12 | Bin .../src/main/resources/etc/realm.properties | 0 .../src/main/resources/exampleserver.xml | 2 +- .../src/main/resources/fileserver.xml | 0 .../resources/java-util-logging.properties | 0 .../main/resources/jetty-logging.properties | 2 +- .../src/main/resources/logback-access.xml | 0 .../ee9}/demos/AbstractEmbeddedTest.java | 2 +- .../jetty/ee9}/demos/ExampleServerTest.java | 2 +- .../ee9}/demos/ExampleServerXmlTest.java | 2 +- .../jetty/ee9}/demos/FastFileServerTest.java | 2 +- .../jetty/ee9}/demos/FileServerTest.java | 2 +- .../jetty/ee9}/demos/FileServerXmlTest.java | 2 +- .../jetty/ee9}/demos/JarServerTest.java | 2 +- .../jetty/ee9}/demos/LikeJettyXmlTest.java | 2 +- .../jetty/ee9}/demos/ManyConnectorsTest.java | 2 +- .../jetty/ee9}/demos/ManyContextsTest.java | 2 +- .../jetty/ee9}/demos/ManyHandlersTest.java | 2 +- .../ee9}/demos/ManyServletContextsTest.java | 2 +- .../jetty/ee9}/demos/MinimalServletsTest.java | 2 +- .../jetty/ee9}/demos/OneConnectorTest.java | 2 +- .../jetty/ee9}/demos/OneContextTest.java | 2 +- .../jetty/ee9}/demos/OneHandlerTest.java | 2 +- .../demos/OneServletContextJmxStatsTest.java | 2 +- .../ee9}/demos/OneServletContextTest.java | 2 +- .../OneServletContextWithSessionTest.java | 2 +- .../jetty/ee9}/demos/OneWebAppTest.java | 2 +- .../ee9}/demos/OneWebAppWithJspTest.java | 2 +- .../jetty/ee9}/demos/ProxyServerTest.java | 2 +- .../jetty/ee9}/demos/RewriteServerTest.java | 2 +- .../ee9}/demos/SecuredHelloHandlerTest.java | 2 +- .../eclipse/jetty/ee9}/demos/ServerUtil.java | 2 +- .../ee9}/demos/ServerWithAnnotationsTest.java | 2 +- .../jetty/ee9}/demos/ServerWithJMXTest.java | 2 +- .../jetty/ee9}/demos/ServerWithJNDITest.java | 2 +- .../jetty/ee9}/demos/SimplestServerTest.java | 2 +- .../jetty/ee9}/demos/SplitFileServerTest.java | 2 +- .../jetty/ee9}/demos/WebSocketServerTest.java | 16 +- .../src/test/resources/dir0/test0.txt | 0 .../src/test/resources/dir1/test1.txt | 0 .../test/resources/jetty-logging.properties | 2 +- .../src/test/resources}/realm.properties | 0 .../demo-ee9-jaas-webapp}/pom.xml | 12 +- .../src/main/config/modules/demo-jaas.mod | 0 .../main/config/modules/demo.d/demo-jaas.xml | 2 +- .../config/modules/demo.d/demo-login.conf | 0 .../modules/demo.d/demo-login.properties | 0 .../src/main/webapp/WEB-INF/jetty-web.xml | 2 +- .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/auth.html | 0 .../src/main/webapp/authfail.html | 0 .../src/main/webapp/demo.css | 0 .../src/main/webapp/index.html | 0 .../src/main/webapp/login.html | 0 .../src/main/webapp/logout.jsp | 0 .../src/main/webapp/small_powered_by.gif | Bin .../src/main/webapp/stylesheet.css | 0 .../demo-ee9-jetty-webapp}/jetty-chat.jmx | 0 .../demo-ee9-jetty-webapp}/pom.xml | 33 +- .../embedded-jetty-web-for-webbundle.xml | 2 +- .../src/main/assembly/web-bundle.xml | 0 .../src/main/config/modules/demo-jetty.mod | 0 .../config/modules/demo-moved-context.mod | 0 .../src/main/config/modules/demo-rewrite.mod | 0 .../demo.d/demo-jetty-override-web.xml | 4 +- .../main/config/modules/demo.d/demo-jetty.xml | 2 +- .../modules/demo.d/demo-moved-context.xml | 0 .../modules/demo.d/demo-rewrite-rules.xml | 0 .../AddListServletRequestListener.java | 2 +- .../main/java/org/example}/ChatServlet.java | 2 +- .../main/java/org/example}/CookieDump.java | 2 +- .../java/org/example}/DispatchServlet.java | 2 +- .../src/main/java/org/example}/Dump.java | 4 +- .../main/java/org/example}/HelloWorld.java | 2 +- .../org/example}/JakartaWebSocketChat.java | 2 +- .../main/java/org/example}/LoginServlet.java | 2 +- .../src/main/java/org/example}/RegTest.java | 2 +- .../java/org/example}/RewriteServlet.java | 2 +- .../java/org/example}/SecureModeServlet.java | 2 +- .../main/java/org/example}/SessionDump.java | 2 +- .../main/java/org/example}/TestFilter.java | 2 +- .../main/java/org/example}/TestListener.java | 2 +- .../main/java/org/example}/TestServlet.java | 2 +- .../org/example}/WebSocketChatServlet.java | 24 +- .../src/main/webapp/WEB-INF/jetty-web.xml | 6 +- .../src/main/webapp/WEB-INF/web.xml | 30 +- .../src/main/webapp/auth.html | 0 .../src/main/webapp/auth/file.txt | 0 .../src/main/webapp/auth/relax.txt | 0 .../src/main/webapp/auth2/index.html | 0 .../src/main/webapp/cgi-bin/hello.sh | 0 .../src/main/webapp/chat/index.html | 0 .../src/main/webapp/d.txt | 0 .../src/main/webapp/da.txt | 0 .../src/main/webapp/da.txt.gz | Bin .../src/main/webapp/dat.txt | 0 .../src/main/webapp/data.txt | 0 .../src/main/webapp/data.txt.gz | Bin .../src/main/webapp/demo.css | 0 .../src/main/webapp/error404.html | 0 .../src/main/webapp/favicon.ico | Bin .../src/main/webapp/index.html | 0 .../main/webapp/jakarta.websocket/index.html | 0 .../src/main/webapp/logon.html | 0 .../src/main/webapp/logonError.html | 0 .../src/main/webapp/remote.html | 0 .../src/main/webapp/rewrite/index.html | 0 .../src/main/webapp/rewrite/info.html | 0 .../src/main/webapp/small_powered_by.gif | Bin .../src/main/webapp/ws/index.html | 0 .../eclipse/jetty/ee9}/ChatServletTest.java | 6 +- .../jetty/ee9}/DispatchServletTest.java | 14 +- .../org/eclipse/jetty/ee9}/TestServer.java | 12 +- .../test/resources/jetty-logging.properties | 3 + .../src/test/resources/test-realm.properties | 2 +- .../demo-ee9-jndi-webapp}/pom.xml | 20 +- .../src/main/config/modules/demo-jndi.mod | 0 .../main/config/modules/demo.d/demo-jndi.xml | 6 +- .../src/main/java/org/example}/JNDITest.java | 2 +- .../src/main/templates/env-definitions.xml | 2 +- .../main/templates/jetty-test-jndi-header.xml | 4 +- .../main/templates/plugin-context-header.xml | 4 +- .../src/main/webapp/WEB-INF/jetty-env.xml | 2 +- .../src/main/webapp/WEB-INF/jetty-web.xml | 2 +- .../src/main/webapp/WEB-INF/web.xml | 10 +- .../src/main/webapp/demo.css | 0 .../src/main/webapp/index.html | 0 .../src/main/webapp/small_powered_by.gif | Bin .../src/main/webapp/stylesheet.css | 0 .../demo-ee9-jsp-webapp}/pom.xml | 14 +- .../src/main/assembly/web-bundle.xml | 0 .../src/main/config/modules/demo-jsp.mod | 0 .../src/main/java/org/example}/Counter.java | 2 +- .../src/main/java/org/example}/Date2Tag.java | 2 +- .../src/main/java/org/example}/DateTag.java | 2 +- .../main/java/org/example}/TagListener.java | 2 +- .../src/main/webapp/WEB-INF/acme-taglib.tld | 4 +- .../src/main/webapp/WEB-INF/acme-taglib2.tld | 2 +- .../src/main/webapp/WEB-INF/jetty-web.xml | 2 +- .../src/main/webapp/WEB-INF/tags/panel.tag | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/bean1.jsp | 2 +- .../src/main/webapp/bean2.jsp | 2 +- .../src/main/webapp/demo.css | 0 .../src/main/webapp/dump.jsp | 0 .../src/main/webapp/expr.jsp | 0 .../src/main/webapp/foo/foo.jsp | 0 .../src/main/webapp/index.jsp | 0 .../src/main/webapp/jstl.jsp | 0 .../src/main/webapp/small_powered_by.gif | Bin .../src/main/webapp/tag.jsp | 0 .../src/main/webapp/tag2.jsp | 0 .../src/main/webapp/tagfile.jsp | 0 .../demo-ee9-mock-resources}/pom.xml | 14 +- .../config/modules/demo-mock-resources.mod | 0 .../java/org/example}/MockDataSource.java | 2 +- .../main/java/org/example}/MockTransport.java | 2 +- .../org/example}/MockUserTransaction.java | 2 +- .../resources/META-INF/javaxmail.providers | 1 + .../demo-ee9-proxy-webapp}/pom.xml | 22 +- .../src/main/config/modules/demo-proxy.mod | 0 .../src/main/webapp/META-INF/MANIFEST.MF | 0 .../src/main/webapp/WEB-INF/jetty-web.xml | 2 +- .../src/main/webapp/WEB-INF/web.xml | 0 .../jetty/ee9}/demos/ProxyWebAppTest.java | 4 +- .../test/resources/jetty-logging.properties | 0 .../demo-ee9-simple-webapp}/pom.xml | 10 +- .../src/main/config/modules/demo-simple.mod | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/index.html | 0 .../src/main/webapp/jetty.icon | Bin .../src/main/webapp/jetty.png | Bin .../src/main/webapp/jetty.webp | Bin .../demo-ee9-container-initializer}/pom.xml | 14 +- .../java/org/example}/initializer/Foo.java | 2 +- .../example}/initializer/FooInitializer.java | 22 +- ...akarta.servlet.ServletContainerInitializer | 1 + .../demo-ee9-spec-webapp}/pom.xml | 30 +- .../src/etc}/realm.properties | 2 +- .../src/main/assembly/web-bundle.xml | 0 .../src/main/config/modules/demo-spec.mod | 0 .../main/config/modules/demo.d/demo-spec.xml | 6 +- .../org/example}/test/AnnotatedListener.java | 10 +- .../org/example}/test/AnnotationTest.java | 54 +- .../example}/test/AsyncListenerServlet.java | 2 +- .../src/main/java/org/example}/test/Bar.java | 4 +- .../org/example}/test/ClassLoaderServlet.java | 2 +- .../java/org/example}/test/MultiPartTest.java | 2 +- .../org/example}/test/RoleAnnotationTest.java | 2 +- .../org/example}/test/SecuredServlet.java | 2 +- .../java/org/example}/test/TestListener.java | 24 +- .../templates/annotations-context-header.xml | 4 +- .../src/main/templates/env-definitions.xml | 2 +- .../main/templates/plugin-context-header.xml | 4 +- .../src/main/webapp/WEB-INF/jetty-env.xml | 2 +- .../src/main/webapp/WEB-INF/jetty-web.xml | 2 +- .../src/main/webapp/WEB-INF/web.xml | 8 +- .../src/main/webapp/authfail.html | 0 .../src/main/webapp/demo.css | 0 .../src/main/webapp/dynamic.jsp | 0 .../src/main/webapp/index.html | 0 .../src/main/webapp/login.html | 0 .../src/main/webapp/logout.jsp | 0 .../src/main/webapp/small_powered_by.gif | Bin .../src/main/webapp/stylesheet.css | 0 .../src/test/jetty-plugin-env.xml | 6 +- .../demo-ee9-web-fragment}/pom.xml | 10 +- .../example}/fragment/FragmentServlet.java | 2 +- .../META-INF/resources/fragmentA/index.html | 0 .../main/resources/META-INF/web-fragment.xml | 4 +- .../jetty-ee9-demos/demo-ee9-spec}/pom.xml | 16 +- .../demo-ee9-template}/pom.xml | 10 +- .../src/main/resources/demo.css | 0 .../src/main/resources/index.html | 0 .../src/main/resources/small_powered_by.gif | Bin {demos => jetty-ee9/jetty-ee9-demos}/pom.xml | 35 +- jetty-ee9/jetty-ee9-fcgi-server-proxy/pom.xml | 51 + .../src/main/java/module-info.java | 19 + .../server/proxy/FastCGIProxyServlet.java | 8 +- .../fcgi/server/proxy/TryFilesFilter.java | 2 +- .../proxy/DrupalHTTP2FastCGIProxyServer.java | 7 +- .../server/proxy/FastCGIProxyServletTest.java | 6 +- .../fcgi/server/proxy/TryFilesFilterTest.java | 7 +- .../WordPressHTTP2FastCGIProxyServer.java | 10 +- .../jetty-ee9-glassfish-jstl}/pom.xml | 30 +- .../main/config/modules/glassfish-jstl.mod | 0 .../src/main/resources/readme.txt | 0 .../eclipse/jetty/ee9}/jstl/JspConfig.java | 4 +- .../jetty/ee9}/jstl/JspIncludeTest.java | 10 +- .../org/eclipse/jetty/ee9}/jstl/JstlTest.java | 6 +- .../test/resources/jetty-logging.properties | 0 .../src/test/taglibjar/META-INF/etag.tld | 0 .../taglibjar/META-INF/tags/errorhandler.tag | 0 .../src/test/webapp/WEB-INF/web.xml | 0 .../src/test/webapp/catch-basic.jsp | 0 .../src/test/webapp/catch-taglib.jsp | 0 .../src/test/webapp/included.jsp | 0 .../src/test/webapp/ref.jsp | 0 .../src/test/webapp/top.jsp | 0 .../src/test/webapp/urls.jsp | 0 .../jetty-ee9-jaas}/pom.xml | 15 +- .../src/main/java/module-info.java | 10 +- .../jetty/ee9/jaas/JAASLoginService.java | 20 +- .../eclipse/jetty/ee9/jaas/JAASPrincipal.java | 2 +- .../org/eclipse/jetty/ee9/jaas/JAASRole.java | 2 +- .../jetty/ee9/jaas/JAASUserPrincipal.java | 2 +- .../ee9/jaas/PropertyUserStoreManager.java | 4 +- .../callback/AbstractCallbackHandler.java | 2 +- .../jaas/callback/DefaultCallbackHandler.java | 8 +- .../ee9/jaas/callback/ObjectCallback.java | 2 +- .../callback/RequestParameterCallback.java | 2 +- .../jaas/callback/ServletRequestCallback.java | 2 +- .../ee9}/jaas/callback/package-info.java | 2 +- .../eclipse/jetty/ee9}/jaas/package-info.java | 2 +- .../jaas/spi/AbstractDatabaseLoginModule.java | 4 +- .../ee9/jaas/spi/AbstractLoginModule.java | 8 +- .../ee9/jaas/spi/DataSourceLoginModule.java | 2 +- .../jetty/ee9/jaas/spi/JDBCLoginModule.java | 2 +- .../jetty/ee9/jaas/spi/LdapLoginModule.java | 6 +- .../ee9/jaas/spi/PropertyFileLoginModule.java | 12 +- .../jetty/ee9}/jaas/spi/package-info.java | 2 +- .../ee9/jaas/JAASLdapLoginServiceTest.java | 181 +- .../jetty/ee9/jaas/JAASLoginServiceTest.java | 26 +- .../jetty/ee9/jaas/TestLoginModule.java | 8 +- .../jaas/spi/PropertyFileLoginModuleTest.java | 28 +- .../jetty-ee9-jaspi}/pom.xml | 14 +- .../src/main/java/module-info.java | 16 +- .../jaspi/DefaultAuthConfigFactory.java | 2 +- .../security/jaspi/JaspiAuthenticator.java | 24 +- .../jaspi/JaspiAuthenticatorFactory.java | 12 +- .../ee9/security/jaspi/JaspiMessageInfo.java | 2 +- .../jaspi/ServletCallbackHandler.java | 12 +- .../ee9}/security/jaspi/SimpleAuthConfig.java | 2 +- .../CredentialValidationCallback.java | 2 +- .../security/jaspi/callback/package-info.java | 2 +- .../jaspi/modules/BaseAuthModule.java | 8 +- .../BasicAuthenticationAuthModule.java | 2 +- .../security/jaspi/modules/package-info.java | 2 +- .../ee9}/security/jaspi/package-info.java | 2 +- .../provider/JaspiAuthConfigProvider.java | 4 +- .../jaspi/provider/SimpleAuthConfig.java | 2 +- .../provider/SimpleServerAuthContext.java | 2 +- .../jaspi/DefaultAuthConfigFactoryTest.java | 4 +- .../security/jaspi/HttpHeaderAuthModule.java | 2 +- .../jetty/ee9/security/jaspi/JaspiTest.java | 12 +- .../jetty-ee9-jspc-maven-plugin}/pom.xml | 100 +- .../src/it/package-root/invoker.properties | 0 .../src/it/package-root/pom.xml | 2 +- .../src/it/package-root/postbuild.groovy | 0 .../it/package-root/src/main/webapp/foo.jsp | 0 .../src/it/simple-jsp-fail/invoker.properties | 0 .../src/it/simple-jsp-fail/pom.xml | 2 +- .../src/it/simple-jsp-fail/postbuild.groovy | 0 .../it/simple-jsp-fail/src/main/jsp/foo.jsp | 0 .../src/it/simple-jsp/invoker.properties | 0 .../src/it/simple-jsp/pom.xml | 2 +- .../src/it/simple-jsp/postbuild.groovy | 0 .../src/it/simple-jsp/src/main/webapp/foo.jsp | 0 .../jetty/ee9/jspc/plugin/JspcMojo.java | 2 +- .../jetty/ee9}/jspc/plugin/package-info.java | 2 +- .../jetty-ee9-maven-plugin}/pom.xml | 118 +- .../src/it/it-parent-pom/invoker.properties | 0 .../src/it/it-parent-pom/pom.xml | 2 +- .../jetty-cdi-start-forked/invoker.properties | 0 .../src/it/jetty-cdi-start-forked/pom.xml | 0 .../jetty-cdi-start-forked/postbuild.groovy | 0 .../src/main/jetty/jetty-context.xml | 6 +- .../src/main/jetty/jetty.xml | 0 .../api/pom.xml | 0 .../invoker.properties | 0 .../pom.xml | 0 .../postbuild.groovy | 0 .../web/pom.xml | 0 .../web/src/config/jetty.xml | 0 .../MyLibrary/pom.xml | 0 .../MyWebApp/pom.xml | 0 .../MyWebApp}/src/config/context.xml | 2 +- .../MyWebApp/src/config/jetty.xml | 0 .../MyWebApp/src/main/webapp/index.html | 0 .../invoker.properties | 0 .../src/it/jetty-run-mojo-jar-scan-it/pom.xml | 0 .../postbuild.groovy | 0 .../invoker.properties | 0 .../jetty-simple-base/pom.xml | 2 +- .../HelloServlet.java | 0 .../PingServlet.java | 0 .../main/resources/META-INF/web-fragment.xml | 0 .../jetty-simple-webapp/pom.xml | 2 +- .../src/base/etc/test-jetty.xml | 0 .../src/base/modules/testmod.mod | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../src/it/jetty-start-distro-mojo-it/pom.xml | 2 +- .../postbuild.groovy | 0 .../invoker.properties | 0 .../jetty-simple-base/pom.xml | 2 +- .../jetty/its/jetty_start_forked/Counter.java | 0 .../its/jetty_start_forked/HelloServlet.java | 0 .../its/jetty_start_forked/PingServlet.java | 0 .../main/resources/META-INF/web-fragment.xml | 0 .../jetty-simple-webapp/pom.xml | 2 +- .../jetty-simple-webapp}/src/config/jetty.xml | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/jsp/bean1.jsp | 0 .../src/it/jetty-start-forked-mojo-it/pom.xml | 2 +- .../postbuild.groovy | 0 .../it/jetty-start-gwt-it/beer-client/pom.xml | 0 .../it/jetty-start-gwt-it/beer-server/pom.xml | 0 .../beer-server}/src/config/jetty.xml | 0 .../src/main/jettyconf/context.xml | 4 +- .../src/main/webapp/WEB-INF/web.xml | 0 .../beer-server/src/main/webapp/index.html | 0 .../it/jetty-start-gwt-it/beer-shared/pom.xml | 0 .../it/jetty-start-gwt-it/invoker.properties | 0 .../src/it/jetty-start-gwt-it/pom.xml | 0 .../it/jetty-start-gwt-it/postbuild.groovy | 0 .../it/jetty-start-mojo-it/invoker.properties | 0 .../jetty-simple-base/pom.xml | 2 +- .../its/jetty_start_mojo_it/Counter.java | 0 .../its/jetty_start_mojo_it/HelloServlet.java | 0 .../its/jetty_start_mojo_it/PingServlet.java | 0 .../main/resources/META-INF/web-fragment.xml | 0 .../jetty-simple-webapp/pom.xml | 2 +- .../src/config/context.xml | 2 +- .../jetty-simple-webapp}/src/config/jetty.xml | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/jsp/bean1.jsp | 0 .../src/it/jetty-start-mojo-it/pom.xml | 2 +- .../it/jetty-start-mojo-it/postbuild.groovy | 0 .../common/pom.xml | 0 .../invoker.properties | 0 .../module/module-api/pom.xml | 0 .../module/module-impl/pom.xml | 0 .../module/pom.xml | 0 .../pom.xml | 2 +- .../postbuild.groovy | 0 .../webapp-war/pom.xml | 0 .../webapp-war}/src/config/jetty.xml | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../invoker.properties | 0 .../jetty-simple-base/pom.xml | 2 +- .../its/jetty_run_mojo_it/HelloServlet.java | 0 .../its/jetty_run_mojo_it/PingServlet.java | 0 .../main/resources/META-INF/web-fragment.xml | 0 .../jetty-simple-webapp/pom.xml | 2 +- .../src/base/etc/test-jetty.xml | 0 .../src/base/modules/testmod.mod | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../it/jetty-start-war-distro-mojo-it/pom.xml | 2 +- .../postbuild.groovy | 0 .../invoker.properties | 0 .../jetty-simple-base/pom.xml | 2 +- .../HelloServlet.java | 0 .../PingServlet.java | 0 .../main/resources/META-INF/web-fragment.xml | 0 .../jetty-simple-webapp/pom.xml | 2 +- .../jetty-simple-webapp}/src/config/jetty.xml | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../it/jetty-start-war-forked-mojo-it/pom.xml | 2 +- .../postbuild.groovy | 0 .../invoker.properties | 0 .../src/it/jetty-start-war-mojo-it/pom.xml | 2 +- .../jetty-start-war-mojo-it/postbuild.groovy | 0 .../src/config/jetty.xml | 0 .../ee9/maven/plugin/AbstractForker.java | 2 +- .../plugin/AbstractUnassembledWebAppMojo.java | 2 +- .../ee9/maven/plugin/AbstractWebAppMojo.java | 8 +- .../jetty/ee9/maven/plugin/ConsoleReader.java | 2 +- .../maven/plugin/JettyEffectiveWebXml.java | 2 +- .../jetty/ee9/maven/plugin/JettyEmbedder.java | 8 +- .../ee9/maven/plugin/JettyForkedChild.java | 2 +- .../jetty/ee9/maven/plugin/JettyForker.java | 2 +- .../ee9/maven/plugin/JettyHomeForker.java | 2 +- .../jetty/ee9/maven/plugin/JettyRunMojo.java | 4 +- .../ee9/maven/plugin/JettyRunWarMojo.java | 2 +- .../ee9/maven/plugin/JettyStartMojo.java | 2 +- .../ee9/maven/plugin/JettyStartWarMojo.java | 2 +- .../jetty/ee9/maven/plugin/JettyStopMojo.java | 2 +- .../plugin/MavenMetaInfConfiguration.java | 8 +- .../plugin/MavenQuickStartConfiguration.java | 8 +- .../maven/plugin/MavenServerConnector.java | 2 +- .../ee9/maven/plugin/MavenWebAppContext.java | 24 +- .../plugin/MavenWebInfConfiguration.java | 10 +- .../jetty/ee9/maven/plugin/Overlay.java | 2 +- .../jetty/ee9/maven/plugin/OverlayConfig.java | 2 +- .../ee9/maven/plugin/OverlayManager.java | 2 +- .../jetty/ee9/maven/plugin/PluginLog.java | 2 +- .../ee9/maven/plugin/QuickStartGenerator.java | 8 +- .../jetty/ee9/maven/plugin/ScanPattern.java | 2 +- .../ee9/maven/plugin/ScanTargetPattern.java | 2 +- .../maven/plugin/SelectiveJarResource.java | 2 +- .../maven/plugin/ServerConnectorListener.java | 2 +- .../ee9/maven/plugin/ServerListener.java | 2 +- .../jetty/ee9/maven/plugin/ServerSupport.java | 8 +- .../jetty/ee9/maven/plugin/WarPluginInfo.java | 2 +- .../maven/plugin/WebAppPropertyConverter.java | 4 +- .../jetty/ee9}/maven/plugin/package-info.java | 2 +- .../plugin/utils/MavenProjectHelper.java | 6 +- .../ee9/maven/plugin/MockShutdownMonitor.java | 2 +- .../plugin/MockShutdownMonitorRunnable.java | 2 +- .../ee9/maven/plugin/TestForkedChild.java | 2 +- .../ee9/maven/plugin/TestJettyEmbedder.java | 5 +- .../ee9/maven/plugin/TestJettyStopMojo.java | 3 +- .../maven/plugin/TestQuickStartGenerator.java | 2 +- .../plugin/TestSelectiveJarResource.java | 2 +- .../plugin/TestWebAppPropertyConverter.java | 4 +- .../plugin/it/IntegrationTestGetContent.java | 3 +- .../src/test/resources/embedder-context.xml | 2 +- .../src/test/resources/root/index.html | 0 jetty-ee9/jetty-ee9-nested/pom.xml | 90 + .../src/main/assembly/site-component.xml | 15 + .../src/main/java/module-info.java | 28 + .../jetty/ee9/nested/AbstractHandler.java | 149 + .../ee9/nested/AbstractHandlerContainer.java | 128 + .../ee9/nested/AllowSymLinkAliasChecker.java | 95 + .../nested/AllowedResourceAliasChecker.java | 237 + .../jetty/ee9/nested}/AsyncAttributes.java | 9 +- .../ee9/nested}/AsyncContentProducer.java | 168 +- .../jetty/ee9/nested}/AsyncContextEvent.java | 11 +- .../jetty/ee9/nested}/AsyncContextState.java | 5 +- .../jetty/ee9/nested}/AsyncDelayHandler.java | 3 +- .../jetty/ee9/nested}/Authentication.java | 2 +- .../ee9/nested}/BlockingContentProducer.java | 19 +- .../ee9/nested/BufferedResponseHandler.java | 325 + .../ee9/nested/CachedContentFactory.java | 652 + .../jetty/ee9/nested}/ContentProducer.java | 46 +- .../jetty/ee9/nested/ContextHandler.java | 2566 + .../eclipse/jetty/ee9/nested}/Cookies.java | 2 +- .../jetty/ee9/nested/DebugHandler.java | 166 + .../jetty/ee9/nested}/DebugListener.java | 11 +- .../eclipse/jetty/ee9/nested}/Dispatcher.java | 46 +- .../jetty/ee9/nested}/EncodingHttpWriter.java | 2 +- .../jetty/ee9/nested}/ErrorHandler.java | 28 +- .../nested/FileBufferedResponseHandler.java | 233 + .../org/eclipse/jetty/ee9/nested/Handler.java | 71 + .../jetty/ee9/nested}/HandlerCollection.java | 5 +- .../jetty/ee9/nested/HandlerContainer.java | 54 + .../eclipse/jetty/ee9/nested/HandlerList.java | 55 + .../jetty/ee9/nested/HandlerWrapper.java | 141 + .../jetty/ee9/nested/HotSwapHandler.java | 120 + .../eclipse/jetty/ee9/nested/HttpChannel.java | 1556 + .../ee9/nested/HttpChannelListeners.java | 282 + .../jetty/ee9/nested/HttpChannelState.java | 1388 + .../ee9/nested}/HttpChannelState_input.puml | 0 .../eclipse/jetty/ee9/nested}/HttpInput.java | 374 +- .../jetty/ee9/nested}/HttpInputState.puml | 0 .../jetty/ee9/nested}/HttpInput_async.puml | 0 .../jetty/ee9/nested}/HttpInput_blocking.puml | 0 .../eclipse/jetty/ee9/nested}/HttpOutput.java | 41 +- .../eclipse/jetty/ee9/nested}/HttpWriter.java | 2 +- .../jetty/ee9/nested/IdleTimeoutHandler.java | 114 + .../jetty/ee9/nested/InclusiveByteRange.java | 264 + .../jetty/ee9/nested/InetAccessHandler.java | 248 + .../jetty/ee9/nested/InetAccessSet.java | 153 + .../jetty/ee9/nested}/Iso88591HttpWriter.java | 2 +- .../ee9/nested/ManagedAttributeListener.java | 97 + .../ee9/nested}/MultiPartFormInputStream.java | 2 +- .../jetty/ee9/nested/MultiPartParser.java | 712 + .../jetty/ee9/nested}/PushBuilderImpl.java | 4 +- .../ee9/nested}/QuietServletException.java | 2 +- .../org/eclipse/jetty/ee9/nested/Request.java | 2199 + .../ee9/nested/ResourceContentFactory.java | 106 + .../jetty/ee9/nested/ResourceHandler.java | 430 + .../jetty/ee9/nested/ResourceService.java | 902 + .../eclipse/jetty/ee9/nested/Response.java | 1502 + .../jetty/ee9/nested}/ResponseWriter.java | 2 +- .../ee9/nested}/SameFileAliasChecker.java | 7 +- .../jetty/ee9/nested}/ScopedHandler.java | 30 +- .../ee9/nested/SecuredRedirectHandler.java | 93 + .../jetty/ee9/nested}/ServletAttributes.java | 17 +- .../jetty/ee9/nested}/ServletPathMapping.java | 8 +- .../nested}/ServletRequestHttpWrapper.java | 2 +- .../nested}/ServletResponseHttpWrapper.java | 2 +- .../jetty/ee9/nested/SessionHandler.java | 799 + .../jetty/ee9/nested/ShutdownHandler.java | 265 + .../jetty/ee9/nested/StatisticsHandler.java | 611 + .../SymlinkAllowedResourceAliasChecker.java | 88 + .../jetty/ee9/nested/ThreadLimitHandler.java | 431 + .../jetty/ee9/nested}/UserIdentity.java | 4 +- .../jetty/ee9/nested}/Utf8HttpWriter.java | 2 +- .../ee9/nested}/jmx/AbstractHandlerMBean.java | 20 +- .../ee9/nested/jmx/ContextHandlerMBean.java | 67 + .../jetty/ee9/nested/jmx/package-info.java | 18 + .../jetty/ee9/nested/package-info.java | 18 + .../resource/ByteBufferRangeWriter.java | 59 + .../resource/HttpContentRangeWriter.java | 39 +- .../resource/InputStreamRangeWriter.java | 120 + .../ee9/nested/resource/RangeWriter.java | 33 + .../SeekableByteChannelRangeWriter.java | 161 + .../src/main/resources/jetty-dir.css | 49 + .../resources/org/eclipse/nested/favicon.ico | Bin 0 -> 1150 bytes .../jetty/ee9/nested/AbstractHttpTest.java | 133 + .../ee9/nested}/AsyncCompletionTest.java | 80 +- .../ee9/nested}/AsyncContentProducerTest.java | 311 +- .../ee9/nested}/AsyncRequestReadTest.java | 58 +- .../nested}/BlockingContentProducerTest.java | 224 +- .../jetty/ee9/nested}/BlockingTest.java | 25 +- .../jetty/ee9/nested/ContextHandlerTest.java | 582 + .../jetty/ee9/nested}/CookiesTest.java | 2 +- .../eclipse/jetty/ee9/nested/DumpHandler.java | 262 + .../jetty/ee9/nested}/ErrorHandlerTest.java | 21 +- .../HttpManyWaysToAsyncCommitTest.java | 33 +- .../ee9/nested}/HttpManyWaysToCommitTest.java | 26 +- .../jetty/ee9/nested}/HttpOutputTest.java | 34 +- .../ee9/nested/HttpServerTestFixture.java | 257 + .../jetty/ee9/nested}/HttpWriterTest.java | 23 +- .../ee9/nested}/LocalAsyncContextTest.java | 29 +- .../ee9/nested/MockConnectionMetaData.java | 132 + .../jetty/ee9/nested/MockConnector.java | 66 + .../eclipse/jetty/ee9/nested/RequestTest.java | 2268 + .../jetty/ee9/nested}/ResponseTest.java | 599 +- .../jetty/ee9/nested}/ScopedHandlerTest.java | 14 +- .../ServerConnectorAsyncContextTest.java | 10 +- .../nested}/ServletRequestWrapperTest.java | 30 +- .../jetty/ee9/nested}/ServletWriterTest.java | 27 +- .../jetty/ee9/nested/SessionHandlerTest.java | 373 + .../jetty/ee9/nested}/SuspendHandler.java | 3 +- .../src/test/resources/example.jar | Bin 0 -> 155993 bytes .../src/test/resources/fakeRequests.txt | 27 + .../test/resources/jetty-logging.properties | 8 + .../src/test/resources/keystore.p12 | Bin 0 -> 2613 bytes .../src/test/resources/keystore_sni.p12 | Bin 0 -> 10672 bytes .../test/resources/keystore_sni_key_types.p12 | Bin 0 -> 3348 bytes .../test/resources/keystore_sni_nowild.p12 | Bin 0 -> 4637 bytes ...ny-urlencoded-apache-httpcomp.expected.txt | 9 + ...ure-company-urlencoded-apache-httpcomp.raw | 7 + ...pture-complex-apache-httpcomp.expected.txt | 15 + ...rowser-capture-complex-apache-httpcomp.raw | Bin 0 -> 22940 bytes ...-capture-complex-jetty-client.expected.txt | 15 + .../browser-capture-complex-jetty-client.raw | Bin 0 -> 22754 bytes ...plicate-names-apache-httpcomp.expected.txt | 8 + ...apture-duplicate-names-apache-httpcomp.raw | Bin 0 -> 1815 bytes ...-duplicate-names-jetty-client.expected.txt | 8 + ...r-capture-duplicate-names-jetty-client.raw | 51 + ...encoding-mess-apache-httpcomp.expected.txt | 11 + ...-capture-encoding-mess-apache-httpcomp.raw | 1015 + ...re-encoding-mess-jetty-client.expected.txt | 11 + ...ser-capture-encoding-mess-jetty-client.raw | 846 + ...re-form-fileupload-alt-chrome.expected.txt | 21 + ...ser-capture-form-fileupload-alt-chrome.raw | Bin 0 -> 22759 bytes ...ture-form-fileupload-alt-edge.expected.txt | 17 + ...owser-capture-form-fileupload-alt-edge.raw | Bin 0 -> 22824 bytes ...e-form-fileupload-alt-firefox.expected.txt | 17 + ...er-capture-form-fileupload-alt-firefox.raw | Bin 0 -> 22774 bytes ...ture-form-fileupload-alt-msie.expected.txt | 17 + ...owser-capture-form-fileupload-alt-msie.raw | Bin 0 -> 22814 bytes ...re-form-fileupload-alt-safari.expected.txt | 18 + ...ser-capture-form-fileupload-alt-safari.raw | Bin 0 -> 22774 bytes ...orm-fileupload-android-chrome.expected.txt | 17 + ...capture-form-fileupload-android-chrome.raw | Bin 0 -> 22054 bytes ...rm-fileupload-android-firefox.expected.txt | 14 + ...apture-form-fileupload-android-firefox.raw | Bin 0 -> 22105 bytes ...apture-form-fileupload-chrome.expected.txt | 18 + ...browser-capture-form-fileupload-chrome.raw | Bin 0 -> 22054 bytes ...-capture-form-fileupload-edge.expected.txt | 14 + .../browser-capture-form-fileupload-edge.raw | Bin 0 -> 22085 bytes ...pture-form-fileupload-firefox.expected.txt | 14 + ...rowser-capture-form-fileupload-firefox.raw | Bin 0 -> 22063 bytes ...re-form-fileupload-ios-safari.expected.txt | 15 + ...ser-capture-form-fileupload-ios-safari.raw | Bin 0 -> 22074 bytes ...-capture-form-fileupload-msie.expected.txt | 14 + .../browser-capture-form-fileupload-msie.raw | Bin 0 -> 22082 bytes ...apture-form-fileupload-safari.expected.txt | 15 + ...browser-capture-form-fileupload-safari.raw | Bin 0 -> 22054 bytes ...-capture-form1-android-chrome.expected.txt | 16 + .../browser-capture-form1-android-chrome.raw | 9 + ...capture-form1-android-firefox.expected.txt | 13 + .../browser-capture-form1-android-firefox.raw | 9 + .../browser-capture-form1-chrome.expected.txt | 17 + .../browser-capture-form1-chrome.raw | 9 + .../browser-capture-form1-edge.expected.txt | 13 + .../multipart/browser-capture-form1-edge.raw | 9 + ...browser-capture-form1-firefox.expected.txt | 13 + .../browser-capture-form1-firefox.raw | 9 + ...wser-capture-form1-ios-safari.expected.txt | 14 + .../browser-capture-form1-ios-safari.raw | 9 + .../browser-capture-form1-msie.expected.txt | 13 + .../multipart/browser-capture-form1-msie.raw | 9 + ...wser-capture-form1-osx-safari.expected.txt | 14 + .../browser-capture-form1-osx-safari.raw | 9 + ...apture-nested-apache-httpcomp.expected.txt | 12 + ...browser-capture-nested-apache-httpcomp.raw | 42 + ...nested-binary-apache-httpcomp.expected.txt | 12 + ...-capture-nested-binary-apache-httpcomp.raw | 50 + ...r-capture-nested-jetty-client.expected.txt | 12 + .../browser-capture-nested-jetty-client.raw | 42 + ...e-number-only-apache-httpcomp.expected.txt | 9 + ...er-capture-number-only-apache-httpcomp.raw | 5 + ...ture-number-only-jetty-client.expected.txt | 12 + ...owser-capture-number-only-jetty-client.raw | 6 + ...-number-only2-apache-httpcomp.expected.txt | 9 + ...r-capture-number-only2-apache-httpcomp.raw | 7 + ...-capture-sjis-apache-httpcomp.expected.txt | 10 + .../browser-capture-sjis-apache-httpcomp.raw | 13 + ...s-charset-form-android-chrome.expected.txt | 17 + ...pture-sjis-charset-form-android-chrome.raw | 13 + ...-charset-form-android-firefox.expected.txt | 14 + ...ture-sjis-charset-form-android-firefox.raw | 13 + ...ture-sjis-charset-form-chrome.expected.txt | 18 + ...owser-capture-sjis-charset-form-chrome.raw | 13 + ...apture-sjis-charset-form-edge.expected.txt | 14 + ...browser-capture-sjis-charset-form-edge.raw | 13 + ...ure-sjis-charset-form-firefox.expected.txt | 14 + ...wser-capture-sjis-charset-form-firefox.raw | 13 + ...-sjis-charset-form-ios-safari.expected.txt | 15 + ...r-capture-sjis-charset-form-ios-safari.raw | 13 + ...apture-sjis-charset-form-msie.expected.txt | 14 + ...browser-capture-sjis-charset-form-msie.raw | 13 + ...ture-sjis-charset-form-safari.expected.txt | 15 + ...owser-capture-sjis-charset-form-safari.raw | 13 + ...ture-sjis-form-android-chrome.expected.txt | 16 + ...owser-capture-sjis-form-android-chrome.raw | 9 + ...ure-sjis-form-android-firefox.expected.txt | 13 + ...wser-capture-sjis-form-android-firefox.raw | 9 + ...wser-capture-sjis-form-chrome.expected.txt | 17 + .../browser-capture-sjis-form-chrome.raw | 9 + ...rowser-capture-sjis-form-edge.expected.txt | 13 + .../browser-capture-sjis-form-edge.raw | 9 + ...ser-capture-sjis-form-firefox.expected.txt | 13 + .../browser-capture-sjis-form-firefox.raw | 9 + ...-capture-sjis-form-ios-safari.expected.txt | 14 + .../browser-capture-sjis-form-ios-safari.raw | 9 + ...rowser-capture-sjis-form-msie.expected.txt | 13 + .../browser-capture-sjis-form-msie.raw | 9 + ...wser-capture-sjis-form-safari.expected.txt | 14 + .../browser-capture-sjis-form-safari.raw | 9 + ...ser-capture-sjis-jetty-client.expected.txt | 10 + .../browser-capture-sjis-jetty-client.raw | 11 + ...range-quoting-apache-httpcomp.expected.txt | 11 + ...apture-strange-quoting-apache-httpcomp.raw | 25 + ...re-text-files-apache-httpcomp.expected.txt | 15 + ...ser-capture-text-files-apache-httpcomp.raw | 23 + ...pture-text-files-jetty-client.expected.txt | 15 + ...rowser-capture-text-files-jetty-client.raw | 20 + ...unicode-names-apache-httpcomp.expected.txt | 11 + ...-capture-unicode-names-apache-httpcomp.raw | 13 + ...re-unicode-names-jetty-client.expected.txt | 11 + ...ser-capture-unicode-names-jetty-client.raw | 11 + ...-whitespace-only-jetty-client.expected.txt | 10 + ...r-capture-whitespace-only-jetty-client.raw | 209748 +++++++++++++++ ...go-text-plain-apache-httpcomp.expected.txt | 12 + ...pture-zalgo-text-plain-apache-httpcomp.raw | Bin 0 -> 1870 bytes .../multipart-base64-long.expected.txt | 4 + .../multipart/multipart-base64-long.raw | 8 + .../multipart/multipart-base64.expected.txt | 4 + .../resources/multipart/multipart-base64.raw | 389 + .../multipart-uppercase.expected.txt | 5 + .../multipart/multipart-uppercase.raw | 13 + .../src/test/resources/reload_keystore_1.p12 | Bin 0 -> 2581 bytes .../src/test/resources/reload_keystore_2.p12 | Bin 0 -> 2581 bytes .../src/test/resources/simple/big.txt | 400 + .../resources/simple/directory/content.txt | 1 + .../resources/simple/directory/welcome.txt | 1 + .../src/test/resources/simple/simple.txt | 1 + .../jetty-ee9-openid}/pom.xml | 18 +- .../src/main/config/etc/jetty-openid.xml | 2 +- .../src/main/java/module-info.java | 6 +- .../jetty/ee9/security/openid/JwtDecoder.java | 2 +- .../openid/OpenIdAuthConfiguration.java | 12 +- .../security/openid/OpenIdAuthenticator.java | 27 +- .../openid/OpenIdAuthenticatorFactory.java | 8 +- .../security/openid/OpenIdConfiguration.java | 2 +- .../security/openid/OpenIdCredentials.java | 2 +- .../security/openid/OpenIdLoginService.java | 8 +- .../security/openid/OpenIdUserIdentity.java | 4 +- .../security/openid/OpenIdUserPrincipal.java | 2 +- ...e.jetty.ee9.security.Authenticator$Factory | 1 + ...lipse.jetty.security.Authenticator$Factory | 2 +- .../ee9/security/openid/JwtDecoderTest.java | 2 +- .../jetty/ee9/security/openid/JwtEncoder.java | 2 +- .../openid/OpenIdAuthenticationTest.java | 8 +- .../openid/OpenIdCredentialsTest.java | 2 +- .../ee9/security/openid/OpenIdProvider.java | 10 +- .../security/openid/OpenIdReamNameTest.java | 22 +- .../jetty-ee9-osgi-alpn}/pom.xml | 10 +- .../jetty-ee9-osgi-boot-jsp}/pom.xml | 27 +- .../jasper/ContainerTldBundleDiscoverer.java | 16 +- .../boot/jasper/JSTLBundleDiscoverer.java | 8 +- .../ee9/osgi/boot/jsp/FragmentActivator.java | 8 +- .../jetty-ee9-osgi-boot-warurl}/pom.xml | 14 +- .../ee9/osgi/boot/warurl/WarUrlActivator.java | 2 +- .../osgi/boot/warurl/WarUrlStreamHandler.java | 6 +- .../internal/WarBundleManifestGenerator.java | 2 +- .../warurl/internal/WarURLConnection.java | 2 +- .../jettyhome/contexts/README | 0 .../jetty-ee9-osgi-boot}/jettyhome/etc/README | 0 .../jettyhome/etc/jetty-deploy.xml | 0 .../jettyhome/etc/jetty-http.xml | 0 .../jettyhome/etc/jetty.xml | 26 +- .../jettyhome/lib/ext/README | 0 .../jettyhome/logs/README | 0 .../jettyhome/resources/README | 0 .../jettyhome/webapps/README | 0 .../jetty-ee9-osgi-boot}/pom.xml | 26 +- .../annotations/AnnotationConfiguration.java | 26 +- .../osgi/annotations/AnnotationParser.java | 6 +- .../osgi/boot/AbstractContextProvider.java | 8 +- .../jetty/ee9/osgi/boot/AbstractOSGiApp.java | 2 +- .../ee9/osgi/boot/AbstractWebAppProvider.java | 12 +- .../ee9/osgi/boot/BundleContextProvider.java | 4 +- .../jetty/ee9/osgi/boot/BundleProvider.java | 2 +- .../ee9/osgi/boot/BundleWebAppProvider.java | 6 +- .../osgi/boot/JettyBootstrapActivator.java | 8 +- .../jetty/ee9/osgi/boot/OSGiDeployer.java | 6 +- .../osgi/boot/OSGiMetaInfConfiguration.java | 28 +- .../ee9/osgi/boot/OSGiServerConstants.java | 4 +- .../jetty/ee9/osgi/boot/OSGiUndeployer.java | 6 +- .../osgi/boot/OSGiWebInfConfiguration.java | 6 +- .../ee9/osgi/boot/OSGiWebappConstants.java | 2 +- .../ee9/osgi/boot/ServiceContextProvider.java | 8 +- .../jetty/ee9/osgi/boot/ServiceProvider.java | 2 +- .../ee9/osgi/boot/ServiceWebAppProvider.java | 8 +- .../DefaultJettyAtJettyHomeHelper.java | 12 +- .../JettyServerServiceTracker.java | 4 +- .../serverfactory/ServerInstanceWrapper.java | 28 +- .../webapp/LibExtClassLoaderHelper.java | 2 +- .../webapp/OSGiWebappClassLoader.java | 10 +- .../boot/utils/BundleClassLoaderHelper.java | 6 +- .../utils/BundleClassLoaderHelperFactory.java | 2 +- .../boot/utils/BundleFileLocatorHelper.java | 6 +- .../utils/BundleFileLocatorHelperFactory.java | 2 +- .../ee9/osgi/boot/utils/EventSender.java | 2 +- .../osgi/boot/utils/FakeURLClassLoader.java | 2 +- .../ee9/osgi/boot/utils/OSGiClassLoader.java | 2 +- .../boot/utils/ServerConnectorListener.java | 2 +- .../osgi/boot/utils/TldBundleDiscoverer.java | 2 +- .../jetty/ee9/osgi/boot/utils/Util.java | 4 +- .../DefaultBundleClassLoaderHelper.java | 4 +- .../internal/DefaultFileLocatorHelper.java | 4 +- .../internal/PackageAdminServiceTracker.java | 4 +- .../org.eclipse.jetty.webapp.Configuration | 4 +- .../contexts/httpservice.xml | 4 +- .../jetty-ee9-osgi-httpservice}/pom.xml | 18 +- .../HttpServiceErrorHandlerHelper.java | 2 +- .../HttpServiceErrorPageErrorHandler.java | 4 +- .../jetty-ee9-osgi}/pom.xml | 44 +- .../test-jetty-ee9-osgi-context}/pom.xml | 16 +- .../main/java/com/acme/osgi/Activator.java | 0 .../src/main/resources/static/index.html | 0 .../test-jetty-ee9-osgi-fragment}/pom.xml | 10 +- .../test-jetty-ee9-osgi-server}/pom.xml | 14 +- .../main/java/com/acme/osgi/Activator.java | 12 +- .../src/main/resources/index.html | 0 .../pom.xml | 8 +- .../test-jetty-ee9-osgi-webapp}/pom.xml | 16 +- .../main/java/com/acme/osgi/Activator.java | 4 +- .../src/main/resources/index.html | 0 .../src/main/resources/webappA/index.html | 0 .../src/main/resources/webappB/index.html | 0 .../test-jetty-ee9-osgi/README.txt | 130 +- .../test-jetty-ee9-osgi}/pom.xml | 110 +- .../src/test/config/etc/jetty-deploy.xml | 0 .../jetty-http-boot-context-as-service.xml | 2 +- .../etc/jetty-http-boot-webapp-as-service.xml | 2 +- .../etc/jetty-http-boot-with-annotations.xml | 2 +- .../etc/jetty-http-boot-with-bundle.xml | 2 +- ...jetty-http-boot-with-jakarta-websocket.xml | 2 +- .../config/etc/jetty-http-boot-with-jsp.xml | 2 +- .../etc/jetty-http-boot-with-resources.xml | 2 +- .../etc/jetty-http-boot-with-websocket.xml | 2 +- .../src/test/config/etc/jetty-http.xml | 2 +- .../src/test/config/etc/jetty-https.xml | 2 +- .../config/etc/jetty-with-custom-class.xml | 28 +- .../src/test/config/etc/jetty.xml | 26 +- .../src/test/config/etc/realm.properties | 2 +- .../src/test/config/etc/webdefault.xml | 12 +- .../jetty/ee9/osgi/test/SimpleEchoSocket.java | 14 +- .../ee9/osgi/test/SimpleJakartaWebSocket.java | 2 +- .../jetty/ee9/osgi/test/SomeCustomBean.java | 2 +- .../test/TestJettyOSGiAnnotationParser.java | 6 +- .../TestJettyOSGiBootContextAsService.java | 8 +- .../test/TestJettyOSGiBootHTTP2Conscrypt.java | 4 +- .../osgi/test/TestJettyOSGiBootHTTP2JDK9.java | 4 +- .../TestJettyOSGiBootWebAppAsService.java | 10 +- .../TestJettyOSGiBootWithAnnotations.java | 6 +- .../test/TestJettyOSGiBootWithBundle.java | 4 +- ...TestJettyOSGiBootWithJakartaWebSocket.java | 2 +- .../osgi/test/TestJettyOSGiBootWithJsp.java | 5 +- .../test/TestJettyOSGiBootWithWebSocket.java | 6 +- .../test/TestJettyOSGiClasspathResources.java | 16 +- .../jetty/ee9/osgi/test/TestOSGiUtil.java | 9 +- .../src/test/resources/module-info.java | 0 .../jetty-ee9-plus}/pom.xml | 16 +- .../src/main/config/etc/jetty-plus.xml | 8 +- .../src/main/java/module-info.java | 21 +- .../plus/annotation/ContainerInitializer.java | 4 +- .../jetty/ee9/plus/annotation/Injection.java | 2 +- .../plus/annotation/InjectionCollection.java | 2 +- .../plus/annotation/LifeCycleCallback.java | 2 +- .../LifeCycleCallbackCollection.java | 2 +- .../annotation/PostConstructCallback.java | 4 +- .../plus/annotation/PreDestroyCallback.java | 4 +- .../jetty/ee9/plus/annotation/RunAs.java | 4 +- .../ee9/plus/annotation/RunAsCollection.java | 4 +- .../ee9}/plus/annotation/package-info.java | 2 +- .../eclipse/jetty/ee9/plus/jndi/EnvEntry.java | 2 +- .../org/eclipse/jetty/ee9/plus/jndi/Link.java | 2 +- .../jetty/ee9/plus/jndi/NamingDump.java | 2 +- .../jetty/ee9/plus/jndi/NamingEntry.java | 2 +- .../jetty/ee9/plus/jndi/NamingEntryUtil.java | 2 +- .../eclipse/jetty/ee9/plus/jndi/Resource.java | 2 +- .../jetty/ee9/plus/jndi/Transaction.java | 2 +- .../jetty/ee9}/plus/jndi/package-info.java | 2 +- .../plus/security/DataSourceLoginService.java | 12 +- .../ee9}/plus/security/package-info.java | 2 +- .../ee9/plus/webapp/EnvConfiguration.java | 22 +- .../ee9/plus/webapp/PlusConfiguration.java | 20 +- .../jetty/ee9/plus/webapp/PlusDecorator.java | 8 +- .../plus/webapp/PlusDescriptorProcessor.java | 32 +- .../jetty/ee9}/plus/webapp/package-info.java | 2 +- ...org.eclipse.jetty.ee9.webapp.Configuration | 2 + .../LifeCycleCallbackCollectionTest.java | 6 +- .../ee9/plus/jndi/NamingEntryUtilTest.java | 2 +- .../ee9/plus/jndi/TestNamingEntries.java | 2 +- .../ee9/plus/jndi/TestNamingEntryUtil.java | 2 +- .../webapp/PlusDescriptorProcessorTest.java | 35 +- .../jetty-ee9-plus/src/test/resources/web.xml | 10 +- .../jetty-ee9-proxy}/pom.xml | 16 +- .../src/main/config/etc/jetty-proxy.xml | 2 +- .../src/main/java/module-info.java | 5 +- .../jetty/ee9/proxy/AbstractProxyServlet.java | 2 +- .../ee9/proxy/AfterContentTransformer.java | 2 +- .../ee9/proxy/AsyncMiddleManServlet.java | 2 +- .../jetty/ee9/proxy/AsyncProxyServlet.java | 2 +- .../jetty/ee9/proxy/BalancerServlet.java | 2 +- .../jetty/ee9/proxy/ConnectHandler.java | 6 +- .../jetty/ee9/proxy/ProxyConnection.java | 2 +- .../eclipse/jetty/ee9/proxy/ProxyServlet.java | 2 +- .../jetty/ee9}/proxy/package-info.java | 2 +- .../ee9/proxy/AbstractConnectHandlerTest.java | 2 +- .../ee9/proxy/AsyncMiddleManServletTest.java | 10 +- .../jetty/ee9/proxy/BalancerServletTest.java | 8 +- .../jetty/ee9/proxy/CachingProxyServlet.java | 2 +- .../jetty/ee9/proxy/ClientAuthProxyTest.java | 6 +- .../ee9/proxy/ConnectHandlerSSLTest.java | 2 +- .../jetty/ee9/proxy/ConnectHandlerTest.java | 2 +- .../jetty/ee9/proxy/EchoHttpServlet.java | 2 +- .../jetty/ee9/proxy/EmptyHttpServlet.java | 2 +- .../jetty/ee9/proxy/EmptyServerHandler.java | 2 +- .../ee9/proxy/ForwardProxyServerTest.java | 6 +- .../ee9/proxy/ForwardProxyTLSServerTest.java | 4 +- .../eclipse/jetty/ee9/proxy/ProxyServer.java | 6 +- .../ee9/proxy/ProxyServletFailureTest.java | 12 +- .../jetty/ee9/proxy/ProxyServletLoadTest.java | 6 +- .../jetty/ee9/proxy/ProxyServletTest.java | 8 +- .../jetty/ee9/proxy/ReverseProxyTest.java | 6 +- .../resources/client_auth/client_keystore.p12 | Bin .../resources/client_auth/proxy_keystore.p12 | Bin .../resources/client_auth/server_keystore.p12 | Bin .../src/test/resources/client_keystore.p12 | Bin .../src/test/resources/proxy_keystore.p12 | Bin .../src/test/resources/server_keystore.p12 | Bin .../jetty-ee9-quickstart}/pom.xml | 24 +- .../jetty-quickstart.d/quickstart-webapp.xml | 2 +- .../src/main/java/module-info.java | 6 +- .../ee9/quickstart/AttributeNormalizer.java | 2 +- .../ExtraXmlDescriptorProcessor.java | 8 +- .../quickstart/PreconfigureQuickStartWar.java | 12 +- .../quickstart/QuickStartConfiguration.java | 30 +- .../QuickStartDescriptorProcessor.java | 22 +- .../QuickStartGeneratorConfiguration.java | 50 +- .../ee9/quickstart/FooContextListener.java | 2 +- .../jetty/ee9/quickstart/FooFilter.java | 2 +- .../jetty/ee9/quickstart/FooServlet.java | 2 +- .../jetty/ee9/quickstart/TestQuickStart.java | 10 +- .../src/test/resources/context.xml | 8 +- .../jetty-ee9-runner}/pom.xml | 36 +- .../invoker.properties | 0 .../pom.xml | 4 +- .../invoker.properties | 0 .../src/it/demo-simple-webapp-runner/pom.xml | 4 +- .../it/test-jar-manifest/invoker.properties | 0 .../src/it/test-jar-manifest/pom.xml | 0 .../org/eclipse/jetty/ee9/runner/Runner.java | 42 +- .../jetty/ee9}/runner/package-info.java | 2 +- .../it/IntegrationTestJettyRunner.java | 2 +- .../jetty-ee9-security}/pom.xml | 16 +- .../src/main/java/module-info.java | 11 +- .../ee9/security/AbstractLoginService.java | 4 +- .../security/AbstractUserAuthentication.java | 12 +- .../jetty/ee9/security/Authenticator.java | 12 +- .../ConfigurableSpnegoLoginService.java | 6 +- .../jetty/ee9/security/ConstraintAware.java | 2 +- .../jetty/ee9/security/ConstraintMapping.java | 2 +- .../security/ConstraintSecurityHandler.java | 12 +- .../security/DefaultAuthenticatorFactory.java | 37 +- .../ee9/security/DefaultIdentityService.java | 10 +- .../ee9/security/DefaultUserIdentity.java | 4 +- .../jetty/ee9/security/EmptyLoginService.java | 4 +- .../jetty/ee9/security/HashLoginService.java | 2 +- .../jetty/ee9/security/IdentityService.java | 7 +- .../jetty/ee9/security/JDBCLoginService.java | 2 +- .../ee9/security/LoggedOutAuthentication.java | 8 +- .../jetty/ee9/security/LoginService.java | 4 +- .../jetty/ee9/security/PropertyUserStore.java | 2 +- .../eclipse/jetty/ee9/security/RoleInfo.java | 2 +- .../jetty/ee9/security/RolePrincipal.java | 2 +- .../jetty/ee9/security/RoleRunAsToken.java | 2 +- .../jetty/ee9/security/RunAsToken.java | 2 +- .../jetty/ee9/security/SecurityHandler.java | 26 +- .../ee9/security/ServerAuthException.java | 2 +- .../ee9/security/SpnegoUserIdentity.java | 4 +- .../ee9/security/SpnegoUserPrincipal.java | 2 +- .../ee9/security/UserAuthentication.java | 4 +- .../ee9/security/UserDataConstraint.java | 2 +- .../jetty/ee9/security/UserPrincipal.java | 2 +- .../eclipse/jetty/ee9/security/UserStore.java | 2 +- .../security/WrappedAuthConfiguration.java | 4 +- .../authentication/AuthorizationService.java | 6 +- .../authentication/BasicAuthenticator.java | 12 +- .../ClientCertAuthenticator.java | 12 +- .../ConfigurableSpnegoAuthenticator.java | 21 +- .../DeferredAuthentication.java | 18 +- .../authentication/DigestAuthenticator.java | 16 +- .../authentication/FormAuthenticator.java | 16 +- .../authentication/LoginAuthenticator.java | 37 +- .../authentication/LoginCallback.java | 2 +- .../authentication/LoginCallbackImpl.java | 4 +- .../authentication/SessionAuthentication.java | 10 +- .../SslClientCertAuthenticator.java | 18 +- .../security/authentication/package-info.java | 2 +- .../jetty/ee9}/security/package-info.java | 2 +- .../ee9/security/AliasedConstraintTest.java | 17 +- .../security/ClientCertAuthenticatorTest.java | 11 +- .../jetty/ee9/security/ConstraintTest.java | 22 +- .../ee9/security/DataConstraintsTest.java | 36 +- .../security/DefaultIdentityServiceTest.java | 8 +- .../ee9/security/HashLoginServiceTest.java | 2 +- .../ee9/security/PropertyUserStoreTest.java | 2 +- .../security/SpecExampleConstraintTest.java | 20 +- .../jetty/ee9/security/TestLoginService.java | 2 +- .../ee9/security/UnauthenticatedTest.java | 16 +- .../jetty/ee9/security/UserStoreTest.java | 2 +- .../SpnegoAuthenticatorTest.java | 29 +- .../src/test/resources/docroot/all/index.txt | 0 .../test/resources/docroot/forbid/index.txt | 0 .../src/test/resources/realm.properties | 2 +- .../jetty-ee9-servlet}/pom.xml | 21 +- .../src/main/java/module-info.java | 12 +- .../eclipse/jetty/ee9/servlet/BaseHolder.java | 16 +- .../jetty/ee9/servlet/DecoratingListener.java | 2 +- .../jetty/ee9/servlet/DefaultServlet.java | 24 +- .../ee9/servlet/ErrorPageErrorHandler.java | 24 +- .../jetty/ee9/servlet/FilterHolder.java | 4 +- .../jetty/ee9/servlet/FilterMapping.java | 2 +- .../org/eclipse/jetty/ee9/servlet/Holder.java | 2 +- .../eclipse/jetty/ee9/servlet/Invoker.java | 14 +- .../ee9/servlet/JspPropertyGroupServlet.java | 6 +- .../jetty/ee9/servlet/ListenerHolder.java | 4 +- .../jetty/ee9/servlet/NoJspServlet.java | 2 +- .../ServletContainerInitializerHolder.java | 6 +- .../ee9/servlet/ServletContextHandler.java | 67 +- .../jetty/ee9/servlet/ServletHandler.java | 33 +- .../jetty/ee9/servlet/ServletHolder.java | 12 +- .../jetty/ee9/servlet/ServletMapping.java | 2 +- .../jetty/ee9/servlet/ServletTester.java | 2 +- .../org/eclipse/jetty/ee9/servlet/Source.java | 2 +- .../jetty/ee9/servlet/StatisticsServlet.java | 11 +- .../ee9/servlet/jmx/FilterMappingMBean.java | 4 +- .../jetty/ee9/servlet/jmx/HolderMBean.java | 4 +- .../ee9/servlet/jmx/ServletMappingMBean.java | 4 +- .../jetty/ee9}/servlet/jmx/package-info.java | 2 +- .../listener/ContainerInitializer.java | 2 +- .../servlet/listener/ELContextCleaner.java | 2 +- .../servlet/listener/IntrospectorCleaner.java | 2 +- .../ee9}/servlet/listener/package-info.java | 2 +- .../jetty/ee9}/servlet/package-info.java | 2 +- .../AsyncContextDispatchWithQueryStrings.java | 8 +- .../servlet/AsyncContextListenersTest.java | 4 +- .../jetty/ee9/servlet/AsyncContextTest.java | 22 +- .../jetty/ee9/servlet/AsyncListenerTest.java | 29 +- .../jetty/ee9/servlet/AsyncServletIOTest.java | 4 +- .../ee9/servlet/AsyncServletLongPollTest.java | 4 +- .../jetty/ee9/servlet/AsyncServletTest.java | 24 +- .../ee9/servlet/CacheControlHeaderTest.java | 4 +- .../servlet/ComplianceViolations2616Test.java | 2 +- .../jetty/ee9/servlet/ComponentWrapTest.java | 2 +- .../ee9/servlet/CustomRequestLogTest.java | 4 +- .../ee9/servlet/DefaultServletRangesTest.java | 2 +- .../jetty/ee9/servlet/DefaultServletTest.java | 37 +- .../ee9/servlet/DispatcherForwardTest.java | 2 +- .../jetty/ee9/servlet/DispatcherTest.java | 17 +- .../jetty/ee9/servlet/EncodedURITest.java | 4 +- .../jetty/ee9/servlet/ErrorPageTest.java | 44 +- .../jetty/ee9/servlet/FilterHolderTest.java | 4 +- .../eclipse/jetty/ee9/servlet/FormTest.java | 4 +- .../servlet/GzipHandlerBreakEvenSizeTest.java | 2 +- .../ee9/servlet/GzipHandlerCommitTest.java | 2 +- .../ee9/servlet/IncludedServletTest.java | 2 +- .../jetty/ee9/servlet/InitServletTest.java | 2 +- .../jetty/ee9/servlet/InvokerTest.java | 2 +- .../jetty/ee9/servlet/ListenerHolderTest.java | 4 +- .../ee9/servlet/MultiPartServletTest.java | 8 +- .../jetty/ee9/servlet/PostServletTest.java | 5 +- .../jetty/ee9/servlet/RegexServletTest.java | 2 +- .../jetty/ee9/servlet/RequestHeadersTest.java | 2 +- .../jetty/ee9/servlet/RequestURITest.java | 2 +- .../ee9/servlet/ResponseHeadersTest.java | 10 +- .../ee9/servlet/SSLAsyncIOServletTest.java | 2 +- ...ServletContainerInitializerHolderTest.java | 6 +- .../servlet/ServletContextHandlerTest.java | 141 +- .../servlet/ServletContextResourcesTest.java | 2 +- .../jetty/ee9/servlet/ServletHandlerTest.java | 22 +- .../jetty/ee9/servlet/ServletHolderTest.java | 6 +- .../ee9/servlet/ServletLifeCycleTest.java | 66 +- .../ee9/servlet/ServletRequestLogTest.java | 35 +- .../jetty/ee9/servlet/ServletUpgradeTest.java | 14 +- .../jetty/ee9/servlet/ServletWrapperTest.java | 8 +- .../ee9/servlet/StatisticsServletTest.java | 16 +- .../resources/contextResources/content.txt | 0 .../dispatchResourceTest/content.txt | 0 .../test/resources/jetty-logging.properties | 2 +- .../jetty-ee9-servlets}/pom.xml | 27 +- .../src/main/config/modules/servlets.mod | 2 +- .../src/main/java/module-info.java | 6 +- .../org/eclipse/jetty/ee9/servlets/CGI.java | 2 +- .../ee9/servlets/CloseableDoSFilter.java | 19 +- .../jetty/ee9/servlets/CrossOriginFilter.java | 4 +- .../eclipse/jetty/ee9/servlets/DoSFilter.java | 2 +- .../jetty/ee9/servlets/EventSource.java | 2 +- .../ee9/servlets/EventSourceServlet.java | 2 +- .../jetty/ee9/servlets/HeaderFilter.java | 2 +- .../servlets/IncludeExcludeBasedFilter.java | 2 +- .../jetty/ee9/servlets/PushCacheFilter.java | 2 +- .../ee9/servlets/PushSessionCacheFilter.java | 2 +- .../eclipse/jetty/ee9/servlets/QoSFilter.java | 2 +- .../jetty/ee9}/servlets/package-info.java | 2 +- .../ee9/servlets/AbstractDoSFilterTest.java | 14 +- .../servlets/AbstractFileContentServlet.java | 2 +- .../jetty/ee9/servlets/AbstractGzipTest.java | 2 +- .../jetty/ee9/servlets/AsyncManipFilter.java | 2 +- .../servlets/AsyncScheduledDispatchWrite.java | 2 +- .../servlets/AsyncTimeoutCompleteWrite.java | 2 +- .../servlets/AsyncTimeoutDispatchWrite.java | 2 +- .../BlockingServletLengthStreamTypeWrite.java | 2 +- .../BlockingServletLengthTypeStreamWrite.java | 2 +- .../BlockingServletStreamLengthTypeWrite.java | 2 +- ...ServletStreamLengthTypeWriteWithFlush.java | 2 +- .../BlockingServletStreamTypeLengthWrite.java | 2 +- .../BlockingServletTypeLengthStreamWrite.java | 2 +- .../BlockingServletTypeStreamLengthWrite.java | 2 +- .../ee9/servlets/CloseableDoSFilterTest.java | 2 +- .../ee9/servlets/CrossOriginFilterTest.java | 8 +- .../jetty/ee9/servlets/DoSFilterJMXTest.java | 6 +- .../jetty/ee9/servlets/DoSFilterTest.java | 37 +- .../ee9/servlets/EventSourceServletTest.java | 6 +- .../ee9/servlets/GzipContentLengthTest.java | 49 +- ...DefaultServletDeferredContentTypeTest.java | 8 +- .../ee9/servlets/GzipDefaultServletTest.java | 78 +- .../servlets/GzipHandlerNoReCompressTest.java | 10 +- .../jetty/ee9/servlets/GzipHandlerTest.java | 4 +- .../jetty/ee9/servlets/HeaderFilterTest.java | 6 +- .../HttpOutputWriteFileContentServlet.java | 4 +- .../IncludeExcludeBasedFilterTest.java | 6 +- .../jetty/ee9/servlets/NoOpOutputStream.java | 2 +- .../ee9/servlets/PassThruInputStream.java | 2 +- .../jetty/ee9/servlets/QoSFilterTest.java | 6 +- .../ee9/servlets/TestMinGzipSizeServlet.java | 2 +- .../servlets/TestStaticMimeTypeServlet.java | 2 +- .../ee9/servlets/ThreadStarvationTest.java | 26 +- .../test/resources/jetty-logging.properties | 6 +- jetty-ee9/jetty-ee9-tests/pom.xml | 52 + .../test-bad-websocket-webapp/pom.xml | 12 +- .../bad/BadOnCloseServerEndpoint.java | 2 +- .../bad/BadOnOpenServerEndpoint.java | 2 +- .../webapp/websocket/bad/StringSequence.java | 2 +- .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/index.jsp | 0 .../jetty-ee9-tests}/test-cdi/pom.xml | 28 +- .../ee9}/cdi/tests/EmbeddedWeldTest.java | 24 +- .../ee9}/cdi/tests/FriendlyGreetings.java | 2 +- .../jetty/ee9}/cdi/tests/Greetings.java | 2 +- .../ee9}/cdi/tests/GreetingsServlet.java | 2 +- .../ee9}/cdi/tests/MyContextListener.java | 2 +- .../jetty/ee9}/cdi/tests/MyFilter.java | 2 +- .../websocket/JavaxWebSocketCdiTest.java | 16 +- .../websocket/JettyWebSocketCdiTest.java | 24 +- .../ee9}/cdi/tests/websocket/LogFactory.java | 2 +- .../src/test/resources/META-INF/beans.xml | 0 .../test/resources/jetty-logging.properties | 2 +- .../test-cdi/src/test/weldtest/.donotdelete | 0 .../test-http-client-transport/pom.xml | 12 +- .../jetty/ee9}/http/client/AbstractTest.java | 2 +- .../ee9}/http/client/AsyncIOServletTest.java | 22 +- .../http/client/AsyncRequestContentTest.java | 2 +- .../jetty/ee9}/http/client/BlockedIOTest.java | 2 +- .../http/client/ConnectionStatisticsTest.java | 6 +- .../ee9}/http/client/EmptyServerHandler.java | 2 +- .../client/HttpChannelAssociationTest.java | 8 +- .../client/HttpClientConnectTimeoutTest.java | 2 +- .../http/client/HttpClientContinueTest.java | 4 +- .../http/client/HttpClientDemandTest.java | 2 +- .../client/HttpClientIdleTimeoutTest.java | 3 +- .../ee9}/http/client/HttpClientLoadTest.java | 2 +- .../http/client/HttpClientStreamTest.java | 2 +- .../ee9}/http/client/HttpClientTest.java | 2 +- .../http/client/HttpClientTimeoutTest.java | 2 +- .../HttpClientTransportDynamicTest.java | 2 +- .../ee9}/http/client/HttpTrailersTest.java | 4 +- .../client/ProxyWithDynamicTransportTest.java | 16 +- .../ee9}/http/client/RequestReaderTest.java | 2 +- .../client/RoundRobinConnectionPoolTest.java | 2 +- .../ee9}/http/client/ServerTimeoutsTest.java | 12 +- .../jetty/ee9}/http/client/Transport.java | 2 +- .../ee9}/http/client/TransportProvider.java | 2 +- .../ee9}/http/client/TransportScenario.java | 6 +- .../test/resources/jetty-logging.properties | 0 .../src/test/resources/keystore.p12 | Bin .../test-http2-webapp/pom.xml | 21 +- .../jetty/test/webapp/HTTP1Servlet.java | 0 .../jetty/test/webapp/HTTP2Servlet.java | 0 .../src/main/webapp/WEB-INF/web.xml | 8 +- .../jetty/test/webapp/HTTP2FromWebAppIT.java | 2 +- .../test/resources/jetty-logging.properties | 0 .../src/test/resources/keystore.p12 | Bin .../jetty-ee9-tests}/test-integration/pom.xml | 40 +- .../ee9}/test/AliasCheckerSymlinkTest.java | 9 +- .../test/AllowedResourceAliasCheckerTest.java | 6 +- .../ee9}/test/AnnotatedAsyncListenerTest.java | 26 +- .../jetty/ee9}/test/CustomRequestLogTest.java | 18 +- .../jetty/ee9}/test/DefaultHandlerTest.java | 8 +- .../ee9}/test/DeploymentErrorInitializer.java | 2 +- .../jetty/ee9}/test/DeploymentErrorTest.java | 18 +- .../jetty/ee9}/test/DigestPostTest.java | 16 +- .../jetty/ee9}/test/FailedSelectorTest.java | 6 +- .../ee9}/test/GzipWithSendErrorTest.java | 12 +- .../ee9}/test/HttpInputIntegrationTest.java | 10 +- .../ee9}/test/HttpInputInterceptorTest.java | 2 +- .../jetty/ee9}/test/KeyStoreScannerTest.java | 2 +- .../ee9}/test/RecoverFailedSelectorTest.java | 2 +- .../jetty/ee9}/test/jsp/FakeJspServlet.java | 2 +- .../jsp/JspAndDefaultWithAliasesTest.java | 10 +- .../jsp/JspAndDefaultWithoutAliasesTest.java | 10 +- .../jetty/ee9}/test/rfcs/RFC2616BaseTest.java | 10 +- .../ee9}/test/rfcs/RFC2616NIOHttpTest.java | 8 +- .../ee9}/test/rfcs/RFC2616NIOHttpsTest.java | 8 +- .../jetty/ee9}/test/support/EchoHandler.java | 2 +- .../jetty/ee9}/test/support/StringUtil.java | 2 +- .../test/support/XmlBasedJettyServer.java | 2 +- .../rawhttp/HttpRequestTesterTest.java | 2 +- .../rawhttp/HttpResponseTesterTest.java | 2 +- .../ee9}/test/support/rawhttp/HttpSocket.java | 2 +- .../test/support/rawhttp/HttpSocketImpl.java | 2 +- .../test/support/rawhttp/HttpTesting.java | 2 +- .../test/support/rawhttp/HttpsSocketImpl.java | 2 +- .../websocket/JakartaSimpleEchoSocket.java | 2 +- .../test/websocket/JakartaWebSocketTest.java | 6 +- .../test/websocket/JettySimpleEchoSocket.java | 14 +- .../test/websocket/JettyWebSocketTest.java | 10 +- .../src/test/resources/DefaultHandler.xml | 0 .../src/test/resources/NIOHttp.xml | 0 .../src/test/resources/NIOHttps.xml | 0 .../src/test/resources/RFC2616Base.xml | 2 +- .../src/test/resources/RFC2616_Filters.xml | 0 .../src/test/resources/RFC2616_Redirects.xml | 0 .../test/resources/add-jetty-test-webapp.xml | 2 +- .../src/test/resources/badKeystore | Bin .../src/test/resources/basic-server.xml | 0 .../src/test/resources/deploy.xml | 2 +- .../test/resources/docroots/default/R1.txt | 0 .../test/resources/docroots/default/R2.txt | 0 .../test/resources/docroots/default/R3.txt | 0 .../test/resources/docroots/default/alpha.txt | 0 .../resources/docroots/default/index.html | 0 .../resources/docroots/default/quotes.txt | 0 .../deployerror/badapp-unavailable-false.xml | 2 +- .../resources/docroots/deployerror/badapp.xml | 2 +- ...akarta.servlet.ServletContainerInitializer | 0 .../deployerror/badapp/WEB-INF/web.xml | 0 .../src/test/resources/docroots/jsp/dump.jsp | 0 .../resources/docroots/virtualhost/R1.txt | 0 .../resources/docroots/virtualhost/R2.txt | 0 .../resources/docroots/virtualhost/R3.txt | 0 .../resources/docroots/virtualhost/index.html | 0 .../test-integration/src/test/resources/file | 0 .../test/resources/jetty-logging.properties | 0 .../src/test/resources/keystore.p12 | Bin .../src/test/resources/logback.xml | 0 .../src/test/resources/login-service.xml | 0 .../src/test/resources/message.txt | 0 .../src/test/resources/newKeystore | Bin .../src/test/resources/oldKeystore | Bin .../src/test/resources/realm.properties | 2 +- .../src/test/resources/sibling/file | 0 .../src/test/resources/ssl.xml | 0 .../RFC2616/rfc2616-webapp.xml | 2 +- .../src/test/resources/webdefault.xml | 12 +- .../test/resources/webroot/WEB-INF/web.xml | 0 .../src/test/resources/webroot/documents/file | 0 .../src/test/resources/webroot/file | 0 .../src/test/resources/webroot/index.html | 0 .../src/test/resources/zeros.gz.gz | Bin .../test-jmx/jmx-webapp-it/pom.xml | 14 +- .../eclipse/jetty/ee9}/test/jmx/JmxIT.java | 6 +- .../test-jmx/jmx-webapp/pom.xml | 8 +- .../jetty/ee9}/test/jmx/CommonComponent.java | 2 +- .../eclipse/jetty/ee9}/test/jmx/Echoer.java | 2 +- .../ee9}/test/jmx/MyContainerInitializer.java | 2 +- .../jetty/ee9}/test/jmx/PingServlet.java | 2 +- .../eclipse/jetty/ee9}/test/jmx/Pinger.java | 2 +- .../jetty/ee9}/test/jmx/jmx/EchoerMBean.java | 2 +- .../jetty/ee9}/test/jmx/jmx/PingerMBean.java | 4 +- ...akarta.servlet.ServletContainerInitializer | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../jmx-webapp/src/main/webapp/index.html | 0 .../jetty-ee9-tests}/test-jmx/pom.xml | 10 +- .../jetty-ee9-tests}/test-jndi/pom.xml | 8 +- .../factories/TestMailSessionReference.java | 3 +- .../test-loginservice/pom.xml | 20 +- .../DataSourceLoginServiceTest.java | 6 +- .../DatabaseLoginServiceTestServer.java | 14 +- .../loginservice}/JdbcLoginServiceTest.java | 4 +- .../src/test/resources/createdb.sql | 0 .../src/test/resources/droptables.sql | 0 .../src/test/resources/jdbcrealm.properties | 0 .../test/resources/jetty-logging.properties | 0 .../jetty-ee9-tests}/test-quickstart/pom.xml | 60 +- .../quickstart/AttributeNormalizerTest.java | 2 +- ...AttributeNormalizerToCanonicalUriTest.java | 2 +- .../jetty/ee9}/quickstart/EnvUtils.java | 2 +- .../ee9}/quickstart/PreconfigureJNDIWar.java | 2 +- .../ee9}/quickstart/PreconfigureSpecWar.java | 2 +- .../PreconfigureStandardTestWar.java | 2 +- .../ee9}/quickstart/QuickStartJNDIWar.java | 2 +- .../ee9}/quickstart/QuickStartSpecWar.java | 2 +- .../quickstart/QuickStartStandardTestWar.java | 2 +- .../jetty/ee9}/quickstart/QuickStartTest.java | 12 +- .../jetty/ee9}/quickstart/Quickstart.java | 10 +- .../test/resources/jetty-logging.properties | 0 .../src/test/resources/realm.properties | 2 +- .../src/test/resources/test-jndi.xml | 2 +- .../src/test/resources/test-spec.xml | 2 +- .../src/test/resources/test.xml | 2 +- .../pom.xml | 12 +- .../tests/webapp/websocket/EchoEndpoint.java | 2 +- .../websocket/WebSocketClientServlet.java | 16 +- .../resources/jetty-websocket-httpclient.xml | 0 .../src/main/webapp/WEB-INF/web.xml | 0 .../test-websocket-client-webapp/pom.xml | 12 +- .../tests/webapp/websocket/EchoEndpoint.java | 2 +- .../websocket/WebSocketClientServlet.java | 16 +- .../src/main/webapp/WEB-INF/web.xml | 0 .../test-websocket-webapp/pom.xml | 12 +- .../tests/webapp/websocket/EchoEndpoint.java | 2 +- .../webapp/websocket/StringSequence.java | 2 +- .../websocket/StringSequenceDecoder.java | 2 +- .../src/main/webapp/WEB-INF/web.xml | 0 .../src/main/webapp/index.jsp | 0 .../jetty-ee9-webapp}/pom.xml | 26 +- .../src/main/config/etc/jetty-webapp.xml | 4 +- .../src/main/config/etc/webdefault.xml | 12 +- .../src/main/java/module-info.java | 53 + .../jetty/ee9/webapp/AbsoluteOrdering.java | 2 +- .../ee9/webapp/AbstractConfiguration.java | 2 +- .../ee9/webapp/CachingWebAppClassLoader.java | 2 +- .../jetty/ee9/webapp/ClassMatcher.java | 2 +- .../jetty/ee9/webapp/Configuration.java | 4 +- .../jetty/ee9/webapp/Configurations.java | 2 +- .../jetty/ee9/webapp/DecoratingListener.java | 12 +- .../jetty/ee9/webapp/DefaultsDescriptor.java | 2 +- .../eclipse/jetty/ee9/webapp/Descriptor.java | 2 +- .../jetty/ee9/webapp/DescriptorProcessor.java | 2 +- .../ee9/webapp/DiscoveredAnnotation.java | 2 +- .../ee9/webapp/FragmentConfiguration.java | 2 +- .../jetty/ee9/webapp/FragmentDescriptor.java | 2 +- .../webapp/IterativeDescriptorProcessor.java | 2 +- .../jetty/ee9/webapp/JaasConfiguration.java | 2 +- .../jetty/ee9/webapp/JaspiConfiguration.java | 2 +- .../ee9/webapp/JettyWebXmlConfiguration.java | 4 +- .../jetty/ee9/webapp/JmxConfiguration.java | 2 +- .../jetty/ee9/webapp/JndiConfiguration.java | 2 +- .../jetty/ee9/webapp/JspConfiguration.java | 2 +- .../eclipse/jetty/ee9/webapp/MetaData.java | 4 +- .../ee9/webapp/MetaInfConfiguration.java | 2 +- .../eclipse/jetty/ee9/webapp/Ordering.java | 2 +- .../org/eclipse/jetty/ee9/webapp/Origin.java | 2 +- .../jetty/ee9/webapp/OverrideDescriptor.java | 2 +- .../jetty/ee9/webapp/RelativeOrdering.java | 2 +- .../ee9/webapp/ServletsConfiguration.java | 10 +- .../webapp/StandardDescriptorProcessor.java | 33 +- .../jetty/ee9/webapp/WebAppClassLoader.java | 4 +- .../jetty/ee9/webapp/WebAppConfiguration.java | 10 +- .../jetty/ee9/webapp/WebAppContext.java | 88 +- .../jetty/ee9/webapp/WebDescriptor.java | 2 +- .../jetty/ee9/webapp/WebInfConfiguration.java | 6 +- .../jetty/ee9/webapp/WebXmlConfiguration.java | 4 +- .../jetty/ee9}/webapp/package-info.java | 2 +- ...org.eclipse.jetty.ee9.webapp.Configuration | 12 + .../java/org/acme/webapp/TestAnnotation.java | 4 +- .../jetty/ee9/webapp/ClassMatcherTest.java | 8 +- .../jetty/ee9/webapp/ConfigurationsTest.java | 4 +- .../jetty/ee9/webapp/ForcedServletTest.java | 10 +- .../jetty/ee9/webapp/HugeResourceTest.java | 12 +- .../ee9/webapp/MetaInfConfigurationTest.java | 4 +- .../jetty/ee9/webapp/OrderingTest.java | 2 +- .../StandardDescriptorProcessorTest.java | 2 +- .../eclipse/jetty/ee9/webapp/TempDirTest.java | 2 +- .../jetty/ee9/webapp/TestMetaData.java | 2 +- .../ee9/webapp/URLStreamHandlerUtil.java | 2 +- .../ee9/webapp/WebAppClassLoaderTest.java | 36 +- .../WebAppClassLoaderUrlStreamTest.java | 2 +- .../jetty/ee9/webapp/WebAppContextTest.java | 61 +- .../ee9/webapp/WebAppDefaultServletTest.java | 18 +- .../ee9/webapp/WebInfConfigurationTest.java | 2 +- .../test/resources/jetty-logging.properties | 2 +- .../src/test/resources/org/acme/resource.txt | 0 .../src/test/webapp-alt-jsp/WEB-INF/web.xml | 2 +- .../WEB-INF/classes/org/acme/resource.txt | 0 .../src/test/webapp/WEB-INF/test.xml | 0 .../src/test/webapp/test.xml | 0 .../pom.xml | 14 +- .../src/main/java/module-info.java | 12 +- ...kartaWebSocketClientContainerProvider.java | 4 +- .../JakartaWebSocketShutdownContainer.java | 4 +- .../AnnotatedClientEndpointConfig.java | 4 +- .../internal/BasicClientEndpointConfig.java | 4 +- .../client/internal/EmptyConfigurator.java | 2 +- .../internal/JakartaClientUpgradeRequest.java | 6 +- .../JakartaWebSocketClientContainer.java | 12 +- ...rtaWebSocketClientFrameHandlerFactory.java | 8 +- .../client/internal/JsrUpgradeListener.java | 2 +- ...akarta.servlet.ServletContainerInitializer | 0 .../SecureClientContainerExample.java | 2 +- .../test/resources/jetty-logging.properties | 0 .../resources/jetty-websocket-httpclient.xml | 0 .../pom.xml | 10 +- .../src/main/java/module-info.java | 10 +- .../common/ClientEndpointConfigWrapper.java | 2 +- .../jakarta/common/ConfiguredEndpoint.java | 2 +- .../jakarta/common/EndpointConfigWrapper.java | 2 +- .../jakarta/common/InitException.java | 2 +- .../common/JakartaWebSocketAsyncRemote.java | 2 +- .../common/JakartaWebSocketBasicRemote.java | 2 +- .../common/JakartaWebSocketContainer.java | 2 +- .../common/JakartaWebSocketExtension.java | 2 +- .../JakartaWebSocketExtensionConfig.java | 2 +- .../common/JakartaWebSocketFrameHandler.java | 14 +- .../JakartaWebSocketFrameHandlerFactory.java | 14 +- .../JakartaWebSocketFrameHandlerMetadata.java | 6 +- .../JakartaWebSocketMessageMetadata.java | 4 +- .../common/JakartaWebSocketPongMessage.java | 2 +- .../JakartaWebSocketRemoteEndpoint.java | 2 +- .../common/JakartaWebSocketSession.java | 6 +- .../JakartaWebSocketSessionListener.java | 2 +- .../jakarta/common/PathParamProvider.java | 2 +- .../jakarta/common/PutListenerMap.java | 2 +- .../common/RegisteredMessageHandler.java | 2 +- .../jakarta/common/SendHandlerCallback.java | 2 +- .../common/ServerEndpointConfigWrapper.java | 2 +- .../jakarta/common/SessionTracker.java | 2 +- .../jakarta/common/UpgradeRequest.java | 2 +- .../jakarta/common/UpgradeRequestAdapter.java | 2 +- .../common/decoders/AbstractDecoder.java | 2 +- .../common/decoders/AvailableDecoders.java | 2 +- .../common/decoders/BooleanDecoder.java | 2 +- .../common/decoders/ByteArrayDecoder.java | 2 +- .../common/decoders/ByteBufferDecoder.java | 2 +- .../jakarta/common/decoders/ByteDecoder.java | 2 +- .../common/decoders/CharacterDecoder.java | 2 +- .../common/decoders/DoubleDecoder.java | 2 +- .../jakarta/common/decoders/FloatDecoder.java | 2 +- .../common/decoders/InputStreamDecoder.java | 2 +- .../common/decoders/IntegerDecoder.java | 2 +- .../jakarta/common/decoders/LongDecoder.java | 2 +- .../common/decoders/ReaderDecoder.java | 2 +- .../common/decoders/RegisteredDecoder.java | 4 +- .../jakarta/common/decoders/ShortDecoder.java | 2 +- .../common/decoders/StringDecoder.java | 2 +- .../common/encoders/AbstractEncoder.java | 2 +- .../common/encoders/AvailableEncoders.java | 4 +- .../common/encoders/BooleanEncoder.java | 2 +- .../common/encoders/ByteArrayEncoder.java | 2 +- .../common/encoders/ByteBufferEncoder.java | 2 +- .../jakarta/common/encoders/ByteEncoder.java | 2 +- .../common/encoders/CharacterEncoder.java | 2 +- .../common/encoders/DoubleEncoder.java | 2 +- .../common/encoders/EncodeFailedFuture.java | 2 +- .../jakarta/common/encoders/FloatEncoder.java | 2 +- .../common/encoders/IntegerEncoder.java | 2 +- .../jakarta/common/encoders/LongEncoder.java | 2 +- .../common/encoders/RegisteredEncoder.java | 2 +- .../jakarta/common/encoders/ShortEncoder.java | 2 +- .../common/encoders/StringEncoder.java | 2 +- .../messages/AbstractDecodedMessageSink.java | 4 +- .../messages/DecodedBinaryMessageSink.java | 6 +- .../DecodedBinaryStreamMessageSink.java | 6 +- .../messages/DecodedTextMessageSink.java | 6 +- .../DecodedTextStreamMessageSink.java | 6 +- ...tractJakartaWebSocketFrameHandlerTest.java | 6 +- .../jakarta/common/AbstractSessionTest.java | 2 +- .../websocket/jakarta/common/Defaults.java | 2 +- .../jakarta/common/DummyContainer.java | 2 +- .../common/DummyFrameHandlerFactory.java | 2 +- ...ebSocketFrameHandlerBadSignaturesTest.java | 2 +- ...kartaWebSocketFrameHandlerOnCloseTest.java | 4 +- ...kartaWebSocketFrameHandlerOnErrorTest.java | 4 +- ...FrameHandlerOnMessageBinaryStreamTest.java | 4 +- ...SocketFrameHandlerOnMessageBinaryTest.java | 4 +- ...etFrameHandlerOnMessageTextStreamTest.java | 4 +- ...ebSocketFrameHandlerOnMessageTextTest.java | 4 +- ...akartaWebSocketFrameHandlerOnOpenTest.java | 4 +- .../common/coders/tests/BadDualDecoder.java | 2 +- .../common/coders/tests/BadDualEncoder.java | 2 +- .../common/coders/tests/ExtDecoder.java | 2 +- .../jakarta/common/coders/tests/Fruit.java | 2 +- .../coders/tests/FruitBinaryEncoder.java | 2 +- .../common/coders/tests/FruitDecoder.java | 2 +- .../common/coders/tests/FruitTextEncoder.java | 2 +- .../endpoints/AbstractStringEndpoint.java | 4 +- .../common/endpoints/DummyEndpoint.java | 2 +- .../common/endpoints/EchoStringEndpoint.java | 2 +- .../common/handlers/BaseMessageHandler.java | 2 +- .../handlers/ByteArrayPartialHandler.java | 2 +- .../handlers/ByteArrayWholeHandler.java | 2 +- .../handlers/ByteBufferPartialHandler.java | 2 +- .../handlers/ByteBufferWholeHandler.java | 2 +- .../common/handlers/ComboMessageHandler.java | 2 +- .../handlers/ExtendedMessageHandler.java | 2 +- .../handlers/InputStreamWholeHandler.java | 2 +- .../common/handlers/LongMessageHandler.java | 2 +- .../common/handlers/ReaderWholeHandler.java | 2 +- .../common/handlers/StringPartialHandler.java | 2 +- .../common/handlers/StringWholeHandler.java | 2 +- .../messages/AbstractMessageSinkTest.java | 8 +- .../DecodedBinaryMessageSinkTest.java | 6 +- .../DecodedBinaryStreamMessageSinkTest.java | 4 +- .../messages/DecodedTextMessageSinkTest.java | 4 +- .../DecodedTextStreamMessageSinkTest.java | 4 +- .../messages/InputStreamMessageSinkTest.java | 4 +- .../common/messages/MessageWriterTest.java | 2 +- .../messages/ReaderMessageSinkTest.java | 2 +- .../common/sockets/TrackingSocket.java | 2 +- .../util/InvokerUtilsStaticParamsTest.java | 4 +- .../jakarta/common/util/InvokerUtilsTest.java | 2 +- .../common/util/NameParamIdentifier.java | 2 +- .../jakarta/common/util/ReflectUtilsTest.java | 2 +- .../test/resources/jetty-logging.properties | 0 .../src/test/resources/quotes-ben.txt | 0 .../src/test/resources/quotes-twain.txt | 0 .../pom.xml | 24 +- .../src/main/java/module-info.java | 20 +- .../config/ContainerDefaultConfigurator.java | 2 +- .../config/JakartaWebSocketConfiguration.java | 16 +- ...aWebSocketServletContainerInitializer.java | 10 +- .../AnnotatedServerEndpointConfig.java | 8 +- .../internal/BasicServerEndpointConfig.java | 6 +- .../internal/JakartaServerUpgradeRequest.java | 4 +- .../internal/JakartaWebSocketCreator.java | 39 +- .../JakartaWebSocketServerContainer.java | 10 +- ...rtaWebSocketServerFrameHandlerFactory.java | 8 +- .../server/internal/JsrHandshakeRequest.java | 2 +- .../server/internal/JsrHandshakeResponse.java | 2 +- .../server/internal/PathParamIdentifier.java | 2 +- .../PathParamServerEndpointConfig.java | 6 +- ...akarta.servlet.ServletContainerInitializer | 0 .../org.eclipse.jetty.webapp.Configuration | 0 .../browser/JsrBrowserConfigurator.java | 2 +- .../server/browser/JsrBrowserDebugTool.java | 10 +- .../server/browser/JsrBrowserSocket.java | 2 +- .../test/resources/jetty-logging.properties | 0 .../jsr-browser-debug-tool/index.html | 0 .../resources/jsr-browser-debug-tool/main.css | 0 .../jsr-browser-debug-tool/websocket.js | 0 .../fuzzingclient.json | 18 + .../fuzzingserver.json | 10 + .../pom.xml | 20 +- .../ee9/websocket/jakarta/tests/BadFrame.java | 2 +- .../tests/BiConsumerServiceServlet.java | 2 +- .../tests/CompletableFutureMethodHandle.java | 4 +- .../websocket/jakarta/tests/CoreServer.java | 6 +- .../websocket/jakarta/tests/DataUtils.java | 2 +- .../jakarta/tests/DummyEndpoint.java | 2 +- .../websocket/jakarta/tests/EchoSocket.java | 2 +- .../websocket/jakarta/tests/EventSocket.java | 2 +- .../jakarta/tests/FunctionMethod.java | 2 +- .../ee9/websocket/jakarta/tests/Fuzzer.java | 2 +- .../websocket/jakarta/tests/LocalFuzzer.java | 2 +- .../websocket/jakarta/tests/LocalServer.java | 14 +- .../websocket/jakarta/tests/MessageType.java | 2 +- .../jakarta/tests/NetworkFuzzer.java | 2 +- .../jakarta/tests/ParserCapture.java | 2 +- .../jakarta/tests/RawFrameBuilder.java | 2 +- .../jakarta/tests/SessionMatchers.java | 8 +- .../ee9/websocket/jakarta/tests/Sha1Sum.java | 2 +- .../ee9/websocket/jakarta/tests/Timeouts.java | 2 +- .../jakarta/tests/UnitGenerator.java | 2 +- .../websocket/jakarta/tests/UpgradeUtils.java | 2 +- .../jakarta/tests/WSEndpointTracker.java | 2 +- .../jakarta/tests/WSEventTracker.java | 2 +- .../ee9/websocket/jakarta/tests/WSServer.java | 11 +- .../ee9}/websocket/jakarta/tests/WSURI.java | 2 +- .../tests/framehandlers/FrameEcho.java | 2 +- .../framehandlers/FrameHandlerTracker.java | 2 +- .../tests/framehandlers/StaticText.java | 2 +- .../tests/framehandlers/WholeMessageEcho.java | 2 +- .../tests/matchers/IsMessageHandlerType.java | 8 +- .../IsMessageHandlerTypeRegistered.java | 10 +- .../com/acme/websocket/BasicEchoEndpoint.java | 0 ...asicEchoEndpointConfigContextListener.java | 0 .../websocket/IdleTimeoutContextListener.java | 0 .../websocket/IdleTimeoutOnOpenEndpoint.java | 0 .../websocket/IdleTimeoutOnOpenSocket.java | 0 .../websocket/LargeEchoContextListener.java | 0 .../websocket/LargeEchoDefaultSocket.java | 0 .../websocket/OnOpenIdleTimeoutEndpoint.java | 0 .../acme/websocket/PongContextListener.java | 0 .../acme/websocket/PongMessageEndpoint.java | 0 .../java/com/acme/websocket/PongSocket.java | 0 .../jakarta/tests/CloseInOnOpenTest.java | 10 +- .../jakarta/tests/GracefulCloseTest.java | 8 +- .../tests/JakartaClientClassLoaderTest.java | 12 +- ...aClientShutdownWithServerEmbeddedTest.java | 10 +- ...rtaClientShutdownWithServerWebAppTest.java | 14 +- .../jakarta/tests/JakartaOnCloseTest.java | 8 +- .../tests/JakartaWebSocketRestartTest.java | 14 +- .../tests/JettySpecificConfigTest.java | 8 +- .../jakarta/tests/PathParamTest.java | 42 +- .../ProgrammaticWebSocketUpgradeTest.java | 12 +- .../jakarta/tests/RestartContextTest.java | 8 +- .../jakarta/tests/ServerConfigTest.java | 6 +- .../tests/SingleMessageHandlerTest.java | 8 +- .../jakarta/tests/SyntheticOnMessageTest.java | 6 +- .../tests/UpgradeRequestResponseTest.java | 8 +- .../autobahn}/JakartaAutobahnClient.java | 56 +- .../autobahn}/JakartaAutobahnServer.java | 43 +- .../autobahn}/JakartaAutobahnSocket.java | 4 +- .../client/AbstractClientSessionTest.java | 18 +- .../client/AnnotatedClientEndpointTest.java | 8 +- .../tests/client/AnnotatedEchoClient.java | 2 +- .../tests/client/AnnotatedEchoTest.java | 4 +- .../tests/client/ConfiguratorTest.java | 6 +- .../jakarta/tests/client/CookiesTest.java | 10 +- .../client/DecoderReaderManySmallTest.java | 6 +- .../tests/client/DelayedStartClientTest.java | 2 +- .../tests/client/EndpointEchoTest.java | 8 +- .../client/JsrClientEchoTrackingSocket.java | 6 +- .../tests/client/JsrClientTrackingSocket.java | 6 +- .../tests/client/MessageReceivingTest.java | 6 +- .../jakarta/tests/client/OnCloseTest.java | 24 +- .../client/SessionAddMessageHandlerTest.java | 30 +- .../tests/client/WriteTimeoutTest.java | 10 +- .../misbehaving/AnnotatedRuntimeOnOpen.java | 2 +- .../misbehaving/EndpointRuntimeOnOpen.java | 2 +- .../misbehaving/MisbehavingClassTest.java | 4 +- .../samples/CloseEndpointConfigSocket.java | 4 +- .../samples/CloseReasonSessionSocket.java | 4 +- .../client/samples/CloseReasonSocket.java | 4 +- .../samples/CloseSessionReasonSocket.java | 4 +- .../client/samples/CloseSessionSocket.java | 4 +- .../tests/client/samples/CloseSocket.java | 4 +- .../tests/client/samples/IntSocket.java | 4 +- .../tests/coders/AvailableDecodersTest.java | 8 +- .../tests/coders/AvailableEncodersTest.java | 8 +- .../jakarta/tests/coders/BadDualDecoder.java | 2 +- .../jakarta/tests/coders/BadDualEncoder.java | 2 +- .../tests/coders/CoderEventTracking.java | 2 +- .../jakarta/tests/coders/DateDecoder.java | 2 +- .../jakarta/tests/coders/DateEncoder.java | 2 +- .../jakarta/tests/coders/DateTimeDecoder.java | 2 +- .../jakarta/tests/coders/DateTimeEncoder.java | 2 +- .../jakarta/tests/coders/DecoderListTest.java | 14 +- .../tests/coders/DecoderTextStreamTest.java | 10 +- .../jakarta/tests/coders/DecoderTextTest.java | 2 +- .../tests/coders/EncoderLifeCycleTest.java | 12 +- .../jakarta/tests/coders/EncoderTextTest.java | 2 +- .../jakarta/tests/coders/ExtDecoder.java | 2 +- .../tests/coders/FloatDecoderTest.java | 4 +- .../websocket/jakarta/tests/coders/Fruit.java | 2 +- .../tests/coders/FruitBinaryEncoder.java | 2 +- .../jakarta/tests/coders/FruitDecoder.java | 2 +- .../tests/coders/FruitTextEncoder.java | 2 +- .../tests/coders/IntegerDecoderTest.java | 4 +- .../jakarta/tests/coders/LongDecoderTest.java | 4 +- .../jakarta/tests/coders/Quotes.java | 2 +- .../jakarta/tests/coders/QuotesDecoder.java | 2 +- .../jakarta/tests/coders/QuotesEncoder.java | 2 +- .../jakarta/tests/coders/QuotesUtil.java | 2 +- .../tests/coders/ShortDecoderTest.java | 4 +- .../jakarta/tests/coders/TimeDecoder.java | 2 +- .../jakarta/tests/coders/TimeEncoder.java | 2 +- .../tests/coders/ValidDualDecoder.java | 2 +- .../tests/coders/ValidDualEncoder.java | 2 +- .../handlers/AbstractAnnotatedHandler.java | 2 +- .../tests/handlers/AbstractHandler.java | 2 +- .../tests/handlers/BaseMessageHandler.java | 2 +- .../tests/handlers/BinaryHandlers.java | 2 +- .../tests/handlers/ComboMessageHandler.java | 2 +- .../handlers/ExtendedMessageHandler.java | 2 +- .../tests/handlers/LongMessageHandler.java | 2 +- .../tests/handlers/MessageHandlerTest.java | 8 +- .../jakarta/tests/handlers/TextHandlers.java | 2 +- .../tests/pathparam/BooleanClassSocket.java | 2 +- .../tests/pathparam/BooleanTypeSocket.java | 2 +- .../tests/pathparam/ByteClassSocket.java | 2 +- .../tests/pathparam/ByteTypeSocket.java | 2 +- .../tests/pathparam/CharacterClassSocket.java | 2 +- .../tests/pathparam/CharacterTypeSocket.java | 2 +- .../tests/pathparam/DoubleClassSocket.java | 2 +- .../tests/pathparam/DoubleTypeSocket.java | 2 +- .../tests/pathparam/FloatClassSocket.java | 2 +- .../tests/pathparam/FloatTypeSocket.java | 2 +- .../tests/pathparam/IntegerClassSocket.java | 2 +- .../tests/pathparam/IntegerTypeSocket.java | 2 +- .../tests/pathparam/LongClassSocket.java | 2 +- .../tests/pathparam/LongTypeSocket.java | 2 +- .../tests/pathparam/ShortClassSocket.java | 2 +- .../tests/pathparam/ShortTypeSocket.java | 2 +- .../tests/pathparam/StringClassSocket.java | 2 +- .../jakarta/tests/quotes/Quotes.java | 2 +- .../jakarta/tests/quotes/QuotesDecoder.java | 2 +- .../tests/quotes/QuotesDecoderTest.java | 6 +- .../quotes/QuotesDecoderTextStreamTest.java | 6 +- .../jakarta/tests/quotes/QuotesEncoder.java | 2 +- .../tests/quotes/QuotesEncoderTest.java | 6 +- .../jakarta/tests/quotes/QuotesSocket.java | 4 +- .../jakarta/tests/quotes/QuotesUtil.java | 2 +- ...akartaWebSocketServerFrameHandlerTest.java | 14 +- .../jakarta/tests/server/AddEndpointTest.java | 10 +- .../jakarta/tests/server/AltFilterTest.java | 12 +- .../server/AnnotatedServerEndpointTest.java | 14 +- ...asicEchoEndpointConfigContextListener.java | 4 +- .../BasicEchoEndpointContextListener.java | 4 +- .../BasicEchoSocketConfigContextListener.java | 4 +- .../BasicEchoSocketContextListener.java | 4 +- .../tests/server/BinaryStreamTest.java | 10 +- .../tests/server/ConfiguratorTest.java | 10 +- .../server/ContainerProviderServerTest.java | 6 +- .../tests/server/DeploymentExceptionTest.java | 18 +- .../jakarta/tests/server/DeploymentTest.java | 10 +- .../tests/server/EndpointViaConfigTest.java | 6 +- .../server/IdleTimeoutContextListener.java | 4 +- .../jakarta/tests/server/IdleTimeoutTest.java | 6 +- .../tests/server/InputStreamEchoTest.java | 6 +- ...etFrameHandlerOnMessageTextStreamTest.java | 10 +- .../JettyServerEndpointConfiguratorTest.java | 4 +- .../tests/server/JsrBatchModeTest.java | 4 +- .../jakarta/tests/server/JsrEchoTest.java | 6 +- .../tests/server/LargeAnnotatedTest.java | 6 +- .../tests/server/LargeContainerTest.java | 6 +- .../server/LargeEchoContextListener.java | 2 +- .../jakarta/tests/server/MemoryUsageTest.java | 6 +- .../tests/server/OnMessageReturnTest.java | 6 +- .../jakarta/tests/server/PartialEchoTest.java | 6 +- .../jakarta/tests/server/PingPongTest.java | 8 +- .../tests/server/PongContextListener.java | 4 +- .../jakarta/tests/server/PongSocket.java | 2 +- .../server/PrimitivesBinaryEchoTest.java | 8 +- .../tests/server/PrimitivesTextEchoTest.java | 8 +- .../jakarta/tests/server/ReaderEchoTest.java | 6 +- .../tests/server/ServerDecoderTest.java | 12 +- .../jakarta/tests/server/SessionTest.java | 10 +- .../tests/server/SessionTrackingTest.java | 8 +- .../jakarta/tests/server/StreamTest.java | 8 +- .../jakarta/tests/server/TextStreamTest.java | 12 +- .../server/UriTemplateParameterTest.java | 6 +- .../tests/server/WebAppClassLoaderTest.java | 10 +- .../WebSocketServerContainerExecutorTest.java | 12 +- .../configs/EchoSocketConfigurator.java | 2 +- .../examples/GetHttpSessionConfigurator.java | 2 +- .../server/examples/GetHttpSessionSocket.java | 2 +- .../server/examples/MyAuthedConfigurator.java | 2 +- .../tests/server/examples/MyAuthedSocket.java | 2 +- .../server/examples/StreamingEchoSocket.java | 2 +- .../examples/WebSocketServerExamplesTest.java | 18 +- .../BasicBinaryMessageByteBufferSocket.java | 2 +- .../BasicCloseReasonSessionSocket.java | 2 +- .../sockets/BasicCloseReasonSocket.java | 2 +- .../BasicCloseSessionReasonSocket.java | 2 +- .../server/sockets/BasicCloseSocket.java | 2 +- .../tests/server/sockets/BasicEchoSocket.java | 2 +- .../sockets/BasicErrorSessionSocket.java | 2 +- .../BasicErrorSessionThrowableSocket.java | 2 +- .../server/sockets/BasicErrorSocket.java | 2 +- .../BasicErrorThrowableSessionSocket.java | 2 +- .../sockets/BasicErrorThrowableSocket.java | 2 +- .../sockets/BasicOpenCloseSessionSocket.java | 2 +- .../server/sockets/BasicOpenCloseSocket.java | 2 +- .../sockets/BasicOpenSessionSocket.java | 2 +- .../tests/server/sockets/BasicOpenSocket.java | 2 +- .../sockets/BasicPongMessageSocket.java | 2 +- .../sockets/BasicTextMessageStringSocket.java | 2 +- .../server/sockets/ByteBufferSocket.java | 2 +- .../server/sockets/ConfiguredEchoSocket.java | 8 +- .../tests/server/sockets/DateTextSocket.java | 6 +- .../sockets/IdleTimeoutOnOpenEndpoint.java | 2 +- .../sockets/IdleTimeoutOnOpenSocket.java | 2 +- .../server/sockets/InvalidCloseIntSocket.java | 2 +- .../sockets/InvalidErrorErrorSocket.java | 2 +- .../server/sockets/InvalidErrorIntSocket.java | 2 +- .../sockets/InvalidOpenCloseReasonSocket.java | 2 +- .../server/sockets/InvalidOpenIntSocket.java | 2 +- .../sockets/InvalidOpenSessionIntSocket.java | 2 +- .../StatelessTextMessageStringSocket.java | 2 +- .../tests/server/sockets/TrackingSocket.java | 2 +- .../sockets/binary/ByteBufferSocket.java | 2 +- .../sockets/echo/BasicEchoEndpoint.java | 2 +- .../server/sockets/echo/BasicEchoSocket.java | 2 +- .../sockets/echo/EchoAsyncTextSocket.java | 2 +- .../sockets/echo/EchoBasicTextSocket.java | 2 +- .../sockets/echo/EchoReturnEndpoint.java | 2 +- .../sockets/echo/EchoReturnTextSocket.java | 2 +- .../echo/EchoStatelessAsyncTextSocket.java | 2 +- .../echo/EchoStatelessBasicTextSocket.java | 2 +- .../echo/LargeEchoConfiguredSocket.java | 2 +- .../sockets/echo/LargeEchoDefaultSocket.java | 2 +- .../OnOpenIdleTimeoutEndpoint.java | 2 +- .../idletimeout/OnOpenIdleTimeoutSocket.java | 2 +- .../partial/PartialTextSessionSocket.java | 2 +- .../sockets/partial/PartialTextSocket.java | 2 +- .../partial/PartialTrackingSocket.java | 4 +- .../sockets/pong/PongMessageEndpoint.java | 2 +- .../BooleanObjectTextParamSocket.java | 2 +- .../primitives/BooleanObjectTextSocket.java | 2 +- .../primitives/BooleanTextParamSocket.java | 2 +- .../sockets/primitives/BooleanTextSocket.java | 2 +- .../primitives/ByteObjectTextSocket.java | 2 +- .../sockets/primitives/ByteTextSocket.java | 2 +- .../sockets/primitives/CharTextSocket.java | 2 +- .../primitives/CharacterObjectTextSocket.java | 2 +- .../primitives/DoubleObjectTextSocket.java | 2 +- .../sockets/primitives/DoubleTextSocket.java | 2 +- .../primitives/FloatObjectTextSocket.java | 2 +- .../sockets/primitives/FloatTextSocket.java | 2 +- .../primitives/IntParamTextSocket.java | 2 +- .../sockets/primitives/IntTextSocket.java | 2 +- .../IntegerObjectParamTextSocket.java | 2 +- .../primitives/IntegerObjectTextSocket.java | 2 +- .../primitives/LongObjectTextSocket.java | 2 +- .../sockets/primitives/LongTextSocket.java | 2 +- .../primitives/ShortObjectTextSocket.java | 2 +- .../sockets/primitives/ShortTextSocket.java | 2 +- .../sockets/streaming/InputStreamSocket.java | 2 +- .../sockets/streaming/ReaderParamSocket.java | 2 +- .../sockets/streaming/ReaderSocket.java | 2 +- .../StringReturnReaderParamSocket.java | 2 +- .../test/resources/jetty-logging.properties | 0 .../resources/jetty-websocket-httpclient.xml | 0 .../src/test/resources/keystore.p12 | Bin .../src/test/resources/quotes-ben.txt | 0 .../src/test/resources/quotes-twain.txt | 0 .../simple/jetty-websocket-httpclient.xml | 0 .../src/test/webapp/index.html | 0 .../jetty-ee9-websocket-jetty-api}/pom.xml | 10 +- .../src/main/java/module-info.java | 22 + .../jetty/ee9/websocket/api/BatchMode.java | 2 +- .../jetty/ee9/websocket/api/CloseStatus.java | 2 +- .../ee9/websocket/api/ExtensionConfig.java | 2 +- .../jetty/ee9/websocket/api/Frame.java | 2 +- .../ee9/websocket/api/RemoteEndpoint.java | 2 +- .../jetty/ee9/websocket/api/Session.java | 4 +- .../jetty/ee9/websocket/api/StatusCode.java | 2 +- .../jetty/ee9/websocket/api/SuspendToken.java | 2 +- .../ee9}/websocket/api/UpgradeRequest.java | 2 +- .../ee9/websocket/api/UpgradeResponse.java | 2 +- .../ee9/websocket/api/WebSocketAdapter.java | 2 +- .../ee9/websocket/api/WebSocketBehavior.java | 2 +- .../api/WebSocketConnectionListener.java | 2 +- .../ee9/websocket/api/WebSocketContainer.java | 2 +- .../websocket/api/WebSocketFrameListener.java | 2 +- .../ee9/websocket/api/WebSocketListener.java | 2 +- .../api/WebSocketPartialListener.java | 2 +- .../api/WebSocketPingPongListener.java | 2 +- .../ee9/websocket/api/WebSocketPolicy.java | 2 +- .../api/WebSocketSessionListener.java | 2 +- .../ee9/websocket/api/WriteCallback.java | 2 +- .../api/annotations/OnWebSocketClose.java | 4 +- .../api/annotations/OnWebSocketConnect.java | 4 +- .../api/annotations/OnWebSocketError.java | 4 +- .../api/annotations/OnWebSocketFrame.java | 6 +- .../api/annotations/OnWebSocketMessage.java | 6 +- .../websocket/api/annotations/WebSocket.java | 6 +- .../api/annotations/package-info.java | 2 +- .../api/exceptions/BadPayloadException.java | 4 +- .../api/exceptions/CloseException.java | 2 +- .../exceptions/InvalidWebSocketException.java | 8 +- .../exceptions/MessageTooLargeException.java | 4 +- .../exceptions/PolicyViolationException.java | 4 +- .../api/exceptions/ProtocolException.java | 4 +- .../api/exceptions/UpgradeException.java | 2 +- .../api/exceptions/WebSocketException.java | 2 +- .../exceptions/WebSocketTimeoutException.java | 2 +- .../api/exceptions/package-info.java | 2 +- .../ee9}/websocket/api/package-info.java | 2 +- .../jetty/ee9}/websocket/api/util/WSURI.java | 2 +- .../api/util/WebSocketConstants.java | 2 +- .../ee9}/websocket/api/util/package-info.java | 2 +- .../jetty-ee9-websocket-jetty-client}/pom.xml | 24 +- .../src/main/java/module-info.java | 10 +- .../client/ClientUpgradeRequest.java | 8 +- .../client/JettyUpgradeListener.java | 2 +- .../ee9/websocket/client/WebSocketClient.java | 22 +- .../JettyWebSocketClientConfiguration.java | 26 +- .../DelegatedJettyClientUpgradeRequest.java | 6 +- .../DelegatedJettyClientUpgradeResponse.java | 6 +- .../impl/JettyClientUpgradeRequest.java | 8 +- .../ee9}/websocket/client/package-info.java | 2 +- .../org.eclipse.jetty.webapp.Configuration | 1 + .../src/test/java/examples/ClientDemo.java | 8 +- .../test/java/examples/SimpleEchoClient.java | 4 +- .../test/java/examples/SimpleEchoSocket.java | 12 +- .../websocket/client/HttpClientInitTest.java | 2 +- .../client/WebSocketClientBadUriTest.java | 8 +- .../client/WebSocketClientInitTest.java | 2 +- .../test/resources/jetty-logging.properties | 16 + .../jetty-ee9-websocket-jetty-common}/pom.xml | 18 +- .../src/main/java/module-info.java | 10 +- .../common/ExtensionConfigParser.java | 4 +- .../common/JettyExtensionConfig.java | 4 +- .../websocket/common/JettyWebSocketFrame.java | 4 +- .../common/JettyWebSocketFrameHandler.java | 26 +- .../JettyWebSocketFrameHandlerFactory.java | 46 +- .../JettyWebSocketFrameHandlerMetadata.java | 6 +- .../common/JettyWebSocketRemoteEndpoint.java | 12 +- .../ee9}/websocket/common/SessionTracker.java | 8 +- .../websocket/common/WebSocketSession.java | 18 +- .../ee9}/websocket/common/package-info.java | 2 +- .../ee9}/websocket/common/DummyContainer.java | 8 +- .../jetty/ee9/websocket/common/EndPoints.java | 28 +- .../ee9/websocket/common/EventQueue.java | 2 +- .../JettyWebSocketFrameHandlerTest.java | 12 +- .../common/LocalEndpointMetadataTest.java | 8 +- .../common/MessageInputStreamTest.java | 2 +- .../common/MessageOutputStreamTest.java | 2 +- .../common/OutgoingMessageCapture.java | 2 +- .../TestableLeakTrackingBufferPool.java | 2 +- .../endpoints/adapters/AdapterEchoSocket.java | 4 +- .../adapters/AnnotatedEchoSocket.java | 8 +- .../adapters/ListenerEchoSocket.java | 6 +- .../common/invoke/InvokerUtilsTest.java | 2 +- .../common/invoke/NameParamIdentifier.java | 2 +- .../test/resources/jetty-logging.properties | 0 .../jetty-ee9-websocket-jetty-server}/pom.xml | 32 +- .../src/main/java/module-info.java | 20 +- .../server/JettyServerUpgradeRequest.java | 4 +- .../server/JettyServerUpgradeResponse.java | 8 +- .../server/JettyWebSocketCreator.java | 2 +- .../server/JettyWebSocketServerContainer.java | 28 +- .../server/JettyWebSocketServlet.java | 10 +- .../server/JettyWebSocketServletFactory.java | 6 +- .../config/JettyWebSocketConfiguration.java | 20 +- ...yWebSocketServletContainerInitializer.java | 6 +- .../DelegatedServerUpgradeRequest.java | 8 +- .../DelegatedServerUpgradeResponse.java | 8 +- .../JettyServerFrameHandlerFactory.java | 8 +- ...akarta.servlet.ServletContainerInitializer | 0 .../org.eclipse.jetty.webapp.Configuration | 0 .../server/browser/BrowserDebugTool.java | 22 +- .../server/browser/BrowserSocket.java | 16 +- .../resources/browser-debug-tool/index.html | 0 .../resources/browser-debug-tool/main.css | 0 .../resources/browser-debug-tool/websocket.js | 0 .../fuzzingclient.json | 18 + .../fuzzingserver.json | 10 + .../jetty-ee9-websocket-jetty-tests}/pom.xml | 24 +- .../tests/AnnoMaxMessageEndpoint.java | 8 +- .../tests/AnnotatedPartialListenerTest.java | 20 +- .../websocket/tests/CloseInOnOpenTest.java | 16 +- .../tests/CloseTrackingEndpoint.java | 8 +- .../tests/ConcurrentConnectTest.java | 22 +- .../tests/ConnectMessageEndpoint.java | 8 +- .../websocket/tests/ConnectionHeaderTest.java | 12 +- .../ee9/websocket/tests/EchoCreator.java | 8 +- .../ee9}/websocket/tests/EchoSocket.java | 4 +- .../ee9/websocket/tests/ErrorCloseTest.java | 16 +- .../ee9/websocket/tests}/EventSocket.java | 16 +- .../tests/GetAuthHeaderEndpoint.java | 8 +- .../websocket/tests/GracefulCloseTest.java | 12 +- .../tests/JettyClientClassLoaderTest.java | 24 +- .../ee9/websocket/tests/JettyOnCloseTest.java | 14 +- .../JettyWebSocketExtensionConfigTest.java | 16 +- .../tests/JettyWebSocketFilterTest.java | 22 +- .../tests/JettyWebSocketNegotiationTest.java | 20 +- .../tests/JettyWebSocketRestartTest.java | 16 +- .../JettyWebSocketServletAttributeTest.java | 10 +- .../tests/JettyWebSocketServletTest.java | 14 +- .../websocket/tests/JettyWebSocketWebApp.java | 9 +- .../ee9/websocket/tests/LargeDeflateTest.java | 14 +- .../tests/MaxOutgoingFramesTest.java | 16 +- .../ee9/websocket/tests/ParamsEndpoint.java | 8 +- .../tests/PermessageDeflateBufferTest.java | 16 +- .../ProgrammaticWebSocketUpgradeTest.java | 16 +- .../websocket/tests/SimpleStatusServlet.java | 2 +- .../websocket/tests/SingleOnMessageTest.java | 24 +- .../websocket/tests/SuspendResumeTest.java | 20 +- .../tests/UpgradeRequestResponseTest.java | 16 +- .../ee9/websocket/tests/WebAppTester.java | 28 +- .../tests/WebSocketOverHTTP2Test.java | 28 +- .../tests/WebSocketServletExamplesTest.java | 30 +- .../websocket/tests/WebSocketStatsTest.java | 10 +- .../websocket/tests/WebSocketStopTest.java | 20 +- .../tests/autobahn}/JettyAutobahnClient.java | 87 +- .../tests/autobahn}/JettyAutobahnServer.java | 44 +- .../tests/autobahn}/JettyAutobahnSocket.java | 7 +- .../tests/client/BadNetworkTest.java | 24 +- .../tests/client/ClientCloseTest.java | 32 +- .../tests/client/ClientConfigTest.java | 26 +- .../tests/client/ClientConnectTest.java | 26 +- .../client/ClientOpenSessionTracker.java | 8 +- .../tests/client/ClientSessionsTest.java | 30 +- .../tests/client/ClientTimeoutTest.java | 20 +- .../tests/client/ClientWriteThread.java | 10 +- .../tests/client/ConnectFutureTest.java | 30 +- .../tests/client/InvalidUpgradeServlet.java | 2 +- .../tests/client/SlowClientTest.java | 24 +- .../tests/client/WebSocketClientTest.java | 30 +- .../tests/examples/MyAdvancedEchoCreator.java | 8 +- .../tests/examples/MyAdvancedEchoServlet.java | 6 +- .../tests/examples/MyAuthedCreator.java | 8 +- .../tests/examples/MyAuthedServlet.java | 6 +- .../tests/examples/MyBinaryEchoSocket.java | 8 +- .../tests/examples/MyEchoServlet.java | 6 +- .../tests/examples/MyEchoSocket.java | 8 +- .../tests/extensions/ExtensionConfigTest.java | 4 +- .../listeners/AbstractAnnotatedListener.java | 10 +- .../tests/listeners/AbstractListener.java | 6 +- .../tests/listeners/BinaryListeners.java | 12 +- .../tests/listeners/TextListeners.java | 12 +- .../listeners/WebSocketListenerTest.java | 18 +- .../websocket/tests/proxy/WebSocketProxy.java | 18 +- .../tests/proxy/WebSocketProxyTest.java | 30 +- .../tests/server/AbstractCloseEndpoint.java | 6 +- .../tests/server/CloseInOnCloseEndpoint.java | 4 +- .../CloseInOnCloseEndpointNewThread.java | 4 +- .../tests/server/ContainerEndpoint.java | 10 +- .../tests/server/FastCloseEndpoint.java | 6 +- .../tests/server/FastFailEndpoint.java | 4 +- .../tests/server/FrameAnnotationTest.java | 44 +- .../tests/server/FrameListenerTest.java | 30 +- .../tests/server/PartialListenerTest.java | 34 +- .../tests/server/ServerCloseCreator.java | 14 +- .../tests/server/ServerCloseTest.java | 26 +- .../tests/server/ServerConfigTest.java | 30 +- .../tests/server/SlowServerEndpoint.java | 8 +- .../tests/server/SlowServerTest.java | 20 +- .../tests/util/FutureWriteCallback.java | 4 +- .../ee9/websocket/tests/util/WSURITest.java | 4 +- .../test/resources/jetty-logging.properties | 0 .../src/test/resources/keystore.p12 | Bin .../jetty-ee9-websocket-servlet}/pom.xml | 14 +- .../src/main/java/module-info.java | 6 +- .../servlet/WebSocketUpgradeFilter.java | 8 +- jetty-ee9/jetty-ee9-websocket/pom.xml | 30 + jetty-ee9/pom.xml | 418 + .../fcgi/client/http/HttpChannelOverFCGI.java | 205 - .../fcgi/server/HttpTransportOverFCGI.java | 151 - .../jetty/fcgi/server/EmptyServerHandler.java | 31 - .../session/TestHazelcastSessions.java | 183 - jetty-home/pom.xml | 122 +- jetty-home/src/main/resources/bin/jetty.sh | 6 +- .../modules/demo.d/demo-realm.properties | 2 +- .../modules/jolokia/jolokia-realm.properties | 31 - .../TestEndpointMultiplePublishProblem.java | 160 - .../jetty/http2/client/EmptyHttpServlet.java | 29 - .../test/resources/jetty-logging.properties | 5 - .../jetty/http2/client/http/AbstractTest.java | 81 - .../http2/client/http/EmptyServerHandler.java | 36 - .../http/PriorKnowledgeHTTP2OverTLSTest.java | 137 - .../test/resources/jetty-logging.properties | 6 - .../src/test/resources/keystore.p12 | Bin 2565 -> 0 bytes .../http2/server/HttpChannelOverHTTP2.java | 788 - .../http/internal/HttpChannelOverHTTP3.java | 108 - .../internal/HttpTransportOverHTTP3.java | 513 - .../infinispan/EmbeddedQueryManagerTest.java | 128 - .../infinispan/RemoteQueryManagerTest.java | 203 - .../src/test/resources/config.yaml | 4 - .../test/resources/simplelogger.properties | 3 - .../jetty-gcloud-session-manager/pom.xml | 6 +- .../session/GCloudSessionDataStore.java | 10 +- .../GCloudSessionDataStoreFactory.java | 8 +- jetty-integrations/jetty-gcloud/pom.xml | 8 +- jetty-integrations/jetty-hazelcast/pom.xml | 26 +- .../src/main/java/module-info.java | 4 +- .../session/HazelcastSessionDataStore.java | 10 +- .../HazelcastSessionDataStoreFactory.java | 12 +- .../session/SessionDataSerializer.java | 2 +- .../infinispan-common/pom.xml | 6 +- .../infinispan/InfinispanSessionData.java | 3 +- .../InfinispanSessionDataStore.java | 6 +- .../InfinispanSessionDataStoreFactory.java | 8 +- .../InfinispanSessionLegacyConverter.java | 2 +- .../session/infinispan/QueryManager.java | 2 +- .../infinispan-embedded-query/pom.xml | 4 +- .../src/main/assembly/config.xml | 0 .../infinispan/EmbeddedQueryManager.java | 2 +- .../infinispan-embedded/pom.xml | 4 +- .../src/main/assembly/config.xml | 0 .../infinispan-remote-query/pom.xml | 4 +- .../src/main/assembly/config.xml | 0 .../infinispan/RemoteQueryManager.java | 2 +- .../infinispan-remote/pom.xml | 4 +- .../src/main/assembly/config.xml | 0 jetty-integrations/jetty-infinispan/pom.xml | 7 +- .../jetty-memcached-sessions/pom.xml | 29 + .../src/main/java/module-info.java | 2 +- .../session/MemcachedSessionDataMap.java | 6 +- .../MemcachedSessionDataMapFactory.java | 4 +- jetty-integrations/jetty-memcached/pom.xml | 6 +- jetty-integrations/jetty-nosql/pom.xml | 16 +- .../src/main/java/module-info.java | 4 +- .../jetty/nosql/NoSqlSessionDataStore.java | 4 +- .../nosql/mongodb/MongoSessionDataStore.java | 8 +- .../mongodb/MongoSessionDataStoreFactory.java | 10 +- .../jetty/nosql/mongodb/package-info.java | 0 .../org/eclipse/jetty/nosql/package-info.java | 0 jetty-integrations/pom.xml | 22 + .../invoker.properties | 1 - .../src/it/exclude-javax-annotation/pom.xml | 104 - .../exclude-javax-annotation/postbuild.groovy | 24 - .../main/java/org/github/unb/TestServlet.java | 82 - .../src/config/jetty.xml | 40 - .../jetty-memcached-sessions/pom.xml | 83 - .../session/TestMemcachedSessions.java | 153 - jetty-p2/pom.xml | 4 +- .../org.eclipse.jetty.webapp.Configuration | 2 - .../config/modules/rewrite/rewrite-msie.xml | 10 - .../jetty/rewrite/handler/MsieRule.java | 111 - .../jetty/rewrite/handler/MsieSslRule.java | 90 - .../jetty/rewrite/handler/RedirectUtil.java | 63 - .../jetty/rewrite/handler/ValidUrlRule.java | 113 - .../rewrite/handler/AbstractRuleTestCase.java | 112 - .../jetty/rewrite/handler/MsieRuleTest.java | 272 - .../rewrite/handler/MsieSslRuleTest.java | 249 - .../rewrite/handler/ValidUrlRuleTest.java | 121 - .../security/SessionAuthenticationTest.java | 88 - .../jetty/server/HttpChannelOverHttp.java | 737 - .../jetty/server/RequestLogCollection.java | 43 - .../server/ServerConnectionStatistics.java | 32 - .../server/handler/RequestLogHandler.java | 59 - .../gzip/GzipHttpInputInterceptor.java | 98 - .../jetty/server/session/SessionHandler.java | 1718 - .../eclipse/jetty/server/AsyncStressTest.java | 349 - .../server/CharEncodingContextHandler.java | 44 - .../org/eclipse/jetty/server/StressTest.java | 481 - .../server/session/SessionCookieTest.java | 164 - .../server/session/SessionHandlerTest.java | 97 - .../jetty/servlet/DefaultHandlerTest.java | 99 - .../eclipse/jetty/servlets/ConcatServlet.java | 144 - .../servlets/DataRateLimitedServlet.java | 309 - .../org/eclipse/jetty/servlets/PutFilter.java | 367 - .../eclipse/jetty/servlets/WelcomeFilter.java | 76 - .../jetty/servlets/ConcatServletTest.java | 204 - .../servlets/DataRateLimitedServletTest.java | 121 - .../eclipse/jetty/servlets/PutFilterTest.java | 285 - .../jetty/servlets/WelcomeFilterTest.java | 144 - .../jetty-unixsocket-client/pom.xml | 48 - .../src/main/java/module-info.java | 25 - .../HttpClientTransportOverUnixSockets.java | 164 - .../jetty/unixsocket/UnixSocketClient.java | 80 - .../jetty/unixsocket/UnixSocketTest.java | 146 - .../test/resources/jetty-logging.properties | 3 - .../jetty-unixsocket-common/pom.xml | 47 - .../src/main/java/module-info.java | 23 - .../unixsocket/common/UnixSocketEndPoint.java | 55 - .../jetty/unixsocket/common/JnrTest.java | 149 - .../jetty-unixsocket-server/pom.xml | 89 - .../src/main/assembly/config.xml | 28 - .../etc/jetty-unixsocket-forwarded.xml | 18 - .../etc/jetty-unixsocket-http.xml | 13 - .../etc/jetty-unixsocket-http2c.xml | 15 - .../etc/jetty-unixsocket-proxy-protocol.xml | 10 - .../etc/jetty-unixsocket-secure.xml | 12 - .../config-template/etc/jetty-unixsocket.xml | 21 - .../modules/unixsocket-forwarded.mod | 33 - .../modules/unixsocket-http.mod | 25 - .../modules/unixsocket-http2c.mod | 31 - .../modules/unixsocket-prefix.mod | 30 - .../modules/unixsocket-proxy-protocol.mod | 25 - .../modules/unixsocket-secure.mod | 27 - .../modules/unixsocket-suffix.mod | 20 - .../src/main/java/module-info.java | 25 - .../server/UnixSocketConnector.java | 406 - .../unixsocket/UnixSocketProxyServer.java | 83 - .../jetty/unixsocket/UnixSocketServer.java | 83 - .../test/resources/jetty-logging.properties | 3 - jetty-webapp/src/main/java/module-info.java | 39 - .../org.eclipse.jetty.webapp.Configuration | 12 - .../internal/UpgradeHttpServletRequest.java | 659 - .../internal/UpgradeHttpServletResponse.java | 368 - .../test/resources/jetty-logging.properties | 15 - .../src/main/java/module-info.java | 22 - .../org.eclipse.jetty.webapp.Configuration | 1 - .../jetty/websocket/tests/EventSocket.java | 101 - pom.xml | 726 +- tests/jetty-jmh/pom.xml | 4 +- tests/pom.xml | 25 +- tests/test-distribution/pom.xml | 37 +- .../jetty/tests/distribution/BadAppTests.java | 2 +- .../tests/distribution/DistributionTests.java | 59 +- .../distribution/openid/OpenIdProvider.java | 6 +- .../badapp_throwonunavailable_false.xml | 2 +- .../badapp/badapp_throwonunavailable_true.xml | 2 +- .../src/test/resources/realm.properties | 2 +- .../resources/stats-webapp/WEB-INF/web.xml | 2 +- tests/test-jpms/pom.xml | 6 +- .../test-jpms-websocket-core/pom.xml | 2 +- tests/test-sessions/pom.xml | 17 +- .../test-sessions/test-file-sessions/pom.xml | 10 +- .../session/ClusteredOrphanedSessionTest.java | 18 +- .../test-gcloud-sessions/pom.xml | 10 +- .../session/ClusteredOrphanedSessionTest.java | 4 +- .../ClusteredSessionScavengingTest.java | 4 +- .../session/GCloudSessionDataStoreTest.java | 6 +- .../session/GCloudSessionTestSupport.java | 6 +- .../session/InvalidationSessionTest.java | 4 +- .../test-hazelcast-sessions/pom.xml | 10 +- .../session/ClusteredOrphanedSessionTest.java | 4 +- .../ClusteredSessionScavengingTest.java | 4 +- ...lcastClusteredInvalidationSessionTest.java | 4 +- .../HazelcastSessionDataStoreTest.java | 16 +- .../session/HazelcastTestHelper.java | 4 +- .../client/ClientOrphanedSessionTest.java | 4 +- .../client/ClientSessionScavengingTest.java | 4 +- .../client/HazelcastSessionDataStoreTest.java | 16 +- .../test-infinispan-sessions/pom.xml | 10 +- .../session/ClusteredOrphanedSessionTest.java | 2 +- ...steredSerializedSessionScavengingTest.java | 4 +- .../ClusteredSessionScavengingTest.java | 2 +- .../InfinispanFileSessionDataStoreTest.java | 2 +- .../InfinispanSessionDataStoreTest.java | 4 +- .../session/InfinispanTestSupport.java | 2 +- .../{server => }/session/LoggingUtil.java | 2 +- ...ializedInfinispanSessionDataStoreTest.java | 4 +- ...emoteClusteredInvalidationSessionTest.java | 8 +- .../RemoteClusteredSessionScavengingTest.java | 8 +- .../RemoteInfinispanSessionDataStoreTest.java | 20 +- .../remote/RemoteInfinispanTestSupport.java | 4 +- .../test-sessions/test-jdbc-sessions/pom.xml | 8 +- .../ClusteredInvalidationSessionTest.java | 2 +- .../session/ClusteredOrphanedSessionTest.java | 2 +- .../ClusteredSessionMigrationTest.java | 10 +- .../ClusteredSessionScavengingTest.java | 2 +- .../session/JDBCSessionDataStoreTest.java | 2 +- .../{server => }/session/JdbcTestHelper.java | 2 +- .../ReloadedSessionMissingClassTest.java | 8 +- .../session/SessionTableSchemaTest.java | 2 +- .../session/WebAppObjectInSessionTest.java | 2 +- .../test-memcached-sessions/pom.xml | 10 +- .../sessions/CachingSessionDataStoreTest.java | 20 +- .../sessions/MemcachedTestHelper.java | 14 +- .../test-mongodb-sessions/pom.xml | 10 +- .../nosql/mongodb/AttributeNameTest.java | 12 +- .../ClusteredInvalidateSessionTest.java | 4 +- .../mongodb/ClusteredOrphanedSessionTest.java | 4 +- .../ClusteredSessionScavengingTest.java | 4 +- .../mongodb/MongoSessionDataStoreTest.java | 14 +- .../jetty/nosql/mongodb/MongoTestHelper.java | 2 +- .../src/test/resources/realm.properties | 2 +- .../test-sessions-common/pom.xml | 10 +- .../TestHttpChannelCompleteListener.java | 40 - ...tHttpSessionListenerWithWebappClasses.java | 54 - .../server/session/TestSessionHandler.java | 33 - ...tractClusteredInvalidationSessionTest.java | 29 +- .../AbstractClusteredOrphanedSessionTest.java | 8 +- ...bstractClusteredSessionScavengingTest.java | 16 +- .../AbstractSessionTestBase.java} | 4 +- .../AbstractWebAppObjectInSessionTest.java | 21 +- .../jetty/{server => }/session/Foo.java | 2 +- .../session/FooInvocationHandler.java | 2 +- .../SessionTestSupport.java} | 10 +- .../jetty/{server => }/session/TestFoo.java | 2 +- .../session/TestHttpSessionListener.java | 2 +- .../session/TestSessionDataStore.java | 2 +- .../session/TestSessionDataStoreFactory.java | 6 +- .../jetty/session/TestSessionHandler.java | 94 + .../session/WebAppObjectInSessionServlet.java | 2 +- .../session/DefaultSessionCacheTest.java | 498 - .../server/session/DirtyAttributeTest.java | 247 - .../server/session/ImmortalSessionTest.java | 147 - .../server/session/SaveOptimizeTest.java | 607 - .../session/SessionEvictionFailureTest.java | 224 - .../server/session/SessionListenerTest.java | 467 - .../jetty/{server => }/session/AsyncTest.java | 62 +- .../ClientCrossContextSessionTest.java | 10 +- .../{server => }/session/ConcurrencyTest.java | 4 +- .../{server => }/session/CreationTest.java | 91 +- .../session/DeleteUnloadableSessionTest.java | 23 +- .../session/DuplicateCookieTest.java | 64 +- .../{server => }/session/IdleSessionTest.java | 20 +- .../ModifyMaxInactiveIntervalTest.java | 44 +- .../NonClusteredSessionScavengingTest.java | 55 +- .../session/RedirectSessionTest.java | 8 +- .../session/ReentrantRequestSessionTest.java | 18 +- .../session/RemoveSessionTest.java | 12 +- .../session/RequestDispatchedSessionTest.java | 6 +- .../session/RequestScopedSessionSaveTest.java | 8 +- .../SameContextForwardedSessionTest.java | 20 +- .../SessionInvalidateCreateScavengeTest.java | 10 +- .../session/SessionInvalidationTest.java | 10 +- .../session/SessionRenewTest.java | 38 +- tests/test-webapps/pom.xml | 34 +- .../test-cdi-common-webapp/pom.xml | 4 +- tests/test-webapps/test-felix-webapp/pom.xml | 4 +- tests/test-webapps/test-openid-webapp/pom.xml | 10 +- .../test-webapps/test-owb-cdi-webapp/pom.xml | 6 +- ...akarta.servlet.ServletContainerInitializer | 2 +- .../src/main/webapp/WEB-INF/jetty-env.xml | 2 +- .../src/main/webapp/WEB-INF/jetty-web-owb.xml | 2 +- .../test-simple-session-webapp/pom.xml | 4 +- .../test-webapps/test-webapp-rfc2616/pom.xml | 12 +- .../test-webapps/test-weld-cdi-webapp/pom.xml | 4 +- .../src/main/webapp/WEB-INF/jetty-env.xml | 2 +- tests/test-websocket-autobahn/pom.xml | 143 - .../jetty/websocket/tests/AutobahnClient.java | 19 - .../jetty/websocket/tests/AutobahnServer.java | 21 - .../jetty/websocket/tests/AutobahnTests.java | 160 - .../tests/core/TestMessageHandler.java | 82 - .../tests/core/TestWebSocketNegotiator.java | 47 - .../websocket/tests/jakarta/EchoSocket.java | 39 - .../websocket/tests/jakarta/EventSocket.java | 96 - .../tests/jakarta/HostConfigurator.java | 30 - .../websocket/tests/jetty/EchoSocket.java | 37 - .../test/resources/simplelogger.properties | 4 - 3483 files changed, 288353 insertions(+), 63353 deletions(-) delete mode 100644 SECURITY.md delete mode 100644 apache-jsp/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer delete mode 100644 apache-jsp/src/main/resources/META-INF/services/org.apache.juli.logging.Log rename {build-resources => build/build-resources}/jetty-codestyle-eclipse-ide.xml (100%) rename {build-resources => build/build-resources}/jetty-codestyle-intellij.xml (100%) rename {build-resources => build/build-resources}/pom.xml (96%) rename {build-resources => build/build-resources}/src/main/resources/jetty-checkstyle.xml (100%) rename {jetty-unixsocket => build}/pom.xml (53%) rename {scripts => build/scripts}/clirr-gen-master-index.output-foot.html (100%) rename {scripts => build/scripts}/clirr-gen-master-index.output-head.html (100%) rename {scripts => build/scripts}/clirr-gen-master-index.output-html.xslt (100%) rename {scripts => build/scripts}/clirr-gen-master-index.sh (100%) rename {scripts => build/scripts}/git-log-csv.sh (100%) rename {scripts => build/scripts}/looptest.sh (100%) rename {scripts => build/scripts}/query-git-stats.sh (100%) rename {scripts => build/scripts}/release-jetty.sh (100%) delete mode 100644 demos/demo-async-rest/pom.xml delete mode 100644 demos/demo-jetty-webapp/src/test/resources/jetty-logging.properties delete mode 100644 demos/demo-mock-resources/src/main/resources/META-INF/javaxmail.providers delete mode 100644 demos/demo-spec/demo-container-initializer/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer delete mode 100644 jetty-annotations/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/internal/RequestContentAdapter.java delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java delete mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/util/StringContentProvider.java delete mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-client/src/main/java/module-info.java (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-conscrypt-client/src/main/java/module-info.java (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-conscrypt-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-conscrypt-client/src/test/resources/jetty-logging.properties (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-conscrypt-server/src/main/java/module-info.java (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-conscrypt-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-conscrypt-server/src/test/resources/jetty-logging.properties (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-conscrypt-server/src/test/resources/keystore.p12 (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-java-client/src/main/java/module-info.java (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-java-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-java-client/src/test/resources/jetty-logging.properties (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-java-server/src/main/java/module-info.java (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-java-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-java-server/src/test/resources/jetty-logging.properties (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-java-server/src/test/resources/keystore.p12 (100%) rename {jetty-alpn => jetty-core/jetty-alpn}/jetty-alpn-server/src/main/java/module-info.java (100%) rename jetty-core/{jetty-bom => jetty-bom/pom.xml} (54%) rename {jetty-client => jetty-core/jetty-client}/src/main/java/module-info.java (100%) rename {jetty-client => jetty-core/jetty-client}/src/main/java/org/eclipse/jetty/client/api/package-info.java (100%) rename {jetty-client => jetty-core/jetty-client}/src/main/java/org/eclipse/jetty/client/package-info.java (100%) rename {jetty-client => jetty-core/jetty-client}/src/main/java/org/eclipse/jetty/client/util/package-info.java (100%) rename {jetty-deploy => jetty-core/jetty-deploy}/src/main/java/module-info.java (95%) rename {jetty-deploy => jetty-core/jetty-deploy}/src/main/java/org/eclipse/jetty/deploy/bindings/package-info.java (100%) rename {jetty-deploy => jetty-core/jetty-deploy}/src/main/java/org/eclipse/jetty/deploy/graph/package-info.java (100%) rename {jetty-deploy => jetty-core/jetty-deploy}/src/main/java/org/eclipse/jetty/deploy/jmx/package-info.java (100%) rename {jetty-deploy => jetty-core/jetty-deploy}/src/main/java/org/eclipse/jetty/deploy/package-info.java (100%) rename {jetty-deploy => jetty-core/jetty-deploy}/src/main/java/org/eclipse/jetty/deploy/providers/package-info.java (100%) rename {jetty-deploy => jetty-core/jetty-deploy}/src/main/java/org/eclipse/jetty/deploy/util/package-info.java (100%) rename {jetty-fcgi => jetty-core/jetty-fcgi}/fcgi-client/src/main/java/module-info.java (83%) rename {jetty-fcgi => jetty-core/jetty-fcgi}/fcgi-client/src/test/resources/jetty-logging.properties (100%) rename {jetty-fcgi => jetty-core/jetty-fcgi}/fcgi-server/src/main/java/module-info.java (79%) create mode 100644 jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java rename {jetty-fcgi => jetty-core/jetty-fcgi}/fcgi-server/src/test/resources/jetty-logging.properties (100%) rename {jetty-http-spi => jetty-core/jetty-http-spi}/src/main/java/module-info.java (100%) rename {tests => jetty-core}/jetty-http-tools/pom.xml (78%) rename {tests => jetty-core}/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsContainsHeaderKey.java (100%) rename {tests => jetty-core}/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsContainsHeaderValue.java (100%) rename {tests => jetty-core}/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsHeaderValue.java (100%) rename {tests => jetty-core}/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsMatchers.java (100%) rename {tests => jetty-core}/jetty-http-tools/src/test/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsMatchersTest.java (100%) rename {jetty-http => jetty-core/jetty-http}/src/main/java/module-info.java (89%) create mode 100644 jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CachingContentFactory.java create mode 100644 jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCache.java create mode 100644 jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http10FieldPreEncoder.java create mode 100644 jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http11FieldPreEncoder.java rename {jetty-http => jetty-core/jetty-http}/src/main/java/org/eclipse/jetty/http/package-info.java (100%) rename {jetty-http2 => jetty-core/jetty-http2}/http2-client/src/main/java/module-info.java (100%) rename {jetty-http2 => jetty-core/jetty-http2}/http2-common/src/main/java/module-info.java (63%) rename {jetty-http2 => jetty-core/jetty-http2}/http2-common/src/test/resources/jetty-logging.properties (100%) rename {jetty-http2 => jetty-core/jetty-http2}/http2-hpack/src/main/java/module-info.java (94%) rename {jetty-http2 => jetty-core/jetty-http2}/http2-hpack/src/test/resources/jetty-logging.properties (100%) rename {jetty-http2 => jetty-core/jetty-http2}/http2-http-client-transport/src/main/java/module-info.java (100%) rename {jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http => jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal}/HttpChannelOverHTTP2.java (97%) rename {jetty-http2 => jetty-core/jetty-http2}/http2-server/src/main/java/module-info.java (100%) create mode 100644 jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpChannelOverHTTP2.java create mode 100644 jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java create mode 100644 jetty-core/jetty-http2/jetty-http2-tests/pom.xml rename {jetty-http2/http2-server => jetty-core/jetty-http2/jetty-http2-tests}/src/test/resources/jetty-logging.properties (73%) rename {jetty-http3 => jetty-core/jetty-http3}/http3-client/src/main/java/module-info.java (100%) rename {jetty-http3 => jetty-core/jetty-http3}/http3-common/src/main/java/module-info.java (100%) rename {jetty-http3 => jetty-core/jetty-http3}/http3-http-client-transport/src/main/java/module-info.java (100%) rename {jetty-http3 => jetty-core/jetty-http3}/http3-qpack/src/main/java/module-info.java (100%) rename {jetty-http3 => jetty-core/jetty-http3}/http3-qpack/src/test/resources/jetty-logging.properties (100%) rename {jetty-http3 => jetty-core/jetty-http3}/http3-server/src/main/java/module-info.java (100%) create mode 100644 jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java rename {jetty-http3 => jetty-core/jetty-http3}/http3-tests/src/test/resources/jetty-logging.properties (81%) rename {jetty-io => jetty-core/jetty-io}/src/main/java/module-info.java (100%) rename {jetty-io => jetty-core/jetty-io}/src/main/java/org/eclipse/jetty/io/package-info.java (100%) rename {jetty-io => jetty-core/jetty-io}/src/main/java/org/eclipse/jetty/io/ssl/package-info.java (100%) rename {jetty-jmx => jetty-core/jetty-jmx}/src/main/java/module-info.java (100%) rename {jetty-jmx => jetty-core/jetty-jmx}/src/main/java/org/eclipse/jetty/jmx/package-info.java (100%) rename {jetty-jndi => jetty-core/jetty-jndi}/src/main/java/module-info.java (92%) rename {jetty-jndi => jetty-core/jetty-jndi}/src/main/java/org/eclipse/jetty/jndi/factories/package-info.java (100%) rename {jetty-jndi => jetty-core/jetty-jndi}/src/main/java/org/eclipse/jetty/jndi/java/package-info.java (100%) rename {jetty-jndi => jetty-core/jetty-jndi}/src/main/java/org/eclipse/jetty/jndi/local/package-info.java (100%) rename {jetty-jndi => jetty-core/jetty-jndi}/src/main/java/org/eclipse/jetty/jndi/package-info.java (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-client/src/main/java/module-info.java (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-client/src/test/resources/jetty-logging.properties (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-client/src/test/resources/keystore.p12 (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-common/src/main/java/module-info.java (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-common/src/main/java/org/eclipse/jetty/quic/common/package-info.java (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-common/pom.xml (92%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-common/src/main/java/module-info.java (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-foreign-incubator/pom.xml (95%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-foreign-incubator/src/main/java/module-info.java (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/sockaddr.java (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-foreign-incubator/src/main/resources/META-INF/services/org.eclipse.jetty.quic.quiche.QuicheBinding (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java (98%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/jetty-logging.properties (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/keystore.p12 (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-jna/pom.xml (94%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-jna/src/main/java/module-info.java (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/sockaddr.java (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-jna/src/main/resources/META-INF/services/org.eclipse.jetty.quic.quiche.QuicheBinding (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-jna/src/test/resources/jetty-logging.properties (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-quiche/quic-quiche-jna/src/test/resources/keystore.p12 (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-server/src/main/java/module-info.java (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-server/src/test/resources/jetty-logging.properties (100%) rename {jetty-quic => jetty-core/jetty-quic}/quic-server/src/test/resources/keystore.p12 (100%) rename {jetty-rewrite => jetty-core/jetty-rewrite}/src/main/config/etc/jetty-rewrite.xml (100%) rename {jetty-rewrite => jetty-core/jetty-rewrite}/src/main/java/module-info.java (95%) create mode 100644 jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/InvalidURIRule.java rename {jetty-rewrite => jetty-core/jetty-rewrite}/src/main/java/org/eclipse/jetty/rewrite/handler/package-info.java (100%) create mode 100644 jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTest.java create mode 100644 jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/InvalidURIRuleTest.java create mode 100644 jetty-core/jetty-rewrite/src/test/resources/jetty-logging.properties rename {jetty-rewrite => jetty-core/jetty-rewrite}/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml (99%) rename {jetty-server => jetty-core/jetty-server}/src/main/config/etc/sessions/file/session-store.xml (100%) rename {jetty-server => jetty-core/jetty-server}/src/main/config/etc/sessions/jdbc/session-store.xml (100%) create mode 100644 jetty-core/jetty-server/src/main/config/modules/test-keystore/test-keystore.p12 rename {jetty-server => jetty-core/jetty-server}/src/main/java/module-info.java (82%) create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Components.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionMetaData.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Content.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ContentProcessor.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Context.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FutureFormFields.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpStream.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextRequest.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextResponse.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorProcessor.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxiedRequestHandler.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipRequest.java rename jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java => jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipResponse.java (59%) rename jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java => jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/HeaderWrappingRequest.java (56%) rename {jetty-server => jetty-core/jetty-server}/src/main/java/org/eclipse/jetty/server/handler/gzip/package-info.java (100%) create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java rename {jetty-server => jetty-core/jetty-server}/src/main/java/org/eclipse/jetty/server/handler/jmx/package-info.java (100%) rename {jetty-server => jetty-core/jetty-server}/src/main/java/org/eclipse/jetty/server/handler/package-info.java (100%) create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/ResponseHttpFields.java create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/AbstractHandlerMBean.java rename {jetty-server => jetty-core/jetty-server}/src/main/java/org/eclipse/jetty/server/jmx/package-info.java (93%) rename {jetty-server => jetty-core/jetty-server}/src/main/java/org/eclipse/jetty/server/package-info.java (100%) create mode 100644 jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/FixJPMS.java create mode 100644 jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ContentTest.java create mode 100644 jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomRequestLogTest.java create mode 100644 jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorProcessorTest.java create mode 100644 jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpChannelTest.java create mode 100644 jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/MockConnectionMetaData.java create mode 100644 jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/MockHttpStream.java create mode 100644 jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DelayedHandlerTest.java create mode 100644 jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/EchoHandler.java create mode 100644 jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/HelloHandler.java rename {jetty-servlet/src/test/java/org/eclipse/jetty/servlet => jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/gzip}/GzipHandlerTest.java (69%) create mode 100644 jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/jmh/HandlerBenchmark.java create mode 100644 jetty-core/jetty-session/pom.xml create mode 100644 jetty-core/jetty-session/src/main/java/module-info.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/AbstractSessionCache.java (92%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/AbstractSessionCacheFactory.java (88%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/AbstractSessionDataStore.java (99%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/AbstractSessionDataStoreFactory.java (97%) create mode 100644 jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/AbstractSessionManager.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/CachingSessionDataStore.java (99%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/CachingSessionDataStoreFactory.java (89%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/DatabaseAdaptor.java (99%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/DefaultSessionCache.java (92%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/DefaultSessionCacheFactory.java (83%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/DefaultSessionIdManager.java (84%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/FileSessionDataStore.java (99%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/FileSessionDataStoreFactory.java (93%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/HouseKeeper.java (93%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/JDBCSessionDataStore.java (99%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/JDBCSessionDataStoreFactory.java (92%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/NullSessionCache.java (85%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/NullSessionCacheFactory.java (87%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/NullSessionDataStore.java (98%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/NullSessionDataStoreFactory.java (85%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/Session.java (63%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/SessionCache.java (91%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/SessionCacheFactory.java (86%) create mode 100644 jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/SessionConfig.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/SessionContext.java (70%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/SessionData.java (99%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/SessionDataMap.java (97%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/SessionDataMapFactory.java (94%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/SessionDataStore.java (98%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/SessionDataStoreFactory.java (86%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session}/SessionIdManager.java (81%) create mode 100644 jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/SessionInactivityTimer.java create mode 100644 jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/SessionManager.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/UnreadableSessionDataException.java (97%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/UnwriteableSessionDataException.java (96%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/main/java/org/eclipse/jetty}/session/package-info.java (93%) rename {tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/test/java/org/eclipse/jetty}/session/AbstractSessionCacheTest.java (65%) rename {tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/test/java/org/eclipse/jetty}/session/AbstractSessionDataStoreTest.java (60%) create mode 100644 jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/AbstractSessionManagerTest.java create mode 100644 jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/DefaultSessionCacheTest.java create mode 100644 jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/DefaultSessionIdManagerTest.java create mode 100644 jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/DirtyAttributeTest.java rename {tests/test-sessions/test-file-sessions/src/test/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/test/java/org/eclipse/jetty}/session/FileSessionDataStoreTest.java (98%) rename tests/test-sessions/test-file-sessions/src/test/java/org/eclipse/jetty/server/session/TestFileSessions.java => jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/FileSessionsTest.java (85%) rename {tests/test-sessions/test-file-sessions/src/test/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/test/java/org/eclipse/jetty}/session/FileTestHelper.java (99%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/test/java/org/eclipse/jetty}/session/HouseKeeperTest.java (95%) rename {tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server => jetty-core/jetty-session/src/test/java/org/eclipse/jetty}/session/NullSessionCacheTest.java (65%) create mode 100644 jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/SessionListenerTest.java create mode 100644 jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/SimpleSessionHandler.java create mode 100644 jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/SimpleSessionHandlerTest.java create mode 100644 jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/TestableRequest.java create mode 100644 jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/TestableSessionDataStore.java create mode 100644 jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/TestableSessionManager.java rename {tests/test-sessions/test-sessions-common/src/main => jetty-core/jetty-session/src/test}/resources/Foo.clazz (100%) rename {tests/test-sessions/test-sessions-common/src/main => jetty-core/jetty-session/src/test}/resources/Foo.java (100%) rename {tests/test-sessions/test-sessions-common/src/main => jetty-core/jetty-session/src/test}/resources/Proxyable.clazz (100%) rename {tests/test-sessions/test-sessions-common/src/main => jetty-core/jetty-session/src/test}/resources/Proxyable.java (100%) rename {tests/test-sessions/test-sessions-common/src/main => jetty-core/jetty-session/src/test}/resources/ProxyableFactory.clazz (100%) rename {tests/test-sessions/test-sessions-common/src/main => jetty-core/jetty-session/src/test}/resources/ProxyableFactory.java (100%) rename {tests/test-sessions/test-sessions-common/src/main => jetty-core/jetty-session/src/test}/resources/ProxyableInvocationHandler.clazz (100%) rename {tests/test-sessions/test-sessions-common/src/main => jetty-core/jetty-session/src/test}/resources/ProxyableInvocationHandler.java (100%) rename {jetty-slf4j-impl => jetty-core/jetty-slf4j-impl}/src/main/java/module-info.java (100%) create mode 100644 jetty-core/jetty-start/src/main/java/org/eclipse/jetty/start/Environment.java rename {jetty-start => jetty-core/jetty-start}/src/main/java/org/eclipse/jetty/start/package-info.java (100%) create mode 100644 jetty-core/jetty-start/src/test/java/org/eclipse/jetty/start/usecases/EnvironmentsTest.java rename {jetty-start => jetty-core/jetty-start}/src/test/resources/dist-home/modules/base.mod (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/dist-home/start.ini (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/empty.home/start.ini (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/extra-jetty-dirs/logging/start.ini (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/hb.1/base/start.d/jmx.ini (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/hb.1/base/start.d/logging.ini (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/hb.1/base/start.ini (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/hb.1/home/start.d/jmx.ini (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/hb.1/home/start.d/logging.ini (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/hb.1/home/start.ini (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/jetty home with spaces/lib/example of a library with spaces.jar (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/jetty home with spaces/modules/base.mod (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/jetty home with spaces/start.ini (100%) rename {jetty-start => jetty-core/jetty-start}/src/test/resources/usecases/minimal-start-ini/start.ini (100%) rename {jetty-unixdomain-server => jetty-core/jetty-unixdomain-server}/src/main/java/module-info.java (100%) rename {jetty-util-ajax => jetty-core/jetty-util-ajax}/src/main/java/module-info.java (100%) rename {jetty-util-ajax => jetty-core/jetty-util-ajax}/src/main/java/org/eclipse/jetty/util/ajax/package-info.java (100%) rename {jetty-util => jetty-core/jetty-util}/src/main/java/module-info.java (100%) create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Blocking.java create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/CharsetStringBuilder.java create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback_State.puml rename {jetty-util => jetty-core/jetty-util}/src/main/java/org/eclipse/jetty/util/annotation/package-info.java (100%) create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/Environment.java rename {jetty-util => jetty-core/jetty-util}/src/main/java/org/eclipse/jetty/util/component/package-info.java (100%) rename {jetty-util => jetty-core/jetty-util}/src/main/java/org/eclipse/jetty/util/package-info.java (100%) rename {jetty-util => jetty-core/jetty-util}/src/main/java/org/eclipse/jetty/util/preventers/package-info.java (100%) create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathCollators.java rename {jetty-util => jetty-core/jetty-util}/src/main/java/org/eclipse/jetty/util/resource/package-info.java (100%) rename {jetty-util => jetty-core/jetty-util}/src/main/java/org/eclipse/jetty/util/security/package-info.java (100%) rename {jetty-util => jetty-core/jetty-util}/src/main/java/org/eclipse/jetty/util/ssl/package-info.java (100%) rename {jetty-util => jetty-core/jetty-util}/src/main/java/org/eclipse/jetty/util/statistic/package-info.java (100%) create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/SerializedInvoker.java rename {jetty-util => jetty-core/jetty-util}/src/main/java/org/eclipse/jetty/util/thread/package-info.java (100%) create mode 100644 jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/AttributesTest.java create mode 100644 jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingTest.java create mode 100644 jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/CharsetStringBuilderTest.java create mode 100644 jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/FieldsTest.java create mode 100644 jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerNestedTest.java create mode 100644 jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/SerializedInvokerTest.java rename {jetty-util => jetty-core/jetty-util}/src/test/resources/TestData/test/alphabet (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/TestData/test/numbers (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/TestData/test/subdir/alphabet (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/TestData/test/subdir/numbers (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/TestData/test/subdir/subsubdir/alphabet (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/TestData/test/subdir/subsubdir/numbers (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/org/eclipse/jetty/util/resource/four/four (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/org/eclipse/jetty/util/resource/one/1.txt (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/org/eclipse/jetty/util/resource/one/dir/1.txt (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/org/eclipse/jetty/util/resource/resource.txt (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/org/eclipse/jetty/util/resource/three/2.txt (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/org/eclipse/jetty/util/resource/three/3.txt (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/org/eclipse/jetty/util/resource/three/dir/3.txt (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/org/eclipse/jetty/util/resource/two/1.txt (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/org/eclipse/jetty/util/resource/two/2.txt (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/org/eclipse/jetty/util/resource/two/dir/2.txt (100%) rename {jetty-util => jetty-core/jetty-util}/src/test/resources/resource.txt (100%) rename {jetty-websocket => jetty-core/jetty-websocket}/websocket-core-client/src/main/java/module-info.java (100%) rename {jetty-websocket => jetty-core/jetty-websocket}/websocket-core-common/src/main/java/module-info.java (56%) rename {jetty-websocket => jetty-core/jetty-websocket}/websocket-core-server/src/main/java/module-info.java (100%) create mode 100644 jetty-core/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/HttpFieldsWrapper.java create mode 100644 jetty-core/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/WebSocketHttpFieldsWrapper.java rename {tests/test-websocket-autobahn => jetty-core/jetty-websocket/websocket-core-tests}/fuzzingclient.json (100%) rename {tests/test-websocket-autobahn => jetty-core/jetty-websocket/websocket-core-tests}/fuzzingserver.json (100%) create mode 100644 jetty-core/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketEchoTest.java rename {tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/core => jetty-core/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn}/AutobahnFrameHandler.java (94%) rename tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/AutobahnUtils.java => jetty-core/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnTests.java (68%) rename {tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/core => jetty-core/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn}/CoreAutobahnClient.java (83%) rename {tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/core => jetty-core/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn}/CoreAutobahnServer.java (75%) rename {jetty-xml => jetty-core/jetty-xml}/src/main/java/module-info.java (100%) rename {jetty-xml => jetty-core/jetty-xml}/src/main/java/org/eclipse/jetty/xml/package-info.java (100%) create mode 100644 jetty-core/pom.xml delete mode 100644 jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/DebugListenerBinding.java delete mode 100644 jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java delete mode 100644 jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/jmx/WebAppProviderMBean.java delete mode 100644 jetty-deploy/src/test/java/org/eclipse/jetty/deploy/BadAppDeployTest.java delete mode 100644 jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentTempDirTest.java delete mode 100644 jetty-deploy/src/test/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBindingTest.java delete mode 100644 jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/WebAppProviderTest.java rename {jetty-annotations => jetty-ee9/jetty-ee9-annotations}/pom.xml (85%) rename {jetty-annotations => jetty-ee9/jetty-ee9-annotations}/src/main/java/module-info.java (68%) rename {jetty-annotations/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-annotations/src/main/java/org/eclipse/jetty/ee9}/annotations/package-info.java (93%) create mode 100644 jetty-ee9/jetty-ee9-annotations/src/main/resources/META-INF/services/org.eclipse.jetty.ee9.webapp.Configuration rename {jetty-ant => jetty-ee9/jetty-ee9-ant}/pom.xml (76%) rename {jetty-ant/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-ant/src/main/java/org/eclipse/jetty/ee9}/ant/package-info.java (94%) rename {jetty-ant/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-ant/src/main/java/org/eclipse/jetty/ee9}/ant/types/package-info.java (93%) rename {jetty-ant/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-ant/src/main/java/org/eclipse/jetty/ee9}/ant/utils/package-info.java (93%) rename {jetty-ant => jetty-ee9/jetty-ee9-ant}/src/test/resources/foo/index.html (100%) rename {jetty-ant => jetty-ee9/jetty-ee9-ant}/src/test/resources/foo/jsp/index.html (100%) rename {apache-jsp => jetty-ee9/jetty-ee9-apache-jsp}/pom.xml (78%) rename {apache-jsp => jetty-ee9/jetty-ee9-apache-jsp}/src/main/config/modules/apache-jsp.mod (100%) rename {apache-jsp => jetty-ee9/jetty-ee9-apache-jsp}/src/main/java/module-info.java (77%) rename {apache-jsp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-apache-jsp/src/main/java/org/eclipse/jetty/ee9}/apache/jsp/JettyJasperInitializer.java (98%) rename {apache-jsp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-apache-jsp/src/main/java/org/eclipse/jetty/ee9}/apache/jsp/JettyTldPreScanned.java (98%) rename {apache-jsp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-apache-jsp/src/main/java/org/eclipse/jetty/ee9}/apache/jsp/JuliLog.java (99%) rename {apache-jsp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-apache-jsp/src/main/java/org/eclipse/jetty/ee9}/jsp/JettyJspServlet.java (99%) create mode 100644 jetty-ee9/jetty-ee9-apache-jsp/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer create mode 100644 jetty-ee9/jetty-ee9-apache-jsp/src/main/resources/META-INF/services/org.apache.juli.logging.Log rename {apache-jsp/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-apache-jsp/src/test/java/org/eclipse/jetty/ee9}/jsp/TestJettyJspServlet.java (96%) rename {apache-jsp/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-apache-jsp/src/test/java/org/eclipse/jetty/ee9}/jsp/TestJettyTldPreScanned.java (93%) rename {apache-jsp/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-apache-jsp/src/test/java/org/eclipse/jetty/ee9}/jsp/TestJspFileNameToClass.java (95%) rename {apache-jsp => jetty-ee9/jetty-ee9-apache-jsp}/src/test/resources/META-INF/foo-taglib.tld (100%) rename {apache-jsp => jetty-ee9/jetty-ee9-apache-jsp}/src/test/resources/base/dir/empty.txt (100%) rename {apache-jsp => jetty-ee9/jetty-ee9-apache-jsp}/src/test/resources/base/foo.jsp (100%) rename {apache-jsp => jetty-ee9/jetty-ee9-apache-jsp}/src/test/resources/taglib.jar (100%) create mode 100644 jetty-ee9/jetty-ee9-bom/pom.xml rename {jetty-cdi => jetty-ee9/jetty-ee9-cdi}/pom.xml (83%) rename {jetty-cdi => jetty-ee9/jetty-ee9-cdi}/src/main/java/module-info.java (76%) rename {demos/demo-async-rest/demo-async-rest-jar => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-jar}/pom.xml (78%) rename {demos/demo-async-rest/demo-async-rest-jar/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-jar/src/main/java/org/eclipse/jetty/ee9}/demos/AbstractRestServlet.java (99%) rename {demos/demo-async-rest/demo-async-rest-jar/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-jar/src/main/java/org/eclipse/jetty/ee9}/demos/AsyncRestServlet.java (99%) rename {demos/demo-async-rest/demo-async-rest-jar/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-jar/src/main/java/org/eclipse/jetty/ee9}/demos/SerialRestServlet.java (98%) rename {demos/demo-async-rest/demo-async-rest-jar => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-jar}/src/main/resources/META-INF/resources/asyncrest.html (100%) rename {demos/demo-async-rest/demo-async-rest-jar => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-jar}/src/main/resources/META-INF/resources/asyncrest/green.png (100%) rename {demos/demo-async-rest/demo-async-rest-jar => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-jar}/src/main/resources/META-INF/resources/asyncrest/red.png (100%) rename {demos/demo-async-rest/demo-async-rest-jar => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-jar}/src/main/resources/META-INF/web-fragment.xml (100%) rename {demos/demo-async-rest/demo-async-rest-server => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-server}/pom.xml (59%) rename {demos/demo-async-rest/demo-async-rest-server/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-server/src/main/java/org/eclipse/jetty/ee9}/demos/AsyncRestServer.java (95%) rename {demos/demo-async-rest/demo-async-rest-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-webapp}/pom.xml (69%) rename {demos/demo-async-rest/demo-async-rest-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-webapp}/src/main/config/modules/demo-async-rest.mod (100%) rename {demos/demo-async-rest/demo-async-rest-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-webapp}/src/main/webapp/META-INF/MANIFEST.MF (100%) rename {demos/demo-async-rest/demo-async-rest-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-webapp}/src/main/webapp/WEB-INF/jetty-web.xml (82%) rename {demos/demo-async-rest/demo-async-rest-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-webapp}/src/main/webapp/WEB-INF/web.xml (100%) rename {demos/demo-async-rest/demo-async-rest-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-webapp}/src/main/webapp/demo.css (100%) rename {demos/demo-async-rest/demo-async-rest-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-webapp}/src/main/webapp/index.html (100%) rename {demos/demo-async-rest/demo-async-rest-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/demo-ee9-async-rest-webapp}/src/main/webapp/small_powered_by.gif (100%) create mode 100644 jetty-ee9/jetty-ee9-demos/demo-ee9-async-rest/pom.xml rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/pom.xml (67%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/prodDb.properties (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/prodDb.script (100%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/AsyncEchoServlet.java (99%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/DumpServlet.java (98%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/ExampleServer.java (94%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/ExampleServerXml.java (97%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/ExampleUtil.java (98%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/FastFileServer.java (98%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/FileServer.java (96%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/FileServerXml.java (98%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/HelloHandler.java (98%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/HelloServlet.java (97%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/HelloSessionServlet.java (98%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/HelloWorld.java (98%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/Http2Server.java (95%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/JarServer.java (90%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/JettyDemos.java (99%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/LikeJettyXml.java (95%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/ManyConnectors.java (99%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/ManyContexts.java (98%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/ManyHandlers.java (99%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/ManyServletContexts.java (92%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/MinimalServlets.java (96%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/OneConnector.java (97%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/OneContext.java (97%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/OneHandler.java (96%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/OneServletContext.java (94%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/OneServletContextJmxStats.java (92%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/OneServletContextWithSession.java (88%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/OneWebApp.java (94%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/OneWebAppWithJsp.java (95%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/ProxyServer.java (87%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/RewriteServer.java (95%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/SecuredHelloHandler.java (93%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/ServerWithAnnotations.java (81%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/ServerWithJMX.java (97%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/ServerWithJNDI.java (84%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/SimplestServer.java (97%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/SplitFileServer.java (99%) rename {demos/embedded/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/main/java/org/eclipse/jetty/ee9}/demos/WebSocketServer.java (78%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/other/content.jar (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/demo/demo-realm.properties (89%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/demo/webdefault.xml (97%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/push.html (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile00.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile01.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile02.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile03.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile04.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile05.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile06.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile07.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile08.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile09.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile10.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile11.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile12.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile13.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile14.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile15.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile16.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile17.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile18.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile19.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile20.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile21.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile22.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile23.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile24.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile25.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile26.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile27.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile28.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile29.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile30.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile31.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile32.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile33.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile34.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile35.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile36.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile37.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile38.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile39.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile40.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile41.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile42.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile43.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile44.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile45.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile46.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile47.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile48.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/pushed/tile49.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/readme.txt (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile00.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile01.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile02.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile03.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile04.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile05.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile06.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile07.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile08.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile09.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile10.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile11.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile12.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile13.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile14.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile15.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile16.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile17.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile18.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile19.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile20.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile21.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile22.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile23.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile24.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile25.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile26.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile27.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile28.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile29.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile30.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile31.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile32.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile33.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile34.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile35.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile36.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile37.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile38.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile39.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile40.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile41.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile42.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile43.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile44.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile45.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile46.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile47.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile48.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/docroot/tiles/tile49.jpg (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/etc/keystore.p12 (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/etc/realm.properties (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/exampleserver.xml (93%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/fileserver.xml (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/java-util-logging.properties (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/jetty-logging.properties (90%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/main/resources/logback-access.xml (100%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/AbstractEmbeddedTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/ExampleServerTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/ExampleServerXmlTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/FastFileServerTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/FileServerTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/FileServerXmlTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/JarServerTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/LikeJettyXmlTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/ManyConnectorsTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/ManyContextsTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/ManyHandlersTest.java (99%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/ManyServletContextsTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/MinimalServletsTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/OneConnectorTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/OneContextTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/OneHandlerTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/OneServletContextJmxStatsTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/OneServletContextTest.java (99%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/OneServletContextWithSessionTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/OneWebAppTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/OneWebAppWithJspTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/ProxyServerTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/RewriteServerTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/SecuredHelloHandlerTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/ServerUtil.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/ServerWithAnnotationsTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/ServerWithJMXTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/ServerWithJNDITest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/SimplestServerTest.java (97%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/SplitFileServerTest.java (98%) rename {demos/embedded/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/java/org/eclipse/jetty/ee9}/demos/WebSocketServerTest.java (85%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/test/resources/dir0/test0.txt (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/test/resources/dir1/test1.txt (100%) rename {demos/embedded => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded}/src/test/resources/jetty-logging.properties (89%) rename {demos/demo-spec/demo-spec-webapp/src/etc => jetty-ee9/jetty-ee9-demos/demo-ee9-embedded/src/test/resources}/realm.properties (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/pom.xml (86%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/config/modules/demo-jaas.mod (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/config/modules/demo.d/demo-jaas.xml (92%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/config/modules/demo.d/demo-login.conf (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/config/modules/demo.d/demo-login.properties (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/webapp/WEB-INF/jetty-web.xml (82%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/webapp/WEB-INF/web.xml (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/webapp/auth.html (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/webapp/authfail.html (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/webapp/demo.css (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/webapp/index.html (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/webapp/login.html (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/webapp/logout.jsp (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/webapp/small_powered_by.gif (100%) rename {demos/demo-jaas-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jaas-webapp}/src/main/webapp/stylesheet.css (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/jetty-chat.jmx (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/pom.xml (88%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/assembly/embedded-jetty-web-for-webbundle.xml (98%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/assembly/web-bundle.xml (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/config/modules/demo-jetty.mod (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/config/modules/demo-moved-context.mod (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/config/modules/demo-rewrite.mod (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/config/modules/demo.d/demo-jetty-override-web.xml (93%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/config/modules/demo.d/demo-jetty.xml (95%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/config/modules/demo.d/demo-moved-context.xml (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/config/modules/demo.d/demo-rewrite-rules.xml (100%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/AddListServletRequestListener.java (98%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/ChatServlet.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/CookieDump.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/DispatchServlet.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/Dump.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/HelloWorld.java (98%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/JakartaWebSocketChat.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/LoginServlet.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/RegTest.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/RewriteServlet.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/SecureModeServlet.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/SessionDump.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/TestFilter.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/TestListener.java (99%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/TestServlet.java (98%) rename {demos/demo-jetty-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/main/java/org/example}/WebSocketChatServlet.java (79%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/WEB-INF/jetty-web.xml (81%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/WEB-INF/web.xml (88%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/auth.html (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/auth/file.txt (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/auth/relax.txt (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/auth2/index.html (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/cgi-bin/hello.sh (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/chat/index.html (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/d.txt (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/da.txt (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/da.txt.gz (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/dat.txt (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/data.txt (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/data.txt.gz (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/demo.css (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/error404.html (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/favicon.ico (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/index.html (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/jakarta.websocket/index.html (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/logon.html (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/logonError.html (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/remote.html (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/rewrite/index.html (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/rewrite/info.html (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/small_powered_by.gif (100%) rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/main/webapp/ws/index.html (100%) rename {demos/demo-jetty-webapp/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/test/java/org/eclipse/jetty/ee9}/ChatServletTest.java (95%) rename {demos/demo-jetty-webapp/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/test/java/org/eclipse/jetty/ee9}/DispatchServletTest.java (91%) rename {demos/demo-jetty-webapp/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/test/java/org/eclipse/jetty/ee9}/TestServer.java (95%) create mode 100644 jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp/src/test/resources/jetty-logging.properties rename {demos/demo-jetty-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jetty-webapp}/src/test/resources/test-realm.properties (89%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/pom.xml (84%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/config/modules/demo-jndi.mod (100%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/config/modules/demo.d/demo-jndi.xml (93%) rename {demos/demo-jndi-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp/src/main/java/org/example}/JNDITest.java (99%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/templates/env-definitions.xml (94%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/templates/jetty-test-jndi-header.xml (84%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/templates/plugin-context-header.xml (78%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/webapp/WEB-INF/jetty-env.xml (94%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/webapp/WEB-INF/jetty-web.xml (82%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/webapp/WEB-INF/web.xml (79%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/webapp/demo.css (100%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/webapp/index.html (100%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/webapp/small_powered_by.gif (100%) rename {demos/demo-jndi-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jndi-webapp}/src/main/webapp/stylesheet.css (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/pom.xml (91%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/assembly/web-bundle.xml (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/config/modules/demo-jsp.mod (100%) rename {demos/demo-jsp-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp/src/main/java/org/example}/Counter.java (97%) rename {demos/demo-jsp-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp/src/main/java/org/example}/Date2Tag.java (98%) rename {demos/demo-jsp-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp/src/main/java/org/example}/DateTag.java (99%) rename {demos/demo-jsp-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp/src/main/java/org/example}/TagListener.java (99%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/WEB-INF/acme-taglib.tld (86%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/WEB-INF/acme-taglib2.tld (95%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/WEB-INF/jetty-web.xml (82%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/WEB-INF/tags/panel.tag (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/WEB-INF/web.xml (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/bean1.jsp (78%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/bean2.jsp (78%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/demo.css (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/dump.jsp (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/expr.jsp (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/foo/foo.jsp (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/index.jsp (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/jstl.jsp (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/small_powered_by.gif (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/tag.jsp (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/tag2.jsp (100%) rename {demos/demo-jsp-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-jsp-webapp}/src/main/webapp/tagfile.jsp (100%) rename {demos/demo-mock-resources => jetty-ee9/jetty-ee9-demos/demo-ee9-mock-resources}/pom.xml (81%) rename {demos/demo-mock-resources => jetty-ee9/jetty-ee9-demos/demo-ee9-mock-resources}/src/main/config/modules/demo-mock-resources.mod (100%) rename {demos/demo-mock-resources/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-mock-resources/src/main/java/org/example}/MockDataSource.java (98%) rename {demos/demo-mock-resources/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-mock-resources/src/main/java/org/example}/MockTransport.java (98%) rename {demos/demo-mock-resources/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-mock-resources/src/main/java/org/example}/MockUserTransaction.java (98%) create mode 100644 jetty-ee9/jetty-ee9-demos/demo-ee9-mock-resources/src/main/resources/META-INF/javaxmail.providers rename {demos/demo-proxy-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-proxy-webapp}/pom.xml (82%) rename {demos/demo-proxy-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-proxy-webapp}/src/main/config/modules/demo-proxy.mod (100%) rename {demos/demo-proxy-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-proxy-webapp}/src/main/webapp/META-INF/MANIFEST.MF (100%) rename {demos/demo-proxy-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-proxy-webapp}/src/main/webapp/WEB-INF/jetty-web.xml (84%) rename {demos/demo-proxy-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-proxy-webapp}/src/main/webapp/WEB-INF/web.xml (100%) rename {demos/demo-proxy-webapp/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-demos/demo-ee9-proxy-webapp/src/test/java/org/eclipse/jetty/ee9}/demos/ProxyWebAppTest.java (97%) rename {demos/demo-proxy-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-proxy-webapp}/src/test/resources/jetty-logging.properties (100%) rename {demos/demo-simple-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-simple-webapp}/pom.xml (65%) rename {demos/demo-simple-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-simple-webapp}/src/main/config/modules/demo-simple.mod (100%) rename {demos/demo-simple-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-simple-webapp}/src/main/webapp/WEB-INF/web.xml (100%) rename {demos/demo-simple-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-simple-webapp}/src/main/webapp/index.html (100%) rename {demos/demo-simple-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-simple-webapp}/src/main/webapp/jetty.icon (100%) rename {demos/demo-simple-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-simple-webapp}/src/main/webapp/jetty.png (100%) rename {demos/demo-simple-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-simple-webapp}/src/main/webapp/jetty.webp (100%) rename {demos/demo-spec/demo-container-initializer => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-container-initializer}/pom.xml (70%) rename {demos/demo-spec/demo-container-initializer/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-container-initializer/src/main/java/org/example}/initializer/Foo.java (96%) rename {demos/demo-spec/demo-container-initializer/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-container-initializer/src/main/java/org/example}/initializer/FooInitializer.java (74%) create mode 100644 jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-container-initializer/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/pom.xml (88%) rename {demos/embedded/src/test/resources => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp/src/etc}/realm.properties (89%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/assembly/web-bundle.xml (100%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/config/modules/demo-spec.mod (100%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/config/modules/demo.d/demo-spec.xml (82%) rename {demos/demo-spec/demo-spec-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp/src/main/java/org/example}/test/AnnotatedListener.java (90%) rename {demos/demo-spec/demo-spec-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp/src/main/java/org/example}/test/AnnotationTest.java (87%) rename {demos/demo-spec/demo-spec-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp/src/main/java/org/example}/test/AsyncListenerServlet.java (99%) rename {demos/demo-spec/demo-spec-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp/src/main/java/org/example}/test/Bar.java (91%) rename {demos/demo-spec/demo-spec-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp/src/main/java/org/example}/test/ClassLoaderServlet.java (99%) rename {demos/demo-spec/demo-spec-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp/src/main/java/org/example}/test/MultiPartTest.java (99%) rename {demos/demo-spec/demo-spec-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp/src/main/java/org/example}/test/RoleAnnotationTest.java (99%) rename {demos/demo-spec/demo-spec-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp/src/main/java/org/example}/test/SecuredServlet.java (98%) rename {demos/demo-spec/demo-spec-webapp/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp/src/main/java/org/example}/test/TestListener.java (82%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/templates/annotations-context-header.xml (87%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/templates/env-definitions.xml (89%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/templates/plugin-context-header.xml (80%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/webapp/WEB-INF/jetty-env.xml (85%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/webapp/WEB-INF/jetty-web.xml (89%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/webapp/WEB-INF/web.xml (90%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/webapp/authfail.html (100%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/webapp/demo.css (100%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/webapp/dynamic.jsp (100%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/webapp/index.html (100%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/webapp/login.html (100%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/webapp/logout.jsp (100%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/webapp/small_powered_by.gif (100%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/main/webapp/stylesheet.css (100%) rename {demos/demo-spec/demo-spec-webapp => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-spec-webapp}/src/test/jetty-plugin-env.xml (84%) rename {demos/demo-spec/demo-web-fragment => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-web-fragment}/pom.xml (72%) rename {demos/demo-spec/demo-web-fragment/src/main/java/com/acme => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-web-fragment/src/main/java/org/example}/fragment/FragmentServlet.java (98%) rename {demos/demo-spec/demo-web-fragment => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-web-fragment}/src/main/resources/META-INF/resources/fragmentA/index.html (100%) rename {demos/demo-spec/demo-web-fragment => jetty-ee9/jetty-ee9-demos/demo-ee9-spec/demo-ee9-web-fragment}/src/main/resources/META-INF/web-fragment.xml (86%) rename {demos/demo-spec => jetty-ee9/jetty-ee9-demos/demo-ee9-spec}/pom.xml (50%) rename {demos/demo-template => jetty-ee9/jetty-ee9-demos/demo-ee9-template}/pom.xml (76%) rename {demos/demo-template => jetty-ee9/jetty-ee9-demos/demo-ee9-template}/src/main/resources/demo.css (100%) rename {demos/demo-template => jetty-ee9/jetty-ee9-demos/demo-ee9-template}/src/main/resources/index.html (100%) rename {demos/demo-template => jetty-ee9/jetty-ee9-demos/demo-ee9-template}/src/main/resources/small_powered_by.gif (100%) rename {demos => jetty-ee9/jetty-ee9-demos}/pom.xml (50%) create mode 100644 jetty-ee9/jetty-ee9-fcgi-server-proxy/pom.xml create mode 100644 jetty-ee9/jetty-ee9-fcgi-server-proxy/src/main/java/module-info.java rename {jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-fcgi-server-proxy/src/main/java/org/eclipse/jetty/ee9}/fcgi/server/proxy/FastCGIProxyServlet.java (97%) rename {jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-fcgi-server-proxy/src/main/java/org/eclipse/jetty/ee9}/fcgi/server/proxy/TryFilesFilter.java (99%) rename {jetty-fcgi/fcgi-server => jetty-ee9/jetty-ee9-fcgi-server-proxy}/src/test/java/org/eclipse/jetty/fcgi/server/proxy/DrupalHTTP2FastCGIProxyServer.java (93%) rename {jetty-fcgi/fcgi-server => jetty-ee9/jetty-ee9-fcgi-server-proxy}/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java (98%) rename {jetty-fcgi/fcgi-server => jetty-ee9/jetty-ee9-fcgi-server-proxy}/src/test/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilterTest.java (94%) rename {jetty-fcgi/fcgi-server => jetty-ee9/jetty-ee9-fcgi-server-proxy}/src/test/java/org/eclipse/jetty/fcgi/server/proxy/WordPressHTTP2FastCGIProxyServer.java (91%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/pom.xml (69%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/main/config/modules/glassfish-jstl.mod (100%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/main/resources/readme.txt (100%) rename {glassfish-jstl/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-glassfish-jstl/src/test/java/org/eclipse/jetty/ee9}/jstl/JspConfig.java (93%) rename {glassfish-jstl/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-glassfish-jstl/src/test/java/org/eclipse/jetty/ee9}/jstl/JspIncludeTest.java (95%) rename {glassfish-jstl/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-glassfish-jstl/src/test/java/org/eclipse/jetty/ee9}/jstl/JstlTest.java (97%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/test/resources/jetty-logging.properties (100%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/test/taglibjar/META-INF/etag.tld (100%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/test/taglibjar/META-INF/tags/errorhandler.tag (100%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/test/webapp/WEB-INF/web.xml (100%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/test/webapp/catch-basic.jsp (100%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/test/webapp/catch-taglib.jsp (100%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/test/webapp/included.jsp (100%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/test/webapp/ref.jsp (100%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/test/webapp/top.jsp (100%) rename {glassfish-jstl => jetty-ee9/jetty-ee9-glassfish-jstl}/src/test/webapp/urls.jsp (100%) rename {jetty-jaas => jetty-ee9/jetty-ee9-jaas}/pom.xml (94%) rename {jetty-jaas => jetty-ee9/jetty-ee9-jaas}/src/main/java/module-info.java (76%) rename {jetty-jaas/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-jaas/src/main/java/org/eclipse/jetty/ee9}/jaas/callback/package-info.java (93%) rename {jetty-jaas/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-jaas/src/main/java/org/eclipse/jetty/ee9}/jaas/package-info.java (94%) rename {jetty-jaas/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-jaas/src/main/java/org/eclipse/jetty/ee9}/jaas/spi/package-info.java (94%) rename {jetty-jaspi => jetty-ee9/jetty-ee9-jaspi}/pom.xml (87%) rename {jetty-jaspi => jetty-ee9/jetty-ee9-jaspi}/src/main/java/module-info.java (59%) rename {jetty-jaspi/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-jaspi/src/main/java/org/eclipse/jetty/ee9}/security/jaspi/SimpleAuthConfig.java (97%) rename {jetty-jaspi/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-jaspi/src/main/java/org/eclipse/jetty/ee9}/security/jaspi/callback/package-info.java (91%) rename {jetty-jaspi/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-jaspi/src/main/java/org/eclipse/jetty/ee9}/security/jaspi/modules/package-info.java (92%) rename {jetty-jaspi/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-jaspi/src/main/java/org/eclipse/jetty/ee9}/security/jaspi/package-info.java (93%) rename {jetty-jaspi/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-jaspi/src/main/java/org/eclipse/jetty/ee9}/security/jaspi/provider/SimpleAuthConfig.java (97%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/pom.xml (56%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/package-root/invoker.properties (100%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/package-root/pom.xml (96%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/package-root/postbuild.groovy (100%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/package-root/src/main/webapp/foo.jsp (100%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/simple-jsp-fail/invoker.properties (100%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/simple-jsp-fail/pom.xml (96%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/simple-jsp-fail/postbuild.groovy (100%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/simple-jsp-fail/src/main/jsp/foo.jsp (100%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/simple-jsp/invoker.properties (100%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/simple-jsp/pom.xml (96%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/simple-jsp/postbuild.groovy (100%) rename {jetty-jspc-maven-plugin => jetty-ee9/jetty-ee9-jspc-maven-plugin}/src/it/simple-jsp/src/main/webapp/foo.jsp (100%) rename {jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-jspc-maven-plugin/src/main/java/org/eclipse/jetty/ee9}/jspc/plugin/package-info.java (93%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/pom.xml (80%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/it-parent-pom/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/it-parent-pom/pom.xml (99%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-cdi-start-forked/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-cdi-start-forked/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-cdi-start-forked/postbuild.groovy (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-cdi-start-forked/src/main/jetty/jetty.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-maven-plugin-provided-module-dep/api/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-maven-plugin-provided-module-dep/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-maven-plugin-provided-module-dep/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-maven-plugin-provided-module-dep/postbuild.groovy (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-maven-plugin-provided-module-dep/web/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-maven-plugin-provided-module-dep/web/src/config/jetty.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/pom.xml (100%) rename {jetty-maven-plugin/src/it/jetty-start-mojo-it/jetty-simple-webapp => jetty-ee9/jetty-ee9-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp}/src/config/context.xml (76%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/jetty.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/webapp/index.html (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-run-mojo-jar-scan-it/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-run-mojo-jar-scan-it/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-run-mojo-jar-scan-it/postbuild.groovy (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-distro-mojo-it/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-distro-mojo-it/jetty-simple-base/pom.xml (96%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-distro-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_start_distro_mojo_it/HelloServlet.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-distro-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_start_distro_mojo_it/PingServlet.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-distro-mojo-it/jetty-simple-base/src/main/resources/META-INF/web-fragment.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-distro-mojo-it/jetty-simple-webapp/pom.xml (98%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-distro-mojo-it/jetty-simple-webapp/src/base/etc/test-jetty.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-distro-mojo-it/jetty-simple-webapp/src/base/modules/testmod.mod (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-distro-mojo-it/jetty-simple-webapp/src/main/webapp/WEB-INF/web.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-distro-mojo-it/pom.xml (97%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-distro-mojo-it/postbuild.groovy (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-forked-mojo-it/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-forked-mojo-it/jetty-simple-base/pom.xml (96%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-forked-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_start_forked/Counter.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-forked-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_start_forked/HelloServlet.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-forked-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_start_forked/PingServlet.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-forked-mojo-it/jetty-simple-base/src/main/resources/META-INF/web-fragment.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-forked-mojo-it/jetty-simple-webapp/pom.xml (98%) rename {jetty-maven-plugin/src/it/exclude-javax-annotation => jetty-ee9/jetty-ee9-maven-plugin/src/it/jetty-start-forked-mojo-it/jetty-simple-webapp}/src/config/jetty.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-forked-mojo-it/jetty-simple-webapp/src/main/webapp/WEB-INF/web.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-forked-mojo-it/jetty-simple-webapp/src/main/webapp/jsp/bean1.jsp (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-forked-mojo-it/pom.xml (97%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-forked-mojo-it/postbuild.groovy (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-gwt-it/beer-client/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-gwt-it/beer-server/pom.xml (100%) rename {jetty-maven-plugin/src/it/jetty-start-forked-mojo-it/jetty-simple-webapp => jetty-ee9/jetty-ee9-maven-plugin/src/it/jetty-start-gwt-it/beer-server}/src/config/jetty.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-gwt-it/beer-server/src/main/jettyconf/context.xml (60%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-gwt-it/beer-server/src/main/webapp/WEB-INF/web.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-gwt-it/beer-server/src/main/webapp/index.html (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-gwt-it/beer-shared/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-gwt-it/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-gwt-it/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-gwt-it/postbuild.groovy (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-it/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-it/jetty-simple-base/pom.xml (96%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_start_mojo_it/Counter.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_start_mojo_it/HelloServlet.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_start_mojo_it/PingServlet.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-it/jetty-simple-base/src/main/resources/META-INF/web-fragment.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-it/jetty-simple-webapp/pom.xml (98%) rename {jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp => jetty-ee9/jetty-ee9-maven-plugin/src/it/jetty-start-mojo-it/jetty-simple-webapp}/src/config/context.xml (76%) rename {jetty-maven-plugin/src/it/jetty-start-gwt-it/beer-server => jetty-ee9/jetty-ee9-maven-plugin/src/it/jetty-start-mojo-it/jetty-simple-webapp}/src/config/jetty.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-it/jetty-simple-webapp/src/main/webapp/WEB-INF/web.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-it/jetty-simple-webapp/src/main/webapp/jsp/bean1.jsp (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-it/pom.xml (97%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-it/postbuild.groovy (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-multi-module-single-war-it/common/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-multi-module-single-war-it/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-multi-module-single-war-it/module/module-api/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-multi-module-single-war-it/module/module-impl/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-multi-module-single-war-it/module/pom.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-multi-module-single-war-it/pom.xml (97%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-multi-module-single-war-it/postbuild.groovy (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-multi-module-single-war-it/webapp-war/pom.xml (100%) rename {jetty-maven-plugin/src/it/jetty-start-mojo-it/jetty-simple-webapp => jetty-ee9/jetty-ee9-maven-plugin/src/it/jetty-start-mojo-multi-module-single-war-it/webapp-war}/src/config/jetty.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-mojo-multi-module-single-war-it/webapp-war/src/main/webapp/WEB-INF/web.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-distro-mojo-it/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-distro-mojo-it/jetty-simple-base/pom.xml (96%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-distro-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_run_mojo_it/HelloServlet.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-distro-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_run_mojo_it/PingServlet.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-distro-mojo-it/jetty-simple-base/src/main/resources/META-INF/web-fragment.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-distro-mojo-it/jetty-simple-webapp/pom.xml (98%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-distro-mojo-it/jetty-simple-webapp/src/base/etc/test-jetty.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-distro-mojo-it/jetty-simple-webapp/src/base/modules/testmod.mod (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-distro-mojo-it/jetty-simple-webapp/src/main/webapp/WEB-INF/web.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-distro-mojo-it/pom.xml (97%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-distro-mojo-it/postbuild.groovy (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-forked-mojo-it/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-forked-mojo-it/jetty-simple-base/pom.xml (96%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-forked-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_run_war_exploded_mojo_it/HelloServlet.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-forked-mojo-it/jetty-simple-base/src/main/java/org/eclipse/jetty/its/jetty_run_war_exploded_mojo_it/PingServlet.java (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-forked-mojo-it/jetty-simple-base/src/main/resources/META-INF/web-fragment.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-forked-mojo-it/jetty-simple-webapp/pom.xml (98%) rename {jetty-maven-plugin/src/it/jetty-start-mojo-multi-module-single-war-it/webapp-war => jetty-ee9/jetty-ee9-maven-plugin/src/it/jetty-start-war-forked-mojo-it/jetty-simple-webapp}/src/config/jetty.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-forked-mojo-it/jetty-simple-webapp/src/main/webapp/WEB-INF/web.xml (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-forked-mojo-it/pom.xml (97%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-forked-mojo-it/postbuild.groovy (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-mojo-it/invoker.properties (100%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-mojo-it/pom.xml (98%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/it/jetty-start-war-mojo-it/postbuild.groovy (100%) rename {jetty-maven-plugin/src/it/jetty-start-war-forked-mojo-it/jetty-simple-webapp => jetty-ee9/jetty-ee9-maven-plugin/src/it/jetty-start-war-mojo-it}/src/config/jetty.xml (100%) rename {jetty-maven-plugin/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-maven-plugin/src/main/java/org/eclipse/jetty/ee9}/maven/plugin/package-info.java (93%) rename {jetty-maven-plugin => jetty-ee9/jetty-ee9-maven-plugin}/src/test/resources/root/index.html (100%) create mode 100644 jetty-ee9/jetty-ee9-nested/pom.xml create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/assembly/site-component.xml create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/module-info.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/AbstractHandler.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/AbstractHandlerContainer.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/AllowSymLinkAliasChecker.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/AllowedResourceAliasChecker.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/AsyncAttributes.java (94%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/AsyncContentProducer.java (78%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/AsyncContextEvent.java (87%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/AsyncContextState.java (96%) rename {jetty-server/src/main/java/org/eclipse/jetty/server/handler => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/AsyncDelayHandler.java (98%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/Authentication.java (99%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/BlockingContentProducer.java (92%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/BufferedResponseHandler.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/CachedContentFactory.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/ContentProducer.java (77%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/Cookies.java (99%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/DebugHandler.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/DebugListener.java (96%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/Dispatcher.java (91%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/EncodingHttpWriter.java (97%) rename {jetty-server/src/main/java/org/eclipse/jetty/server/handler => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/ErrorHandler.java (94%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/FileBufferedResponseHandler.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Handler.java rename {jetty-server/src/main/java/org/eclipse/jetty/server/handler => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/HandlerCollection.java (97%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HandlerContainer.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HandlerList.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HandlerWrapper.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HotSwapHandler.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannel.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelListeners.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpChannelState.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/HttpChannelState_input.puml (100%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/HttpInput.java (63%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/HttpInputState.puml (100%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/HttpInput_async.puml (100%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/HttpInput_blocking.puml (100%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/HttpOutput.java (98%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/HttpWriter.java (98%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/IdleTimeoutHandler.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/InclusiveByteRange.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/InetAccessHandler.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/InetAccessSet.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/Iso88591HttpWriter.java (97%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ManagedAttributeListener.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/MultiPartFormInputStream.java (99%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/MultiPartParser.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/PushBuilderImpl.java (98%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/QuietServletException.java (97%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Request.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceContentFactory.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceHandler.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ResourceService.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/ResponseWriter.java (99%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/SameFileAliasChecker.java (91%) rename {jetty-server/src/main/java/org/eclipse/jetty/server/handler => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/ScopedHandler.java (81%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SecuredRedirectHandler.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/ServletAttributes.java (83%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/ServletPathMapping.java (95%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/ServletRequestHttpWrapper.java (99%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/ServletResponseHttpWrapper.java (98%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ShutdownHandler.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/StatisticsHandler.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SymlinkAllowedResourceAliasChecker.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ThreadLimitHandler.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/UserIdentity.java (97%) rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/Utf8HttpWriter.java (99%) rename {jetty-server/src/main/java/org/eclipse/jetty/server/handler => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/jmx/AbstractHandlerMBean.java (85%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/jmx/ContextHandlerMBean.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/jmx/package-info.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/package-info.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/ByteBufferRangeWriter.java rename {jetty-server/src/main/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested}/resource/HttpContentRangeWriter.java (53%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/InputStreamRangeWriter.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/RangeWriter.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/resource/SeekableByteChannelRangeWriter.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/resources/jetty-dir.css create mode 100644 jetty-ee9/jetty-ee9-nested/src/main/resources/org/eclipse/nested/favicon.ico create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/AbstractHttpTest.java rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/AsyncCompletionTest.java (92%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/AsyncContentProducerTest.java (58%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/AsyncRequestReadTest.java (88%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/BlockingContentProducerTest.java (64%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/BlockingTest.java (97%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/ContextHandlerTest.java rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/CookiesTest.java (99%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/DumpHandler.java rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/ErrorHandlerTest.java (98%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/HttpManyWaysToAsyncCommitTest.java (97%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/HttpManyWaysToCommitTest.java (96%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/HttpOutputTest.java (98%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/HttpServerTestFixture.java rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/HttpWriterTest.java (95%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/LocalAsyncContextTest.java (94%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/MockConnectionMetaData.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/MockConnector.java create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/RequestTest.java rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/ResponseTest.java (85%) rename {jetty-server/src/test/java/org/eclipse/jetty/server/handler => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/ScopedHandlerTest.java (92%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/ServerConnectorAsyncContextTest.java (89%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/ServletRequestWrapperTest.java (73%) rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/ServletWriterTest.java (85%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java rename {jetty-server/src/test/java/org/eclipse/jetty/server => jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested}/SuspendHandler.java (98%) create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/example.jar create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/fakeRequests.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/jetty-logging.properties create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/keystore.p12 create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/keystore_sni.p12 create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/keystore_sni_key_types.p12 create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/keystore_sni_nowild.p12 create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-company-urlencoded-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-company-urlencoded-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-complex-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-complex-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-complex-jetty-client.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-complex-jetty-client.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-duplicate-names-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-duplicate-names-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-duplicate-names-jetty-client.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-duplicate-names-jetty-client.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-encoding-mess-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-encoding-mess-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-encoding-mess-jetty-client.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-encoding-mess-jetty-client.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-alt-chrome.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-alt-chrome.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-alt-edge.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-alt-edge.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-alt-firefox.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-alt-firefox.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-alt-msie.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-alt-msie.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-alt-safari.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-alt-safari.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-android-chrome.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-android-chrome.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-android-firefox.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-android-firefox.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-chrome.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-chrome.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-edge.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-edge.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-firefox.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-firefox.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-ios-safari.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-ios-safari.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-msie.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-msie.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-safari.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form-fileupload-safari.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-android-chrome.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-android-chrome.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-android-firefox.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-android-firefox.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-chrome.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-chrome.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-edge.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-edge.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-firefox.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-firefox.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-ios-safari.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-ios-safari.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-msie.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-msie.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-osx-safari.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-form1-osx-safari.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-nested-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-nested-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-nested-binary-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-nested-binary-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-nested-jetty-client.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-nested-jetty-client.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-number-only-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-number-only-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-number-only-jetty-client.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-number-only-jetty-client.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-number-only2-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-number-only2-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-android-chrome.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-android-chrome.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-android-firefox.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-android-firefox.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-chrome.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-chrome.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-edge.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-edge.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-firefox.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-firefox.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-ios-safari.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-ios-safari.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-msie.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-msie.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-safari.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-charset-form-safari.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-android-chrome.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-android-chrome.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-android-firefox.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-android-firefox.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-chrome.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-chrome.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-edge.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-edge.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-firefox.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-firefox.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-ios-safari.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-ios-safari.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-msie.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-msie.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-safari.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-form-safari.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-jetty-client.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-sjis-jetty-client.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-strange-quoting-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-strange-quoting-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-text-files-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-text-files-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-text-files-jetty-client.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-text-files-jetty-client.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-unicode-names-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-unicode-names-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-unicode-names-jetty-client.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-unicode-names-jetty-client.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-whitespace-only-jetty-client.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-whitespace-only-jetty-client.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-zalgo-text-plain-apache-httpcomp.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/browser-capture-zalgo-text-plain-apache-httpcomp.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/multipart-base64-long.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/multipart-base64-long.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/multipart-base64.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/multipart-base64.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/multipart-uppercase.expected.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/multipart/multipart-uppercase.raw create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/reload_keystore_1.p12 create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/reload_keystore_2.p12 create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/simple/big.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/simple/directory/content.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/simple/directory/welcome.txt create mode 100644 jetty-ee9/jetty-ee9-nested/src/test/resources/simple/simple.txt rename {jetty-openid => jetty-ee9/jetty-ee9-openid}/pom.xml (84%) rename {jetty-openid => jetty-ee9/jetty-ee9-openid}/src/main/java/module-info.java (78%) create mode 100644 jetty-ee9/jetty-ee9-openid/src/main/resources/META-INF/services/org.eclipse.jetty.ee9.security.Authenticator$Factory rename {jetty-osgi/jetty-osgi-alpn => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-alpn}/pom.xml (81%) rename {jetty-osgi/jetty-osgi-boot-jsp => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot-jsp}/pom.xml (91%) rename {jetty-osgi/jetty-osgi-boot-warurl => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot-warurl}/pom.xml (72%) rename {jetty-osgi/jetty-osgi-boot => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot}/jettyhome/contexts/README (100%) rename {jetty-osgi/jetty-osgi-boot => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot}/jettyhome/etc/README (100%) rename {jetty-osgi/jetty-osgi-boot => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot}/jettyhome/etc/jetty-deploy.xml (100%) rename {jetty-osgi/jetty-osgi-boot => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot}/jettyhome/etc/jetty-http.xml (100%) rename {jetty-osgi/jetty-osgi-boot => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot}/jettyhome/etc/jetty.xml (85%) rename {jetty-osgi/jetty-osgi-boot => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot}/jettyhome/lib/ext/README (100%) rename {jetty-osgi/jetty-osgi-boot => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot}/jettyhome/logs/README (100%) rename {jetty-osgi/jetty-osgi-boot => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot}/jettyhome/resources/README (100%) rename {jetty-osgi/jetty-osgi-boot => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot}/jettyhome/webapps/README (100%) rename {jetty-osgi/jetty-osgi-boot => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot}/pom.xml (84%) rename {jetty-osgi/jetty-osgi-httpservice => jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-httpservice}/pom.xml (82%) rename {jetty-osgi => jetty-ee9/jetty-ee9-osgi}/pom.xml (74%) rename {jetty-osgi/test-jetty-osgi-context => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-context}/pom.xml (87%) rename {jetty-osgi/test-jetty-osgi-context => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-context}/src/main/java/com/acme/osgi/Activator.java (100%) rename {jetty-osgi/test-jetty-osgi-context => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-context}/src/main/resources/static/index.html (100%) rename {jetty-osgi/test-jetty-osgi-fragment => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-fragment}/pom.xml (88%) rename {jetty-osgi/test-jetty-osgi-server => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-server}/pom.xml (89%) rename {jetty-osgi/test-jetty-osgi-server => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-server}/src/main/java/com/acme/osgi/Activator.java (85%) rename {jetty-osgi/test-jetty-osgi-server => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-server}/src/main/resources/index.html (100%) rename {jetty-osgi/test-jetty-osgi-webapp-resources => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-webapp-resources}/pom.xml (93%) rename {jetty-osgi/test-jetty-osgi-webapp => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-webapp}/pom.xml (85%) rename {jetty-osgi/test-jetty-osgi-webapp => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-webapp}/src/main/java/com/acme/osgi/Activator.java (96%) rename {jetty-osgi/test-jetty-osgi-webapp => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-webapp}/src/main/resources/index.html (100%) rename {jetty-osgi/test-jetty-osgi-webapp => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-webapp}/src/main/resources/webappA/index.html (100%) rename {jetty-osgi/test-jetty-osgi-webapp => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi-webapp}/src/main/resources/webappB/index.html (100%) rename {jetty-osgi/test-jetty-osgi => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi}/pom.xml (86%) rename {jetty-osgi/test-jetty-osgi => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi}/src/test/config/etc/jetty-deploy.xml (100%) rename {jetty-osgi/test-jetty-osgi => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi}/src/test/config/etc/jetty-http.xml (96%) rename {jetty-osgi/test-jetty-osgi => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi}/src/test/config/etc/jetty.xml (82%) rename {jetty-osgi/test-jetty-osgi => jetty-ee9/jetty-ee9-osgi/test-jetty-ee9-osgi}/src/test/resources/module-info.java (100%) rename {jetty-plus => jetty-ee9/jetty-ee9-plus}/pom.xml (85%) rename {jetty-plus => jetty-ee9/jetty-ee9-plus}/src/main/java/module-info.java (63%) rename {jetty-plus/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-plus/src/main/java/org/eclipse/jetty/ee9}/plus/annotation/package-info.java (93%) rename {jetty-plus/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-plus/src/main/java/org/eclipse/jetty/ee9}/plus/jndi/package-info.java (93%) rename {jetty-plus/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-plus/src/main/java/org/eclipse/jetty/ee9}/plus/security/package-info.java (93%) rename {jetty-plus/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-plus/src/main/java/org/eclipse/jetty/ee9}/plus/webapp/package-info.java (93%) create mode 100644 jetty-ee9/jetty-ee9-plus/src/main/resources/META-INF/services/org.eclipse.jetty.ee9.webapp.Configuration rename {jetty-proxy => jetty-ee9/jetty-ee9-proxy}/pom.xml (86%) rename {jetty-proxy => jetty-ee9/jetty-ee9-proxy}/src/main/java/module-info.java (85%) rename {jetty-proxy/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-proxy/src/main/java/org/eclipse/jetty/ee9}/proxy/package-info.java (94%) rename {jetty-proxy => jetty-ee9/jetty-ee9-proxy}/src/test/resources/client_auth/client_keystore.p12 (100%) rename {jetty-proxy => jetty-ee9/jetty-ee9-proxy}/src/test/resources/client_auth/proxy_keystore.p12 (100%) rename {jetty-proxy => jetty-ee9/jetty-ee9-proxy}/src/test/resources/client_auth/server_keystore.p12 (100%) rename {jetty-proxy => jetty-ee9/jetty-ee9-proxy}/src/test/resources/client_keystore.p12 (100%) rename {jetty-proxy => jetty-ee9/jetty-ee9-proxy}/src/test/resources/proxy_keystore.p12 (100%) rename {jetty-proxy => jetty-ee9/jetty-ee9-proxy}/src/test/resources/server_keystore.p12 (100%) rename {jetty-quickstart => jetty-ee9/jetty-ee9-quickstart}/pom.xml (74%) rename {jetty-quickstart => jetty-ee9/jetty-ee9-quickstart}/src/main/java/module-info.java (81%) rename {jetty-runner => jetty-ee9/jetty-ee9-runner}/pom.xml (85%) rename {jetty-runner => jetty-ee9/jetty-ee9-runner}/src/it/demo-simple-webapp-runner-with-path/invoker.properties (100%) rename {jetty-runner => jetty-ee9/jetty-ee9-runner}/src/it/demo-simple-webapp-runner-with-path/pom.xml (97%) rename {jetty-runner => jetty-ee9/jetty-ee9-runner}/src/it/demo-simple-webapp-runner/invoker.properties (100%) rename {jetty-runner => jetty-ee9/jetty-ee9-runner}/src/it/demo-simple-webapp-runner/pom.xml (97%) rename {jetty-runner => jetty-ee9/jetty-ee9-runner}/src/it/test-jar-manifest/invoker.properties (100%) rename {jetty-runner => jetty-ee9/jetty-ee9-runner}/src/it/test-jar-manifest/pom.xml (100%) rename {jetty-runner/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-runner/src/main/java/org/eclipse/jetty/ee9}/runner/package-info.java (94%) rename {jetty-security => jetty-ee9/jetty-ee9-security}/pom.xml (86%) rename {jetty-security => jetty-ee9/jetty-ee9-security}/src/main/java/module-info.java (74%) rename {jetty-security/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-security/src/main/java/org/eclipse/jetty/ee9}/security/authentication/package-info.java (91%) rename {jetty-security/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-security/src/main/java/org/eclipse/jetty/ee9}/security/package-info.java (94%) rename {jetty-security => jetty-ee9/jetty-ee9-security}/src/test/resources/docroot/all/index.txt (100%) rename {jetty-security => jetty-ee9/jetty-ee9-security}/src/test/resources/docroot/forbid/index.txt (100%) rename {jetty-servlet => jetty-ee9/jetty-ee9-servlet}/pom.xml (81%) rename {jetty-servlet => jetty-ee9/jetty-ee9-servlet}/src/main/java/module-info.java (76%) rename {jetty-servlet/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9}/servlet/jmx/package-info.java (93%) rename {jetty-servlet/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9}/servlet/listener/package-info.java (92%) rename {jetty-servlet/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9}/servlet/package-info.java (94%) rename {jetty-servlet => jetty-ee9/jetty-ee9-servlet}/src/test/resources/contextResources/content.txt (100%) rename {jetty-servlet => jetty-ee9/jetty-ee9-servlet}/src/test/resources/dispatchResourceTest/content.txt (100%) rename {jetty-servlets => jetty-ee9/jetty-ee9-servlets}/pom.xml (75%) rename {jetty-servlets => jetty-ee9/jetty-ee9-servlets}/src/main/java/module-info.java (86%) rename {jetty-servlets/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-servlets/src/main/java/org/eclipse/jetty/ee9}/servlets/package-info.java (94%) create mode 100644 jetty-ee9/jetty-ee9-tests/pom.xml rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-bad-websocket-webapp/pom.xml (75%) rename {tests/test-webapps/test-bad-websocket-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-bad-websocket-webapp/src/main/java/org/eclipse/jetty/ee9}/tests/webapp/websocket/bad/BadOnCloseServerEndpoint.java (96%) rename {tests/test-webapps/test-bad-websocket-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-bad-websocket-webapp/src/main/java/org/eclipse/jetty/ee9}/tests/webapp/websocket/bad/BadOnOpenServerEndpoint.java (95%) rename {tests/test-webapps/test-bad-websocket-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-bad-websocket-webapp/src/main/java/org/eclipse/jetty/ee9}/tests/webapp/websocket/bad/StringSequence.java (95%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-bad-websocket-webapp/src/main/webapp/WEB-INF/web.xml (100%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-bad-websocket-webapp/src/main/webapp/index.jsp (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-cdi/pom.xml (76%) rename {tests/test-cdi/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-cdi/src/test/java/org/eclipse/jetty/ee9}/cdi/tests/EmbeddedWeldTest.java (93%) rename {tests/test-cdi/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-cdi/src/test/java/org/eclipse/jetty/ee9}/cdi/tests/FriendlyGreetings.java (96%) rename {tests/test-cdi/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-cdi/src/test/java/org/eclipse/jetty/ee9}/cdi/tests/Greetings.java (93%) rename {tests/test-cdi/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-cdi/src/test/java/org/eclipse/jetty/ee9}/cdi/tests/GreetingsServlet.java (97%) rename {tests/test-cdi/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-cdi/src/test/java/org/eclipse/jetty/ee9}/cdi/tests/MyContextListener.java (96%) rename {tests/test-cdi/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-cdi/src/test/java/org/eclipse/jetty/ee9}/cdi/tests/MyFilter.java (97%) rename {tests/test-cdi/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-cdi/src/test/java/org/eclipse/jetty/ee9}/cdi/tests/websocket/JavaxWebSocketCdiTest.java (95%) rename {tests/test-cdi/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-cdi/src/test/java/org/eclipse/jetty/ee9}/cdi/tests/websocket/JettyWebSocketCdiTest.java (84%) rename {tests/test-cdi/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-cdi/src/test/java/org/eclipse/jetty/ee9}/cdi/tests/websocket/LogFactory.java (94%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-cdi/src/test/resources/META-INF/beans.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-cdi/src/test/resources/jetty-logging.properties (70%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-cdi/src/test/weldtest/.donotdelete (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-http-client-transport/pom.xml (92%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/AbstractTest.java (96%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/AsyncIOServletTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/AsyncRequestContentTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/BlockedIOTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/ConnectionStatisticsTest.java (96%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/EmptyServerHandler.java (96%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/HttpChannelAssociationTest.java (97%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/HttpClientConnectTimeoutTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/HttpClientContinueTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/HttpClientDemandTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/HttpClientIdleTimeoutTest.java (98%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/HttpClientLoadTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/HttpClientStreamTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/HttpClientTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/HttpClientTimeoutTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/HttpClientTransportDynamicTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/HttpTrailersTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/ProxyWithDynamicTransportTest.java (98%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/RequestReaderTest.java (98%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/RoundRobinConnectionPoolTest.java (99%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/ServerTimeoutsTest.java (98%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/Transport.java (95%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/TransportProvider.java (97%) rename {tests/test-http-client-transport/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-http-client-transport/src/test/java/org/eclipse/jetty/ee9}/http/client/TransportScenario.java (99%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-http-client-transport/src/test/resources/jetty-logging.properties (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-http-client-transport/src/test/resources/keystore.p12 (100%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-http2-webapp/pom.xml (86%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-http2-webapp/src/main/java/org/eclipse/jetty/test/webapp/HTTP1Servlet.java (100%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-http2-webapp/src/main/java/org/eclipse/jetty/test/webapp/HTTP2Servlet.java (100%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-http2-webapp/src/main/webapp/WEB-INF/web.xml (70%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-http2-webapp/src/test/java/org/eclipse/jetty/test/webapp/HTTP2FromWebAppIT.java (98%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-http2-webapp/src/test/resources/jetty-logging.properties (100%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-http2-webapp/src/test/resources/keystore.p12 (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/pom.xml (83%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/AliasCheckerSymlinkTest.java (97%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/AllowedResourceAliasCheckerTest.java (97%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/AnnotatedAsyncListenerTest.java (87%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/CustomRequestLogTest.java (98%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/DefaultHandlerTest.java (95%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/DeploymentErrorInitializer.java (97%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/DeploymentErrorTest.java (96%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/DigestPostTest.java (96%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/FailedSelectorTest.java (98%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/GzipWithSendErrorTest.java (98%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/HttpInputIntegrationTest.java (98%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/HttpInputInterceptorTest.java (99%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/KeyStoreScannerTest.java (99%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/RecoverFailedSelectorTest.java (99%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/jsp/FakeJspServlet.java (97%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/jsp/JspAndDefaultWithAliasesTest.java (95%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/jsp/JspAndDefaultWithoutAliasesTest.java (96%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/rfcs/RFC2616BaseTest.java (99%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/rfcs/RFC2616NIOHttpTest.java (86%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/rfcs/RFC2616NIOHttpsTest.java (86%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/support/EchoHandler.java (97%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/support/StringUtil.java (99%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/support/XmlBasedJettyServer.java (99%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/support/rawhttp/HttpRequestTesterTest.java (97%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/support/rawhttp/HttpResponseTesterTest.java (99%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/support/rawhttp/HttpSocket.java (93%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/support/rawhttp/HttpSocketImpl.java (94%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/support/rawhttp/HttpTesting.java (99%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/support/rawhttp/HttpsSocketImpl.java (97%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/websocket/JakartaSimpleEchoSocket.java (97%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/websocket/JakartaWebSocketTest.java (94%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/websocket/JettySimpleEchoSocket.java (82%) rename {tests/test-integration/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-integration/src/test/java/org/eclipse/jetty/ee9}/test/websocket/JettyWebSocketTest.java (88%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/DefaultHandler.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/NIOHttp.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/NIOHttps.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/RFC2616Base.xml (98%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/RFC2616_Filters.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/RFC2616_Redirects.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/add-jetty-test-webapp.xml (91%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/badKeystore (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/basic-server.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/deploy.xml (94%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/default/R1.txt (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/default/R2.txt (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/default/R3.txt (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/default/alpha.txt (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/default/index.html (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/default/quotes.txt (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/deployerror/badapp-unavailable-false.xml (90%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/deployerror/badapp.xml (89%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/deployerror/badapp/WEB-INF/classes/META-INF/services/jakarta.servlet.ServletContainerInitializer (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/deployerror/badapp/WEB-INF/web.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/jsp/dump.jsp (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/virtualhost/R1.txt (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/virtualhost/R2.txt (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/virtualhost/R3.txt (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/docroots/virtualhost/index.html (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/file (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/jetty-logging.properties (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/keystore.p12 (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/logback.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/login-service.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/message.txt (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/newKeystore (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/oldKeystore (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/realm.properties (89%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/sibling/file (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/ssl.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/webapp-contexts/RFC2616/rfc2616-webapp.xml (92%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/webdefault.xml (97%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/webroot/WEB-INF/web.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/webroot/documents/file (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/webroot/file (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/webroot/index.html (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-integration/src/test/resources/zeros.gz.gz (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-jmx/jmx-webapp-it/pom.xml (86%) rename {tests/test-jmx/jmx-webapp-it/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-jmx/jmx-webapp-it/src/test/java/org/eclipse/jetty/ee9}/test/jmx/JmxIT.java (97%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-jmx/jmx-webapp/pom.xml (89%) rename {tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/ee9}/test/jmx/CommonComponent.java (97%) rename {tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/ee9}/test/jmx/Echoer.java (96%) rename {tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/ee9}/test/jmx/MyContainerInitializer.java (97%) rename {tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/ee9}/test/jmx/PingServlet.java (97%) rename {tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/ee9}/test/jmx/Pinger.java (95%) rename {tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/ee9}/test/jmx/jmx/EchoerMBean.java (95%) rename {tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-jmx/jmx-webapp/src/main/java/org/eclipse/jetty/ee9}/test/jmx/jmx/PingerMBean.java (93%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-jmx/jmx-webapp/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-jmx/jmx-webapp/src/main/webapp/WEB-INF/web.xml (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-jmx/jmx-webapp/src/main/webapp/index.html (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-jmx/pom.xml (67%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-jndi/pom.xml (85%) rename {tests/test-jndi/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-jndi/src/test/java/org/eclipse/jetty/ee9}/jndi/factories/TestMailSessionReference.java (96%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-loginservice/pom.xml (80%) rename {tests/test-loginservice/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-loginservice/src/test/java/org/eclipse/jetty/ee9/loginservice}/DataSourceLoginServiceTest.java (96%) rename {tests/test-loginservice/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-loginservice/src/test/java/org/eclipse/jetty/ee9/loginservice}/DatabaseLoginServiceTestServer.java (94%) rename {tests/test-loginservice/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-loginservice/src/test/java/org/eclipse/jetty/ee9/loginservice}/JdbcLoginServiceTest.java (98%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-loginservice/src/test/resources/createdb.sql (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-loginservice/src/test/resources/droptables.sql (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-loginservice/src/test/resources/jdbcrealm.properties (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-loginservice/src/test/resources/jetty-logging.properties (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-quickstart/pom.xml (77%) rename {tests/test-quickstart/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-quickstart/src/test/java/org/eclipse/jetty/ee9}/quickstart/AttributeNormalizerTest.java (99%) rename {tests/test-quickstart/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-quickstart/src/test/java/org/eclipse/jetty/ee9}/quickstart/AttributeNormalizerToCanonicalUriTest.java (98%) rename {tests/test-quickstart/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-quickstart/src/test/java/org/eclipse/jetty/ee9}/quickstart/EnvUtils.java (98%) rename {tests/test-quickstart/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-quickstart/src/test/java/org/eclipse/jetty/ee9}/quickstart/PreconfigureJNDIWar.java (97%) rename {tests/test-quickstart/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-quickstart/src/test/java/org/eclipse/jetty/ee9}/quickstart/PreconfigureSpecWar.java (98%) rename {tests/test-quickstart/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-quickstart/src/test/java/org/eclipse/jetty/ee9}/quickstart/PreconfigureStandardTestWar.java (97%) rename {tests/test-quickstart/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-quickstart/src/test/java/org/eclipse/jetty/ee9}/quickstart/QuickStartJNDIWar.java (95%) rename {tests/test-quickstart/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-quickstart/src/test/java/org/eclipse/jetty/ee9}/quickstart/QuickStartSpecWar.java (95%) rename {tests/test-quickstart/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-quickstart/src/test/java/org/eclipse/jetty/ee9}/quickstart/QuickStartStandardTestWar.java (95%) rename {tests/test-quickstart/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-quickstart/src/test/java/org/eclipse/jetty/ee9}/quickstart/QuickStartTest.java (95%) rename {tests/test-quickstart/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-quickstart/src/test/java/org/eclipse/jetty/ee9}/quickstart/Quickstart.java (89%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-quickstart/src/test/resources/jetty-logging.properties (100%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-quickstart/src/test/resources/realm.properties (89%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-quickstart/src/test/resources/test-jndi.xml (95%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-quickstart/src/test/resources/test-spec.xml (93%) rename {tests => jetty-ee9/jetty-ee9-tests}/test-quickstart/src/test/resources/test.xml (96%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-websocket-client-provided-webapp/pom.xml (77%) rename {tests/test-webapps/test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/ee9}/tests/webapp/websocket/EchoEndpoint.java (93%) rename {tests/test-webapps/test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/ee9}/tests/webapp/websocket/WebSocketClientServlet.java (88%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-websocket-client-provided-webapp/src/main/resources/jetty-websocket-httpclient.xml (100%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-websocket-client-provided-webapp/src/main/webapp/WEB-INF/web.xml (100%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-websocket-client-webapp/pom.xml (76%) rename {tests/test-webapps/test-websocket-client-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-websocket-client-webapp/src/main/java/org/eclipse/jetty/ee9}/tests/webapp/websocket/EchoEndpoint.java (93%) rename {tests/test-webapps/test-websocket-client-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-websocket-client-webapp/src/main/java/org/eclipse/jetty/ee9}/tests/webapp/websocket/WebSocketClientServlet.java (88%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-websocket-client-webapp/src/main/webapp/WEB-INF/web.xml (100%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-websocket-webapp/pom.xml (78%) rename {tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-websocket-webapp/src/main/java/org/eclipse/jetty/ee9}/tests/webapp/websocket/EchoEndpoint.java (95%) rename {tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-websocket-webapp/src/main/java/org/eclipse/jetty/ee9}/tests/webapp/websocket/StringSequence.java (95%) rename {tests/test-webapps/test-websocket-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-tests/test-websocket-webapp/src/main/java/org/eclipse/jetty/ee9}/tests/webapp/websocket/StringSequenceDecoder.java (95%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-websocket-webapp/src/main/webapp/WEB-INF/web.xml (100%) rename {tests/test-webapps => jetty-ee9/jetty-ee9-tests}/test-websocket-webapp/src/main/webapp/index.jsp (100%) rename {jetty-webapp => jetty-ee9/jetty-ee9-webapp}/pom.xml (79%) create mode 100644 jetty-ee9/jetty-ee9-webapp/src/main/java/module-info.java rename {jetty-webapp/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9}/webapp/package-info.java (94%) create mode 100644 jetty-ee9/jetty-ee9-webapp/src/main/resources/META-INF/services/org.eclipse.jetty.ee9.webapp.Configuration rename {jetty-webapp => jetty-ee9/jetty-ee9-webapp}/src/test/resources/org/acme/resource.txt (100%) rename {jetty-webapp => jetty-ee9/jetty-ee9-webapp}/src/test/webapp/WEB-INF/classes/org/acme/resource.txt (100%) rename {jetty-webapp => jetty-ee9/jetty-ee9-webapp}/src/test/webapp/WEB-INF/test.xml (100%) rename {jetty-webapp => jetty-ee9/jetty-ee9-webapp}/src/test/webapp/test.xml (100%) rename {jetty-websocket/websocket-jakarta-client => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-client}/pom.xml (88%) rename {jetty-websocket/websocket-jakarta-client => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-client}/src/main/java/module-info.java (68%) rename {jetty-websocket/websocket-jakarta-client => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-client}/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer (100%) rename {jetty-websocket/websocket-jakarta-client => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-client}/src/test/resources/jetty-logging.properties (100%) rename {jetty-websocket/websocket-jakarta-client => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-client}/src/test/resources/jetty-websocket-httpclient.xml (100%) rename {jetty-websocket/websocket-jakarta-common => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common}/pom.xml (87%) rename {jetty-websocket/websocket-jakarta-common => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common}/src/main/java/module-info.java (68%) rename {jetty-websocket/websocket-jakarta-common/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/SessionTracker.java (98%) rename {jetty-websocket/websocket-jakarta-common/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/UpgradeRequest.java (95%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/DummyContainer.java (98%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/JakartaWebSocketFrameHandlerOnMessageTextStreamTest.java (95%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/coders/tests/BadDualDecoder.java (97%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/coders/tests/BadDualEncoder.java (95%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/coders/tests/ExtDecoder.java (91%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/coders/tests/Fruit.java (90%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/coders/tests/FruitBinaryEncoder.java (96%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/coders/tests/FruitDecoder.java (96%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/coders/tests/FruitTextEncoder.java (93%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/endpoints/DummyEndpoint.java (92%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/handlers/BaseMessageHandler.java (92%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/handlers/ComboMessageHandler.java (94%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/handlers/ExtendedMessageHandler.java (93%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/handlers/LongMessageHandler.java (92%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/sockets/TrackingSocket.java (95%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/util/InvokerUtilsTest.java (99%) rename {jetty-websocket/websocket-jakarta-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/common/util/NameParamIdentifier.java (96%) rename {jetty-websocket/websocket-jakarta-common => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common}/src/test/resources/jetty-logging.properties (100%) rename {jetty-websocket/websocket-jakarta-common => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common}/src/test/resources/quotes-ben.txt (100%) rename {jetty-websocket/websocket-jakarta-common => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common}/src/test/resources/quotes-twain.txt (100%) rename {jetty-websocket/websocket-jakarta-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server}/pom.xml (75%) rename {jetty-websocket/websocket-jakarta-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server}/src/main/java/module-info.java (52%) rename {jetty-websocket/websocket-jakarta-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server}/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer (100%) rename {jetty-websocket/websocket-jakarta-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server}/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration (100%) rename {jetty-websocket/websocket-jakarta-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server}/src/test/resources/jetty-logging.properties (100%) rename {jetty-websocket/websocket-jakarta-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server}/src/test/resources/jsr-browser-debug-tool/index.html (100%) rename {jetty-websocket/websocket-jakarta-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server}/src/test/resources/jsr-browser-debug-tool/main.css (100%) rename {jetty-websocket/websocket-jakarta-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server}/src/test/resources/jsr-browser-debug-tool/websocket.js (100%) create mode 100644 jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/fuzzingclient.json create mode 100644 jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/fuzzingserver.json rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/pom.xml (84%) rename {jetty-websocket/websocket-jakarta-tests/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/DummyEndpoint.java (93%) rename {jetty-websocket/websocket-jakarta-tests/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/EchoSocket.java (95%) rename {jetty-websocket/websocket-jakarta-tests/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/EventSocket.java (98%) rename {jetty-websocket/websocket-jakarta-tests/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/WSURI.java (98%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/java/com/acme/websocket/BasicEchoEndpoint.java (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/java/com/acme/websocket/BasicEchoEndpointConfigContextListener.java (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/java/com/acme/websocket/IdleTimeoutContextListener.java (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/java/com/acme/websocket/IdleTimeoutOnOpenEndpoint.java (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/java/com/acme/websocket/IdleTimeoutOnOpenSocket.java (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/java/com/acme/websocket/LargeEchoContextListener.java (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/java/com/acme/websocket/LargeEchoDefaultSocket.java (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/java/com/acme/websocket/OnOpenIdleTimeoutEndpoint.java (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/java/com/acme/websocket/PongContextListener.java (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/java/com/acme/websocket/PongMessageEndpoint.java (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/java/com/acme/websocket/PongSocket.java (100%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/CloseInOnOpenTest.java (88%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/GracefulCloseTest.java (93%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/ProgrammaticWebSocketUpgradeTest.java (92%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/ServerConfigTest.java (95%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/UpgradeRequestResponseTest.java (91%) rename {tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/jakarta => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/autobahn}/JakartaAutobahnClient.java (84%) rename {tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/jakarta => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/autobahn}/JakartaAutobahnServer.java (70%) rename {tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/jakarta => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/autobahn}/JakartaAutobahnSocket.java (94%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/client/ConfiguratorTest.java (95%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/coders/BadDualDecoder.java (98%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/coders/BadDualEncoder.java (95%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/coders/ExtDecoder.java (92%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/coders/Fruit.java (91%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/coders/FruitBinaryEncoder.java (96%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/coders/FruitDecoder.java (96%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/coders/FruitTextEncoder.java (94%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/coders/Quotes.java (94%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/coders/QuotesDecoder.java (97%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/coders/QuotesEncoder.java (95%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/coders/QuotesUtil.java (97%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/handlers/BaseMessageHandler.java (92%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/handlers/ComboMessageHandler.java (94%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/handlers/ExtendedMessageHandler.java (93%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/handlers/LongMessageHandler.java (92%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/quotes/Quotes.java (94%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/quotes/QuotesDecoder.java (96%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/quotes/QuotesEncoder.java (95%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/quotes/QuotesUtil.java (97%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/BasicEchoEndpointConfigContextListener.java (92%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/ConfiguratorTest.java (98%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/IdleTimeoutContextListener.java (91%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/JakartaWebSocketFrameHandlerOnMessageTextStreamTest.java (93%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/LargeEchoContextListener.java (95%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/PongContextListener.java (93%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/PongSocket.java (96%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/sockets/BasicEchoSocket.java (93%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/sockets/ByteBufferSocket.java (95%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/sockets/IdleTimeoutOnOpenEndpoint.java (94%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/sockets/IdleTimeoutOnOpenSocket.java (94%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/sockets/TrackingSocket.java (95%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/sockets/binary/ByteBufferSocket.java (94%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/sockets/echo/BasicEchoEndpoint.java (94%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/sockets/echo/BasicEchoSocket.java (92%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/sockets/echo/LargeEchoDefaultSocket.java (93%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/sockets/idletimeout/OnOpenIdleTimeoutEndpoint.java (93%) rename {jetty-websocket/websocket-jakarta-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/jakarta/tests/server/sockets/pong/PongMessageEndpoint.java (95%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/resources/jetty-logging.properties (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/resources/jetty-websocket-httpclient.xml (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/resources/keystore.p12 (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/resources/quotes-ben.txt (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/resources/quotes-twain.txt (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/resources/simple/jetty-websocket-httpclient.xml (100%) rename {jetty-websocket/websocket-jakarta-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests}/src/test/webapp/index.html (100%) rename {jetty-websocket/websocket-jetty-api => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-api}/pom.xml (60%) create mode 100644 jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-api/src/main/java/module-info.java rename {jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-api/src/main/java/org/eclipse/jetty/ee9}/websocket/api/UpgradeRequest.java (99%) rename {jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-api/src/main/java/org/eclipse/jetty/ee9}/websocket/api/annotations/package-info.java (91%) rename {jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-api/src/main/java/org/eclipse/jetty/ee9}/websocket/api/exceptions/package-info.java (91%) rename {jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-api/src/main/java/org/eclipse/jetty/ee9}/websocket/api/package-info.java (93%) rename {jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-api/src/main/java/org/eclipse/jetty/ee9}/websocket/api/util/WSURI.java (98%) rename {jetty-websocket/websocket-jetty-api/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-api/src/main/java/org/eclipse/jetty/ee9}/websocket/api/util/package-info.java (92%) rename {jetty-websocket/websocket-jetty-client => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client}/pom.xml (73%) rename {jetty-websocket/websocket-jetty-client => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client}/src/main/java/module-info.java (71%) rename {jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9}/websocket/client/package-info.java (92%) create mode 100644 jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration create mode 100644 jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/test/resources/jetty-logging.properties rename {jetty-websocket/websocket-jetty-common => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common}/pom.xml (76%) rename {jetty-websocket/websocket-jetty-common => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common}/src/main/java/module-info.java (68%) rename {jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/main/java/org/eclipse/jetty/ee9}/websocket/common/SessionTracker.java (92%) rename {jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/main/java/org/eclipse/jetty/ee9}/websocket/common/package-info.java (95%) rename {jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/test/java/org/eclipse/jetty/ee9}/websocket/common/DummyContainer.java (90%) rename {jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/test/java/org/eclipse/jetty/ee9}/websocket/common/invoke/InvokerUtilsTest.java (99%) rename {jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/test/java/org/eclipse/jetty/ee9}/websocket/common/invoke/NameParamIdentifier.java (96%) rename {jetty-websocket/websocket-jetty-common => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common}/src/test/resources/jetty-logging.properties (100%) rename {jetty-websocket/websocket-jetty-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server}/pom.xml (67%) rename {jetty-websocket/websocket-jetty-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server}/src/main/java/module-info.java (56%) rename {jetty-websocket/websocket-jetty-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server}/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer (100%) rename {jetty-websocket/websocket-jetty-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server}/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration (100%) rename {jetty-websocket/websocket-jetty-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server}/src/test/resources/browser-debug-tool/index.html (100%) rename {jetty-websocket/websocket-jetty-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server}/src/test/resources/browser-debug-tool/main.css (100%) rename {jetty-websocket/websocket-jetty-server => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server}/src/test/resources/browser-debug-tool/websocket.js (100%) create mode 100644 jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/fuzzingclient.json create mode 100644 jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/fuzzingserver.json rename {jetty-websocket/websocket-jetty-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests}/pom.xml (84%) rename {jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/tests/CloseInOnOpenTest.java (84%) rename {jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/tests/EchoSocket.java (90%) rename {tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9/websocket/tests}/EventSocket.java (85%) rename {jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/tests/GracefulCloseTest.java (91%) rename {jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/tests/ProgrammaticWebSocketUpgradeTest.java (87%) rename {jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/tests/UpgradeRequestResponseTest.java (91%) rename {tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9/websocket/tests/autobahn}/JettyAutobahnClient.java (74%) rename {tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9/websocket/tests/autobahn}/JettyAutobahnServer.java (70%) rename {tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9/websocket/tests/autobahn}/JettyAutobahnSocket.java (81%) rename {jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9}/websocket/tests/server/ServerConfigTest.java (91%) rename {jetty-websocket/websocket-jetty-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests}/src/test/resources/jetty-logging.properties (100%) rename {jetty-websocket/websocket-jetty-tests => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests}/src/test/resources/keystore.p12 (100%) rename {jetty-websocket/websocket-servlet => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-servlet}/pom.xml (75%) rename {jetty-websocket/websocket-servlet => jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-servlet}/src/main/java/module-info.java (80%) create mode 100644 jetty-ee9/jetty-ee9-websocket/pom.xml create mode 100644 jetty-ee9/pom.xml delete mode 100644 jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java delete mode 100644 jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java delete mode 100644 jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/EmptyServerHandler.java delete mode 100644 jetty-hazelcast/src/test/java/org/eclipse/jetty/hazelcast/session/TestHazelcastSessions.java delete mode 100644 jetty-home/src/main/resources/modules/jolokia/jolokia-realm.properties delete mode 100644 jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestEndpointMultiplePublishProblem.java delete mode 100644 jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/EmptyHttpServlet.java delete mode 100644 jetty-http2/http2-client/src/test/resources/jetty-logging.properties delete mode 100644 jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/AbstractTest.java delete mode 100644 jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/EmptyServerHandler.java delete mode 100644 jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/PriorKnowledgeHTTP2OverTLSTest.java delete mode 100644 jetty-http2/http2-http-client-transport/src/test/resources/jetty-logging.properties delete mode 100644 jetty-http2/http2-http-client-transport/src/test/resources/keystore.p12 delete mode 100644 jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java delete mode 100644 jetty-http3/http3-http-client-transport/src/main/java/org/eclipse/jetty/http3/client/http/internal/HttpChannelOverHTTP3.java delete mode 100644 jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpTransportOverHTTP3.java delete mode 100644 jetty-infinispan/infinispan-embedded-query/src/test/java/org/eclipse/jetty/server/session/infinispan/EmbeddedQueryManagerTest.java delete mode 100644 jetty-infinispan/infinispan-remote-query/src/test/java/org/eclipse/jetty/server/session/infinispan/RemoteQueryManagerTest.java delete mode 100644 jetty-infinispan/infinispan-remote-query/src/test/resources/config.yaml delete mode 100644 jetty-infinispan/infinispan-remote-query/src/test/resources/simplelogger.properties rename {jetty-gcloud => jetty-integrations/jetty-gcloud}/jetty-gcloud-session-manager/pom.xml (97%) rename {jetty-hazelcast => jetty-integrations/jetty-hazelcast}/src/main/java/module-info.java (93%) rename {jetty-infinispan => jetty-integrations/jetty-infinispan}/infinispan-common/pom.xml (94%) rename {jetty-infinispan => jetty-integrations/jetty-infinispan}/infinispan-embedded-query/pom.xml (97%) rename {jetty-infinispan => jetty-integrations/jetty-infinispan}/infinispan-embedded-query/src/main/assembly/config.xml (100%) rename {jetty-infinispan => jetty-integrations/jetty-infinispan}/infinispan-embedded/pom.xml (97%) rename {jetty-infinispan => jetty-integrations/jetty-infinispan}/infinispan-embedded/src/main/assembly/config.xml (100%) rename {jetty-infinispan => jetty-integrations/jetty-infinispan}/infinispan-remote-query/pom.xml (98%) rename {jetty-infinispan => jetty-integrations/jetty-infinispan}/infinispan-remote-query/src/main/assembly/config.xml (100%) rename {jetty-infinispan => jetty-integrations/jetty-infinispan}/infinispan-remote/pom.xml (97%) rename {jetty-infinispan => jetty-integrations/jetty-infinispan}/infinispan-remote/src/main/assembly/config.xml (100%) create mode 100644 jetty-integrations/jetty-memcached/jetty-memcached-sessions/pom.xml rename {jetty-memcached => jetty-integrations/jetty-memcached}/jetty-memcached-sessions/src/main/java/module-info.java (93%) rename {jetty-nosql => jetty-integrations/jetty-nosql}/src/main/java/module-info.java (93%) rename {jetty-nosql => jetty-integrations/jetty-nosql}/src/main/java/org/eclipse/jetty/nosql/mongodb/package-info.java (100%) rename {jetty-nosql => jetty-integrations/jetty-nosql}/src/main/java/org/eclipse/jetty/nosql/package-info.java (100%) create mode 100644 jetty-integrations/pom.xml delete mode 100644 jetty-maven-plugin/src/it/exclude-javax-annotation/invoker.properties delete mode 100644 jetty-maven-plugin/src/it/exclude-javax-annotation/pom.xml delete mode 100644 jetty-maven-plugin/src/it/exclude-javax-annotation/postbuild.groovy delete mode 100644 jetty-maven-plugin/src/it/exclude-javax-annotation/src/main/java/org/github/unb/TestServlet.java delete mode 100644 jetty-maven-plugin/src/it/jetty-start-war-mojo-it/src/config/jetty.xml delete mode 100644 jetty-memcached/jetty-memcached-sessions/pom.xml delete mode 100644 jetty-memcached/jetty-memcached-sessions/src/test/java/org/eclipse/jetty/memcached/session/TestMemcachedSessions.java delete mode 100644 jetty-plus/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration delete mode 100644 jetty-rewrite/src/main/config/modules/rewrite/rewrite-msie.xml delete mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieRule.java delete mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/MsieSslRule.java delete mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectUtil.java delete mode 100644 jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ValidUrlRule.java delete mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTestCase.java delete mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieRuleTest.java delete mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/MsieSslRuleTest.java delete mode 100644 jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ValidUrlRuleTest.java delete mode 100644 jetty-security/src/test/java/org/eclipse/jetty/security/SessionAuthenticationTest.java delete mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java delete mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java delete mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnectionStatistics.java delete mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java delete mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpInputInterceptor.java delete mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java delete mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/AsyncStressTest.java delete mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/CharEncodingContextHandler.java delete mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java delete mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java delete mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionHandlerTest.java delete mode 100644 jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultHandlerTest.java delete mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java delete mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DataRateLimitedServlet.java delete mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PutFilter.java delete mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java delete mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java delete mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DataRateLimitedServletTest.java delete mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/PutFilterTest.java delete mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-client/pom.xml delete mode 100644 jetty-unixsocket/jetty-unixsocket-client/src/main/java/module-info.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-client/src/main/java/org/eclipse/jetty/unixsocket/client/HttpClientTransportOverUnixSockets.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-client/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketClient.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-client/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketTest.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-client/src/test/resources/jetty-logging.properties delete mode 100644 jetty-unixsocket/jetty-unixsocket-common/pom.xml delete mode 100644 jetty-unixsocket/jetty-unixsocket-common/src/main/java/module-info.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-common/src/main/java/org/eclipse/jetty/unixsocket/common/UnixSocketEndPoint.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-common/src/test/java/org/eclipse/jetty/unixsocket/common/JnrTest.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/pom.xml delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/assembly/config.xml delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/etc/jetty-unixsocket-forwarded.xml delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/etc/jetty-unixsocket-http.xml delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/etc/jetty-unixsocket-http2c.xml delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/etc/jetty-unixsocket-proxy-protocol.xml delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/etc/jetty-unixsocket-secure.xml delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/etc/jetty-unixsocket.xml delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/modules/unixsocket-forwarded.mod delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/modules/unixsocket-http.mod delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/modules/unixsocket-http2c.mod delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/modules/unixsocket-prefix.mod delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/modules/unixsocket-proxy-protocol.mod delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/modules/unixsocket-secure.mod delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/config-template/modules/unixsocket-suffix.mod delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/java/module-info.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/main/java/org/eclipse/jetty/unixsocket/server/UnixSocketConnector.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketProxyServer.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/test/java/org/eclipse/jetty/unixsocket/UnixSocketServer.java delete mode 100644 jetty-unixsocket/jetty-unixsocket-server/src/test/resources/jetty-logging.properties delete mode 100644 jetty-webapp/src/main/java/module-info.java delete mode 100644 jetty-webapp/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration delete mode 100644 jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletRequest.java delete mode 100644 jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletResponse.java delete mode 100644 jetty-websocket/websocket-core-tests/src/test/resources/jetty-logging.properties delete mode 100644 jetty-websocket/websocket-jetty-api/src/main/java/module-info.java delete mode 100644 jetty-websocket/websocket-jetty-client/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration delete mode 100644 jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java rename tests/test-sessions/test-file-sessions/src/test/java/org/eclipse/jetty/{server => }/session/ClusteredOrphanedSessionTest.java (68%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/ClusteredOrphanedSessionTest.java (97%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/ClusteredSerializedSessionScavengingTest.java (93%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/ClusteredSessionScavengingTest.java (97%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/InfinispanFileSessionDataStoreTest.java (97%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/InfinispanSessionDataStoreTest.java (98%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/InfinispanTestSupport.java (99%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/LoggingUtil.java (96%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/SerializedInfinispanSessionDataStoreTest.java (98%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/remote/RemoteClusteredInvalidationSessionTest.java (87%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/remote/RemoteClusteredSessionScavengingTest.java (87%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/remote/RemoteInfinispanSessionDataStoreTest.java (91%) rename tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/{server => }/session/remote/RemoteInfinispanTestSupport.java (98%) rename tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/{server => }/session/ClusteredInvalidationSessionTest.java (96%) rename tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/{server => }/session/ClusteredOrphanedSessionTest.java (96%) rename tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/{server => }/session/ClusteredSessionMigrationTest.java (93%) rename tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/{server => }/session/ClusteredSessionScavengingTest.java (96%) rename tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/{server => }/session/JDBCSessionDataStoreTest.java (98%) rename tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/{server => }/session/JdbcTestHelper.java (99%) rename tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/{server => }/session/ReloadedSessionMissingClassTest.java (94%) rename tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/{server => }/session/SessionTableSchemaTest.java (99%) rename tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/{server => }/session/WebAppObjectInSessionTest.java (97%) delete mode 100644 tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpChannelCompleteListener.java delete mode 100644 tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListenerWithWebappClasses.java delete mode 100644 tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestSessionHandler.java rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server => }/session/AbstractClusteredInvalidationSessionTest.java (83%) rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server => }/session/AbstractClusteredOrphanedSessionTest.java (95%) rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server => }/session/AbstractClusteredSessionScavengingTest.java (93%) rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server/session/AbstractTestBase.java => session/AbstractSessionTestBase.java} (89%) rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server => }/session/AbstractWebAppObjectInSessionTest.java (88%) rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server => }/session/Foo.java (94%) rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server => }/session/FooInvocationHandler.java (96%) rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server/session/TestServer.java => session/SessionTestSupport.java} (93%) rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server => }/session/TestFoo.java (96%) rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server => }/session/TestHttpSessionListener.java (98%) rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server => }/session/TestSessionDataStore.java (98%) rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server => }/session/TestSessionDataStoreFactory.java (79%) create mode 100644 tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/session/TestSessionHandler.java rename tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/{server => }/session/WebAppObjectInSessionServlet.java (98%) delete mode 100644 tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DefaultSessionCacheTest.java delete mode 100644 tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/DirtyAttributeTest.java delete mode 100644 tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/ImmortalSessionTest.java delete mode 100644 tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SaveOptimizeTest.java delete mode 100644 tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionEvictionFailureTest.java delete mode 100644 tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionListenerTest.java rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/AsyncTest.java (82%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/ClientCrossContextSessionTest.java (93%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/ConcurrencyTest.java (98%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/CreationTest.java (82%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/DeleteUnloadableSessionTest.java (87%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/DuplicateCookieTest.java (84%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/IdleSessionTest.java (94%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/ModifyMaxInactiveIntervalTest.java (92%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/NonClusteredSessionScavengingTest.java (82%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/RedirectSessionTest.java (94%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/ReentrantRequestSessionTest.java (88%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/RemoveSessionTest.java (94%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/RequestDispatchedSessionTest.java (96%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/RequestScopedSessionSaveTest.java (96%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/SameContextForwardedSessionTest.java (88%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/SessionInvalidateCreateScavengeTest.java (96%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/SessionInvalidationTest.java (96%) rename tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/{server => }/session/SessionRenewTest.java (87%) delete mode 100644 tests/test-websocket-autobahn/pom.xml delete mode 100644 tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/AutobahnClient.java delete mode 100644 tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/AutobahnServer.java delete mode 100644 tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/AutobahnTests.java delete mode 100644 tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/core/TestMessageHandler.java delete mode 100644 tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/core/TestWebSocketNegotiator.java delete mode 100644 tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/jakarta/EchoSocket.java delete mode 100644 tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/jakarta/EventSocket.java delete mode 100644 tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/jakarta/HostConfigurator.java delete mode 100644 tests/test-websocket-autobahn/src/test/java/org/eclipse/jetty/websocket/tests/jetty/EchoSocket.java delete mode 100644 tests/test-websocket-autobahn/src/test/resources/simplelogger.properties diff --git a/Jenkinsfile b/Jenkinsfile index a07d6021278..71fefe0221a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,86 +1,118 @@ #!groovy pipeline { - agent any + agent { + node { label 'linux' } + } // save some io during the build options { durabilityHint('PERFORMANCE_OPTIMIZED') } stages { - stage("Parallel Stage") { - parallel { - stage("Build / Test - JDK17") { - agent { node { label 'linux' } } + stage("Checkout Jetty") { + steps { + container('jetty-build') { + dir("${env.WORKSPACE}/buildy") { + checkout scm + } + } + } + } + stage("Build & Test - JDK17") { + stages { + stage("Setup") { steps { container('jetty-build') { - timeout( time: 180, unit: 'MINUTES' ) { - mavenBuild( "jdk17", "clean install -Perrorprone", "maven3") - // Collect up the jacoco execution results (only on main build) - jacoco inclusionPattern: '**/org/eclipse/jetty/**/*.class', - exclusionPattern: '' + - // build tools - '**/org/eclipse/jetty/ant/**' + ',**/org/eclipse/jetty/maven/**' + - ',**/org/eclipse/jetty/jspc/**' + - // example code / documentation - ',**/org/eclipse/jetty/embedded/**' + ',**/org/eclipse/jetty/asyncrest/**' + - ',**/org/eclipse/jetty/demo/**' + - // special environments / late integrations - ',**/org/eclipse/jetty/gcloud/**' + ',**/org/eclipse/jetty/infinispan/**' + - ',**/org/eclipse/jetty/osgi/**' + - ',**/org/eclipse/jetty/http/spi/**' + - // test classes - ',**/org/eclipse/jetty/tests/**' + ',**/org/eclipse/jetty/test/**', - execPattern: '**/target/jacoco.exec', - classPattern: '**/target/classes', - sourcePattern: '**/src/main/java' - recordIssues id: "jdk17", name: "Static Analysis jdk17", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle(), errorProne(), spotBugs()] + timeout(time: 120, unit: 'MINUTES') { + dir("${env.WORKSPACE}/buildy") { + echo "Install org.eclipse.jetty:build-resources" + mavenBuild("jdk17", "clean install -f build", "maven3") + echo "Install org.eclipse.jetty:jetty-project" + mavenBuild("jdk17", "-N clean install", "maven3") + } } } } } - - stage("Build / Test - JDK11") { - agent { node { label 'linux' } } + stage("Module : /jetty-core/") { steps { - container( 'jetty-build' ) { - timeout( time: 180, unit: 'MINUTES' ) { - mavenBuild( "jdk11", "clean install -Dspotbugs.skip=true -Djacoco.skip=true", "maven3") - recordIssues id: "jdk11", name: "Static Analysis jdk11", aggregatingResults: true, enabledForFailure: true, tools: [mavenConsole(), java(), checkStyle()] + container('jetty-build') { + timeout(time: 120, unit: 'MINUTES') { + dir("${env.WORKSPACE}/buildy") { + mavenBuild("jdk17", "clean install -f jetty-core", "maven3") + } } } } } - + stage("Module : /jetty-ee9/") { + steps { + container('jetty-build') { + timeout(time: 120, unit: 'MINUTES') { + dir("${env.WORKSPACE}/buildy") { + mavenBuild("jdk17", "clean install -f jetty-ee9", "maven3") + } + } + } + } + } + stage("Module : /jetty-ee10/") { + steps { + container('jetty-build') { + timeout(time: 120, unit: 'MINUTES') { + dir("${env.WORKSPACE}/buildy") { + mavenBuild("jdk17", "clean install -f jetty-ee10", "maven3") + } + } + } + } + } + /*stage("Module : /jetty-integrations/") { + steps { + container('jetty-build') { + timeout(time: 120, unit: 'MINUTES') { + dir("${env.WORKSPACE}/buildy") { + mavenBuild("jdk17", "clean install -f jetty-integrations", "maven3") + } + } + } + } + }*/ + /*stage("Module : /jetty-home/") { + steps { + container('jetty-build') { + timeout(time: 120, unit: 'MINUTES') { + dir("${env.WORKSPACE}/buildy") { + mavenBuild("jdk17", "clean install -f jetty-home", "maven3") + } + } + } + } + }*/ + /* + stage("Module : /tests/") { + steps { + container('jetty-build') { + timeout(time: 120, unit: 'MINUTES') { + dir("${env.WORKSPACE}/buildy") { + mavenBuild("jdk17", "clean install -f tests", "maven3") + } + } + } + } + }*/ + /*stage("Module : /documentation/") { + steps { + container('jetty-build') { + timeout(time: 120, unit: 'MINUTES') { + dir("${env.WORKSPACE}/buildy") { + mavenBuild("jdk17", "clean install -f documentation", "maven3") + } + } + } + } + }*/ } } } - post { - failure { - slackNotif() - } - unstable { - slackNotif() - } - fixed { - slackNotif() - } - } -} - -def slackNotif() { - script { - try { - if ( env.BRANCH_NAME == 'jetty-10.0.x' || env.BRANCH_NAME == 'jetty-11.0.x') { - //BUILD_USER = currentBuild.rawBuild.getCause(Cause.UserIdCause).getUserId() - // by ${BUILD_USER} - COLOR_MAP = ['SUCCESS': 'good', 'FAILURE': 'danger', 'UNSTABLE': 'danger', 'ABORTED': 'danger'] - slackSend channel: '#jenkins', - color: COLOR_MAP[currentBuild.currentResult], - message: "*${currentBuild.currentResult}:* Job ${env.JOB_NAME} build ${env.BUILD_NUMBER} - ${env.BUILD_URL}" - } - } catch (Exception e) { - e.printStackTrace() - echo "skip failure slack notification: " + e.getMessage() - } - } } /** @@ -93,14 +125,13 @@ def slackNotif() { * @return the Jenkinsfile step representing a maven build */ def mavenBuild(jdk, cmdline, mvnName) { - script { try { withEnv(["JAVA_HOME=${ tool "$jdk" }", "PATH+MAVEN=${ tool "$jdk" }/bin:${tool "$mvnName"}/bin", "MAVEN_OPTS=-Xms2g -Xmx4g -Djava.awt.headless=true"]) { configFileProvider( [configFile(fileId: 'oss-settings.xml', variable: 'GLOBAL_MVN_SETTINGS')]) { - sh "mvn --no-transfer-progress -s $GLOBAL_MVN_SETTINGS -Dmaven.repo.local=.repository -Pci -V -B -e -Djetty.testtracker.log=true $cmdline" + sh "mvn --no-transfer-progress -s $GLOBAL_MVN_SETTINGS -Pci --show-version --batch-mode --errors -Djetty.testtracker.log=true -Dmaven.test.failure.ignore=true $cmdline" } } } @@ -108,7 +139,6 @@ def mavenBuild(jdk, cmdline, mvnName) { { junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml', allowEmptyResults: true } - } } // vim: et:ts=2:sw=2:ft=groovy diff --git a/Jenkinsfile-autobahn b/Jenkinsfile-autobahn index eee85b9bbbe..0e92efd217d 100644 --- a/Jenkinsfile-autobahn +++ b/Jenkinsfile-autobahn @@ -10,17 +10,8 @@ pipeline { // save some io during the build durabilityHint( 'PERFORMANCE_OPTIMIZED' ) } - parameters { - string( defaultValue: 'jetty-10.0.x', description: 'GIT branch name to build (jetty-10.0.x/jetty-11.0.x/etc.)', - name: 'JETTY_BRANCH' ) - } stages { - stage("Checkout Jetty Branch") { - steps { - git url: 'https://github.com/eclipse/jetty.project/', branch: '${JETTY_BRANCH}' - } - } stage( "Build / Test - JDK11" ) { agent { node { label 'linux' } @@ -28,14 +19,8 @@ pipeline { steps { container( 'jetty-build' ) { timeout( time: 120, unit: 'MINUTES' ) { - mavenBuild( "jdk11", "-T3 clean install -Djacoco.skip=true -pl :test-websocket-autobahn -am -Pautobahn -Dtest=AutobahnTests", "maven3" ) // + mavenBuild( "jdk11", "-T3 clean install -Djacoco.skip=true -Pautobahn", "maven3", true ) // junit testResults: '**/target/surefire-reports/*.xml,**/target/invoker-reports/TEST*.xml,**/target/autobahntestsuite-reports/*.xml' - publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/core/servers", reportFiles: 'index.html', reportName: 'Autobahn Report Core Server', reportTitles: '']) - publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/core/clients", reportFiles: 'index.html', reportName: 'Autobahn Report Core Client', reportTitles: '']) - publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/javax/servers", reportFiles: 'index.html', reportName: 'Autobahn Report Javax Server', reportTitles: '']) - publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/javax/clients", reportFiles: 'index.html', reportName: 'Autobahn Report Javax Client', reportTitles: '']) - publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/jetty/servers", reportFiles: 'index.html', reportName: 'Autobahn Report Jetty Server', reportTitles: '']) - publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "${env.WORKSPACE}/tests/test-websocket-autobahn/target/reports/jetty/clients", reportFiles: 'index.html', reportName: 'Autobahn Report Jetty Client', reportTitles: '']) } } } @@ -83,16 +68,19 @@ def slackNotif() { * @paran mvnName maven installation to use * @return the Jenkinsfile step representing a maven build */ -def mavenBuild(jdk, cmdline, mvnName) { - script { - withEnv(["JAVA_HOME=${ tool "$jdk" }", - "PATH+MAVEN=${ tool "$jdk" }/bin:${tool "$mvnName"}/bin", - "MAVEN_OPTS=-Xms2g -Xmx8g -Djava.awt.headless=true"]) { - configFileProvider( - [configFile(fileId: 'oss-settings.xml', variable: 'GLOBAL_MVN_SETTINGS')]) { - sh "mvn -Denforcer.skip=true -Dlicense.skip=true -Dspotbugs.skip=true -Dcheckstyle.skip=true --no-transfer-progress -s $GLOBAL_MVN_SETTINGS -Dmaven.repo.local=.repository -Pci -V -B -e -Djetty.testtracker.log=true $cmdline" - } - } +def mavenBuild(jdk, cmdline, mvnName, junitPublishDisabled) { + def localRepo = ".repository" + def mavenOpts = '-Xms1g -Xmx4g -Djava.awt.headless=true' + + withMaven( + maven: mvnName, + jdk: "$jdk", + publisherStrategy: 'EXPLICIT', + options: [junitPublisher(disabled: junitPublishDisabled), mavenLinkerPublisher(disabled: false), pipelineGraphPublisher(disabled: false)], + mavenOpts: mavenOpts, + mavenLocalRepo: localRepo) { + // Some common Maven command line + provided command line + sh "mvn -Pci -V -B -e -fae -Dmaven.test.failure.ignore=true -Djetty.testtracker.log=true $cmdline" } } diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 1af414ee5d5..00000000000 --- a/SECURITY.md +++ /dev/null @@ -1,47 +0,0 @@ -# Security Policy - -## Supported Versions - -All [stable versions](https://www.eclipse.org/jetty/download.php) of jetty are actively supported for security issues. [Deprecated versions](https://www.eclipse.org/jetty/download.php) may be supported for serious security issues or on a commercial support basis. - -## Reporting a Vulnerability - -Do not open a public issue to report a security vulnerability. Please send a message to security@eclipse.org and we will create a private issue in which the issue can be triaged and handled. - -## Handling a Vulnerability - -The [following checklist](https://www.eclipse.org/jetty/security_processes.php) is used to handle security issues: - -- [ ] On receipt of a security report via security@webtide.com or other channels, if it cannot be trivially dismissed (already fixed, known not a problem, etc.), then a Github security advisory is created by project leadership. -- [ ] Copy this list as a markdown in the security advisory for tracking the completion of various tasks. -- [ ] Jetty committers and the reporters are added to the security advisory. Individual committers can also be named in the comments for addition. -- [ ] Initial triage and discussion are performed in the comments of the advisory. -- [ ] If enough information exists to attempt reproduction or fix, then a private repository is created as part of the GitHub security advisory. -- [ ] If the vulnerability cannot be confirmed then close the security advisory, else continue. -- [ ] Generate a CVE score and add it to the advisory description. -- [ ] Identify a CWE Definition and add it to the advisory description. -- [ ] Identify vulnerable version(s), including current and past versions that are affected (e.g. 9.4.0 through 9.4.35, and 10.0.0.alpha1 through 10.0.0.beta3…​etc.) -- [ ] Identify and document workaround(s), if applicable, in the comments of the security advisory. -- [ ] Open an Eclipse Bugzilla issue to have a CVE allocated. The issue should be opened under the Community "Product" category with a "Component" of Vulnerability Reports. The CVE should include the following: - 1. Version(s) affected - 2. CVE Score - 3. CWE Identifier(s) - 4. Brief description of the issue -- [ ] Once the CVE is allocated update the Security Advisory with the number -- [ ] Build and test fix(es) locally and in CI environment. -- [ ] Merge tests and fix - ensure description does not mention vulnerability directly. Do not merge directly from the security advisory as it can be traced back before publication. -- [ ] Build and stage release candidate. -- [ ] Notify interested parties of pending security advisory and staged release: - 1. Include CVE number, CVE score, and CWE - 2. Include Workarounds - 3. Stress that it is confidential - 4. Advise the security advisory will be published in 2 days unless they indicate they need more time. -- [ ] If testing is OK, then the release is promoted. -- [ ] Interested parties are notified of the availability of release on Maven Central. -- [ ] Publish security advisory and CVE publicly. -- [ ] Edit VERSION.txt and so that the CVE number is now recorded against merged PR. -- [ ] Edit the release(s) on Github to identify CVE number that was addressed/resolved. -- [ ] Update downstream images (Docker, etc.). -- [ ] Update project website with new security entry. -- [ ] Review security processes & completion. - diff --git a/VERSION.txt b/VERSION.txt index 17d9ac1c850..8b6842d5029 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,228 +1,4 @@ -jetty-11.0.10-SNAPSHOT - -jetty-11.0.9 - 30 March 2022 - + 5681 Unrecognized jetty-home/start.jar command line option not reported - clearly - + 5965 Option --write-module-graph produces wrong .dot file - + 6879 Remove jminix (not maintained) module as hawtio provide same features - + 7182 jetty.sh start process should remove jetty_state whenever deleting the - pid - + 7344 Incompatible with jacoco due to shutdown race condition - + 7414 QoSFilter.setMaxRequests throws NullPointerException - + 7513 Getter/setter type mismatch for mbean attribute file in class - org.eclipse.jetty.deploy.PropertiesConfigurationManager - + 7517 Some ArrayTrie methods throw StackOverflowError when cointaining a very - large entry - + 7518 ArrayTrie getBest fails to match the empty string entry in certain - cases - + 7545 Named arguments do not work in jetty-openid.xml - + 7548 Interrupt flag is not always cleared in between requests - + 7567 Gzip compression not working for multipart/form-data when added to the - allowed list using addIncludedMimeTypes. - + 7573 WebSockets - "Unsupported PathParam Type: java.lang.Integer" - + 7575 Misleading docs for `HttpClientTransportDynamic` - + 7613 Configurations.add(Configuration) results in - UnsupportedOperationException - + 7615 HttpServletResponse.encodeURL not working for URLs starting with ../ - + 7617 Logback-access RequestLog not working - + 7625 HTTP/3 error against www.google.com - + 7677 jetty-maven-plugin - maven internal dependencies included on webapp - classloader - + 7683 GZIPContentDecoder ignores setUseInputDirectByteBuffers setting and - always uses non-direct buffers (causing GC locking) - + 7688 Read data to native memory from HttpInput - + 7748 Allow overriding of url-pattern mapping in ServletContextHandler to - allow for regex or uri-template matching - -jetty-11.0.8 - 07 February 2022 - + 2504 Expose more WebSocket details in JMX and Server Dump - + 4275 Path Normalization/Traversal - Context Matching - + 4317 EventSource does not work with GzipHandler - + 6017 Property overriding does not work - + 6282 SecuredRedirectHandler should probably redirect with 301 - + 6497 Replace SameFileAliasChecker - + 6728 QUIC and HTTP/3 - + 6730 HTTP3: update Quiche to 0.9.0 - + 6965 Expose Spec `ServerContainer.upgrade()` API - + 6973 Jetty starts consuming CPU that remains high even without any traffic - + 6974 Major websocket memory change in 9.4.36 - + 6980 ELContextCleaner failed because cannot access a member of class - javax.el.BeanELResolver with modifiers "private static final" - + 6985 ELContextCleaner references javax class in jetty-11 - + 6987 jetty-unixdomain-server is missing from jetty-bom - + 6990 UnixDomainServerConnector throws misleading exception on invalid socket - path - + 7008 Problem with jetty.sh start regression 10.0.6 -> 10.0.7 when using - JETTY_USER - + 7012 Remove all old geronimo spec jars from jetty-10 - + 7031 ResponseWriter.println(char) does not print newline - + 7042 Simplify configuration to use different OpenIdConfiguration per webapp - + 7059 NPE in AllowedResourceAliasChecker.getPath() - + 7063 Simplify command line use of org.eclipse.jetty.util.Password - + 7064 Cleanup or clarify `(null)` in output of `--list-config` - + 7086 WebSocket: java.lang.IllegalStateException: already released - RetainableByteBuffer - + 7103 Rework LaF of distro landing page - + 7107 Client timeout and async close exceptions when setting max duration on - pool - + 7109 Deprecate UnixSocket JNR support - + 7111 Add support to deprecate jetty-home modules - + 7113 Improve Unix-Domain client documentation - + 7124 Add default methods on LifeCycle.Listener interface - + 7131 Use Charset instead of encoding string where possible - + 7157 Multiplexed connection pools retain CLOSED entries - + 7160 HttpURI considers %25 to be ambiguous, preventing access to static - resources with % in their name - + 7240 Clarify and javadoc InvocationType - + 7243 Reset pooled ByteBuffer endianness - + 7262 Allow the SerlvetHandler.getFilterChain method to be overridden - + 7277 Allow override of `ServletRequest.getLocalName()` and `.getLocalPort()` - in post-intermediary scenarios - + 7280 Interceptors don't get destroyed in HttpInput - + 7281 EOFs are not passed to interceptors any more - shouldn't they? - + 7284 HttpInput reopen/recycle cleanup - + 7297 Deprecate log4j 1.x support - + 7313 addBean(_attributes); only called in the Convenience constructor of - org.eclipse.jetty.server.Server - + 7327 jetty-slf4j-impl missing from BOM - + 7348 Slow CONNECT request causes NPE - + 7351 Large WebSocket payloads with permessage-deflate hang on 10.0.7 - + 7354 Demo jars should not be in jetty-home - + 7369 Document CustomRequestLog - + 7375 Some environments require Request scoping during session save - + 7435 Investigate Infinispan transitive dependencies - + 7494 Remove modules in Jetty 11 that are not supporting jakarta.servlet yet - + 7496 Transient 400: Bad Request responses in jetty-9.4.45.v20220128 - + 7514 Adding InheritedListeners to already-started components can cause - IllegalStateException - + 7523 Typo in AnnotationConfiguration - + 7524 Missing package in JmxConfiguration - + 7529 Upgrade quiche to version 0.11.0 - -jetty-10.0.9 - 30 March 2022 - + 5681 Unrecognized jetty-home/start.jar command line option not reported - clearly - + 5965 Option --write-module-graph produces wrong .dot file - + 6879 Remove jminix (not maintained) module as hawtio provide same features - + 7182 jetty.sh start process should remove jetty_state whenever deleting the - pid - + 7344 Incompatible with jacoco due to shutdown race condition - + 7414 QoSFilter.setMaxRequests throws NullPointerException - + 7513 Getter/setter type mismatch for mbean attribute file in class - org.eclipse.jetty.deploy.PropertiesConfigurationManager - + 7517 Some ArrayTrie methods throw StackOverflowError when cointaining a very - large entry - + 7518 ArrayTrie getBest fails to match the empty string entry in certain - cases - + 7545 Named arguments do not work in jetty-openid.xml - + 7548 Interrupt flag is not always cleared in between requests - + 7567 Gzip compression not working for multipart/form-data when added to the - allowed list using addIncludedMimeTypes. - + 7573 WebSockets - "Unsupported PathParam Type: java.lang.Integer" - + 7575 Misleading docs for `HttpClientTransportDynamic` - + 7613 Configurations.add(Configuration) results in - UnsupportedOperationException - + 7615 HttpServletResponse.encodeURL not working for URLs starting with ../ - + 7617 Logback-access RequestLog not working - + 7625 HTTP/3 error against www.google.com - + 7677 jetty-maven-plugin - maven internal dependencies included on webapp - classloader - + 7683 GZIPContentDecoder ignores setUseInputDirectByteBuffers setting and - always uses non-direct buffers (causing GC locking) - + 7688 Read data to native memory from HttpInput - + 7748 Allow overriding of url-pattern mapping in ServletContextHandler to - allow for regex or uri-template matching - -jetty-10.0.8 - 07 February 2022 - + 2504 Expose more WebSocket details in JMX and Server Dump - + 4275 Path Normalization/Traversal - Context Matching - + 4317 EventSource does not work with GzipHandler - + 6017 Property overriding does not work - + 6282 SecuredRedirectHandler should probably redirect with 301 - + 6497 Replace SameFileAliasChecker - + 6728 QUIC and HTTP/3 - + 6730 HTTP3: update Quiche to 0.9.0 - + 6965 Expose Spec `ServerContainer.upgrade()` API - + 6973 Jetty starts consuming CPU that remains high even without any traffic - + 6974 Major websocket memory change in 9.4.36 - + 6980 ELContextCleaner failed because cannot access a member of class - javax.el.BeanELResolver with modifiers "private static final" - + 6987 jetty-unixdomain-server is missing from jetty-bom - + 6990 UnixDomainServerConnector throws misleading exception on invalid socket - path - + 7008 Problem with jetty.sh start regression 10.0.6 -> 10.0.7 when using - JETTY_USER - + 7012 Remove all old geronimo spec jars from jetty-10 - + 7031 ResponseWriter.println(char) does not print newline - + 7042 Simplify configuration to use different OpenIdConfiguration per webapp - + 7059 NPE in AllowedResourceAliasChecker.getPath() - + 7063 Simplify command line use of org.eclipse.jetty.util.Password - + 7064 Cleanup or clarify `(null)` in output of `--list-config` - + 7086 WebSocket: java.lang.IllegalStateException: already released - RetainableByteBuffer - + 7103 Rework LaF of distro landing page - + 7107 Client timeout and async close exceptions when setting max duration on - pool - + 7109 Deprecate UnixSocket JNR support - + 7111 Add support to deprecate jetty-home modules - + 7113 Improve Unix-Domain client documentation - + 7124 Add default methods on LifeCycle.Listener interface - + 7131 Use Charset instead of encoding string where possible - + 7157 Multiplexed connection pools retain CLOSED entries - + 7160 HttpURI considers %25 to be ambiguous, preventing access to static - resources with % in their name - + 7240 Clarify and javadoc InvocationType - + 7243 Reset pooled ByteBuffer endianness - + 7262 Allow the SerlvetHandler.getFilterChain method to be overridden - + 7277 Allow override of `ServletRequest.getLocalName()` and `.getLocalPort()` - in post-intermediary scenarios - + 7280 Interceptors don't get destroyed in HttpInput - + 7281 EOFs are not passed to interceptors any more - shouldn't they? - + 7284 HttpInput reopen/recycle cleanup - + 7297 Deprecate log4j 1.x support - + 7313 addBean(_attributes); only called in the Convenience constructor of - org.eclipse.jetty.server.Server - + 7327 jetty-slf4j-impl missing from BOM - + 7348 Slow CONNECT request causes NPE - + 7351 Large WebSocket payloads with permessage-deflate hang on 10.0.7 - + 7354 Demo jars should not be in jetty-home - + 7369 Document CustomRequestLog - + 7375 Some environments require Request scoping during session save - + 7435 Investigate Infinispan transitive dependencies - + 7496 Transient 400: Bad Request responses in jetty-9.4.45.v20220128 - + 7514 Adding InheritedListeners to already-started components can cause - IllegalStateException - + 7523 Typo in AnnotationConfiguration - + 7524 Missing package in JmxConfiguration - + 7529 Upgrade quiche to version 0.11.0 - -jetty-9.4.45.v20220203 - 03 February 2022 - + 4275 Path Normalization/Traversal - Context Matching - + 6497 Replace SameFileAliasChecker - + 6687 Upgrade Infinispan in all active Jetty branches - + 6965 Expose Spec `ServerContainer.upgrade()` API - + 6969 Getting 404 failures when trying to enable `logging-log4j` module - + 6974 Major websocket memory change in 9.4.36 - + 7031 ResponseWriter.println(char) does not print newline - + 7059 NPE in AllowedResourceAliasChecker.getPath() - + 7073 Error in parse parameter in broken UTF-8 encoding - + 7078 CompressionPools are not shared between multiple contexts for 9.4 - WebSocket - + 7107 Client timeout and async close exceptions when setting max duration on - pool - + 7124 Add default methods on LifeCycle.Listener interface - + 7157 Multiplexed connection pools retain CLOSED entries - + 7243 Reset pooled ByteBuffer endianness - + 7266 Wrong ALPN jars are selected for newer versions of JDK8 - + 7271 It is necessary to set MAX_CAPACITY to ArrayTernaryTrie/ArrayTrie - + 7277 Allow override of `ServletRequest.getLocalName()` and `.getLocalPort()` - in post-intermediary scenarios - + 7297 Deprecate log4j 1.x support - + 7348 Slow CONNECT request causes NPE - + 7375 Some environments require Request scoping during session save - + 7435 Investigate Infinispan transitive dependencies - + 7440 ContextHandler.getAliasChecks() breaks Spring Boot - + 7496 Transient 400: Bad Request responses in jetty-9.4.45.v20220128 +jetty-12.0.0-SNAPSHOT jetty-11.0.7 - 06 October 2021 + 3514 Use interpolation of versions from pom in mod files @@ -238,15 +14,15 @@ jetty-11.0.7 - 06 October 2021 active modules + 6487 Expose ServletHolder getter in ServletHandler$ChainEnd for auditing libraries to use - + 6489 Some URI valid compliance modes cannot be set in .ini file + + 6489 Some URI valid compliance modes cannot be set in .ini file. + 6491 onDataAvailable() not called when HttpParser is closed prematurely + 6497 Replace SameFileAliasChecker - + 6520 Error page has HTML error when writePoweredBy is enabled + + 6520 Error page has HTML error when writePoweredBy is enabled. + 6544 Using jetty.gzip.excludedMimeTypeList property results in an error + 6545 image/webp MIME type support + 6552 FileBufferedInterceptor.dispose not working due to locked file + 6553 Review usage of Authentication.UNAUTHENTICATED in SecurityHandler - + 6554 Allow creation of DefaultIdentityService without realmName + + 6554 Allow creation of DefaultIdentityService without realmName. + 6556 MemcachedSessionDataMap needs to set the context classloader before serialization/deseriazliation. + 6558 Allow to configure return type in JSON array parsing @@ -262,7 +38,7 @@ jetty-11.0.7 - 06 October 2021 + 6617 Add basic auth support for OpenId token endpoint (client_secret_basic) + 6618 ID token `azp` claim should not be required if `aud` is single value array - + 6642 WebSocket handling of Connection: upgrade,close + + 6642 WebSocket handling of Connection: upgrade,close. + 6646 Deadlock in HTTP2Flusher when using a small thread pool due to incorrect InvocableType + 6652 Improve ReservedThreadExecutor dump @@ -285,6 +61,19 @@ jetty-11.0.7 - 06 October 2021 + 6883 Welcome file redirects do not honor the relativeRedirectAllowed option + 6938 module-info.java file do not use the canonical order for the elements +jetty-11.0.6 - 29 June 2021 + + 6375 Always check XML `Set` elements with `property` attribute + + 6382 HttpClient TimeoutException message reports transient values + + 6394 Review osgi manifests within Jetty 10 + + 6407 Malformed scheme logical expression check in WebSocket + ClientUpgradeRequest + + 6410 Ensure Jetty IO uses SocketAddress instead of InetSocketAddress + + 6418 Bad and/or missing Require-Capability for osgi.serviceloader + + 6425 Update to asm 9.1 + + 6447 Deprecate support for UTF16 encoding in URIs + + 6451 Request#getServletPath() returns null for ROOT mapping + + 6464 Wrong files/lib definitions in certain *-capture.mod files? + jetty-10.0.7 - 06 October 2021 + 3514 Use interpolation of versions from pom in mod files + 6043 Reimplement UnixSocket support based on Java 16 @@ -298,15 +87,15 @@ jetty-10.0.7 - 06 October 2021 active modules + 6487 Expose ServletHolder getter in ServletHandler$ChainEnd for auditing libraries to use - + 6489 Some URI valid compliance modes cannot be set in .ini file + + 6489 Some URI valid compliance modes cannot be set in .ini file. + 6491 onDataAvailable() not called when HttpParser is closed prematurely + 6497 Replace SameFileAliasChecker - + 6520 Error page has HTML error when writePoweredBy is enabled + + 6520 Error page has HTML error when writePoweredBy is enabled. + 6544 Using jetty.gzip.excludedMimeTypeList property results in an error + 6545 image/webp MIME type support + 6552 FileBufferedInterceptor.dispose not working due to locked file + 6553 Review usage of Authentication.UNAUTHENTICATED in SecurityHandler - + 6554 Allow creation of DefaultIdentityService without realmName + + 6554 Allow creation of DefaultIdentityService without realmName. + 6556 MemcachedSessionDataMap needs to set the context classloader before serialization/deseriazliation. + 6558 Allow to configure return type in JSON array parsing @@ -321,7 +110,7 @@ jetty-10.0.7 - 06 October 2021 + 6617 Add basic auth support for OpenId token endpoint (client_secret_basic) + 6618 ID token `azp` claim should not be required if `aud` is single value array - + 6642 WebSocket handling of Connection: upgrade,close + + 6642 WebSocket handling of Connection: upgrade,close. + 6646 Deadlock in HTTP2Flusher when using a small thread pool due to incorrect InvocableType + 6652 Improve ReservedThreadExecutor dump @@ -342,45 +131,6 @@ jetty-10.0.7 - 06 October 2021 + 6883 Welcome file redirects do not honor the relativeRedirectAllowed option + 6938 module-info.java file do not use the canonical order for the elements -jetty-9.4.44.v20210927 - 27 September 2021 - + 3514 Use interpolation of versions from pom in mod files - + 6369 Increment default jetty.http2.rateControl.maxEventsPerSecond - + 6372 Review socket options configuration - + 6487 Expose ServletHolder getter in ServletHandler$ChainEnd for auditing - libraries to use - + 6491 onDataAvailable() not called when HttpParser is closed prematurely - + 6520 Error page has HTML error when writePoweredBy is enabled - + 6545 image/webp MIME type support - + 6553 Review usage of Authentication.UNAUTHENTICATED in SecurityHandler - + 6554 Allow creation of DefaultIdentityService without realmName - + 6558 Allow to configure return type in JSON array parsing - + 6562 HttpOutput.write(ByteBuffer buffer) - + 6603 HTTP/2 max local stream count exceeded - + 6617 Add basic auth support for OpenId token endpoint (client_secret_basic) - + 6618 ID token `azp` claim should not be required if `aud` is single value - array - + 6652 Improve ReservedThreadExecutor dump - + 6671 Update to apache jsp 8.5.70 - + 6772 Update to asm 9.2 - + 6853 Remove pack200 plugins - + 6860 Correct IPv6 format - + 6869 Correct Content-Type within HTML error pages - + 6870 Encode control characters in URIUtil.encodePath - + 6883 Welcome file redirects do not honor the relativeRedirectAllowed option - -jetty-11.0.6 - 29 June 2021 - + 6375 Always check XML `Set` elements with `property` attribute - + 6382 HttpClient TimeoutException message reports transient values - + 6394 Review osgi manifests within Jetty 10 - + 6407 Malformed scheme logical expression check in WebSocket - ClientUpgradeRequest - + 6410 Ensure Jetty IO uses SocketAddress instead of InetSocketAddress - + 6418 Bad and/or missing Require-Capability for osgi.serviceloader - + 6425 Update to asm 9.1 - + 6447 Deprecate support for UTF16 encoding in URIs - + 6451 Request#getServletPath() returns null for ROOT mapping - + 6464 Wrong files/lib definitions in certain *-capture.mod files? - jetty-10.0.6 - 29 June 2021 + 6375 Always check XML `Set` elements with `property` attribute + 6382 HttpClient TimeoutException message reports transient values diff --git a/apache-jsp/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer b/apache-jsp/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer deleted file mode 100644 index 634670591f6..00000000000 --- a/apache-jsp/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer +++ /dev/null @@ -1 +0,0 @@ -org.eclipse.jetty.apache.jsp.JettyJasperInitializer diff --git a/apache-jsp/src/main/resources/META-INF/services/org.apache.juli.logging.Log b/apache-jsp/src/main/resources/META-INF/services/org.apache.juli.logging.Log deleted file mode 100644 index efa397b7c60..00000000000 --- a/apache-jsp/src/main/resources/META-INF/services/org.apache.juli.logging.Log +++ /dev/null @@ -1 +0,0 @@ -org.eclipse.jetty.apache.jsp.JuliLog diff --git a/build-resources/jetty-codestyle-eclipse-ide.xml b/build/build-resources/jetty-codestyle-eclipse-ide.xml similarity index 100% rename from build-resources/jetty-codestyle-eclipse-ide.xml rename to build/build-resources/jetty-codestyle-eclipse-ide.xml diff --git a/build-resources/jetty-codestyle-intellij.xml b/build/build-resources/jetty-codestyle-intellij.xml similarity index 100% rename from build-resources/jetty-codestyle-intellij.xml rename to build/build-resources/jetty-codestyle-intellij.xml diff --git a/build-resources/pom.xml b/build/build-resources/pom.xml similarity index 96% rename from build-resources/pom.xml rename to build/build-resources/pom.xml index 4dcfa070076..2cfa013be4f 100644 --- a/build-resources/pom.xml +++ b/build/build-resources/pom.xml @@ -7,7 +7,7 @@ --> org.eclipse.jetty build-resources - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT Jetty :: Build Resources jar @@ -15,7 +15,7 @@ UTF-8 3.0.0-M2 - 3.4.0 + 3.3.1 1.6.0 3.0.0-M5 diff --git a/build-resources/src/main/resources/jetty-checkstyle.xml b/build/build-resources/src/main/resources/jetty-checkstyle.xml similarity index 100% rename from build-resources/src/main/resources/jetty-checkstyle.xml rename to build/build-resources/src/main/resources/jetty-checkstyle.xml diff --git a/jetty-unixsocket/pom.xml b/build/pom.xml similarity index 53% rename from jetty-unixsocket/pom.xml rename to build/pom.xml index 38055172b8a..58db6fd7ca4 100644 --- a/jetty-unixsocket/pom.xml +++ b/build/pom.xml @@ -1,25 +1,22 @@ + org.eclipse.jetty jetty-project - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT 4.0.0 - jetty-unixsocket + org.eclipse.jetty.build + build + Build :: Parent pom - Jetty :: UnixSocket - - Jetty UnixSocket Parent - - - jetty-unixsocket-common - jetty-unixsocket-client - jetty-unixsocket-server - - org.eclipse.jetty.unixsocket.* + true + + build-resources + diff --git a/scripts/clirr-gen-master-index.output-foot.html b/build/scripts/clirr-gen-master-index.output-foot.html similarity index 100% rename from scripts/clirr-gen-master-index.output-foot.html rename to build/scripts/clirr-gen-master-index.output-foot.html diff --git a/scripts/clirr-gen-master-index.output-head.html b/build/scripts/clirr-gen-master-index.output-head.html similarity index 100% rename from scripts/clirr-gen-master-index.output-head.html rename to build/scripts/clirr-gen-master-index.output-head.html diff --git a/scripts/clirr-gen-master-index.output-html.xslt b/build/scripts/clirr-gen-master-index.output-html.xslt similarity index 100% rename from scripts/clirr-gen-master-index.output-html.xslt rename to build/scripts/clirr-gen-master-index.output-html.xslt diff --git a/scripts/clirr-gen-master-index.sh b/build/scripts/clirr-gen-master-index.sh similarity index 100% rename from scripts/clirr-gen-master-index.sh rename to build/scripts/clirr-gen-master-index.sh diff --git a/scripts/git-log-csv.sh b/build/scripts/git-log-csv.sh similarity index 100% rename from scripts/git-log-csv.sh rename to build/scripts/git-log-csv.sh diff --git a/scripts/looptest.sh b/build/scripts/looptest.sh similarity index 100% rename from scripts/looptest.sh rename to build/scripts/looptest.sh diff --git a/scripts/query-git-stats.sh b/build/scripts/query-git-stats.sh similarity index 100% rename from scripts/query-git-stats.sh rename to build/scripts/query-git-stats.sh diff --git a/scripts/release-jetty.sh b/build/scripts/release-jetty.sh similarity index 100% rename from scripts/release-jetty.sh rename to build/scripts/release-jetty.sh diff --git a/demos/demo-async-rest/pom.xml b/demos/demo-async-rest/pom.xml deleted file mode 100644 index a344b4b5a40..00000000000 --- a/demos/demo-async-rest/pom.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - org.eclipse.jetty.demos - demos-parent - 11.0.10-SNAPSHOT - - - 4.0.0 - demo-async-rest-parent - pom - Demo :: Async Rest - - - demo-async-rest-jar - demo-async-rest-webapp - demo-async-rest-server - - - diff --git a/demos/demo-jetty-webapp/src/test/resources/jetty-logging.properties b/demos/demo-jetty-webapp/src/test/resources/jetty-logging.properties deleted file mode 100644 index 04724735a9d..00000000000 --- a/demos/demo-jetty-webapp/src/test/resources/jetty-logging.properties +++ /dev/null @@ -1,3 +0,0 @@ -# Jetty Logging using jetty-slf4j-impl -com.acme.LEVEL=INFO -# org.eclipse.jetty.annotations.LEVEL=DEBUG \ No newline at end of file diff --git a/demos/demo-mock-resources/src/main/resources/META-INF/javaxmail.providers b/demos/demo-mock-resources/src/main/resources/META-INF/javaxmail.providers deleted file mode 100644 index 5ab3340c05a..00000000000 --- a/demos/demo-mock-resources/src/main/resources/META-INF/javaxmail.providers +++ /dev/null @@ -1 +0,0 @@ - protocol=smtp; type=transport; class=com.acme.MockTransport; vendor=Acme Tests; diff --git a/demos/demo-spec/demo-container-initializer/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer b/demos/demo-spec/demo-container-initializer/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer deleted file mode 100644 index 622cbd01213..00000000000 --- a/demos/demo-spec/demo-container-initializer/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer +++ /dev/null @@ -1 +0,0 @@ -com.acme.initializer.FooInitializer diff --git a/documentation/jetty-asciidoctor-extensions/pom.xml b/documentation/jetty-asciidoctor-extensions/pom.xml index 4bab6afb942..939986ccc5e 100644 --- a/documentation/jetty-asciidoctor-extensions/pom.xml +++ b/documentation/jetty-asciidoctor-extensions/pom.xml @@ -2,8 +2,8 @@ org.eclipse.jetty.documentation - documentation-parent - 11.0.10-SNAPSHOT + documentation + 12.0.0-SNAPSHOT 4.0.0 diff --git a/documentation/jetty-documentation/pom.xml b/documentation/jetty-documentation/pom.xml index f1351320e2d..641f05f9b8a 100644 --- a/documentation/jetty-documentation/pom.xml +++ b/documentation/jetty-documentation/pom.xml @@ -2,14 +2,17 @@ org.eclipse.jetty.documentation - documentation-parent - 11.0.10-SNAPSHOT + documentation + 12.0.0-SNAPSHOT 4.0.0 jetty-documentation Jetty :: Documentation jar + + ee9 + @@ -136,6 +139,18 @@ + + + + org.eclipse.jetty.${ee.version} + jetty-${ee.version}-bom + ${project.version} + pom + import + + + + org.eclipse.jetty.toolchain @@ -162,20 +177,20 @@ jetty-server - org.eclipse.jetty - jetty-servlet + org.eclipse.jetty.${ee.version} + jetty-${ee.version}-servlet - org.eclipse.jetty - jetty-servlets + org.eclipse.jetty.${ee.version} + jetty-${ee.version}-servlets org.eclipse.jetty jetty-util-ajax - org.eclipse.jetty - jetty-webapp + org.eclipse.jetty.${ee.version} + jetty-${ee.version}-webapp org.eclipse.jetty.fcgi @@ -215,16 +230,16 @@ jetty-nosql - org.eclipse.jetty.websocket - websocket-jetty-client + org.eclipse.jetty.${ee.version} + jetty-${ee.version}-websocket-jetty-client - org.eclipse.jetty.websocket - websocket-jakarta-server + org.eclipse.jetty.${ee.version} + jetty-${ee.version}-websocket-jakarta-server - org.eclipse.jetty.websocket - websocket-jetty-server + org.eclipse.jetty.${ee.version} + jetty-${ee.version}-websocket-jetty-server diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/annotations/quick-annotations-setup.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/annotations/quick-annotations-setup.adoc index 2cdb351b97b..b7e82bee667 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/annotations/quick-annotations-setup.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/annotations/quick-annotations-setup.adoc @@ -28,7 +28,7 @@ Annotations and JNDI are pre-enabled for the Maven plugin. ==== Embedding To use annotations in an embedded scenario, you will need to include the `jetty-annotations` jar and all its dependencies onto your classpath. -You will also need to include the `org.eclipse.jetty.annotations.AnnotationConfiguration` class into the list of link:#webapp-configurations[Configuration classes] applied to the `org.eclipse.jetty.webapp.WebAppContext` class representing your webapp. +You will also need to include the `org.eclipse.jetty.annotations.AnnotationConfiguration` class into the list of link:#webapp-configurations[Configuration classes] applied to the `org.eclipse.jetty.ee9.webapp.WebAppContext` class representing your webapp. Below is an example application that sets up the standard `test-spec.war` webapp from the distribution in embedded fashion. It can also be found in the Jetty GitHub repository on the examples/embedded page as link:{GITBROWSEURL}/examples/embedded/src/main/java/org/eclipse/jetty/embedded[`ServerWithAnnotations.java`.] diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/annotations/using-annotations-embedded.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/annotations/using-annotations-embedded.adoc index 29c7c315e9c..739924a4167 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/annotations/using-annotations-embedded.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/annotations/using-annotations-embedded.adoc @@ -38,7 +38,7 @@ The code is as follows: ---- import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.ee9.webapp.WebAppContext; /** * ServerWithAnnotations @@ -53,9 +53,9 @@ public class ServerWithAnnotations Server server = new Server(8080); //Enable parsing of jndi-related parts of web.xml and jetty-env.xml - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); - classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration"); - classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.annotations.AnnotationConfiguration"); + org.eclipse.jetty.ee9.webapp.Configuration.ClassList classlist = org.eclipse.jetty.ee9.webapp.Configuration.ClassList.setServerDefault(server); + classlist.addAfter("org.eclipse.jetty.ee9.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration"); + classlist.addBefore("org.eclipse.jetty.ee9.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.annotations.AnnotationConfiguration"); //Create a WebApp WebAppContext webapp = new WebAppContext(); diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/ant/jetty-ant.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/ant/jetty-ant.adoc index 324c5ede3a0..e6da9506c7b 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/ant/jetty-ant.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/ant/jetty-ant.adoc @@ -462,7 +462,7 @@ deploying more than one web application::: As the `org.eclipse.jetty.ant.AntWebAppContext` class is an extension of the -link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html[`org.eclipse.jetty.webapp.WebAppContext`] +link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html[`org.eclipse.jetty.ee9.webapp.WebAppContext`] class, you can configure it by adding attributes of the same name (without the `set` or `add` prefix) as the setter methods. diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/architecture/jetty-classloading.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/architecture/jetty-classloading.adoc index 074900b4adf..5b97ab66493 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/architecture/jetty-classloading.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/architecture/jetty-classloading.adoc @@ -57,12 +57,12 @@ Below is an example of implementing this feature using Jetty IoC XML format: [source, xml, options="header"] ---- - + ... - + @@ -97,7 +97,7 @@ The default system classes are: |org.eclipse.jetty.jndi. |Webapp can see and not change naming classes. |org.eclipse.jetty.jaas. |Webapp can see and not change JAAS classes. |org.eclipse.jetty.websocket. |WebSocket is a Jetty extension. -|org.eclipse.jetty.servlet.DefaultServlet |Webapp can see and not change default servlet. +|org.eclipse.jetty.ee9.servlet.DefaultServlet |Webapp can see and not change default servlet. |======================================================================= Absolute classname can be passed, names ending with `.` are treated as packages names, and names starting with `-` are treated as negative matches and must be listed before any enclosing packages. @@ -105,8 +105,8 @@ Absolute classname can be passed, names ending with `.` are treated as packages [[setting-server-classes]] ===== Setting Server Classes -You can call the methods link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html#setServerClasses%28java.lang.String%5B%5D%29[org.eclipse.jetty.webapp.WebAppContext.setServerClasses(String Array)] or -link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html#addServerClass(java.lang.String)[org.eclipse.jetty.webapp.WebAppContext.addServerClass(String)] to allow fine control over which classes are considered Server classes. +You can call the methods link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html#setServerClasses%28java.lang.String%5B%5D%29[org.eclipse.jetty.ee9.webapp.WebAppContext.setServerClasses(String Array)] or +link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html#addServerClass(java.lang.String)[org.eclipse.jetty.ee9.webapp.WebAppContext.addServerClass(String)] to allow fine control over which classes are considered Server classes. * A web application cannot see a Server class. * A WEB-INF class can replace a Server class. @@ -120,9 +120,9 @@ The default server classes are: |-org.eclipse.jetty.continuation. |Don't hide continuation classes. |-org.eclipse.jetty.jndi. |Don't hide naming classes. |-org.eclipse.jetty.jaas. |Don't hide jaas classes. -|-org.eclipse.jetty.servlets. |Don't hide utility servlet classes if provided. -|-org.eclipse.jetty.servlet.DefaultServlet |Don't hide default servlet. -|-org.eclipse.jetty.servlet.listener. |Don't hide utility listeners +|-org.eclipse.jetty.ee9.servlets. |Don't hide utility servlet classes if provided. +|-org.eclipse.jetty.ee9.servlet.DefaultServlet |Don't hide default servlet. +|-org.eclipse.jetty.ee9.servlet.listener. |Don't hide utility listeners |-org.eclipse.jetty.websocket. |Don't hide websocket extension. | org.eclipse.jetty. |Do hide all other Jetty classes. |======================================================================= @@ -145,12 +145,12 @@ You can place additional Jars here. [[using-extra-classpath-method]] ===== Using the extraClasspath() method -You can add an additional classpath to a context classloader by calling link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html#setExtraClasspath(java.lang.String)[org.eclipse.jetty.webapp.WebAppContext.setExtraClasspath(String)] with a comma-separated list of paths. +You can add an additional classpath to a context classloader by calling link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html#setExtraClasspath(java.lang.String)[org.eclipse.jetty.ee9.webapp.WebAppContext.setExtraClasspath(String)] with a comma-separated list of paths. You can do so directly to the API via a context XML file such as the following: [source, xml, subs="{sub-order}"] ---- - + ... ../my/classes,../my/jars/special.jar,../my/jars/other.jar ... diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/custom-error-pages.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/custom-error-pages.adoc index e4ed161a3fc..95616785c53 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/custom-error-pages.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/custom-error-pages.adoc @@ -74,7 +74,7 @@ Context files are normally located in `${jetty.base}/webapps/` (see `DeployerMan - + /test /webapps/test @@ -114,8 +114,8 @@ Context files are normally located in `${jetty.base}/webapps/` (see `DeployerMan ==== Custom ErrorHandler class -If no error page mapping is defined, or if the error page resource itself has an error, then the error page will be generated by an instance of `ErrorHandler` configured either the Context or the Server. -An `ErrorHandler` may extend the `ErrorHandler` class and may totally replace the handle method to generate an error page, or it can implement some or all of the following methods to partially modify the error pages: +If no error page mapping is defined, or if the error page resource itself has an error, then the error page will be generated by an instance of `ErrorProcessor` configured either the Context or the Server. +An `ErrorProcessor` may extend the `ErrorProcessor` class and may totally replace the handle method to generate an error page, or it can implement some or all of the following methods to partially modify the error pages: [source, java, subs="{sub-order}"] ---- @@ -128,7 +128,7 @@ void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, void writeErrorPageStacks(HttpServletRequest request, Writer writer) throws IOException ---- -An `ErrorHandler` instance may be set on a Context by calling the `ContextHandler.setErrorHandler` method. This can be done by embedded code or via context IoC XML. +An `ErrorProcessor` instance may be set on a Context by calling the `ContextHandler.setErrorHandler` method. This can be done by embedded code or via context IoC XML. For example: [source, xml, subs="{sub-order}"] @@ -142,7 +142,7 @@ For example: ---- -An `ErrorHandler` instance may be set on the entire server by setting it as a dependent bean on the Server instance. +An `ErrorProcessor` instance may be set on the entire server by setting it as a dependent bean on the Server instance. This can be done by calling `Server.addBean(Object)` via embedded code or in `jetty.xml` IoC XML: [source, xml, subs="{sub-order}"] diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/setting-context-path.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/setting-context-path.adoc index b8e1967563b..1e78118fe85 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/setting-context-path.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/setting-context-path.adoc @@ -42,7 +42,7 @@ For example: [source, xml, subs="{sub-order}"] ---- - + /test ... diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/setting-form-size.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/setting-form-size.adoc index 3bb44ee25c6..072bdee408f 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/setting-form-size.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/setting-form-size.adoc @@ -36,7 +36,7 @@ In either case the syntax of the XML file is the same: [source,xml,subs="{sub-order}"] ---- - + diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/temporary-directories.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/temporary-directories.adoc index 0c85b607ced..da025dc2bf7 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/temporary-directories.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/contexts/temporary-directories.adoc @@ -39,7 +39,7 @@ Once the temp directory is created, it is retrievable as the value (as a File) o ===== The location of the temp directory By default, Jetty will create this directory inside the directory named by the `java.io.tmpdir` System property. -You can instruct Jetty to use a different parent directory by setting the context attribute `org.eclipse.jetty.webapp.basetempdir` to the name of the desired parent directory. +You can instruct Jetty to use a different parent directory by setting the context attribute `org.eclipse.jetty.ee9.webapp.basetempdir` to the name of the desired parent directory. The directory named by this attribute _must_ exist and be __writeable__. As usual with Jetty, you can either set this attribute in a context xml file, or you can do it in code. @@ -48,13 +48,13 @@ Here's an example of setting it in an xml file: [source, xml, subs="{sub-order}"] ---- - + /test foo.war - org.eclipse.jetty.webapp.basetempdir + org.eclipse.jetty.ee9.webapp.basetempdir /home/my/foo @@ -67,7 +67,7 @@ The equivalent in code is: WebAppContext context = new WebAppContext(); context.setContextPath("/test"); context.setWar("foo.war"); -context.setAttribute("org.eclipse.jetty.webapp.basetempdir", "/tmp/foo"); +context.setAttribute("org.eclipse.jetty.ee9.webapp.basetempdir", "/tmp/foo"); ---- ==== Setting a Specific Temp Directory @@ -80,7 +80,7 @@ Here is an example of setting the temp directory in XML: [source, xml, subs="{sub-order}"] ---- - + /test foo.war @@ -105,7 +105,7 @@ Again, you can do this in XML: [source, xml, subs="{sub-order}"] ---- - + /test foo.war diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/configuring-specific-webapp-deployment.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/configuring-specific-webapp-deployment.adoc index b1fd94de0af..13b7d92689a 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/configuring-specific-webapp-deployment.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/configuring-specific-webapp-deployment.adoc @@ -43,7 +43,7 @@ For example, here is a descriptor file that deploys the file `/opt/myapp/myapp.w - + /wiki /opt/myapp/myapp.war @@ -57,7 +57,7 @@ For example, if the system property is set to `myapp.home=/opt/myapp`, the previ - + /wiki /myapp.war @@ -85,7 +85,7 @@ This can help make it clear that users should not make changes to the temporary - + /wiki /myapp.war false @@ -101,7 +101,7 @@ However, since the `web.xml` for the web application is processed after the depl - + /wiki /myapp.war @@ -122,7 +122,7 @@ This feature is useful when adding parameters or additional Servlet mappings wit - + /wiki /myapp.war /opt/myapp/overlay-web.xml @@ -137,7 +137,7 @@ If the `web.xml` does not include a reference to this data source, an override d - + /wiki /myapp.war diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/deployment-architecture.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/deployment-architecture.adoc index 19cb134d76c..349e4758219 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/deployment-architecture.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/deployment-architecture.adoc @@ -76,11 +76,11 @@ The format for the XML file is the same as any context XML file and can be used To use this binding, you can either modify the existing `jetty-deploy.xml` which comes with the Jetty distribution (be sure to link:#startup-base-and-home[copy it to your $JETTY_BASE/etc directory first]), or by link:#custom-modules[creating a new module] file which calls to an additional XML file. -[source, xml, subs="{sub-order}"] +[source,xml,subs="{sub-order}"] ---- - + /etc/global-webapp-config.xml @@ -96,7 +96,7 @@ It supports hot (re)deployment. The basic operation of the `WebAppProvider` is to periodically scan a directory for deployables. In the standard Jetty Distribution, this is configured in the `${jetty.home}/etc/jetty-deploy.xml` file. -[source, xml, subs="{sub-order}"] +[source,xml,subs="{sub-order}"] ---- @@ -110,7 +110,7 @@ In the standard Jetty Distribution, this is configured in the `${jetty.home}/etc - + /webapps /etc/webdefault.xml 1 diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/deployment-processing-webapps.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/deployment-processing-webapps.adoc index fe7fbd87606..1a3b5536179 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/deployment-processing-webapps.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/deployment-processing-webapps.adoc @@ -23,27 +23,27 @@ If instead you're looking for information on how to configure a specific `WebApp [[webapp-configurations]] ==== Configuration Classes -As a webapp is being deployed, a series of link:{JDURL}/org/eclipse/jetty/webapp/Configuration.html[org.eclipse.jetty.webapp.Configuration] classes are applied to it, each one performing a specific function. +As a webapp is being deployed, a series of link:{JDURL}/org/eclipse/jetty/webapp/Configuration.html[org.eclipse.jetty.ee9.webapp.Configuration] classes are applied to it, each one performing a specific function. The ordering of these Configurations is significant as subsequent Configurations tend to build on information extracted or setup in foregoing Configurations. -These are the default list, in order, of Configurations that are applied to each link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html[org.eclipse.jetty.webapp.WebAppContex]t: +These are the default list, in order, of Configurations that are applied to each link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html[org.eclipse.jetty.ee9.webapp.WebAppContex]t: .Default Configuration classes [cols=",",] |======================================================================= -|link:{JDURL}/org/eclipse/jetty/webapp/WebInfConfiguration.html[org.eclipse.jetty.webapp.WebInfConfiguration] +|link:{JDURL}/org/eclipse/jetty/webapp/WebInfConfiguration.html[org.eclipse.jetty.ee9.webapp.WebInfConfiguration] |Extracts war, orders jars and defines classpath -|link:{JDURL}/org/eclipse/jetty/webapp/WebXmlConfiguration.html[org.eclipse.jetty.webapp.WebXmlConfiguration] +|link:{JDURL}/org/eclipse/jetty/webapp/WebXmlConfiguration.html[org.eclipse.jetty.ee9.webapp.WebXmlConfiguration] |Processes a WEB-INF/web.xml file -|link:{JDURL}/org/eclipse/jetty/webapp/MetaInfConfiguration.html[org.eclipse.jetty.webapp.MetaInfConfiguration] +|link:{JDURL}/org/eclipse/jetty/webapp/MetaInfConfiguration.html[org.eclipse.jetty.ee9.webapp.MetaInfConfiguration] |Looks in container and webapp jars for META-INF/resources and META-INF/web-fragment.xml -|link:{JDURL}/org/eclipse/jetty/webapp/FragmentConfiguration.html[org.eclipse.jetty.webapp.FragmentConfiguration] +|link:{JDURL}/org/eclipse/jetty/webapp/FragmentConfiguration.html[org.eclipse.jetty.ee9.webapp.FragmentConfiguration] |Processes all discovered META-INF/web-fragment.xml files -|link:{JDURL}/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.html[org.eclipse.jetty.webapp.JettyWebXmlConfiguration] +|link:{JDURL}/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.html[org.eclipse.jetty.ee9.webapp.JettyWebXmlConfiguration] |Processes a WEB-INF/jetty-web.xml file |======================================================================= @@ -99,7 +99,7 @@ To achieve that, we use 2 extra Configurations: |Processes JNDI related aspects of `WEB-INF/web.xml` and hooks up naming entries |======================================================================= -These configurations must be added in _exactly_ the order shown above and should be inserted _immediately before_ the link:{JDURL}/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.html[org.eclipse.jetty.webapp.JettyWebXmlConfiguration] class in the list of configurations. +These configurations must be added in _exactly_ the order shown above and should be inserted _immediately before_ the link:{JDURL}/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.html[org.eclipse.jetty.ee9.webapp.JettyWebXmlConfiguration] class in the list of configurations. To fully support JNDI additional configuration is required, full details of which can be found link:#jndi[here]. ====== Example: Annotation Configurations @@ -114,7 +114,7 @@ We need just one extra Configuration class to help provide servlet annotation sc @WebListener etc |======================================================================= -The above configuration class must be _inserted immediately before_ the link:{JDURL}/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.html[org.eclipse.jetty.webapp.JettyWebXmlConfiguration] class in the list of configurations. +The above configuration class must be _inserted immediately before_ the link:{JDURL}/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.html[org.eclipse.jetty.ee9.webapp.JettyWebXmlConfiguration] class in the list of configurations. To fully support annotations additional configuration is require, details of which can be found link:#webapp-context-attributes[below.] ===== How to Set the List of Configurations @@ -132,20 +132,20 @@ Let's see an example of how we would add in the Configurations for both JNDI _an - + /webapps/my-cool-webapp - org.eclipse.jetty.webapp.WebInfConfiguration - org.eclipse.jetty.webapp.WebXmlConfiguration - org.eclipse.jetty.webapp.MetaInfConfiguration - org.eclipse.jetty.webapp.FragmentConfiguration + org.eclipse.jetty.ee9.webapp.WebInfConfiguration + org.eclipse.jetty.ee9.webapp.WebXmlConfiguration + org.eclipse.jetty.ee9.webapp.MetaInfConfiguration + org.eclipse.jetty.ee9.webapp.FragmentConfiguration org.eclipse.jetty.plus.webapp.EnvConfiguration org.eclipse.jetty.plus.webapp.PlusConfiguration org.eclipse.jetty.annotations.AnnotationConfiguration - org.eclipse.jetty.webapp.JettyWebXmlConfiguration + org.eclipse.jetty.ee9.webapp.JettyWebXmlConfiguration @@ -159,7 +159,7 @@ Of course, you can also use this method to reduce the Configurations applied to If you use the link:#deployment-architecture[deployer], you can set up the list of Configuration classes on the link:#default-web-app-provider[WebAppProvider]. They will then be applied to each `WebAppContext` deployed by the deployer: -[source, xml, subs="{sub-order}"] +[source,xml,subs="{sub-order}"] ---- @@ -174,18 +174,18 @@ They will then be applied to each `WebAppContext` deployed by the deployer: - + /webapps - org.eclipse.jetty.webapp.WebInfConfiguration - org.eclipse.jetty.webapp.WebXmlConfiguration - org.eclipse.jetty.webapp.MetaInfConfiguration - org.eclipse.jetty.webapp.FragmentConfiguration + org.eclipse.jetty.ee9.webapp.WebInfConfiguration + org.eclipse.jetty.ee9.webapp.WebXmlConfiguration + org.eclipse.jetty.ee9.webapp.MetaInfConfiguration + org.eclipse.jetty.ee9.webapp.FragmentConfiguration org.eclipse.jetty.plus.webapp.EnvConfiguration org.eclipse.jetty.plus.webapp.PlusConfiguration org.eclipse.jetty.annotations.AnnotationConfiguration - org.eclipse.jetty.webapp.JettyWebXmlConfiguration + org.eclipse.jetty.ee9.webapp.JettyWebXmlConfiguration @@ -213,10 +213,10 @@ This example uses an xml file, in fact it is the `$JETTY_HOME/etc/jetty-plus.xml - + - org.eclipse.jetty.webapp.FragmentConfiguration + org.eclipse.jetty.ee9.webapp.FragmentConfiguration org.eclipse.jetty.plus.webapp.EnvConfiguration @@ -229,7 +229,7 @@ This example uses an xml file, in fact it is the `$JETTY_HOME/etc/jetty-plus.xml ---- -The link:{JDURL}/org/eclipse/jetty/webapp/Configuration.html[org.eclipse.jetty.webapp.Configuration.ClassList] class provides these methods for insertion: +The link:{JDURL}/org/eclipse/jetty/webapp/Configuration.html[org.eclipse.jetty.ee9.webapp.Configuration.ClassList] class provides these methods for insertion: addAfter:: Inserts the supplied list of `Configuration` class names after the given Configuration class name. @@ -242,7 +242,7 @@ addBefore:: [[container-include-jar-pattern]] ===== org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern -This is a link:#context_attributes[context attribute] that can be set on link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html[an org.eclipse.jetty.webapp.WebAppContext] to control which parts of the _container's_ classpath should be processed for things like annotations, `META-INF/resources`, `META-INF/web-fragment.xml` and `tlds` inside `META-INF`. +This is a link:#context_attributes[context attribute] that can be set on link:{JDURL}/org/eclipse/jetty/webapp/WebAppContext.html[an org.eclipse.jetty.ee9.webapp.WebAppContext] to control which parts of the _container's_ classpath should be processed for things like annotations, `META-INF/resources`, `META-INF/web-fragment.xml` and `tlds` inside `META-INF`. Normally, nothing from the container classpath will be included for processing. However, sometimes you will need to include some. @@ -258,7 +258,7 @@ Here's an example from a context xml file (although as always, you could have ac - + org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern @@ -284,7 +284,7 @@ Here's an example in a xml file of a pattern that matches any jar that starts wi - + org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/quickstart-webapp.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/quickstart-webapp.adoc index d45c93f7276..180d5ffdb22 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/quickstart-webapp.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/deploying/quickstart-webapp.adoc @@ -59,9 +59,9 @@ In a Maven project you add a dependency on the artifact `jetty-quickstart`. ===== Configuration -Webapps need to be instances of link:{JDURL}/org/eclipse/jetty/quickstart/QuickStartWebApp.html[`org.eclipse.jetty.quickstart.QuickStartWebApp`] rather than the normal `org.eclipse.jetty.webapp.WebAppContext`. +Webapps need to be instances of link:{JDURL}/org/eclipse/jetty/quickstart/QuickStartWebApp.html[`org.eclipse.jetty.quickstart.QuickStartWebApp`] rather than the normal `org.eclipse.jetty.ee9.webapp.WebAppContext`. -`org.eclipse.jetty.quickstart.QuickStartWebApp` instances offer the same setters as the familiar `org.eclipse.jetty.webapp.WebAppContext`, with the addition of: +`org.eclipse.jetty.quickstart.QuickStartWebApp` instances offer the same setters as the familiar `org.eclipse.jetty.ee9.webapp.WebAppContext`, with the addition of: autoPreconfigure:: (true/false). @@ -108,7 +108,7 @@ Otherwise, create a context xml file with the following information (in addition ====== In Code -Create an instance of link:{JDURL}/org/eclipse/jetty/quickstart/QuickStartWebApp.html[`org.eclipse.jetty.quickstart.QuickStartWebApp`] rather than the normal `org.eclipse.jetty.webapp.WebAppContext`. You then use the QuickStartWebApp instance in exactly the same way that you would a WebAppContext. +Create an instance of link:{JDURL}/org/eclipse/jetty/quickstart/QuickStartWebApp.html[`org.eclipse.jetty.quickstart.QuickStartWebApp`] rather than the normal `org.eclipse.jetty.ee9.webapp.WebAppContext`. You then use the QuickStartWebApp instance in exactly the same way that you would a WebAppContext. Here's a snippet: diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/cgi-servlet.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/cgi-servlet.adoc index e3bcb41b516..1dc3f359d5d 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/cgi-servlet.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/cgi-servlet.adoc @@ -17,7 +17,7 @@ [[cgi-servlet-metadata]] ==== Info -* Classname: `org.eclipse.jetty.servlets.CGI` +* Classname: `org.eclipse.jetty.ee9.servlets.CGI` * Maven Artifact: org.eclipse.jetty:jetty-servlets * Javadoc: {JDURL}/org/eclipse/jetty/servlets/CGI.html diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/cross-origin-filter.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/cross-origin-filter.adoc index 9be9657f778..2bd60159d20 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/cross-origin-filter.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/cross-origin-filter.adoc @@ -17,7 +17,7 @@ [[cross-origin-filter-metadata]] ==== Info -* Classname: `org.eclipse.jetty.servlets.CrossOriginFilter` +* Classname: `org.eclipse.jetty.ee9.servlets.CrossOriginFilter` * Maven Artifact: org.eclipse.jetty:jetty-servlets * Javadoc: {JDURL}/org/eclipse/jetty/servlets/CrossOriginFilter.html @@ -80,7 +80,7 @@ A typical configuration could be: cross-origin - org.eclipse.jetty.servlets.CrossOriginFilter + org.eclipse.jetty.ee9.servlets.CrossOriginFilter cross-origin diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/default-servlet.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/default-servlet.adoc index 4cdf18602a1..5ba7ec0b4d0 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/default-servlet.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/default-servlet.adoc @@ -17,7 +17,7 @@ [[default-servlet-metadata]] ==== Info -* Classname: `org.eclipse.jetty.servlet.DefaultServlet` +* Classname: `org.eclipse.jetty.ee9.servlet.DefaultServlet` * Maven Artifact: org.eclipse.jetty:jetty-servlet * Javadoc: {JDURL}/org/eclipse/jetty/servlet/DefaultServlet.html diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/dos-filter.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/dos-filter.adoc index 7058841396f..cc2455f4d4a 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/dos-filter.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/dos-filter.adoc @@ -17,7 +17,7 @@ [[dos-filter-metadata]] ==== Info -* Classname: `org.eclipse.jetty.servlets.DoSFilter` +* Classname: `org.eclipse.jetty.ee9.servlets.DoSFilter` * Maven Artifact: org.eclipse.jetty:jetty-servlets * Javadoc: {JDURL}/org/eclipse/jetty/servlets/DoSFilter.html @@ -53,7 +53,7 @@ This example allow 30 requests at a time: ---- DoSFilter - org.eclipse.jetty.servlets.DoSFilter + org.eclipse.jetty.ee9.servlets.DoSFilter maxRequestsPerSec 30 diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/header-filter.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/header-filter.adoc index f17ce6c1849..0e587382113 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/header-filter.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/header-filter.adoc @@ -17,7 +17,7 @@ [[header-filter-metadata]] ==== Info -* Classname: `org.eclipse.jetty.servlets.HeaderFilter` +* Classname: `org.eclipse.jetty.ee9.servlets.HeaderFilter` * Maven Artifact: org.eclipse.jetty:jetty-servlets * Javadoc: {JDURL}/org/eclipse/jetty/servlets/HeaderFilter.html @@ -54,7 +54,7 @@ ____ ---- HeaderFilter - org.eclipse.jetty.servlets.HeaderFilter + org.eclipse.jetty.ee9.servlets.HeaderFilter headerConfig diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/qos-filter.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/qos-filter.adoc index 857420490e6..c40a5b2b326 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/qos-filter.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/extras/qos-filter.adoc @@ -17,7 +17,7 @@ [[qos-filter-metadata]] ==== Info -* Classname: `org.eclipse.jetty.servlets.QoSFilter` +* Classname: `org.eclipse.jetty.ee9.servlets.QoSFilter` * Maven Artifact: org.eclipse.jetty:jetty-servlets * Javadoc: {JDURL}/org/eclipse/jetty/servlets/QoSFilter.html @@ -82,7 +82,7 @@ This example processes fifty requests at a time: ---- QoSFilter - org.eclipse.jetty.servlets.QoSFilter + org.eclipse.jetty.ee9.servlets.QoSFilter maxRequests 50 diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/fastcgi/configuring-fastcgi.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/fastcgi/configuring-fastcgi.adoc index d0260b6af46..70d10757200 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/fastcgi/configuring-fastcgi.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/fastcgi/configuring-fastcgi.adoc @@ -41,11 +41,11 @@ For FastCGI there is no web application that needs developed - all the work has As such you only need to deploy a Jetty context XML file that configures the web application directly. Copy and paste the following content as `$JETTY_BASE/webapps/jetty-wordpress.xml`: -[source, xml, subs="{sub-order}"] +[source,xml,subs="{sub-order}"] ---- - + /var/www/wordpress @@ -58,7 +58,7 @@ Copy and paste the following content as `$JETTY_BASE/webapps/jetty-wordpress.xml - org.eclipse.jetty.fcgi.server.proxy.TryFilesFilter + org.eclipse.jetty.ee9.fcgi.server.proxy.TryFilesFilterorg.eclipse.jetty.ee9.fcgi.server.proxy.TryFilesFilter /* @@ -73,11 +73,11 @@ Copy and paste the following content as `$JETTY_BASE/webapps/jetty-wordpress.xml - + default - org.eclipse.jetty.servlet.DefaultServlet + org.eclipse.jetty.ee9.servlet.DefaultServlet diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/frameworks/cdi.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/frameworks/cdi.adoc index b25ff07f786..96cd3d17318 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/frameworks/cdi.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/frameworks/cdi.adoc @@ -33,12 +33,12 @@ The Jetty distribution come with several CDI modules. These modules do not provide CDI, but instead enable one of more integration mechanisms. ===== Jetty `cdi` Module -The `cdi` module supports either two modes of CDI integration which can be selected either by the "org.eclipse.jetty.cdi" context init parameter or the "org.eclipse.jetty.cdi" server attribute (which is initialised from the "jetty.cdi.mode" start property). +The `cdi` module supports either two modes of CDI integration which can be selected either by the "org.eclipse.jetty.ee9.cdi" context init parameter or the "org.eclipse.jetty.ee9.cdi" server attribute (which is initialised from the "jetty.cdi.mode" start property). Supported modes are: * `CdiSpiDecorator` Jetty will call the CDI SPI within the webapp to decorate objects (default). - * `CdiDecoratingLister` The webapp may register a decorator on the context attribute "org.eclipse.jetty.cdi.decorator". + * `CdiDecoratingLister` The webapp may register a decorator on the context attribute "org.eclipse.jetty.ee9.cdi.decorator". ------------------------- cd $JETTY_BASE java -jar $JETTY_HOME/start.jar --add-to-start=cdi @@ -74,7 +74,7 @@ This module is equivalent to directly modifying the class path configuration wit ------------------------------------------------------------- - + -org.eclipse.jetty.util.Decorator @@ -88,7 +88,7 @@ This module is equivalent to directly modifying the class path configuration wit -org.eclipse.jetty.server.handler.ContextHandler - -org.eclipse.jetty.servlet.ServletContextHandler + -org.eclipse.jetty.ee9.servlet.ServletContextHandler ------------------------------------------------------------- diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/frameworks/osgi.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/frameworks/osgi.adoc index e5cd9600c35..2b931c12444 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/frameworks/osgi.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/frameworks/osgi.adoc @@ -115,7 +115,7 @@ Here's how you would specify it: + [source, plain, subs="{sub-order}"] ---- -jetty.home.bundle=org.eclipse.jetty.osgi.boot +jetty.home.bundle=org.eclipse.jetty.ee9.osgi.boot ---- + @@ -357,7 +357,7 @@ Here's an example of the contents of a `META-INF/jetty-webapp-context.xml` file: - + META-INF/webdefault.xml ---- @@ -440,7 +440,7 @@ include::{SRCDIR}/jetty-osgi/test-jetty-osgi-context/src/main/context/acme.xml[] [[services-as-webapps]] ==== Deploying Services as Webapps -In addition to listening for bundles whose format or `MANIFEST` entries define a webapp or `ContextHandler` for to be deployed, the Jetty OSGi container also listens for the registration of OSGi services that are instances of `org.eclipse.jetty.webapp.WebAppContext`. +In addition to listening for bundles whose format or `MANIFEST` entries define a webapp or `ContextHandler` for to be deployed, the Jetty OSGi container also listens for the registration of OSGi services that are instances of `org.eclipse.jetty.ee9.webapp.WebAppContext`. So you may programmatically create a `WebAppContext`, register it as a service, and have Jetty pick it up and deploy it. Here's an example of doing that with a simple bundle that serves static content, and an `org.osgi.framework.BundleActivator` that instantiates the `WebAppContext`: @@ -679,7 +679,7 @@ Here is the list of recommended jars (NOTE the version numbers may change in fut |org.eclipse.jdt.core.compiler.batch |Distribution lib/apache-jsp |org.eclipse.jetty.osgi:jetty-osgi-boot-jsp -|org.eclipse.jetty.osgi.boot.jsp +|org.eclipse.jetty.ee9.osgi.boot.jsp |https://repo1.maven.org/maven2/org/eclipse/jetty/osgi/jetty-osgi-boot-jsp[Maven central] |======================================================================= @@ -748,15 +748,15 @@ The format of the *Require-TldBundle* header is a comma separated list of one or Some TLD jars are required to be found on the Jetty OSGi container's classpath, rather than considered part of the web bundle's classpath. For example, this is true of JSTL and Java Server Faces. -The Jetty OSGi container takes care of JSTL for you, but you can control which other jars are considered as part of the container's classpath by using the System property **org.eclipse.jetty.osgi.tldbundles**: +The Jetty OSGi container takes care of JSTL for you, but you can control which other jars are considered as part of the container's classpath by using the System property **org.eclipse.jetty.ee9.osgi.tldbundles**: -org.eclipse.jetty.osgi.tldbundles:: +org.eclipse.jetty.ee9.osgi.tldbundles:: System property defined on the OSGi environment that is a comma separated list of symbolic names of bundles containing taglibs that will be treated as if they are on the container's classpath for web bundles. For example: + [source, plain, subs="{sub-order}"] ---- -org.eclipse.jetty.osgi.tldbundles=com.acme.special.tags,com.foo.web,org.bar.web.framework +org.eclipse.jetty.ee9.osgi.tldbundles=com.acme.special.tags,com.foo.web,org.bar.web.framework ---- + You will still need to define the *Import-Bundle* header in the `MANIFEST` file for the web bundle to ensure that the TLD bundles are on the OSGi classpath. diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/gettingstarted/configuring/what-to-configure.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/gettingstarted/configuring/what-to-configure.adoc index 410da188650..c3195a46c45 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/gettingstarted/configuring/what-to-configure.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/gettingstarted/configuring/what-to-configure.adoc @@ -201,7 +201,7 @@ To set the contextPath from within the WAR file, you can include a `WEB-INF/jett - + /contextpath ---- @@ -214,7 +214,7 @@ Instead of allowing the WAR file to be discovered by the deployer, an IoC XML fi - + /webapps/test.war /test diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/gettingstarted/getting-started/jetty-running.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/gettingstarted/getting-started/jetty-running.adoc index c1290ac09f1..f4b5230b1e5 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/gettingstarted/getting-started/jetty-running.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/gettingstarted/getting-started/jetty-running.adoc @@ -69,9 +69,9 @@ $ cd demo-base/ 2017-09-20 16:23:04.306:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@371a67ec{/async-rest,[file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-async-rest.war-_async-rest-any-5319296087878801290.dir/webapp/, jar:file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-async-rest.war-_async-rest-any-5319296087878801290.dir/webapp/WEB-INF/lib/example-async-rest-jar-{VERSION}.jar!/META-INF/resources],AVAILABLE}{/async-rest.war} 2017-09-20 16:23:04.429:INFO:oeja.AnnotationConfiguration:main: Scanning elapsed time=53ms 2017-09-20 16:23:04.432:WARN::main: test webapp is deployed. DO NOT USE IN PRODUCTION! -2017-09-20 16:23:04.511:INFO:oejsh.ManagedAttributeListener:main: update PushFilter null->org.eclipse.jetty.servlets.PushCacheFilter@2362f559 on o.e.j.w.WebAppContext@35e2d654{/test,file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test.war-_test-any-6279588879522983394.dir/webapp/,STARTING}{/test.war} -2017-09-20 16:23:04.516:INFO:oejsh.ManagedAttributeListener:main: update QoSFilter null->org.eclipse.jetty.servlets.QoSFilter@7770f470 on o.e.j.w.WebAppContext@35e2d654{/test,file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test.war-_test-any-6279588879522983394.dir/webapp/,STARTING}{/test.war} -2017-09-20 16:23:04.519:WARN:oeju.DeprecationWarning:main: Using @Deprecated Class org.eclipse.jetty.servlets.MultiPartFilter +2017-09-20 16:23:04.511:INFO:oejsh.ManagedAttributeListener:main: update PushFilter null->org.eclipse.jetty.ee9.servlets.PushCacheFilter@2362f559 on o.e.j.w.WebAppContext@35e2d654{/test,file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test.war-_test-any-6279588879522983394.dir/webapp/,STARTING}{/test.war} +2017-09-20 16:23:04.516:INFO:oejsh.ManagedAttributeListener:main: update QoSFilter null->org.eclipse.jetty.ee9.servlets.QoSFilter@7770f470 on o.e.j.w.WebAppContext@35e2d654{/test,file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test.war-_test-any-6279588879522983394.dir/webapp/,STARTING}{/test.war} +2017-09-20 16:23:04.519:WARN:oeju.DeprecationWarning:main: Using @Deprecated Class org.eclipse.jetty.ee9.servlets.MultiPartFilter 2017-09-20 16:23:04.549:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@35e2d654{/test,file:///private/var/folders/h6/yb_lbnnn11g0y1jjlvqg631h0000gn/T/jetty-0.0.0.0-8080-test.war-_test-any-6279588879522983394.dir/webapp/,AVAILABLE}{/test.war} 2017-09-20 16:23:04.646:INFO:oeja.AnnotationConfiguration:main: Scanning elapsed time=12ms 2017-09-20 16:23:04.649:WARN::main: test-jndi webapp is deployed. DO NOT USE IN PRODUCTION! diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/http2/configuring-push.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/http2/configuring-push.adoc index 4768aefaece..4280d753d8c 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/http2/configuring-push.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/http2/configuring-push.adoc @@ -32,7 +32,7 @@ HTTP/2 Push can be automated in your application by configuring a link:{JDURL}/o ... PushFilter - org.eclipse.jetty.servlets.PushCacheFilter + org.eclipse.jetty.ee9.servlets.PushCacheFilter true diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/jetty-env-xml.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/jetty-env-xml.adoc index 5254aa3a423..f9b857fdd8f 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/jetty-env-xml.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/jetty-env-xml.adoc @@ -23,7 +23,7 @@ You define global naming resources on the server via `jetty.xml`. [[jetty-env-root-element]] ==== jetty-env.xml Root Element -Jetty applies `jetty-env.xml` on a per-webapp basis, and configures an instance of `org.eclipse.jetty.webapp.WebAppContext.` +Jetty applies `jetty-env.xml` on a per-webapp basis, and configures an instance of `org.eclipse.jetty.ee9.webapp.WebAppContext.` [source, xml, subs="{sub-order}"] ---- @@ -31,7 +31,7 @@ Jetty applies `jetty-env.xml` on a per-webapp basis, and configures an instance - + .. @@ -54,7 +54,7 @@ Place the `jetty-env.xml` file in your web application's WEB-INF folder. - + diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/jetty-web-xml-config.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/jetty-web-xml-config.adoc index 63a9b21ddab..683a521e917 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/jetty-web-xml-config.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/jetty-web-xml-config.adoc @@ -23,14 +23,14 @@ For a more in-depth look at the syntax, see xref:jetty-xml-syntax[]. [[root-element-jetty-web-xml]] ==== Root Element -`jetty-web.xml` applies on a per-webapp basis, and configures an instance of `org.eclipse.jetty.webapp.WebAppContext`. +`jetty-web.xml` applies on a per-webapp basis, and configures an instance of `org.eclipse.jetty.ee9.webapp.WebAppContext`. [source, xml, subs="{sub-order}"] ---- - + .. ---- diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/override-web-xml.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/override-web-xml.adoc index 726a90f91ff..407c313a990 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/override-web-xml.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/override-web-xml.adoc @@ -31,7 +31,7 @@ For example, if you had a webapp named MyApp, you would place a deployable xml f [source, xml, subs="{sub-order}"] ---- - + ... @@ -44,7 +44,7 @@ The equivalent in code is: [source, java, subs="{sub-order}"] ---- -import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.ee9.webapp.WebAppContext; ... diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/webdefault-xml.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/webdefault-xml.adoc index 78b8f8d1474..c0d2f613102 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/webdefault-xml.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jetty-xml/webdefault-xml.adoc @@ -57,7 +57,7 @@ You can specify a custom `webdefault.xml` for an individual web application in t [source, xml, subs="{sub-order}"] ---- - + ... /my/path/to/webdefault.xml @@ -72,7 +72,7 @@ The equivalent in code is: [source, java, subs="{sub-order}"] ---- -import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.ee9.webapp.WebAppContext; ... diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/jndi-configuration.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/jndi-configuration.adoc index 53df5dacf95..11f6c6e4303 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/jndi-configuration.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/jndi-configuration.adoc @@ -100,7 +100,7 @@ This example configures it as scoped to a web app with the id of __wac__: [source, xml, subs="{sub-order}"] ---- - + jdbc/myds @@ -164,7 +164,7 @@ Here is an example of binding an http://activemq.apache.org[ActiveMQ] in-JVM con [source, xml, subs="{sub-order}"] ---- - + jms/connectionFactory @@ -197,7 +197,7 @@ Jetty also provides infrastructure for access to `javax.mail.Sessions` from with [source, xml, subs="{sub-order}"] ---- - + mail/Session @@ -259,7 +259,7 @@ In a context xml file: [source, xml, subs="{sub-order}"] ---- - + jdbc/mydatasource @@ -295,7 +295,7 @@ In a context xml file declare `jdbc/mydatasource`: [source, xml, subs="{sub-order}"] ---- - + jdbc/mydatasource diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/jndi-embedded.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/jndi-embedded.adoc index ab2d8908c29..6a9e5c9e6b1 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/jndi-embedded.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/jndi-embedded.adoc @@ -38,7 +38,7 @@ We'll use some mocked up classes for the transaction manager and the DataSource ---- import java.util.Properties; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.ee9.webapp.WebAppContext; /** * ServerWithJNDI @@ -54,8 +54,8 @@ public class ServerWithJNDI Server server = new Server(8080); //Enable parsing of jndi-related parts of web.xml and jetty-env.xml - org.eclipse.jetty.webapp.Configuration.ClassList classlist = org.eclipse.jetty.webapp.Configuration.ClassList.setServerDefault(server); - classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration"); + org.eclipse.jetty.ee9.webapp.Configuration.ClassList classlist = org.eclipse.jetty.ee9.webapp.Configuration.ClassList.setServerDefault(server); + classlist.addAfter("org.eclipse.jetty.ee9.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration", "org.eclipse.jetty.plus.webapp.PlusConfiguration"); //Create a WebApp WebAppContext webapp = new WebAppContext(); diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/using-jndi.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/using-jndi.adoc index b67c9eed4c4..cf6977c8737 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/using-jndi.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jndi/using-jndi.adoc @@ -125,7 +125,7 @@ For example: + [source, xml, subs="{sub-order}"] ---- - + jms/connectionFactory diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jsp/configuring-jsp.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jsp/configuring-jsp.adoc index 610606984e0..e5bf14d6e4c 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/jsp/configuring-jsp.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/jsp/configuring-jsp.adoc @@ -52,14 +52,14 @@ Alternatively, you can manually wire in the appropriate ServletContainerInitiali You can either follow the instructions on precompilation provided by Apache, or if you are using Maven for your builds, you can use the link:#jetty-jspc-maven-plugin[jetty-jspc-maven] plugin to do it for you. If you have precompiled your JSPs, and have customized the output package prefix (which is `org.apache.jsp` by default), you should configure your webapp context to tell Jetty about this custom package name. -You can do this using a servlet context init-param called `org.eclipse.jetty.servlet.jspPackagePrefix`. +You can do this using a servlet context init-param called `org.eclipse.jetty.ee9.servlet.jspPackagePrefix`. For example, suppose you have precompiled your JSPs with the custom package prefix of `com.acme`, then you would add the following lines to your `web.xml` file: [source, xml, subs="{sub-order}"] ---- - org.eclipse.jetty.servlet.jspPackagePrefix + org.eclipse.jetty.ee9.servlet.jspPackagePrefix com.acme ---- @@ -206,7 +206,7 @@ If you are using the Jetty distribution, and you want to change the JSP settings [source, xml, subs="{sub-order}"] ---- - + /foo /webapps/foobar.war /home/smith/dev/webdefault.xml diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/runner/jetty-runner.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/runner/jetty-runner.adoc index 60e8d41d291..245cddbd9de 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/runner/jetty-runner.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/runner/jetty-runner.adoc @@ -60,11 +60,11 @@ By default, `jetty-runner` implements all Configuration Classes so that users ca If you wish to only implement certain Configuration Classes, they will need to be defined in the context xml for the webapp/context. The default Configuration Classes are: -`org.eclipse.jetty.webapp.WebInfConfiguration` -`org.eclipse.jetty.webapp.WebXmlConfiguration` -`org.eclipse.jetty.webapp.MetaInfConfiguration` -`org.eclipse.jetty.webapp.FragmentConfiguration` -`org.eclipse.jetty.webapp.JettyWebXmlConfiguration` +`org.eclipse.jetty.ee9.webapp.WebInfConfiguration` +`org.eclipse.jetty.ee9.webapp.WebXmlConfiguration` +`org.eclipse.jetty.ee9.webapp.MetaInfConfiguration` +`org.eclipse.jetty.ee9.webapp.FragmentConfiguration` +`org.eclipse.jetty.ee9.webapp.JettyWebXmlConfiguration` `org.eclipse.jetty.plus.webapp.EnvConfiguration` `org.eclipse.jetty.plus.webapp.PlusConfiguration` `org.eclipse.jetty.annotations.AnnotationConfiguration` diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc index 8139c8d4818..64dde19ae00 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/authentication.adoc @@ -149,7 +149,7 @@ Here's an example of doing both of these, using a link:#deployable-descriptor-fi [source, xml, subs="{sub-order}"] ---- - + @@ -174,7 +174,7 @@ Here's how to define the same HashLoginService, but inside a link:#deployable-de [source, xml, subs="{sub-order}"] ---- - + /test /webapps/test @@ -467,7 +467,7 @@ In the example below, a `HashLoginService` is defined with authorization being g [source, xml, subs="{sub-order}"] ---- - + Test Realm BASIC diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/configuring-form-size.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/configuring-form-size.adoc index 242d979c357..1da8a847816 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/configuring-form-size.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/configuring-form-size.adoc @@ -36,7 +36,7 @@ These methods may be called directly when embedding Jetty, but more commonly are [source, xml, subs="{sub-order}"] ---- - + ... diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/jaas-support.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/jaas-support.adoc index 3779c694211..603ba99794c 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/jaas-support.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/security/jaas-support.adoc @@ -101,7 +101,7 @@ An example: + [source, xml, subs="{sub-order}"] ---- - + diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/troubleshooting/troubleshooting-locked-files.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/troubleshooting/troubleshooting-locked-files.adoc index a9816dfade4..427feadca01 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/troubleshooting/troubleshooting-locked-files.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/troubleshooting/troubleshooting-locked-files.adoc @@ -49,7 +49,7 @@ Add the following to your context xml file: [source, xml, subs="{sub-order}"] ---- - org.eclipse.jetty.servlet.Default.useFileMappedBuffer + org.eclipse.jetty.ee9.servlet.Default.useFileMappedBuffer false ---- @@ -63,7 +63,7 @@ Add the following to the plugin's configuration under the `` element: [source, xml, subs="{sub-order}"] ---- <_initParams> - false + false ---- @@ -77,7 +77,7 @@ Configure this in an xml file like so: [source, xml, subs="{sub-order}"] ---- - + / ./webapps/fredapp true diff --git a/documentation/jetty-documentation/src/main/asciidoc/old_docs/websockets/jetty/jetty-websocket-server-api.adoc b/documentation/jetty-documentation/src/main/asciidoc/old_docs/websockets/jetty/jetty-websocket-server-api.adoc index d21985fe6f2..d13e31bcf9b 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/old_docs/websockets/jetty/jetty-websocket-server-api.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/old_docs/websockets/jetty/jetty-websocket-server-api.adoc @@ -73,4 +73,4 @@ Other uses for a `JettyWebSocketCreator`: * Obtaining the Servlet HttpSession object (if it exists) * Specifying a response status code and reason -If you don't want to accept the upgrade, simply return null from the link:{JDURL}/org/eclipse/jetty/websocket/servlet/JettyWebSocketCreator.html#createWebSocket(org.eclipse.jetty.websocket.api.UpgradeRequest,org.eclipse.jetty.websocket.api.UpgradeResponse)[`JettyWebSocketCreator.createWebSocket(UpgradeRequest req, UpgradeResponse resp)`] method. +If you don't want to accept the upgrade, simply return null from the link:{JDURL}/org/eclipse/jetty/websocket/servlet/JettyWebSocketCreator.html#createWebSocket(org.eclipse.jetty.ee9.websocket.api.UpgradeRequest,org.eclipse.jetty.ee9.websocket.api.UpgradeResponse)[`JettyWebSocketCreator.createWebSocket(UpgradeRequest req, UpgradeResponse resp)`] method. diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/annotations/chapter.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/annotations/chapter.adoc index 45404597423..ce55492f206 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/annotations/chapter.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/annotations/chapter.adoc @@ -50,7 +50,7 @@ Here's an example context xml file that calls this method: - <1> + <1> false <2> ---- @@ -78,7 +78,7 @@ Here's an example from a context xml file that includes any jar whose name start - <1> + <1> <2> org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern <3> .*/foo-[^/]*\.jar$|.*/bar-[^/]*\.jar$|.*/classes/.* <4> @@ -108,7 +108,7 @@ Here's an example of a context xml file that sets a pattern that matches any jar - <1> + <1> <2> org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern <3> .*/spring-[^/]*\.jar$ <4> @@ -174,7 +174,7 @@ Here's an example of setting the context attribute in a context xml file: - <1> + <1> <2> org.eclipse.jetty.containerInitializerExclusionPattern <3> com.acme.*|com.corp.SlowContainerInitializer <4> @@ -205,7 +205,7 @@ Here is an example context xml file that ensures the `com.example.PrioritySCI` w - <1> + <1> <2> org.eclipse.jetty.containerInitializerOrder <3> org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServletContainerInitializer, com.acme.FooSCI, * <4> diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/begin/deploy.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/begin/deploy.adoc index f622c9fe55c..ed20e96c220 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/begin/deploy.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/begin/deploy.adoc @@ -42,7 +42,7 @@ mywebapp.war <1> Publicly accessible resources such as `+*.html+`, `+*.jsp+`, `+*.css+`, `+*.js+` files, etc. are placed in `+*.war+` or in sub-directories of the `+*.war+`. <2> `WEB-INF` is a special directory used to store anything related to the web application that must not be publicly accessible, but may be accessed by other resources. <3> `WEB-INF/classes` stores the web application compiled `+*.class+` files -<4> `WEB-INF/lib` stores the web application `+*.jar+` files +<4> `WEB-INF/classes` stores the web application `+*.jar+` files <5> `WEB-INF/web.xml` is the web application deployment descriptor defines the components and the configuration of your web application. ==== diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-extract-war.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-extract-war.adoc index d1cf9f3fa4f..38b8794a9d4 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-extract-war.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-extract-war.adoc @@ -26,7 +26,7 @@ If you do not want Jetty to extract the `+*.war+` files, you can disable this fe - + /mywebapp /opt/webapps/mywebapp.war false diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-jetty.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-jetty.adoc index 1b129a68172..bbb4f0e11fb 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-jetty.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-jetty.adoc @@ -28,7 +28,7 @@ A simple Jetty context XML file, for example named `wiki.xml` is the following: - <1> + <1> /wiki <2> /opt/myapps/myapp.war <3> @@ -62,7 +62,7 @@ You can use the features of xref:og-xml[Jetty XML files] to avoid to hard-code f - + /wiki /myapp.war diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-jndi.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-jndi.adoc index 469db12af2d..989bd427a99 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-jndi.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-jndi.adoc @@ -23,7 +23,7 @@ The JNDI entry must be _defined_ in a xref:og-jndi-xml[Jetty XML file], for exam - + /mywebapp /opt/webapps/mywebapp.war #   diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-override-webxml.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-override-webxml.adoc index d8d234b0dc0..23f40f4b1dd 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-override-webxml.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-override-webxml.adoc @@ -24,7 +24,7 @@ This allows you to add host specific configuration or server specific configurat - + /mywebapp /opt/webapps/mywebapp.war /opt/webapps/mywebapp-web.xml diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-virtual-hosts.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-virtual-hosts.adoc index 6c1a66f84b6..c3bcf340cbb 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-virtual-hosts.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/deploy/deploy-virtual-hosts.adoc @@ -58,7 +58,7 @@ If you have a web application `mywebapp.war` you can configure its virtual hosts - + /mywebapp /opt/webapps/mywebapp.war @@ -103,7 +103,7 @@ To achieve this, you simply use the same context path of `/` for each of your we - + / /opt/webapps/domain.war @@ -120,7 +120,7 @@ To achieve this, you simply use the same context path of `/` for each of your we - + / /opt/webapps/hobby.war @@ -148,7 +148,7 @@ In this case, you want to xref:og-protocols[configure multiple connectors], each - + / /opt/webapps/domain.war @@ -165,7 +165,7 @@ In this case, you want to xref:og-protocols[configure multiple connectors], each - + / /opt/webapps/hobby.war diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jaas/chapter.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jaas/chapter.adoc index 6a748a98a33..f7bde4aaa8d 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jaas/chapter.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jaas/chapter.adoc @@ -115,7 +115,7 @@ Here's an example of this type of XML file: ---- - + diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jndi/chapter.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jndi/chapter.adoc index b13dfa2b276..8fe02e965f0 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jndi/chapter.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jndi/chapter.adoc @@ -120,7 +120,7 @@ In this example, we'll configure a link:http://db.apache.org/derby[Derby] DataSo [source,xml,subs=verbatim] ---- - + jdbc/myds @@ -166,7 +166,7 @@ Here is an example of binding an link:http://activemq.apache.org[ActiveMQ] in-JV [source,xml,subs=verbatim] ---- - + jms/connectionFactory @@ -202,7 +202,7 @@ To configure access to `javax.mail.Session` from within a webapp, declare an `or [source,xml,subs=verbatim] ---- - + mail/Session @@ -277,7 +277,7 @@ The context XML file declares `jdbc/workforce`: [source,xml,subs=verbatim] ---- - + jdbc/workforce @@ -374,7 +374,7 @@ For example: <1> We refer to the id `Server` which identifies the default `org.eclipse.jetty.server.Server` instance. *Webapp scope:* -The name is unique to the `org.eclipse.jetty.webapp.WebAppContext` instance, and is only visible to that application. +The name is unique to the `org.eclipse.jetty.ee9.webapp.WebAppContext` instance, and is only visible to that application. This scope is represented by referencing the instance as the first parameter to the resource declaration. For example: [source,xml,subs=verbatim] @@ -389,4 +389,4 @@ For example: ---- -<1> We refer to an instance of an `org.eclipse.jetty.webapp.WebAppContext` which has been previously defined. +<1> We refer to an instance of an `org.eclipse.jetty.ee9.webapp.WebAppContext` which has been previously defined. diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jsp/chapter.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jsp/chapter.adoc index bccda278906..993d39c71d5 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jsp/chapter.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/jsp/chapter.adoc @@ -197,7 +197,7 @@ Here's an example of using a context xml file to add in a pattern to match files - + org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern .*/jetty-servlet-api-[^/]*\.jar$|.*/javax.servlet.jsp.jstl-.*\.jar$|.*/org.apache.taglibs.taglibs-standard-impl-.*\.jar$|.*/jsf-[^/]*\.jar$ diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/sessions/session-xml.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/sessions/session-xml.adoc index 759a6f982af..f47de2b9482 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/sessions/session-xml.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/sessions/session-xml.adoc @@ -22,7 +22,7 @@ Below is an example of how you could configure a the xref:og-session-filesystem[ [source,xml] ---- - + diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/xml/xml-syntax.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/xml/xml-syntax.adoc index fe2e097f838..f5f6121475b 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/xml/xml-syntax.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/xml/xml-syntax.adoc @@ -351,7 +351,7 @@ For example, you may want to configure the context path of your web application - + ## diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-transport.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-transport.adoc index 91cf621ec8c..c3baee55716 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-transport.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-transport.adoc @@ -156,12 +156,9 @@ The dynamic transport, however, has been implemented to support multiple transpo include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=dynamicH1H2] ---- -NOTE: The order in which the protocols are specified to `HttpClientTransportDynamic` indicates what is the client preference. - -IMPORTANT: When using TLS (i.e. URIs with the `https` scheme), the application protocol is _negotiated_ between client and server via ALPN, and it is the server that decides what is the application protocol to use for the communication, regardless of the client preference. - -When clear-text communication is used (i.e. URIs with the `http` scheme) there is no application protocol negotiation, and therefore the application must know _a priori_ whether the server supports the protocol or not. -For example, if the server only supports clear-text HTTP/2, and `HttpClientTransportDynamic` is configured as in the example above, the client will send, by default, a clear-text HTTP/1.1 request to a clear-text HTTP/2 only server, which will result in a communication failure. +IMPORTANT: The order in which the protocols are specified to `HttpClientTransportDynamic` indicates what is the client preference. +If the protocol is negotiated via ALPN, it is the server that decides what is the protocol to use for the communication, regardless of the client preference. +If the protocol is not negotiated, the client preference is honored. Provided that the server supports both HTTP/1.1 and HTTP/2 clear-text, client applications can explicitly hint the version they want to use: @@ -170,13 +167,13 @@ Provided that the server supports both HTTP/1.1 and HTTP/2 clear-text, client ap include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java[tag=dynamicClearText] ---- -In case of TLS encrypted communication using the `https` scheme, things are a little more complicated. +In case of TLS encrypted communication using the HTTPS scheme, things are a little more complicated. -If the client application explicitly specifies the HTTP version, then ALPN is not used by the client. +If the client application explicitly specifies the HTTP version, then ALPN is not used on the client. By specifying the HTTP version explicitly, the client application has prior-knowledge of what HTTP version the server supports, and therefore ALPN is not needed. If the server does not support the HTTP version chosen by the client, then the communication will fail. -If the client application does not explicitly specify the HTTP version, then ALPN will be used by the client. +If the client application does not explicitly specify the HTTP version, then ALPN will be used on the client. If the server also supports ALPN, then the protocol will be negotiated via ALPN and the server will choose the protocol to use. If the server does not support ALPN, the client will try to use the first protocol configured in `HttpClientTransportDynamic`, and the communication may succeed or fail depending on whether the server supports the protocol chosen by the client. diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/websocket/client-websocket.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/websocket/client-websocket.adoc index af95fd0b4d6..cc2a5291af3 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/websocket/client-websocket.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/websocket/client-websocket.adoc @@ -35,7 +35,7 @@ The Maven artifact coordinates are the following: [[pg-client-websocket-start]] ==== Starting WebSocketClient -The main class is `org.eclipse.jetty.websocket.client.WebSocketClient`; you instantiate it, configure it, and then start it like may other Jetty components. +The main class is `org.eclipse.jetty.ee9.websocket.client.WebSocketClient`; you instantiate it, configure it, and then start it like may other Jetty components. This is a minimal example: [source,java,indent=0] @@ -119,7 +119,7 @@ In code: include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java[tags=connectHTTP11] ---- -`WebSocketClient.connect()` links the client-side WebSocket _endpoint_ to a specific server URI, and returns a `CompletableFuture` of an `org.eclipse.jetty.websocket.api.Session`. +`WebSocketClient.connect()` links the client-side WebSocket _endpoint_ to a specific server URI, and returns a `CompletableFuture` of an `org.eclipse.jetty.ee9.websocket.api.Session`. The endpoint offers APIs to _receive_ WebSocket data (or errors) from the server, while the session offers APIs to _send_ WebSocket data to the server. diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/maven/jetty-maven-plugin.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/maven/jetty-maven-plugin.adoc index e8621cda731..84a1ec58051 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/maven/jetty-maven-plugin.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/maven/jetty-maven-plugin.adoc @@ -338,7 +338,7 @@ Here is an example, which turns on scanning for changes every ten seconds, and s ===== Configuration webApp:: -This is an instance of link:{javadoc-url}/org/eclipse/jetty/maven/plugin/MavenWebAppContext.html[org.eclipse.jetty.maven.plugin.MavenWebAppContext], which is an extension to the class link:{javadoc-url}/org/eclipse/jetty/webapp/WebAppContext.hml[`org.eclipse.jetty.webapp.WebAppContext`]. +This is an instance of link:{javadoc-url}/org/eclipse/jetty/maven/plugin/MavenWebAppContext.html[org.eclipse.jetty.maven.plugin.MavenWebAppContext], which is an extension to the class link:{javadoc-url}/org/eclipse/jetty/webapp/WebAppContext.hml[`org.eclipse.jetty.ee9.webapp.WebAppContext`]. You can use any of the setter methods on this object to configure your webapp. Here are a few of the most useful ones: + @@ -565,7 +565,7 @@ This goal will generate output from jetty into the `target/jetty-start.out` file These configuration parameters are available: webApp:: -This is an instance of link:{javadoc-url}/org/eclipse/jetty/maven/plugin/MavenWebAppContext.html[org.eclipse.jetty.maven.plugin.MavenWebAppContext], which is an extension to the class link:{javadoc-url}/org/eclipse/jetty/webapp/WebAppContext.hml[`org.eclipse.jetty.webapp.WebAppContext`]. +This is an instance of link:{javadoc-url}/org/eclipse/jetty/maven/plugin/MavenWebAppContext.html[org.eclipse.jetty.maven.plugin.MavenWebAppContext], which is an extension to the class link:{javadoc-url}/org/eclipse/jetty/webapp/WebAppContext.hml[`org.eclipse.jetty.ee9.webapp.WebAppContext`]. You can use any of the setter methods on this object to configure your webapp. Here are a few of the most useful ones: + @@ -1032,7 +1032,7 @@ Putting the configuration in webapp A's `pom.xml`: [IMPORTANT] ==== -If the `ContextHandler` you are deploying is a webapp, it is *essential* that you use an `org.eclipse.jetty.maven.plugin.MavenWebAppContext` instance rather than a standard `org.eclipse.jetty.webapp.WebAppContext` instance. +If the `ContextHandler` you are deploying is a webapp, it is *essential* that you use an `org.eclipse.jetty.maven.plugin.MavenWebAppContext` instance rather than a standard `org.eclipse.jetty.ee9.webapp.WebAppContext` instance. Only the former will allow the webapp to function correctly in the maven environment. ==== diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-handler-use.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-handler-use.adoc index f29dafcedcc..a3df908ba12 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-handler-use.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http-handler-use.adoc @@ -383,7 +383,7 @@ Web applications cannot downcast Servlet's `HttpServletRequest` to Jetty's `Requ Lastly, the Servlet specification requires that other classes, also called _system classes_, such as `javax.servlet.http.HttpServletRequest` or JDK classes such as `java.lang.String` or `java.sql.Connection` cannot be modified by web applications by putting, for example, modified versions of those classes in `WEB-INF/classes` so that they are loaded first by the web application class loader (instead of the class-path class loader where they are normally loaded from). -`WebAppContext` implements this class loader logic using a single class loader, `org.eclipse.jetty.webapp.WebAppClassLoader`, with filtering capabilities: when it loads a class, it checks whether the class is a _system class_ or a _server class_ and acts according to the Servlet specification. +`WebAppContext` implements this class loader logic using a single class loader, `org.eclipse.jetty.ee9.webapp.WebAppClassLoader`, with filtering capabilities: when it loads a class, it checks whether the class is a _system class_ or a _server class_ and acts according to the Servlet specification. When `WebAppClassLoader` is asked to load a class, it first tries to find the class locally (since it must use the inverted child-first model); if the class is found, and it is not a _system class_, the class is loaded; otherwise the class is not found locally. If the class is not found locally, the parent class loader is asked to load the class; the parent class loader uses the standard parent-first model, so it delegates the class loading to its parent, and so on. diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http.adoc index 6b37f3b2368..24d4e51c662 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/http/server-http.adoc @@ -110,23 +110,23 @@ This event is converted to a call to `AbstractConnection.onFillable()`, where th The parser emit events that are protocol specific; the HTTP/2 parser, for example, emits events for each HTTP/2 frame that has been parsed, and similarly does the HTTP/3 parser. The parser events are then converted to protocol independent events such as _"request start"_, _"request headers"_, _"request content chunk"_, etc. -that in turn are converted into method calls to `HttpChannel`. +that in turn are converted into method calls to `HttpChannelState`. When enough of the HTTP request is arrived, the `Connection` calls `HttpChannel.handle()` that calls the `Handler` chain, that eventually calls the server application code. [[pg-server-http-channel-events]] ===== HttpChannel Events -The central component processing HTTP requests is `HttpChannel`. -There is a 1-to-1 relationship between an HTTP request/response and an `HttpChannel`, no matter what is the specific protocol that carries the HTTP request over the network (HTTP/1.1, HTTP/2, HTTP/3 or FastCGI). +The central component processing HTTP requests is `HttpChannelState`. +There is a 1-to-1 relationship between an HTTP request/response and an `HttpChannelState`, no matter what is the specific protocol that carries the HTTP request over the network (HTTP/1.1, HTTP/2, HTTP/3 or FastCGI). -Advanced server applications may be interested in the progress of the processing of an HTTP request/response by `HttpChannel`. +Advanced server applications may be interested in the progress of the processing of an HTTP request/response by `HttpChannelState`. A typical case is to know exactly _when_ the HTTP request/response processing is complete, for example to monitor processing times. NOTE: A `Handler` or a Servlet `Filter` may not report precisely when an HTTP request/response processing is finished. A server application may write a small enough content that is aggregated by Jetty for efficiency reasons; the write returns immediately, but nothing has been written to the network yet. -`HttpChannel` notifies ``HttpChannel.Listener``s of the progress of the HTTP request/response handling. +`HttpChannelState` notifies ``HttpChannel.Listener``s of the progress of the HTTP request/response handling. Currently, the following events are available: * `requestBegin` diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/session-sessionhandler.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/session-sessionhandler.adoc index 60f3807d066..918d84b0a99 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/session-sessionhandler.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/sessions/session-sessionhandler.adoc @@ -27,7 +27,7 @@ checkingRemoteSessionIdEncoding:: Boolean, default `false`. This controls whether or not the `javax.servlet.http.Response.encodeURL(String)` method will include the session id as a path parameter when the URL is destined for a remote node. This can also be configured by: -* setting the `org.eclipse.jetty.servlet.CheckingRemoteSessionIdEncoding` context init paramter +* setting the `org.eclipse.jetty.ee9.servlet.CheckingRemoteSessionIdEncoding` context init paramter setMaxInactiveInterval:: Integer, seconds. @@ -69,14 +69,14 @@ This is the name of the session cookie. It can alternatively be configured by: * using `javax.servlet.SessionCookieConfig.setName(String)` method -* setting the `org.eclipse.jetty.servlet.SessionCookie` context init parameter. +* setting the `org.eclipse.jetty.ee9.servlet.SessionCookie` context init parameter. sessionIdPathParameterName:: String, default is `jsessionid`. This is the name of the path parameter used to transmit the session id on request URLs, and on encoded URLS in responses. It can alternatively be configured by: -* setting the `org.eclipse.jetty.servlet.SessionIdPathParameterName` context init parameter +* setting the `org.eclipse.jetty.ee9.servlet.SessionIdPathParameterName` context init parameter sessionTrackingModes:: `Set`. @@ -99,7 +99,7 @@ This can also be configured by: There are also a few session settings that do not have SessionHandler setters, but can be configured with context init parameters: [[pg-server-session-handler-maxAge]] -org.eclipse.jetty.servlet.MaxAge:: +org.eclipse.jetty.ee9.servlet.MaxAge:: This is the maximum number of seconds that the session cookie will be considered to be valid. By default, the cookie has no maximum validity time. See also xref:pg-server-session-handler-refreshcookie[refreshing the session cookie]. @@ -107,7 +107,7 @@ The value can also be configured by: * calling the `SessionCookieConfig.setMaxAge(int)` method. -org.eclipse.jetty.servlet.SessionDomain:: +org.eclipse.jetty.ee9.servlet.SessionDomain:: String, default `null`. This is the domain of the session cookie. This can also be configured by: @@ -115,7 +115,7 @@ This can also be configured by: * using the `javax.servlet.SessionCookieConfig.setDomain(String)` method * defining the `` element in `web.xml` -org.eclipse.jetty.servlet.SessionPath:: +org.eclipse.jetty.ee9.servlet.SessionPath:: String, default `null`. This is used when creating a new session cookie. If nothing is configured, the context path is used instead, defaulting to `/`. diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-filter.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-filter.adoc index b7db4b7929d..8723882a5e4 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-filter.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-filter.adoc @@ -44,7 +44,7 @@ For example: cross-origin - org.eclipse.jetty.servlets.CrossOriginFilter + org.eclipse.jetty.ee9.servlets.CrossOriginFilter true diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-jetty.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-jetty.adoc index 82542a534ef..8ad0f12cdcf 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-jetty.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/server/websocket/server-websocket-jetty.adoc @@ -76,7 +76,7 @@ Calling `JettyWebSocketServletContainerInitializer.configure(\...)` must be done Once you have xref:pg-server-websocket-jetty-container[setup] the `JettyWebSocketServerContainer`, you can configure your xref:pg-websocket-endpoints[WebSocket endpoints]. -Differently from the xref:pg-server-websocket-standard-endpoints[configuration of standard WebSocket endpoints], WebSocket endpoint classes may be annotated with Jetty WebSocket API annotations, or extend the `org.eclipse.jetty.websocket.api.WebSocketListener` interface, but they are not automatically discovered, not even when deploying web applications using xref:pg-server-http-handler-use-webapp-context[`WebAppContext`]. +Differently from the xref:pg-server-websocket-standard-endpoints[configuration of standard WebSocket endpoints], WebSocket endpoint classes may be annotated with Jetty WebSocket API annotations, or extend the `org.eclipse.jetty.ee9.websocket.api.WebSocketListener` interface, but they are not automatically discovered, not even when deploying web applications using xref:pg-server-http-handler-use-webapp-context[`WebAppContext`]. [IMPORTANT] ==== diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/websocket.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/websocket.adoc index 225f4dc4a85..2d99d9ce277 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/websocket.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/websocket.adoc @@ -57,7 +57,7 @@ Applications interested in this type of messages receive a `byte[]` representing [[pg-websocket-endpoints-listener]] ===== Listener Endpoints -A WebSocket endpoint may implement the `org.eclipse.jetty.websocket.api.WebSocketListener` interface to receive WebSocket events: +A WebSocket endpoint may implement the `org.eclipse.jetty.ee9.websocket.api.WebSocketListener` interface to receive WebSocket events: [source,java,indent=0] ---- @@ -70,7 +70,7 @@ include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=l If you need to deal with large WebSocket messages, you may reduce the memory usage by streaming the message content. For large WebSocket messages, the memory usage may be large due to the fact that the text or the bytes must be accumulated until the message is complete before delivering the message event. -To stream textual or binary messages, you must implement interface `org.eclipse.jetty.websocket.api.WebSocketPartialListener` instead of `WebSocketListener`. +To stream textual or binary messages, you must implement interface `org.eclipse.jetty.ee9.websocket.api.WebSocketPartialListener` instead of `WebSocketListener`. Interface `WebSocketPartialListener` exposes one method for textual messages, and one method to binary messages that receive _chunks_ of, respectively, text and bytes that form the whole WebSocket message. @@ -84,7 +84,7 @@ include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=s [[pg-websocket-endpoints-annotated]] ===== Annotated Endpoints -A WebSocket endpoint may annotate methods with `org.eclipse.jetty.websocket.api.annotations.*` annotations to receive WebSocket events. +A WebSocket endpoint may annotate methods with `org.eclipse.jetty.ee9.websocket.api.annotations.*` annotations to receive WebSocket events. Each annotated method may take an optional `Session` argument as its first parameter: [source,java,indent=0] @@ -144,7 +144,7 @@ A WebSocket session is the entity that offers an API to send data to the remote [[pg-websocket-session-configure]] ===== Configuring the Session -You may configure the WebSocket session behavior using the `org.eclipse.jetty.websocket.api.Session` APIs. +You may configure the WebSocket session behavior using the `org.eclipse.jetty.ee9.websocket.api.Session` APIs. You want to do this as soon as you have access to the `Session` object, typically from the xref:pg-websocket-endpoints[_connect_ event] handler: [source,java,indent=0] @@ -254,7 +254,7 @@ The WebSocket protocol defines two special frame, named `Ping` and `Pong` that m * Calculate the round-trip time with the remote peer. * Keep the connection from being closed due to idle timeout -- a heartbeat-like mechanism. -To handle `Ping`/`Pong` events, you may implement interface `org.eclipse.jetty.websocket.api.WebSocketPingPongListener`. +To handle `Ping`/`Pong` events, you may implement interface `org.eclipse.jetty.ee9.websocket.api.WebSocketPingPongListener`. [NOTE] ==== diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/WebSocketDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/WebSocketDocs.java index ef3ceff4149..c29db7a07df 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/WebSocketDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/WebSocketDocs.java @@ -20,19 +20,19 @@ import java.nio.ByteBuffer; import java.nio.file.Path; import java.time.Duration; +import org.eclipse.jetty.ee9.websocket.api.RemoteEndpoint; +import org.eclipse.jetty.ee9.websocket.api.Session; +import org.eclipse.jetty.ee9.websocket.api.StatusCode; +import org.eclipse.jetty.ee9.websocket.api.WebSocketListener; +import org.eclipse.jetty.ee9.websocket.api.WebSocketPartialListener; +import org.eclipse.jetty.ee9.websocket.api.WebSocketPingPongListener; +import org.eclipse.jetty.ee9.websocket.api.WriteCallback; +import org.eclipse.jetty.ee9.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.ee9.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.ee9.websocket.api.annotations.OnWebSocketError; +import org.eclipse.jetty.ee9.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.ee9.websocket.api.annotations.WebSocket; import org.eclipse.jetty.util.IteratingCallback; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.WebSocketListener; -import org.eclipse.jetty.websocket.api.WebSocketPartialListener; -import org.eclipse.jetty.websocket.api.WebSocketPingPongListener; -import org.eclipse.jetty.websocket.api.WriteCallback; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; -import org.eclipse.jetty.websocket.api.annotations.WebSocket; @SuppressWarnings("unused") public class WebSocketDocs diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java index d1214006493..dfbf13d2e25 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java @@ -26,7 +26,6 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.client.HTTP2Client; @@ -35,6 +34,7 @@ import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.util.Callback; diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java index dce2373692b..fb234689778 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java @@ -22,14 +22,14 @@ import org.eclipse.jetty.client.HttpProxy; import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.HttpResponse; import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; +import org.eclipse.jetty.ee9.websocket.api.Session; +import org.eclipse.jetty.ee9.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.ee9.websocket.client.JettyUpgradeListener; +import org.eclipse.jetty.ee9.websocket.client.WebSocketClient; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2; import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; import org.eclipse.jetty.util.component.LifeCycle; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.JettyUpgradeListener; -import org.eclipse.jetty.websocket.client.WebSocketClient; @SuppressWarnings("unused") public class WebSocketClientDocs diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java index c1abe671d88..86615425cd1 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java @@ -27,6 +27,12 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.ee9.servlet.DefaultServlet; +import org.eclipse.jetty.ee9.servlet.FilterHolder; +import org.eclipse.jetty.ee9.servlet.ServletContextHandler; +import org.eclipse.jetty.ee9.servlet.ServletHolder; +import org.eclipse.jetty.ee9.servlets.CrossOriginFilter; +import org.eclipse.jetty.ee9.webapp.WebAppContext; import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; @@ -42,7 +48,6 @@ import org.eclipse.jetty.rewrite.handler.RewriteHandler; import org.eclipse.jetty.rewrite.handler.RewriteRegexRule; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.CustomRequestLog; -import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.ProxyConnectionFactory; @@ -65,18 +70,13 @@ import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.server.handler.SecuredRedirectHandler; import org.eclipse.jetty.server.handler.StatisticsHandler; import org.eclipse.jetty.server.handler.gzip.GzipHandler; -import org.eclipse.jetty.servlet.DefaultServlet; -import org.eclipse.jetty.servlet.FilterHolder; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.servlets.CrossOriginFilter; +import org.eclipse.jetty.server.internal.HttpChannelState; import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceCollection; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.jetty.webapp.WebAppContext; import static java.lang.System.Logger.Level.INFO; @@ -119,7 +119,7 @@ public class HTTPServerDocs public void httpChannelListener() throws Exception { // tag::httpChannelListener[] - class TimingHttpChannelListener implements HttpChannel.Listener + class TimingHttpChannelListener implements HttpChannelState.Listener { private final ConcurrentMap times = new ConcurrentHashMap<>(); diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java index c8dc47bf141..a3dac9ec95c 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java @@ -24,7 +24,6 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -33,6 +32,7 @@ import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java index c5985193406..9d4c3eb7d4d 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/session/SessionDocs.java @@ -16,25 +16,25 @@ package org.eclipse.jetty.docs.programming.server.session; import java.io.File; import java.net.InetSocketAddress; +import org.eclipse.jetty.ee9.servlet.ServletContextHandler; +import org.eclipse.jetty.ee9.webapp.WebAppContext; import org.eclipse.jetty.memcached.session.MemcachedSessionDataMapFactory; import org.eclipse.jetty.nosql.mongodb.MongoSessionDataStoreFactory; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandlerCollection; -import org.eclipse.jetty.server.session.CachingSessionDataStoreFactory; -import org.eclipse.jetty.server.session.DatabaseAdaptor; -import org.eclipse.jetty.server.session.DefaultSessionCache; -import org.eclipse.jetty.server.session.DefaultSessionCacheFactory; -import org.eclipse.jetty.server.session.DefaultSessionIdManager; -import org.eclipse.jetty.server.session.FileSessionDataStore; -import org.eclipse.jetty.server.session.FileSessionDataStoreFactory; -import org.eclipse.jetty.server.session.HouseKeeper; -import org.eclipse.jetty.server.session.NullSessionCache; -import org.eclipse.jetty.server.session.NullSessionCacheFactory; -import org.eclipse.jetty.server.session.NullSessionDataStore; -import org.eclipse.jetty.server.session.SessionCache; -import org.eclipse.jetty.server.session.SessionHandler; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.session.CachingSessionDataStoreFactory; +import org.eclipse.jetty.session.DatabaseAdaptor; +import org.eclipse.jetty.session.DefaultSessionCache; +import org.eclipse.jetty.session.DefaultSessionCacheFactory; +import org.eclipse.jetty.session.DefaultSessionIdManager; +import org.eclipse.jetty.session.FileSessionDataStore; +import org.eclipse.jetty.session.FileSessionDataStoreFactory; +import org.eclipse.jetty.session.HouseKeeper; +import org.eclipse.jetty.session.NullSessionCache; +import org.eclipse.jetty.session.NullSessionCacheFactory; +import org.eclipse.jetty.session.NullSessionDataStore; +import org.eclipse.jetty.session.SessionCache; +import org.eclipse.jetty.session.SessionHandler; @SuppressWarnings("unused") public class SessionDocs @@ -114,7 +114,7 @@ public class SessionDocs //EVICT_ON_INACTIVE: evict a session after 60sec inactivity cacheFactory.setEvictionPolicy(60); //Only useful with the EVICT_ON_INACTIVE policy - cacheFactory.setSaveOnInactiveEvict(true); + cacheFactory.setSaveOnInactiveEviction(true); cacheFactory.setFlushOnResponseCommit(true); cacheFactory.setInvalidateOnShutdown(false); cacheFactory.setRemoveUnloadableSessions(true); diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java index c5a6d7ad10d..8776f670b9c 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/websocket/WebSocketServerDocs.java @@ -24,16 +24,16 @@ import jakarta.websocket.DeploymentException; import jakarta.websocket.server.ServerContainer; import jakarta.websocket.server.ServerEndpoint; import jakarta.websocket.server.ServerEndpointConfig; +import org.eclipse.jetty.ee9.servlet.ServletContextHandler; +import org.eclipse.jetty.ee9.webapp.WebAppContext; +import org.eclipse.jetty.ee9.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.ee9.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; +import org.eclipse.jetty.ee9.websocket.server.JettyWebSocketCreator; +import org.eclipse.jetty.ee9.websocket.server.JettyWebSocketServerContainer; +import org.eclipse.jetty.ee9.websocket.server.JettyWebSocketServlet; +import org.eclipse.jetty.ee9.websocket.server.JettyWebSocketServletFactory; +import org.eclipse.jetty.ee9.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.webapp.WebAppContext; -import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; -import org.eclipse.jetty.websocket.server.JettyWebSocketCreator; -import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; -import org.eclipse.jetty.websocket.server.JettyWebSocketServlet; -import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory; -import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; @SuppressWarnings("unused") public class WebSocketServerDocs diff --git a/documentation/pom.xml b/documentation/pom.xml index 02955c82956..5f5f75e909f 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -3,12 +3,12 @@ org.eclipse.jetty jetty-project - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT 4.0.0 org.eclipse.jetty.documentation - documentation-parent + documentation Jetty :: Documentation :: Parent pom diff --git a/javadoc/pom.xml b/javadoc/pom.xml index 56785ebdd3b..8e780427e5f 100644 --- a/javadoc/pom.xml +++ b/javadoc/pom.xml @@ -3,7 +3,7 @@ org.eclipse.jetty jetty-project - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT 4.0.0 @@ -14,6 +14,7 @@ ${project.build.directory}/jetty-sources true + true true true @@ -108,10 +109,6 @@ infinispan-remote, jetty-test-helper, alpn-api, - quic-quiche, - quic-quiche-common, - quic-quiche-foreign-incubator, - quic-quiche-jna, javax.servlet, javax.websocket, jakarta.servlet, @@ -157,6 +154,8 @@ org.eclipse.jetty.http3.qpack.internal.*; org.eclipse.jetty.http3.server.internal; org.eclipse.jetty.quic.common.internal; + org.eclipse.jetty.quic.quiche; + org.eclipse.jetty.quic.quiche.*; org.eclipse.jetty.quic.server.internal; @@ -347,11 +346,6 @@ jetty-unixsocket-client provided - - org.eclipse.jetty - jetty-unixsocket-server - provided - org.eclipse.jetty.websocket websocket-jakarta-server @@ -400,12 +394,6 @@ org.eclipse.jetty jetty-jspc-maven-plugin provided - - - javax.annotation - javax.annotation-api - - org.eclipse.jetty diff --git a/jetty-annotations/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration b/jetty-annotations/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration deleted file mode 100644 index 87590589c0e..00000000000 --- a/jetty-annotations/src/main/resources/META-INF/services/org.eclipse.jetty.webapp.Configuration +++ /dev/null @@ -1 +0,0 @@ -org.eclipse.jetty.annotations.AnnotationConfiguration diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java deleted file mode 100644 index 5b8f9cf7012..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client; - -import java.util.EventListener; - -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.client.api.Request; - -/** - * A {@link ContentProvider} that notifies listeners that content is available. - * - * @deprecated no replacement, use {@link Request.Content} instead. - */ -@Deprecated -public interface AsyncContentProvider extends ContentProvider -{ - /** - * @param listener the listener to be notified of content availability - */ - public void setListener(Listener listener); - - /** - * A listener that is notified of content availability - */ - public interface Listener extends EventListener - { - /** - * Callback method invoked when content is available - */ - public void onContent(); - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java deleted file mode 100644 index b27ddd4ac5b..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java +++ /dev/null @@ -1,94 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.api; - -import java.io.Closeable; -import java.nio.ByteBuffer; -import java.util.Iterator; - -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.internal.RequestContentAdapter; -import org.eclipse.jetty.client.util.ByteBufferContentProvider; -import org.eclipse.jetty.client.util.PathContentProvider; - -/** - *

{@link ContentProvider} provides a source of request content.

- *

Implementations should return an {@link Iterator} over the request content. - * If the request content comes from a source that needs to be closed (for - * example, an {@link java.io.InputStream}), then the iterator implementation class - * must implement {@link Closeable} and will be closed when the request is - * completed (either successfully or failed).

- *

Applications should rely on utility classes such as {@link ByteBufferContentProvider} - * or {@link PathContentProvider}.

- *

{@link ContentProvider} provides a {@link #getLength() length} of the content - * it represents. - * If the length is positive, it typically overrides any {@code Content-Length} - * header set by applications; if the length is negative, it typically removes - * any {@code Content-Length} header set by applications, resulting in chunked - * content (i.e. {@code Transfer-Encoding: chunked}) being sent to the server.

- * - * @deprecated use {@link Request.Content} instead, or {@link #toRequestContent(ContentProvider)} - * to convert ContentProvider to {@link Request.Content}. - */ -@Deprecated -public interface ContentProvider extends Iterable -{ - /** - *

Converts a ContentProvider to a {@link Request.Content}.

- * - * @param provider the ContentProvider to convert - * @return a {@link Request.Content} that wraps the ContentProvider - */ - public static Request.Content toRequestContent(ContentProvider provider) - { - return new RequestContentAdapter(provider); - } - - /** - * @return the content length, if known, or -1 if the content length is unknown - */ - long getLength(); - - /** - *

Whether this ContentProvider can produce exactly the same content more - * than once.

- *

Implementations should return {@code true} only if the content can be - * produced more than once, which means that invocations to {@link #iterator()} - * must return a new, independent, iterator instance over the content.

- *

The {@link HttpClient} implementation may use this method in particular - * cases where it detects that it is safe to retry a request that failed.

- * - * @return whether the content can be produced more than once - */ - default boolean isReproducible() - { - return false; - } - - /** - * An extension of {@link ContentProvider} that provides a content type string - * to be used as a {@code Content-Type} HTTP header in requests. - * - * @deprecated use {@link Request.Content} instead - */ - @Deprecated - public interface Typed extends ContentProvider - { - /** - * @return the content type string such as "application/octet-stream" or - * "application/json;charset=UTF8", or null if no content type must be set - */ - public String getContentType(); - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/internal/RequestContentAdapter.java b/jetty-client/src/main/java/org/eclipse/jetty/client/internal/RequestContentAdapter.java deleted file mode 100644 index a810c7f0f1f..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/internal/RequestContentAdapter.java +++ /dev/null @@ -1,324 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.internal; - -import java.io.Closeable; -import java.nio.ByteBuffer; -import java.util.Iterator; - -import org.eclipse.jetty.client.AsyncContentProvider; -import org.eclipse.jetty.client.Synchronizable; -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.thread.AutoLock; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - *

Implements the conversion from {@link ContentProvider} to {@link Request.Content}.

- */ -public class RequestContentAdapter implements Request.Content, Request.Content.Subscription, AsyncContentProvider.Listener, Callback -{ - private static final Logger LOG = LoggerFactory.getLogger(RequestContentAdapter.class); - - private final AutoLock lock = new AutoLock(); - private final ContentProvider provider; - private Iterator iterator; - private Consumer consumer; - private boolean emitInitialContent; - private boolean lastContent; - private boolean committed; - private int demand; - private boolean stalled; - private boolean hasContent; - private Throwable failure; - - public RequestContentAdapter(ContentProvider provider) - { - this.provider = provider; - if (provider instanceof AsyncContentProvider) - ((AsyncContentProvider)provider).setListener(this); - } - - public ContentProvider getContentProvider() - { - return provider; - } - - @Override - public String getContentType() - { - return provider instanceof ContentProvider.Typed ? ((ContentProvider.Typed)provider).getContentType() : null; - } - - @Override - public long getLength() - { - return provider.getLength(); - } - - @Override - public boolean isReproducible() - { - return provider.isReproducible(); - } - - @Override - public Subscription subscribe(Consumer consumer, boolean emitInitialContent) - { - try (AutoLock ignored = lock.lock()) - { - if (this.consumer != null && !isReproducible()) - throw new IllegalStateException("Multiple subscriptions not supported on " + this); - this.iterator = provider.iterator(); - this.consumer = consumer; - this.emitInitialContent = emitInitialContent; - this.lastContent = false; - this.committed = false; - this.demand = 0; - this.stalled = true; - this.hasContent = false; - } - return this; - } - - @Override - public void demand() - { - boolean produce; - try (AutoLock ignored = lock.lock()) - { - ++demand; - produce = stalled; - if (stalled) - stalled = false; - } - if (LOG.isDebugEnabled()) - LOG.debug("Content demand, producing {} for {}", produce, this); - if (produce) - produce(); - } - - @Override - public void fail(Throwable failure) - { - try (AutoLock ignored = lock.lock()) - { - if (this.failure == null) - this.failure = failure; - } - failed(failure); - } - - @Override - public void onContent() - { - boolean produce = false; - try (AutoLock ignored = lock.lock()) - { - hasContent = true; - if (demand > 0) - { - produce = stalled; - if (stalled) - stalled = false; - } - } - if (LOG.isDebugEnabled()) - LOG.debug("Content event, processing {} for {}", produce, this); - if (produce) - produce(); - } - - @Override - public void succeeded() - { - if (iterator instanceof Callback) - ((Callback)iterator).succeeded(); - if (lastContent && iterator instanceof Closeable) - IO.close((Closeable)iterator); - } - - @Override - public void failed(Throwable x) - { - if (iterator == null) - failed(provider, x); - else - failed(iterator, x); - } - - private void failed(Object object, Throwable failure) - { - if (object instanceof Callback) - ((Callback)object).failed(failure); - if (object instanceof Closeable) - IO.close((Closeable)object); - } - - @Override - public InvocationType getInvocationType() - { - return InvocationType.NON_BLOCKING; - } - - private void produce() - { - while (true) - { - Throwable failure; - try (AutoLock ignored = lock.lock()) - { - failure = this.failure; - } - if (failure != null) - { - notifyFailure(failure); - return; - } - - if (committed) - { - ByteBuffer content = advance(); - if (content != null) - { - notifyContent(content, lastContent); - } - else - { - try (AutoLock ignored = lock.lock()) - { - // Call to advance() said there was no content, - // but some content may have arrived meanwhile. - if (hasContent) - { - hasContent = false; - continue; - } - else - { - stalled = true; - } - } - if (LOG.isDebugEnabled()) - LOG.debug("No content, processing stalled for {}", this); - return; - } - } - else - { - committed = true; - if (emitInitialContent) - { - ByteBuffer content = advance(); - if (content != null) - notifyContent(content, lastContent); - else - notifyContent(BufferUtil.EMPTY_BUFFER, false); - } - else - { - notifyContent(BufferUtil.EMPTY_BUFFER, false); - } - } - boolean noDemand; - try (AutoLock ignored = lock.lock()) - { - noDemand = demand == 0; - if (noDemand) - stalled = true; - } - if (noDemand) - { - if (LOG.isDebugEnabled()) - LOG.debug("No demand, processing stalled for {}", this); - return; - } - } - } - - private ByteBuffer advance() - { - if (iterator instanceof Synchronizable) - { - synchronized (((Synchronizable)iterator).getLock()) - { - return next(); - } - } - else - { - return next(); - } - } - - private ByteBuffer next() - { - boolean hasNext = iterator.hasNext(); - ByteBuffer bytes = hasNext ? iterator.next() : null; - boolean hasMore = hasNext && iterator.hasNext(); - lastContent = !hasMore; - return hasNext ? bytes : BufferUtil.EMPTY_BUFFER; - } - - private void notifyContent(ByteBuffer buffer, boolean last) - { - try (AutoLock ignored = lock.lock()) - { - --demand; - hasContent = false; - } - - try - { - if (LOG.isDebugEnabled()) - LOG.debug("Notifying content last={} {} for {}", last, BufferUtil.toDetailString(buffer), this); - consumer.onContent(buffer, last, this); - } - catch (Throwable x) - { - fail(x); - } - } - - private void notifyFailure(Throwable failure) - { - try - { - if (LOG.isDebugEnabled()) - LOG.debug("Notifying failure for {}", this, failure); - consumer.onFailure(failure); - } - catch (Exception x) - { - LOG.trace("Failure while notifying content failure {}", failure, x); - } - } - - @Override - public String toString() - { - int demand; - boolean stalled; - try (AutoLock ignored = lock.lock()) - { - demand = this.demand; - stalled = this.stalled; - } - return String.format("%s@%x[demand=%d,stalled=%b]", getClass().getSimpleName(), hashCode(), demand, stalled); - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java deleted file mode 100644 index 938e8f70f6d..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java +++ /dev/null @@ -1,96 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.util; - -import java.nio.ByteBuffer; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.eclipse.jetty.client.api.ContentProvider; - -/** - * A {@link ContentProvider} for {@link ByteBuffer}s. - *

- * The position and limit of the {@link ByteBuffer}s passed to the constructor are not modified, - * and each invocation of the {@link #iterator()} method returns a {@link ByteBuffer#slice() slice} - * of the original {@link ByteBuffer}. - * - * @deprecated use {@link ByteBufferRequestContent} instead. - */ -@Deprecated -public class ByteBufferContentProvider extends AbstractTypedContentProvider -{ - private final ByteBuffer[] buffers; - private final int length; - - public ByteBufferContentProvider(ByteBuffer... buffers) - { - this("application/octet-stream", buffers); - } - - public ByteBufferContentProvider(String contentType, ByteBuffer... buffers) - { - super(contentType); - this.buffers = buffers; - int length = 0; - for (ByteBuffer buffer : buffers) - { - length += buffer.remaining(); - } - this.length = length; - } - - @Override - public long getLength() - { - return length; - } - - @Override - public boolean isReproducible() - { - return true; - } - - @Override - public Iterator iterator() - { - return new Iterator() - { - private int index; - - @Override - public boolean hasNext() - { - return index < buffers.length; - } - - @Override - public ByteBuffer next() - { - try - { - ByteBuffer buffer = buffers[index]; - buffers[index] = buffer.slice(); - ++index; - return buffer; - } - catch (ArrayIndexOutOfBoundsException x) - { - throw new NoSuchElementException(); - } - } - }; - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java deleted file mode 100644 index 16ff874ef83..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java +++ /dev/null @@ -1,89 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.util; - -import java.nio.ByteBuffer; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.eclipse.jetty.client.api.ContentProvider; - -/** - * A {@link ContentProvider} for byte arrays. - * - * @deprecated use {@link BytesRequestContent} instead. - */ -@Deprecated -public class BytesContentProvider extends AbstractTypedContentProvider -{ - private final byte[][] bytes; - private final long length; - - public BytesContentProvider(byte[]... bytes) - { - this("application/octet-stream", bytes); - } - - public BytesContentProvider(String contentType, byte[]... bytes) - { - super(contentType); - this.bytes = bytes; - long length = 0; - for (byte[] buffer : bytes) - { - length += buffer.length; - } - this.length = length; - } - - @Override - public long getLength() - { - return length; - } - - @Override - public boolean isReproducible() - { - return true; - } - - @Override - public Iterator iterator() - { - return new Iterator() - { - private int index; - - @Override - public boolean hasNext() - { - return index < bytes.length; - } - - @Override - public ByteBuffer next() - { - try - { - return ByteBuffer.wrap(bytes[index++]); - } - catch (ArrayIndexOutOfBoundsException x) - { - throw new NoSuchElementException(); - } - } - }; - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java deleted file mode 100644 index 690bccf4e67..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java +++ /dev/null @@ -1,343 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.util; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.nio.ByteBuffer; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -import org.eclipse.jetty.client.AsyncContentProvider; -import org.eclipse.jetty.client.Synchronizable; -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; - -/** - * A {@link ContentProvider} that allows to add content after {@link Request#send(Response.CompleteListener)} - * has been called, therefore providing the request content at a later time. - *

- * {@link DeferredContentProvider} can only be used in conjunction with - * {@link Request#send(Response.CompleteListener)} (and not with its blocking counterpart {@link Request#send()}) - * because it provides content asynchronously. - *

- * The deferred content is provided once and then fully consumed. - * Invocations to the {@link #iterator()} method after the first will return an "empty" iterator - * because the stream has been consumed on the first invocation. - * However, it is possible for subclasses to override {@link #offer(ByteBuffer)} and {@link #close()} to copy - * the content to another location (for example a file) and be able to support multiple invocations - * of of {@link #iterator()} returning the iterator provided by this - * class on the first invocation, and an iterator on the bytes copied to the other location - * for subsequent invocations. - *

- * Typical usage of {@link DeferredContentProvider} is in asynchronous proxies, where HTTP headers arrive - * separately from HTTP content chunks. - *

- * The deferred content must be provided through {@link #offer(ByteBuffer)}, which can be invoked multiple - * times, and when all content has been provided it must be signaled with a call to {@link #close()}. - *

- * Example usage: - *

- * HttpClient httpClient = ...;
- *
- * // Use try-with-resources to autoclose DeferredContentProvider
- * try (DeferredContentProvider content = new DeferredContentProvider())
- * {
- *     httpClient.newRequest("localhost", 8080)
- *             .content(content)
- *             .send(new Response.CompleteListener()
- *             {
- *                 @Override
- *                 public void onComplete(Result result)
- *                 {
- *                     // Your logic here
- *                 }
- *             });
- *
- *     // At a later time...
- *     content.offer(ByteBuffer.wrap("some content".getBytes()));
- * }
- * 
- * - * @deprecated use {@link AsyncRequestContent} instead. - */ -@Deprecated -public class DeferredContentProvider implements AsyncContentProvider, Callback, Closeable -{ - private static final Chunk CLOSE = new Chunk(BufferUtil.EMPTY_BUFFER, Callback.NOOP); - - private final Object lock = this; - private final Deque chunks = new ArrayDeque<>(); - private final AtomicReference listener = new AtomicReference<>(); - private final DeferredContentProviderIterator iterator = new DeferredContentProviderIterator(); - private final AtomicBoolean closed = new AtomicBoolean(); - private long length = -1; - private int size; - private Throwable failure; - - /** - * Creates a new {@link DeferredContentProvider} with the given initial content - * - * @param buffers the initial content - */ - public DeferredContentProvider(ByteBuffer... buffers) - { - for (ByteBuffer buffer : buffers) - { - offer(buffer); - } - } - - @Override - public void setListener(Listener listener) - { - if (!this.listener.compareAndSet(null, listener)) - throw new IllegalStateException(String.format("The same %s instance cannot be used in multiple requests", - AsyncContentProvider.class.getName())); - - if (isClosed()) - { - synchronized (lock) - { - long total = 0; - for (Chunk chunk : chunks) - { - total += chunk.buffer.remaining(); - } - length = total; - } - } - } - - @Override - public long getLength() - { - return length; - } - - /** - * Adds the given content buffer to this content provider - * and notifies the listener that content is available. - * - * @param buffer the content to add - * @return true if the content was added, false otherwise - */ - public boolean offer(ByteBuffer buffer) - { - return offer(buffer, Callback.NOOP); - } - - public boolean offer(ByteBuffer buffer, Callback callback) - { - return offer(new Chunk(buffer, callback)); - } - - private boolean offer(Chunk chunk) - { - Throwable failure; - boolean result = false; - synchronized (lock) - { - failure = this.failure; - if (failure == null) - { - result = chunks.offer(chunk); - if (result && chunk != CLOSE) - ++size; - } - } - if (failure != null) - chunk.callback.failed(failure); - else if (result) - notifyListener(); - return result; - } - - private void clear() - { - synchronized (lock) - { - chunks.clear(); - } - } - - public void flush() throws IOException - { - synchronized (lock) - { - try - { - while (true) - { - if (failure != null) - throw new IOException(failure); - if (size == 0) - break; - lock.wait(); - } - } - catch (InterruptedException x) - { - throw new InterruptedIOException(); - } - } - } - - /** - * No more content will be added to this content provider - * and notifies the listener that no more content is available. - */ - @Override - public void close() - { - if (closed.compareAndSet(false, true)) - offer(CLOSE); - } - - public boolean isClosed() - { - return closed.get(); - } - - @Override - public void failed(Throwable failure) - { - iterator.failed(failure); - } - - private void notifyListener() - { - Listener listener = this.listener.get(); - if (listener != null) - listener.onContent(); - } - - @Override - public Iterator iterator() - { - return iterator; - } - - private class DeferredContentProviderIterator implements Iterator, Callback, Synchronizable - { - private Chunk current; - - @Override - public boolean hasNext() - { - synchronized (lock) - { - return chunks.peek() != CLOSE; - } - } - - @Override - public ByteBuffer next() - { - synchronized (lock) - { - Chunk chunk = current = chunks.poll(); - if (chunk == CLOSE) - { - // Slow path: reinsert the CLOSE chunk - // so that hasNext() works correctly. - chunks.offerFirst(CLOSE); - throw new NoSuchElementException(); - } - return chunk == null ? null : chunk.buffer; - } - } - - @Override - public void remove() - { - throw new UnsupportedOperationException(); - } - - @Override - public void succeeded() - { - Chunk chunk; - synchronized (lock) - { - chunk = current; - current = null; - if (chunk != null) - { - --size; - lock.notify(); - } - } - if (chunk != null) - chunk.callback.succeeded(); - } - - @Override - public void failed(Throwable x) - { - List chunks = new ArrayList<>(); - synchronized (lock) - { - failure = x; - // Transfer all chunks to fail them all. - Chunk chunk = current; - current = null; - if (chunk != null) - chunks.add(chunk); - chunks.addAll(DeferredContentProvider.this.chunks); - clear(); - lock.notify(); - } - for (Chunk chunk : chunks) - { - chunk.callback.failed(x); - } - } - - @Override - public Object getLock() - { - return lock; - } - } - - public static class Chunk - { - public final ByteBuffer buffer; - public final Callback callback; - - public Chunk(ByteBuffer buffer, Callback callback) - { - this.buffer = Objects.requireNonNull(buffer); - this.callback = Objects.requireNonNull(callback); - } - - @Override - public String toString() - { - return String.format("%s@%x", getClass().getSimpleName(), hashCode()); - } - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java deleted file mode 100644 index 78684d19b1c..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.util; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.util.Fields; - -/** - * A {@link ContentProvider} for form uploads with the - * "application/x-www-form-urlencoded" content type. - * - * @deprecated use {@link FormRequestContent} instead. - */ -@Deprecated -public class FormContentProvider extends StringContentProvider -{ - public FormContentProvider(Fields fields) - { - this(fields, StandardCharsets.UTF_8); - } - - public FormContentProvider(Fields fields, Charset charset) - { - super("application/x-www-form-urlencoded", convert(fields, charset), charset); - } - - public static String convert(Fields fields) - { - return convert(fields, StandardCharsets.UTF_8); - } - - public static String convert(Fields fields, Charset charset) - { - return FormRequestContent.convert(fields, charset); - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java deleted file mode 100644 index 74bf1a794ed..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java +++ /dev/null @@ -1,256 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.util; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A {@link ContentProvider} for an {@link InputStream}. - *

- * The input stream is read once and therefore fully consumed. - * Invocations to the {@link #iterator()} method after the first will return an "empty" iterator - * because the stream has been consumed on the first invocation. - *

- * However, it is possible for subclasses to override {@link #onRead(byte[], int, int)} to copy - * the content read from the stream to another location (for example a file), and be able to - * support multiple invocations of {@link #iterator()}, returning the iterator provided by this - * class on the first invocation, and an iterator on the bytes copied to the other location - * for subsequent invocations. - *

- * It is possible to specify, at the constructor, a buffer size used to read content from the - * stream, by default 4096 bytes. - *

- * The {@link InputStream} passed to the constructor is by default closed when is it fully - * consumed (or when an exception is thrown while reading it), unless otherwise specified - * to the {@link #InputStreamContentProvider(java.io.InputStream, int, boolean) constructor}. - * - * @deprecated use {@link InputStreamRequestContent} instead - */ -@Deprecated -public class InputStreamContentProvider implements ContentProvider, Callback, Closeable -{ - private static final Logger LOG = LoggerFactory.getLogger(InputStreamContentProvider.class); - - private final InputStreamContentProviderIterator iterator = new InputStreamContentProviderIterator(); - private final InputStream stream; - private final int bufferSize; - private final boolean autoClose; - - public InputStreamContentProvider(InputStream stream) - { - this(stream, 4096); - } - - public InputStreamContentProvider(InputStream stream, int bufferSize) - { - this(stream, bufferSize, true); - } - - public InputStreamContentProvider(InputStream stream, int bufferSize, boolean autoClose) - { - this.stream = stream; - this.bufferSize = bufferSize; - this.autoClose = autoClose; - } - - @Override - public long getLength() - { - return -1; - } - - /** - * Callback method invoked just after having read from the stream, - * but before returning the iteration element (a {@link ByteBuffer} - * to the caller. - *

- * Subclasses may override this method to copy the content read from - * the stream to another location (a file, or in memory if the content - * is known to fit). - * - * @param buffer the byte array containing the bytes read - * @param offset the offset from where bytes should be read - * @param length the length of the bytes read - * @return a {@link ByteBuffer} wrapping the byte array - */ - protected ByteBuffer onRead(byte[] buffer, int offset, int length) - { - if (length <= 0) - return BufferUtil.EMPTY_BUFFER; - return ByteBuffer.wrap(buffer, offset, length); - } - - /** - * Callback method invoked when an exception is thrown while reading - * from the stream. - * - * @param failure the exception thrown while reading from the stream. - */ - protected void onReadFailure(Throwable failure) - { - } - - @Override - public Iterator iterator() - { - return iterator; - } - - @Override - public void close() - { - if (autoClose) - { - try - { - stream.close(); - } - catch (IOException x) - { - LOG.trace("IGNORED", x); - } - } - } - - @Override - public void failed(Throwable failure) - { - // TODO: forward the failure to the iterator. - close(); - } - - /** - * Iterating over an {@link InputStream} is tricky, because {@link #hasNext()} must return false - * if the stream reads -1. However, we don't know what to return until we read the stream, which - * means that stream reading must be performed by {@link #hasNext()}, which introduces a side-effect - * on what is supposed to be a simple query method (with respect to the Query Command Separation - * Principle). - *

- * Alternatively, we could return {@code true} from {@link #hasNext()} even if we don't know that - * we will read -1, but then when {@link #next()} reads -1 it must return an empty buffer. - * However this is problematic, since GETs with no content indication would become GET with chunked - * content, and not understood by servers. - *

- * Therefore we need to make sure that {@link #hasNext()} does not perform any side effect (so that - * it can be called multiple times) until {@link #next()} is called. - */ - private class InputStreamContentProviderIterator implements Iterator, Closeable - { - private Throwable failure; - private ByteBuffer buffer; - private Boolean hasNext; - - @Override - public boolean hasNext() - { - try - { - if (hasNext != null) - return hasNext; - - byte[] bytes = new byte[bufferSize]; - int read = stream.read(bytes); - if (LOG.isDebugEnabled()) - LOG.debug("Read {} bytes from {}", read, stream); - if (read > 0) - { - hasNext = Boolean.TRUE; - buffer = onRead(bytes, 0, read); - return true; - } - else if (read < 0) - { - hasNext = Boolean.FALSE; - buffer = null; - close(); - return false; - } - else - { - hasNext = Boolean.TRUE; - buffer = BufferUtil.EMPTY_BUFFER; - return true; - } - } - catch (Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("Failed to read", x); - if (failure == null) - { - failure = x; - onReadFailure(x); - // Signal we have more content to cause a call to - // next() which will throw NoSuchElementException. - hasNext = Boolean.TRUE; - buffer = null; - close(); - return true; - } - throw new IllegalStateException(); - } - } - - @Override - public ByteBuffer next() - { - if (failure != null) - { - // Consume the failure so that calls to hasNext() will return false. - hasNext = Boolean.FALSE; - buffer = null; - throw (NoSuchElementException)new NoSuchElementException().initCause(failure); - } - if (!hasNext()) - throw new NoSuchElementException(); - - ByteBuffer result = buffer; - if (result == null) - { - hasNext = Boolean.FALSE; - buffer = null; - throw new NoSuchElementException(); - } - else - { - hasNext = null; - buffer = null; - return result; - } - } - - @Override - public void remove() - { - throw new UnsupportedOperationException(); - } - - @Override - public void close() - { - InputStreamContentProvider.this.close(); - } - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java deleted file mode 100644 index 302fa196379..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java +++ /dev/null @@ -1,408 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.util; - -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.eclipse.jetty.client.AsyncContentProvider; -import org.eclipse.jetty.client.Synchronizable; -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.http.HttpField; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.io.RuntimeIOException; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.IO; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - *

A {@link ContentProvider} for form uploads with the {@code "multipart/form-data"} - * content type.

- *

Example usage:

- *
- * MultiPartContentProvider multiPart = new MultiPartContentProvider();
- * multiPart.addFieldPart("field", new StringContentProvider("foo"), null);
- * multiPart.addFilePart("icon", "img.png", new PathContentProvider(Paths.get("/tmp/img.png")), null);
- * multiPart.close();
- * ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
- *         .method(HttpMethod.POST)
- *         .content(multiPart)
- *         .send();
- * 
- *

The above example would be the equivalent of submitting this form:

- *
- * <form method="POST" enctype="multipart/form-data"  accept-charset="UTF-8">
- *     <input type="text" name="field" value="foo" />
- *     <input type="file" name="icon" />
- * </form>
- * 
- * - * @deprecated use {@link MultiPartRequestContent} instead. - */ -@Deprecated -public class MultiPartContentProvider extends AbstractTypedContentProvider implements AsyncContentProvider, Closeable -{ - private static final Logger LOG = LoggerFactory.getLogger(MultiPartContentProvider.class); - private static final byte[] COLON_SPACE_BYTES = new byte[]{':', ' '}; - private static final byte[] CR_LF_BYTES = new byte[]{'\r', '\n'}; - - private final List parts = new ArrayList<>(); - private final ByteBuffer firstBoundary; - private final ByteBuffer middleBoundary; - private final ByteBuffer onlyBoundary; - private final ByteBuffer lastBoundary; - private final AtomicBoolean closed = new AtomicBoolean(); - private Listener listener; - private long length = -1; - - public MultiPartContentProvider() - { - this(makeBoundary()); - } - - public MultiPartContentProvider(String boundary) - { - super("multipart/form-data; boundary=" + boundary); - String firstBoundaryLine = "--" + boundary + "\r\n"; - this.firstBoundary = ByteBuffer.wrap(firstBoundaryLine.getBytes(StandardCharsets.US_ASCII)); - String middleBoundaryLine = "\r\n" + firstBoundaryLine; - this.middleBoundary = ByteBuffer.wrap(middleBoundaryLine.getBytes(StandardCharsets.US_ASCII)); - String onlyBoundaryLine = "--" + boundary + "--\r\n"; - this.onlyBoundary = ByteBuffer.wrap(onlyBoundaryLine.getBytes(StandardCharsets.US_ASCII)); - String lastBoundaryLine = "\r\n" + onlyBoundaryLine; - this.lastBoundary = ByteBuffer.wrap(lastBoundaryLine.getBytes(StandardCharsets.US_ASCII)); - } - - private static String makeBoundary() - { - StringBuilder builder = new StringBuilder("JettyHttpClientBoundary"); - builder.append(Long.toString(System.identityHashCode(builder), 36)); - builder.append(Long.toString(System.identityHashCode(Thread.currentThread()), 36)); - builder.append(Long.toString(System.nanoTime(), 36)); - return builder.toString(); - } - - /** - *

Adds a field part with the given {@code name} as field name, and the given - * {@code content} as part content.

- *

The {@code Content-Type} of this part will be obtained from:

- *
    - *
  • the {@code Content-Type} header in the {@code fields} parameter; otherwise
  • - *
  • the {@link org.eclipse.jetty.client.api.ContentProvider.Typed#getContentType()} method if the {@code content} parameter - * implements {@link org.eclipse.jetty.client.api.ContentProvider.Typed}; otherwise
  • - *
  • "text/plain"
  • - *
- * - * @param name the part name - * @param content the part content - * @param fields the headers associated with this part - */ - public void addFieldPart(String name, ContentProvider content, HttpFields fields) - { - addPart(new Part(name, null, "text/plain", content, fields)); - } - - /** - *

Adds a file part with the given {@code name} as field name, the given - * {@code fileName} as file name, and the given {@code content} as part content.

- *

The {@code Content-Type} of this part will be obtained from:

- *
    - *
  • the {@code Content-Type} header in the {@code fields} parameter; otherwise
  • - *
  • the {@link org.eclipse.jetty.client.api.ContentProvider.Typed#getContentType()} method if the {@code content} parameter - * implements {@link org.eclipse.jetty.client.api.ContentProvider.Typed}; otherwise
  • - *
  • "application/octet-stream"
  • - *
- * - * @param name the part name - * @param fileName the file name associated to this part - * @param content the part content - * @param fields the headers associated with this part - */ - public void addFilePart(String name, String fileName, ContentProvider content, HttpFields fields) - { - addPart(new Part(name, fileName, "application/octet-stream", content, fields)); - } - - private void addPart(Part part) - { - parts.add(part); - if (LOG.isDebugEnabled()) - LOG.debug("Added {}", part); - } - - @Override - public void setListener(Listener listener) - { - this.listener = listener; - if (closed.get()) - this.length = calculateLength(); - } - - private long calculateLength() - { - // Compute the length, if possible. - if (parts.isEmpty()) - { - return onlyBoundary.remaining(); - } - else - { - long result = 0; - for (int i = 0; i < parts.size(); ++i) - { - result += (i == 0) ? firstBoundary.remaining() : middleBoundary.remaining(); - Part part = parts.get(i); - long partLength = part.length; - result += partLength; - if (partLength < 0) - { - result = -1; - break; - } - } - if (result > 0) - result += lastBoundary.remaining(); - return result; - } - } - - @Override - public long getLength() - { - return length; - } - - @Override - public Iterator iterator() - { - return new MultiPartIterator(); - } - - @Override - public void close() - { - closed.compareAndSet(false, true); - } - - private static class Part - { - private final String name; - private final String fileName; - private final String contentType; - private final ContentProvider content; - private final HttpFields fields; - private final ByteBuffer headers; - private final long length; - - private Part(String name, String fileName, String contentType, ContentProvider content, HttpFields fields) - { - this.name = name; - this.fileName = fileName; - this.contentType = contentType; - this.content = content; - this.fields = fields; - this.headers = headers(); - this.length = content.getLength() < 0 ? -1 : headers.remaining() + content.getLength(); - } - - private ByteBuffer headers() - { - try - { - // Compute the Content-Disposition. - String contentDisposition = "Content-Disposition: form-data; name=\"" + name + "\""; - if (fileName != null) - contentDisposition += "; filename=\"" + fileName + "\""; - contentDisposition += "\r\n"; - - // Compute the Content-Type. - String contentType = fields == null ? null : fields.get(HttpHeader.CONTENT_TYPE); - if (contentType == null) - { - if (content instanceof Typed) - contentType = ((Typed)content).getContentType(); - else - contentType = this.contentType; - } - contentType = "Content-Type: " + contentType + "\r\n"; - - if (fields == null || fields.size() == 0) - { - String headers = contentDisposition; - headers += contentType; - headers += "\r\n"; - return ByteBuffer.wrap(headers.getBytes(StandardCharsets.UTF_8)); - } - - ByteArrayOutputStream buffer = new ByteArrayOutputStream((fields.size() + 1) * contentDisposition.length()); - buffer.write(contentDisposition.getBytes(StandardCharsets.UTF_8)); - buffer.write(contentType.getBytes(StandardCharsets.UTF_8)); - for (HttpField field : fields) - { - if (HttpHeader.CONTENT_TYPE.equals(field.getHeader())) - continue; - buffer.write(field.getName().getBytes(StandardCharsets.US_ASCII)); - buffer.write(COLON_SPACE_BYTES); - String value = field.getValue(); - if (value != null) - buffer.write(value.getBytes(StandardCharsets.UTF_8)); - buffer.write(CR_LF_BYTES); - } - buffer.write(CR_LF_BYTES); - return ByteBuffer.wrap(buffer.toByteArray()); - } - catch (IOException x) - { - throw new RuntimeIOException(x); - } - } - - @Override - public String toString() - { - return String.format("%s@%x[name=%s,fileName=%s,length=%d,headers=%s]", - getClass().getSimpleName(), - hashCode(), - name, - fileName, - content.getLength(), - fields); - } - } - - private class MultiPartIterator implements Iterator, Synchronizable, Callback, Closeable - { - private Iterator iterator; - private int index; - private State state = State.FIRST_BOUNDARY; - - @Override - public boolean hasNext() - { - return state != State.COMPLETE; - } - - @Override - public ByteBuffer next() - { - while (true) - { - switch (state) - { - case FIRST_BOUNDARY: - { - if (parts.isEmpty()) - { - state = State.COMPLETE; - return onlyBoundary.slice(); - } - else - { - state = State.HEADERS; - return firstBoundary.slice(); - } - } - case HEADERS: - { - Part part = parts.get(index); - ContentProvider content = part.content; - if (content instanceof AsyncContentProvider) - ((AsyncContentProvider)content).setListener(listener); - iterator = content.iterator(); - state = State.CONTENT; - return part.headers.slice(); - } - case CONTENT: - { - if (iterator.hasNext()) - return iterator.next(); - ++index; - if (index < parts.size()) - { - state = State.MIDDLE_BOUNDARY; - if (iterator instanceof Closeable) - IO.close((Closeable)iterator); - } - else - { - state = State.LAST_BOUNDARY; - } - break; - } - case MIDDLE_BOUNDARY: - { - state = State.HEADERS; - return middleBoundary.slice(); - } - case LAST_BOUNDARY: - { - state = State.COMPLETE; - return lastBoundary.slice(); - } - case COMPLETE: - { - throw new NoSuchElementException(); - } - - default: - throw new IllegalStateException(state.toString()); - } - } - } - - @Override - public Object getLock() - { - if (iterator instanceof Synchronizable) - return ((Synchronizable)iterator).getLock(); - return this; - } - - @Override - public void succeeded() - { - if (state == State.CONTENT && iterator instanceof Callback) - ((Callback)iterator).succeeded(); - } - - @Override - public void failed(Throwable x) - { - if (state == State.CONTENT && iterator instanceof Callback) - ((Callback)iterator).failed(x); - } - - @Override - public void close() throws IOException - { - if (iterator instanceof Closeable) - ((Closeable)iterator).close(); - } - } - - private enum State - { - FIRST_BOUNDARY, HEADERS, CONTENT, MIDDLE_BOUNDARY, LAST_BOUNDARY, COMPLETE - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java deleted file mode 100644 index fa1fcf384b8..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java +++ /dev/null @@ -1,158 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.util; - -import java.io.Closeable; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Iterator; - -import org.eclipse.jetty.client.AsyncContentProvider; -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.util.Callback; - -/** - * A {@link ContentProvider} that provides content asynchronously through an {@link OutputStream} - * similar to {@link DeferredContentProvider}. - *

- * {@link OutputStreamContentProvider} can only be used in conjunction with - * {@link Request#send(Response.CompleteListener)} (and not with its blocking counterpart {@link Request#send()}) - * because it provides content asynchronously. - *

- * The deferred content is provided once by writing to the {@link #getOutputStream() output stream} - * and then fully consumed. - * Invocations to the {@link #iterator()} method after the first will return an "empty" iterator - * because the stream has been consumed on the first invocation. - * However, it is possible for subclasses to support multiple invocations of {@link #iterator()} - * by overriding {@link #write(ByteBuffer)} and {@link #close()}, copying the bytes and making them - * available for subsequent invocations. - *

- * Content must be provided by writing to the {@link #getOutputStream() output stream}, that must be - * {@link OutputStream#close() closed} when all content has been provided. - *

- * Example usage: - *

- * HttpClient httpClient = ...;
- *
- * // Use try-with-resources to autoclose the output stream
- * OutputStreamContentProvider content = new OutputStreamContentProvider();
- * try (OutputStream output = content.getOutputStream())
- * {
- *     httpClient.newRequest("localhost", 8080)
- *             .content(content)
- *             .send(new Response.CompleteListener()
- *             {
- *                 @Override
- *                 public void onComplete(Result result)
- *                 {
- *                     // Your logic here
- *                 }
- *             });
- *
- *     // At a later time...
- *     output.write("some content".getBytes());
- * }
- * 
- * - * @deprecated use {@link OutputStreamRequestContent} instead - */ -@Deprecated -public class OutputStreamContentProvider implements AsyncContentProvider, Callback, Closeable -{ - private final DeferredContentProvider deferred = new DeferredContentProvider(); - private final OutputStream output = new DeferredOutputStream(); - - @Override - public InvocationType getInvocationType() - { - return deferred.getInvocationType(); - } - - @Override - public long getLength() - { - return deferred.getLength(); - } - - @Override - public Iterator iterator() - { - return deferred.iterator(); - } - - @Override - public void setListener(Listener listener) - { - deferred.setListener(listener); - } - - public OutputStream getOutputStream() - { - return output; - } - - protected void write(ByteBuffer buffer) - { - deferred.offer(buffer); - } - - @Override - public void close() - { - deferred.close(); - } - - @Override - public void succeeded() - { - deferred.succeeded(); - } - - @Override - public void failed(Throwable failure) - { - deferred.failed(failure); - } - - private class DeferredOutputStream extends OutputStream - { - @Override - public void write(int b) throws IOException - { - write(new byte[]{(byte)b}, 0, 1); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException - { - OutputStreamContentProvider.this.write(ByteBuffer.wrap(b, off, len)); - flush(); - } - - @Override - public void flush() throws IOException - { - deferred.flush(); - } - - @Override - public void close() throws IOException - { - OutputStreamContentProvider.this.close(); - } - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java deleted file mode 100644 index d13ae2e7f31..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java +++ /dev/null @@ -1,188 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.util; - -import java.io.Closeable; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.SeekableByteChannel; -import java.nio.file.AccessDeniedException; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.util.BufferUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - *

A {@link ContentProvider} for files using JDK 7's {@code java.nio.file} APIs.

- *

It is possible to specify, at the constructor, a buffer size used to read - * content from the stream, by default 4096 bytes. - * If a {@link ByteBufferPool} is provided via {@link #setByteBufferPool(ByteBufferPool)}, - * the buffer will be allocated from that pool, otherwise one buffer will be - * allocated and used to read the file.

- * - * @deprecated use {@link PathRequestContent} instead. - */ -@Deprecated -public class PathContentProvider extends AbstractTypedContentProvider -{ - private static final Logger LOG = LoggerFactory.getLogger(PathContentProvider.class); - - private final Path filePath; - private final long fileSize; - private final int bufferSize; - private ByteBufferPool bufferPool; - private boolean useDirectByteBuffers = true; - - public PathContentProvider(Path filePath) throws IOException - { - this(filePath, 4096); - } - - public PathContentProvider(Path filePath, int bufferSize) throws IOException - { - this("application/octet-stream", filePath, bufferSize); - } - - public PathContentProvider(String contentType, Path filePath) throws IOException - { - this(contentType, filePath, 4096); - } - - public PathContentProvider(String contentType, Path filePath, int bufferSize) throws IOException - { - super(contentType); - if (!Files.isRegularFile(filePath)) - throw new NoSuchFileException(filePath.toString()); - if (!Files.isReadable(filePath)) - throw new AccessDeniedException(filePath.toString()); - this.filePath = filePath; - this.fileSize = Files.size(filePath); - this.bufferSize = bufferSize; - } - - @Override - public long getLength() - { - return fileSize; - } - - @Override - public boolean isReproducible() - { - return true; - } - - public ByteBufferPool getByteBufferPool() - { - return bufferPool; - } - - public void setByteBufferPool(ByteBufferPool byteBufferPool) - { - this.bufferPool = byteBufferPool; - } - - public boolean isUseDirectByteBuffers() - { - return useDirectByteBuffers; - } - - public void setUseDirectByteBuffers(boolean useDirectByteBuffers) - { - this.useDirectByteBuffers = useDirectByteBuffers; - } - - @Override - public Iterator iterator() - { - return new PathIterator(); - } - - private class PathIterator implements Iterator, Closeable - { - private ByteBuffer buffer; - private SeekableByteChannel channel; - private long position; - - @Override - public boolean hasNext() - { - return position < getLength(); - } - - @Override - public ByteBuffer next() - { - try - { - if (channel == null) - { - buffer = bufferPool == null - ? BufferUtil.allocate(bufferSize, isUseDirectByteBuffers()) - : bufferPool.acquire(bufferSize, isUseDirectByteBuffers()); - channel = Files.newByteChannel(filePath, StandardOpenOption.READ); - if (LOG.isDebugEnabled()) - LOG.debug("Opened file {}", filePath); - } - - buffer.clear(); - int read = channel.read(buffer); - if (read < 0) - throw new NoSuchElementException(); - - if (LOG.isDebugEnabled()) - LOG.debug("Read {} bytes from {}", read, filePath); - - position += read; - - buffer.flip(); - return buffer; - } - catch (NoSuchElementException x) - { - close(); - throw x; - } - catch (Throwable x) - { - close(); - throw (NoSuchElementException)new NoSuchElementException().initCause(x); - } - } - - @Override - public void close() - { - try - { - if (bufferPool != null && buffer != null) - bufferPool.release(buffer); - if (channel != null) - channel.close(); - } - catch (Throwable x) - { - LOG.trace("IGNORED", x); - } - } - } -} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringContentProvider.java deleted file mode 100644 index 2d5c8e81589..00000000000 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringContentProvider.java +++ /dev/null @@ -1,51 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.util; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -import org.eclipse.jetty.client.api.ContentProvider; - -/** - * A {@link ContentProvider} for strings. - *

- * It is possible to specify, at the constructor, an encoding used to convert - * the string into bytes, by default UTF-8. - * - * @deprecated use {@link StringRequestContent} instead. - */ -@Deprecated -public class StringContentProvider extends BytesContentProvider -{ - public StringContentProvider(String content) - { - this(content, StandardCharsets.UTF_8); - } - - public StringContentProvider(String content, String encoding) - { - this(content, Charset.forName(encoding)); - } - - public StringContentProvider(String content, Charset charset) - { - this("text/plain;charset=" + charset.name(), content, charset); - } - - public StringContentProvider(String contentType, String content, Charset charset) - { - super(contentType, content.getBytes(charset)); - } -} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java deleted file mode 100644 index cb7ed303813..00000000000 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java +++ /dev/null @@ -1,1950 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.ssl; - -import java.io.BufferedReader; -import java.io.EOFException; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.SocketTimeoutException; -import java.nio.ByteBuffer; -import java.nio.channels.SelectionKey; -import java.nio.channels.SocketChannel; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLSocket; - -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.client.ssl.SslBytesTest.TLSRecord.Type; -import org.eclipse.jetty.http.HttpCompliance; -import org.eclipse.jetty.http.HttpParser; -import org.eclipse.jetty.io.Connection; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.ManagedSelector; -import org.eclipse.jetty.io.SocketChannelEndPoint; -import org.eclipse.jetty.io.ssl.SslConnection; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpConnection; -import org.eclipse.jetty.server.HttpConnectionFactory; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.SecureRequestCustomizer; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.SslConnectionFactory; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.util.component.Dumpable; -import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnJre; -import org.junit.jupiter.api.condition.EnabledOnOs; -import org.junit.jupiter.api.condition.JRE; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.condition.OS.LINUX; - -// Other JREs have slight differences in how TLS work -// and this test expects a very specific TLS behavior. -@EnabledOnJre(JRE.JAVA_11) -public class SslBytesServerTest extends SslBytesTest -{ - private final AtomicInteger sslFills = new AtomicInteger(); - private final AtomicInteger sslFlushes = new AtomicInteger(); - private final AtomicInteger httpParses = new AtomicInteger(); - private final AtomicReference serverEndPoint = new AtomicReference<>(); - private final int idleTimeout = 2000; - private ExecutorService threadPool; - private Server server; - private SslContextFactory.Server sslContextFactory; - private int serverPort; - private SSLContext sslContext; - private SimpleProxy proxy; - private Runnable idleHook; - - @BeforeEach - public void init() throws Exception - { - QueuedThreadPool serverThreads = new QueuedThreadPool(); - serverThreads.setName("server"); - server = new Server(serverThreads); - - sslFills.set(0); - sslFlushes.set(0); - httpParses.set(0); - serverEndPoint.set(null); - - File keyStore = MavenTestingUtils.getTestResourceFile("keystore.p12"); - sslContextFactory = new SslContextFactory.Server(); - // This whole test is very specific to how TLS < 1.3 works. - sslContextFactory.setIncludeProtocols("TLSv1.2"); - sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath()); - sslContextFactory.setKeyStorePassword("storepwd"); - - HttpConnectionFactory httpFactory = new HttpConnectionFactory() - { - @Override - public Connection newConnection(Connector connector, EndPoint endPoint) - { - return configure(new HttpConnection(getHttpConfiguration(), connector, endPoint, isRecordHttpComplianceViolations()) - { - @Override - protected HttpParser newHttpParser(HttpCompliance compliance) - { - return new HttpParser(newRequestHandler(), getHttpConfiguration().getRequestHeaderSize(), compliance) - { - @Override - public boolean parseNext(ByteBuffer buffer) - { - httpParses.incrementAndGet(); - return super.parseNext(buffer); - } - }; - } - - @Override - protected boolean onReadTimeout(Throwable timeout) - { - final Runnable idleHook = SslBytesServerTest.this.idleHook; - if (idleHook != null) - idleHook.run(); - return super.onReadTimeout(timeout); - } - }, connector, endPoint); - } - }; - httpFactory.getHttpConfiguration().addCustomizer(new SecureRequestCustomizer()); - SslConnectionFactory sslFactory = new SslConnectionFactory(sslContextFactory, httpFactory.getProtocol()) - { - @Override - protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine) - { - return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine) - { - @Override - protected DecryptedEndPoint newDecryptedEndPoint() - { - return new DecryptedEndPoint() - { - @Override - public int fill(ByteBuffer buffer) throws IOException - { - sslFills.incrementAndGet(); - return super.fill(buffer); - } - - @Override - public boolean flush(ByteBuffer... appOuts) throws IOException - { - sslFlushes.incrementAndGet(); - return super.flush(appOuts); - } - }; - } - }; - } - }; - - ServerConnector connector = new ServerConnector(server, null, null, null, 1, 1, sslFactory, httpFactory) - { - @Override - protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException - { - SocketChannelEndPoint endPoint = super.newEndPoint(channel, selectSet, key); - serverEndPoint.set(endPoint); - return endPoint; - } - }; - connector.setIdleTimeout(idleTimeout); - connector.setPort(0); - - server.addConnector(connector); - server.setHandler(new AbstractHandler() - { - @Override - public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException - { - try - { - request.setHandled(true); - String contentLength = request.getHeader("Content-Length"); - if (contentLength != null) - { - int length = Integer.parseInt(contentLength); - ServletInputStream input = httpRequest.getInputStream(); - ServletOutputStream output = httpResponse.getOutputStream(); - byte[] buffer = new byte[32 * 1024]; - while (length > 0) - { - int read = input.read(buffer); - if (read < 0) - throw new EOFException(); - length -= read; - if (target.startsWith("/echo")) - output.write(buffer, 0, read); - } - } - } - catch (IOException x) - { - if (!(target.endsWith("suppress_exception"))) - throw x; - } - } - }); - server.start(); - serverPort = connector.getLocalPort(); - - sslContext = sslContextFactory.getSslContext(); - - threadPool = Executors.newCachedThreadPool(); - proxy = new SimpleProxy(threadPool, "localhost", serverPort); - proxy.start(); - logger.info("proxy:{} <==> server:{}", proxy.getPort(), serverPort); - } - - @AfterEach - public void destroy() throws Exception - { - if (proxy != null) - proxy.stop(); - if (server != null) - server.stop(); - if (threadPool != null) - threadPool.shutdownNow(); - } - - @Test - public void testHandshake() throws Exception - { - final SSLSocket client = newClient(); - - Future handshake = threadPool.submit(() -> - { - client.startHandshake(); - return null; - }); - - // Client Hello - TLSRecord record = proxy.readFromClient(); - assertNotNull(record); - proxy.flushToServer(record); - - // Server Hello + Certificate + Server Done - record = proxy.readFromServer(); - assertNotNull(record); - proxy.flushToClient(record); - - // Client Key Exchange - record = proxy.readFromClient(); - assertNotNull(record); - proxy.flushToServer(record); - - // Change Cipher Spec - record = proxy.readFromClient(); - assertNotNull(record); - proxy.flushToServer(record); - - // Client Done - record = proxy.readFromClient(); - assertNotNull(record); - proxy.flushToServer(record); - - // Change Cipher Spec - record = proxy.readFromServer(); - assertNotNull(record); - proxy.flushToClient(record); - - // Server Done - record = proxy.readFromServer(); - assertNotNull(record); - proxy.flushToClient(record); - - assertNull(handshake.get(5, TimeUnit.SECONDS)); - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - closeClient(client); - } - - @Test - public void testHandshakeWithResumedSessionThenClose() throws Exception - { - // First socket will establish the SSL session - SSLSocket client1 = newClient(); - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client1.startHandshake(); - client1.close(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - int proxyPort = proxy.getPort(); - proxy.stop(); - - proxy = new SimpleProxy(threadPool, proxyPort, "localhost", serverPort); - proxy.start(); - logger.info("proxy:{} <==> server:{}", proxy.getPort(), serverPort); - - final SSLSocket client2 = newClient(proxy); - - Future handshakeFuture = threadPool.submit(() -> - { - client2.startHandshake(); - return null; - }); - - // Client Hello with SessionID - TLSRecord record = proxy.readFromClient(); - assertNotNull(record); - proxy.flushToServer(record); - - // Server Hello - record = proxy.readFromServer(); - assertNotNull(record); - proxy.flushToClient(record); - - // Change Cipher Spec - record = proxy.readFromServer(); - assertNotNull(record); - proxy.flushToClient(record); - - // Server Done - record = proxy.readFromServer(); - assertNotNull(record); - proxy.flushToClient(record); - - // Client Key Exchange - record = proxy.readFromClient(); - assertNotNull(record); - // Client Done - TLSRecord doneRecord = proxy.readFromClient(); - assertNotNull(doneRecord); - - // Wait for socket to be done with handshake - handshakeFuture.get(5, TimeUnit.SECONDS); - // Close - client2.close(); - TLSRecord closeRecord = proxy.readFromClient(); - assertNotNull(closeRecord); - assertEquals(TLSRecord.Type.ALERT, closeRecord.getType()); - // Flush to server Client Key Exchange + Client Done + Close in one chunk - byte[] recordBytes = record.getBytes(); - byte[] doneBytes = doneRecord.getBytes(); - byte[] closeRecordBytes = closeRecord.getBytes(); - byte[] chunk = new byte[recordBytes.length + doneBytes.length + closeRecordBytes.length]; - System.arraycopy(recordBytes, 0, chunk, 0, recordBytes.length); - System.arraycopy(doneBytes, 0, chunk, recordBytes.length, doneBytes.length); - System.arraycopy(closeRecordBytes, 0, chunk, recordBytes.length + doneBytes.length, closeRecordBytes.length); - proxy.flushToServer(0, chunk); - // Close the raw socket - proxy.flushToServer(null); - - // Expect the server to send a TLS Alert. - record = proxy.readFromServer(); - assertNotNull(record); - assertEquals(TLSRecord.Type.ALERT, record.getType()); - record = proxy.readFromServer(); - assertNull(record); - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - } - - @Test - public void testHandshakeWithSplitBoundary() throws Exception - { - final SSLSocket client = newClient(); - - Future handshake = threadPool.submit(() -> - { - client.startHandshake(); - return null; - }); - - // Client Hello - TLSRecord record = proxy.readFromClient(); - byte[] bytes = record.getBytes(); - byte[] chunk1 = new byte[2 * bytes.length / 3]; - System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - byte[] chunk2 = new byte[bytes.length - chunk1.length]; - System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); - proxy.flushToServer(100, chunk1); - proxy.flushToServer(100, chunk2); - - // Server Hello + Certificate + Server Done - record = proxy.readFromServer(); - proxy.flushToClient(record); - - // Client Key Exchange - record = proxy.readFromClient(); - bytes = record.getBytes(); - chunk1 = new byte[2 * bytes.length / 3]; - System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - chunk2 = new byte[bytes.length - chunk1.length]; - System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); - proxy.flushToServer(100, chunk1); - proxy.flushToServer(100, chunk2); - - // Change Cipher Spec - record = proxy.readFromClient(); - bytes = record.getBytes(); - chunk1 = new byte[2 * bytes.length / 3]; - System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - chunk2 = new byte[bytes.length - chunk1.length]; - System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); - proxy.flushToServer(100, chunk1); - proxy.flushToServer(100, chunk2); - - // Client Done - record = proxy.readFromClient(); - bytes = record.getBytes(); - chunk1 = new byte[2 * bytes.length / 3]; - System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - chunk2 = new byte[bytes.length - chunk1.length]; - System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); - proxy.flushToServer(100, chunk1); - proxy.flushToServer(100, chunk2); - - // Change Cipher Spec - record = proxy.readFromServer(); - assertNotNull(record); - proxy.flushToClient(record); - - // Server Done - record = proxy.readFromServer(); - assertNotNull(record); - proxy.flushToClient(record); - - assertNull(handshake.get(5, TimeUnit.SECONDS)); - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(40)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - client.close(); - - // Close Alert - record = proxy.readFromClient(); - bytes = record.getBytes(); - chunk1 = new byte[2 * bytes.length / 3]; - System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - chunk2 = new byte[bytes.length - chunk1.length]; - System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); - proxy.flushToServer(100, chunk1); - proxy.flushToServer(100, chunk2); - // Socket close - record = proxy.readFromClient(); - assertNull(record, String.valueOf(record)); - proxy.flushToServer(record); - - // Socket close - record = proxy.readFromServer(); - if (record != null) - { - assertEquals(record.getType(), Type.ALERT); - - // Now should be a raw close - record = proxy.readFromServer(); - assertNull(record, String.valueOf(record)); - } - } - - @Test - public void testClientHelloIncompleteThenReset() throws Exception - { - final SSLSocket client = newClient(); - - threadPool.submit(() -> - { - client.startHandshake(); - return null; - }); - - // Client Hello - TLSRecord record = proxy.readFromClient(); - byte[] bytes = record.getBytes(); - byte[] chunk1 = new byte[2 * bytes.length / 3]; - System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - proxy.flushToServer(100, chunk1); - - proxy.sendRSTToServer(); - - // Wait a while to detect spinning - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - client.close(); - } - - @Test - public void testClientHelloThenReset() throws Exception - { - final SSLSocket client = newClient(); - - threadPool.submit(() -> - { - client.startHandshake(); - return null; - }); - - // Client Hello - TLSRecord record = proxy.readFromClient(); - assertNotNull(record); - proxy.flushToServer(record); - - proxy.sendRSTToServer(); - - // Wait a while to detect spinning - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - client.close(); - } - - @Test - public void testHandshakeThenReset() throws Exception - { - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - proxy.sendRSTToServer(); - - // Wait a while to detect spinning - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - client.close(); - } - - @Test - public void testRequestIncompleteThenReset() throws Exception - { - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "\r\n").getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Application data - TLSRecord record = proxy.readFromClient(); - byte[] bytes = record.getBytes(); - byte[] chunk1 = new byte[2 * bytes.length / 3]; - System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - proxy.flushToServer(100, chunk1); - - proxy.sendRSTToServer(); - - // Wait a while to detect spinning - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - client.close(); - } - - @Test - public void testRequestResponse() throws Exception - { - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "\r\n").getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Application data - TLSRecord record = proxy.readFromClient(); - proxy.flushToServer(record); - assertNull(request.get(5, TimeUnit.SECONDS)); - - // Application data - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8)); - String line = reader.readLine(); - assertNotNull(line); - assertTrue(line.startsWith("HTTP/1.1 200 ")); - while ((line = reader.readLine()) != null) - { - if (line.trim().length() == 0) - break; - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - closeClient(client); - } - - @Test - public void testHandshakeAndRequestOneByteAtATime() throws Exception - { - final SSLSocket client = newClient(); - - Future handshake = threadPool.submit(() -> - { - client.startHandshake(); - return null; - }); - - // Client Hello - TLSRecord record = proxy.readFromClient(); - for (byte b : record.getBytes()) - { - proxy.flushToServer(5, b); - } - - // Server Hello + Certificate + Server Done - record = proxy.readFromServer(); - proxy.flushToClient(record); - - // Client Key Exchange - record = proxy.readFromClient(); - for (byte b : record.getBytes()) - { - proxy.flushToServer(5, b); - } - - // Change Cipher Spec - record = proxy.readFromClient(); - for (byte b : record.getBytes()) - { - proxy.flushToServer(5, b); - } - - // Client Done - record = proxy.readFromClient(); - for (byte b : record.getBytes()) - { - proxy.flushToServer(5, b); - } - - // Change Cipher Spec - record = proxy.readFromServer(); - proxy.flushToClient(record); - - // Server Done - record = proxy.readFromServer(); - proxy.flushToClient(record); - - assertNull(handshake.get(1, TimeUnit.SECONDS)); - - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "\r\n").getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Application data - record = proxy.readFromClient(); - for (byte b : record.getBytes()) - { - proxy.flushToServer(5, b); - } - assertNull(request.get(1, TimeUnit.SECONDS)); - - // Application data - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8)); - String line = reader.readLine(); - assertNotNull(line); - assertTrue(line.startsWith("HTTP/1.1 200 ")); - while ((line = reader.readLine()) != null) - { - if (line.trim().length() == 0) - break; - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(1000); - assertThat(sslFills.get(), Matchers.lessThan(2000)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - // An average of 958 httpParses is seen in standard Oracle JDK's - // An average of 1183 httpParses is seen in OpenJDK JVMs. - assertThat(httpParses.get(), Matchers.lessThan(2000)); - - client.close(); - - // Close Alert - record = proxy.readFromClient(); - for (byte b : record.getBytes()) - { - proxy.flushToServer(5, b); - } - // Socket close - record = proxy.readFromClient(); - assertNull(record, String.valueOf(record)); - proxy.flushToServer(record); - - // Socket close - record = proxy.readFromServer(); - // Raw close or alert - if (record != null) - { - assertEquals(record.getType(), Type.ALERT); - - // Now should be a raw close - record = proxy.readFromServer(); - assertNull(record, String.valueOf(record)); - } - } - - @Test - @EnabledOnOs(LINUX) // See next test on why we only run in Linux - public void testRequestWithCloseAlertAndShutdown() throws Exception - { - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "\r\n").getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Application data - TLSRecord record = proxy.readFromClient(); - proxy.flushToServer(record); - assertNull(request.get(5, TimeUnit.SECONDS)); - - client.close(); - - // Close Alert - record = proxy.readFromClient(); - proxy.flushToServer(record); - // Socket close - record = proxy.readFromClient(); - assertNull(record, String.valueOf(record)); - proxy.flushToServer(record); - - // Expect response from server - // SSLSocket is limited and we cannot read the response, but we make sure - // it is application data and not a close alert - record = proxy.readFromServer(); - assertNotNull(record); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - // Socket close - record = proxy.readFromServer(); - if (record != null) - { - assertEquals(record.getType(), Type.ALERT); - - // Now should be a raw close - record = proxy.readFromServer(); - assertNull(record, String.valueOf(record)); - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - } - - @Test - @EnabledOnOs(LINUX) - public void testRequestWithCloseAlert() throws Exception - { - // Currently we are ignoring this test on anything other then linux - // http://tools.ietf.org/html/rfc2246#section-7.2.1 - - // TODO (react to this portion which seems to allow win/mac behavior) - // It is required that the other party respond with a close_notify alert of its own - // and close down the connection immediately, discarding any pending writes. It is not - // required for the initiator of the close to wait for the responding - // close_notify alert before closing the read side of the connection. - - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "\r\n").getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Application data - TLSRecord record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToServer(record); - assertNull(request.get(5, TimeUnit.SECONDS)); - - client.close(); - - // Close Alert - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.ALERT, record.getType()); - proxy.flushToServer(record); - - // Do not close the raw socket yet - - // Expect response from server - // SSLSocket is limited and we cannot read the response, but we make sure - // it is application data and not a close alert - record = proxy.readFromServer(); - assertNotNull(record); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - // Socket close - record = proxy.readFromServer(); - if (record != null) - { - assertEquals(record.getType(), Type.ALERT); - - // Now should be a raw close - record = proxy.readFromServer(); - assertNull(record, String.valueOf(record)); - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - // Socket close - record = proxy.readFromClient(); - assertNull(record, String.valueOf(record)); - proxy.flushToServer(record); - } - - @Test - public void testRequestWithRawClose() throws Exception - { - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "\r\n").getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Application data - TLSRecord record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToServer(record); - assertNull(request.get(5, TimeUnit.SECONDS)); - - // Application data - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - // Close the raw socket, this generates a truncation attack - proxy.flushToServer(null); - - // Expect raw close from server OR ALERT - record = proxy.readFromServer(); - // TODO check that this is OK? - if (record != null) - { - assertEquals(record.getType(), Type.ALERT); - - // Now should be a raw close - record = proxy.readFromServer(); - assertNull(record, String.valueOf(record)); - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - client.close(); - } - - @Test - public void testRequestWithImmediateRawClose() throws Exception - { - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "\r\n").getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Application data - TLSRecord record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToServer(record, 0); - // Close the raw socket, this generates a truncation attack - proxy.flushToServer(null); - assertNull(request.get(5, TimeUnit.SECONDS)); - - // Application data - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - // Expect raw close from server - record = proxy.readFromServer(); - if (record != null) - { - assertEquals(record.getType(), Type.ALERT); - - // Now should be a raw close - record = proxy.readFromServer(); - assertNull(record, String.valueOf(record)); - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - client.close(); - } - - @Test - public void testRequestWithBigContentWriteBlockedThenReset() throws Exception - { - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - byte[] data = new byte[128 * 1024]; - Arrays.fill(data, (byte)'X'); - final String content = new String(data, StandardCharsets.UTF_8); - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "GET /echo HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Content-Length: " + content.length() + "\r\n" + - "\r\n" + - content).getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Nine TLSRecords will be generated for the request - for (int i = 0; i < 9; ++i) - { - // Application data - TLSRecord record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToServer(record, 0); - } - assertNull(request.get(5, TimeUnit.SECONDS)); - - // We asked the server to echo back the data we sent - // but we do not read it, thus causing a write interest - // on the server. - // However, we then simulate that the client resets the - // connection, and this will cause an exception in the - // server that is trying to write the data - - TimeUnit.MILLISECONDS.sleep(500); - proxy.sendRSTToServer(); - - // Wait a while to detect spinning - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(40)); - assertThat(sslFlushes.get(), Matchers.lessThan(40)); - assertThat(httpParses.get(), Matchers.lessThan(50)); - - client.close(); - } - - @Test - public void testRequestWithBigContentReadBlockedThenReset() throws Exception - { - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - byte[] data = new byte[128 * 1024]; - Arrays.fill(data, (byte)'X'); - final String content = new String(data, StandardCharsets.UTF_8); - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "GET /echo_suppress_exception HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Content-Length: " + content.length() + "\r\n" + - "\r\n" + - content).getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Nine TLSRecords will be generated for the request, - // but we write only 5 of them, so the server goes in read blocked state - for (int i = 0; i < 5; ++i) - { - // Application data - TLSRecord record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToServer(record, 0); - } - assertNull(request.get(5, TimeUnit.SECONDS)); - - // The server should be read blocked, and we send a RST - TimeUnit.MILLISECONDS.sleep(500); - proxy.sendRSTToServer(); - - // Wait a while to detect spinning - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(40)); - assertThat(sslFlushes.get(), Matchers.lessThan(40)); - assertThat(httpParses.get(), Matchers.lessThan(50)); - - client.close(); - } - - @Test - public void testRequestResponseServerIdleTimeoutClientResets() throws Exception - { - SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "\r\n").getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Application data - TLSRecord record = proxy.readFromClient(); - proxy.flushToServer(record); - assertNull(request.get(5, TimeUnit.SECONDS)); - - // Application data - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8)); - String line = reader.readLine(); - assertNotNull(line); - assertTrue(line.startsWith("HTTP/1.1 200 ")); - while ((line = reader.readLine()) != null) - { - if (line.trim().length() == 0) - break; - } - - // Wait for the server idle timeout. - Thread.sleep(idleTimeout); - - // We expect that the server sends the TLS Alert. - record = proxy.readFromServer(); - assertNotNull(record); - assertEquals(TLSRecord.Type.ALERT, record.getType()); - - // Send a RST to the server. - proxy.sendRSTToServer(); - - // Wait for the RST to be processed by the server. - Thread.sleep(1000); - - // The server EndPoint must be closed. - assertFalse(serverEndPoint.get().isOpen()); - - client.close(); - } - - @Test - @EnabledOnOs(LINUX) // see message below - public void testRequestWithCloseAlertWithSplitBoundary() throws Exception - { - // currently we are ignoring this test on anything other then linux - - // http://tools.ietf.org/html/rfc2246#section-7.2.1 - - // TODO (react to this portion which seems to allow win/mac behavior) - //It is required that the other party respond with a close_notify alert of its own - //and close down the connection immediately, discarding any pending writes. It is not - //required for the initiator of the close to wait for the responding - //close_notify alert before closing the read side of the connection. - - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "\r\n").getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Application data - TLSRecord dataRecord = proxy.readFromClient(); - assertNull(request.get(5, TimeUnit.SECONDS)); - - client.close(); - - // Close Alert - TLSRecord closeRecord = proxy.readFromClient(); - - // Send request and half of the close alert bytes - byte[] dataBytes = dataRecord.getBytes(); - byte[] closeBytes = closeRecord.getBytes(); - byte[] bytes = new byte[dataBytes.length + closeBytes.length / 2]; - System.arraycopy(dataBytes, 0, bytes, 0, dataBytes.length); - System.arraycopy(closeBytes, 0, bytes, dataBytes.length, closeBytes.length / 2); - proxy.flushToServer(100, bytes); - - // Send the other half of the close alert bytes - bytes = new byte[closeBytes.length - closeBytes.length / 2]; - System.arraycopy(closeBytes, closeBytes.length / 2, bytes, 0, bytes.length); - proxy.flushToServer(100, bytes); - - // Do not close the raw socket yet - - // Expect response from server - // SSLSocket is limited and we cannot read the response, but we make sure - // it is application data and not a close alert - TLSRecord record = proxy.readFromServer(); - assertNotNull(record); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - // Socket close - record = proxy.readFromServer(); - if (record != null) - { - assertEquals(record.getType(), Type.ALERT); - - // Now should be a raw close - record = proxy.readFromServer(); - assertNull(record, String.valueOf(record)); - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - } - - @Test - public void testRequestWithContentWithSplitBoundary() throws Exception - { - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - final String content = "0123456789ABCDEF"; - - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "POST / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Content-Type: text/plain\r\n" + - "Content-Length: " + content.length() + "\r\n" + - "\r\n" + - content).getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Application data - TLSRecord record = proxy.readFromClient(); - assertNull(request.get(5, TimeUnit.SECONDS)); - byte[] chunk1 = new byte[2 * record.getBytes().length / 3]; - System.arraycopy(record.getBytes(), 0, chunk1, 0, chunk1.length); - proxy.flushToServer(100, chunk1); - - byte[] chunk2 = new byte[record.getBytes().length - chunk1.length]; - System.arraycopy(record.getBytes(), chunk1.length, chunk2, 0, chunk2.length); - proxy.flushToServer(100, chunk2); - - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8)); - String line = reader.readLine(); - assertNotNull(line); - assertTrue(line.startsWith("HTTP/1.1 200 ")); - while ((line = reader.readLine()) != null) - { - if (line.trim().length() == 0) - break; - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - closeClient(client); - } - - @Test - public void testRequestWithBigContentWithSplitBoundary() throws Exception - { - final SSLSocket client = newClient(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - // Use a content that is larger than the TLS record which is 2^14 (around 16k) - byte[] data = new byte[128 * 1024]; - Arrays.fill(data, (byte)'X'); - final String content = new String(data, StandardCharsets.UTF_8); - - Future request = threadPool.submit(() -> - { - OutputStream clientOutput = client.getOutputStream(); - clientOutput.write(( - "POST / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Content-Type: text/plain\r\n" + - "Content-Length: " + content.length() + "\r\n" + - "\r\n" + - content).getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Nine TLSRecords will be generated for the request - for (int i = 0; i < 9; ++i) - { - // Application data - TLSRecord record = proxy.readFromClient(); - byte[] bytes = record.getBytes(); - byte[] chunk1 = new byte[2 * bytes.length / 3]; - System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - byte[] chunk2 = new byte[bytes.length - chunk1.length]; - System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); - proxy.flushToServer(100, chunk1); - proxy.flushToServer(100, chunk2); - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - // The new HttpInput impl tends to call fill and parse more often than the previous one - // b/c HttpChannel.needContent() does a fill and parse before doing a fill interested; - // this runs the parser an goes to the OS more often but requires less rescheduling. - assertThat(sslFills.get(), Matchers.lessThan(150)); - assertThat(sslFlushes.get(), Matchers.lessThan(50)); - assertThat(httpParses.get(), Matchers.lessThan(150)); - - assertNull(request.get(5, TimeUnit.SECONDS)); - - TLSRecord record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8)); - String line = reader.readLine(); - assertNotNull(line); - assertTrue(line.startsWith("HTTP/1.1 200 ")); - while ((line = reader.readLine()) != null) - { - if (line.trim().length() == 0) - break; - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - // The new HttpInput impl tends to call fill and parse more often than the previous one - // b/c HttpChannel.needContent() does a fill and parse before doing a fill interested; - // this runs the parser an goes to the OS more often but requires less rescheduling. - assertThat(sslFills.get(), Matchers.lessThan(150)); - assertThat(sslFlushes.get(), Matchers.lessThan(50)); - assertThat(httpParses.get(), Matchers.lessThan(150)); - - closeClient(client); - } - - @Test - public void testRequestWithContentWithRenegotiationInMiddleOfContentWhenRenegotiationIsForbidden() throws Exception - { - assumeJavaVersionSupportsTLSRenegotiations(); - - sslContextFactory.setRenegotiationAllowed(false); - - final SSLSocket client = newClient(); - final OutputStream clientOutput = client.getOutputStream(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - byte[] data1 = new byte[1024]; - Arrays.fill(data1, (byte)'X'); - String content1 = new String(data1, StandardCharsets.UTF_8); - byte[] data2 = new byte[1024]; - Arrays.fill(data2, (byte)'Y'); - final String content2 = new String(data2, StandardCharsets.UTF_8); - - // Write only part of the body - automaticProxyFlow = proxy.startAutomaticFlow(); - clientOutput.write(( - "POST / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Content-Type: text/plain\r\n" + - "Content-Length: " + (content1.length() + content2.length()) + "\r\n" + - "\r\n" + - content1).getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - // Renegotiate - threadPool.submit(() -> - { - client.startHandshake(); - return null; - }); - - // Renegotiation Handshake - TLSRecord record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToServer(record); - - // Renegotiation not allowed, server has closed - loop: - while (true) - { - record = proxy.readFromServer(); - if (record == null) - break; - switch (record.getType()) - { - case APPLICATION: - fail("application data not allows after renegotiate"); - return; // this is just to avoid checkstyle warning - case ALERT: - break loop; - default: - continue; - } - } - assertEquals(TLSRecord.Type.ALERT, record.getType()); - proxy.flushToClient(record); - - record = proxy.readFromServer(); - assertNull(record); - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(50)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(50)); - - client.close(); - } - - @Test - public void testRequestWithBigContentWithRenegotiationInMiddleOfContent() throws Exception - { - assumeJavaVersionSupportsTLSRenegotiations(); - - final SSLSocket client = newClient(); - final OutputStream clientOutput = client.getOutputStream(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - // Use a content that is larger than the TLS record which is 2^14 (around 16k) - byte[] data1 = new byte[80 * 1024]; - Arrays.fill(data1, (byte)'X'); - String content1 = new String(data1, StandardCharsets.UTF_8); - byte[] data2 = new byte[48 * 1024]; - Arrays.fill(data2, (byte)'Y'); - final String content2 = new String(data2, StandardCharsets.UTF_8); - - // Write only part of the body - automaticProxyFlow = proxy.startAutomaticFlow(); - clientOutput.write(( - "POST / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Content-Type: text/plain\r\n" + - "Content-Length: " + (content1.length() + content2.length()) + "\r\n" + - "\r\n" + - content1).getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - // Renegotiate - Future renegotiation = threadPool.submit(() -> - { - client.startHandshake(); - return null; - }); - - // Renegotiation Handshake - TLSRecord record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToServer(record); - - // Renegotiation Handshake - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToClient(record); - - // Renegotiation Change Cipher - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType()); - proxy.flushToClient(record); - - // Renegotiation Handshake - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToClient(record); - - // Trigger a read to have the client write the final renegotiation steps - client.setSoTimeout(100); - - assertThrows(SocketTimeoutException.class, () -> client.getInputStream().read()); - - // Renegotiation Change Cipher - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType()); - proxy.flushToServer(record); - - // Renegotiation Handshake - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToServer(record); - - assertNull(renegotiation.get(5, TimeUnit.SECONDS)); - - // Write the rest of the request - Future request = threadPool.submit(() -> - { - clientOutput.write(content2.getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Three TLSRecords will be generated for the remainder of the content - for (int i = 0; i < 3; ++i) - { - // Application data - record = proxy.readFromClient(); - proxy.flushToServer(record); - } - - assertNull(request.get(5, TimeUnit.SECONDS)); - - // Read response - // Application Data - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8)); - String line = reader.readLine(); - assertNotNull(line); - assertTrue(line.startsWith("HTTP/1.1 200 ")); - while ((line = reader.readLine()) != null) - { - if (line.trim().length() == 0) - break; - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - // The new HttpInput impl tends to call fill and parse more often than the previous one - // b/c HttpChannel.needContent() does a fill and parse before doing a fill interested; - // this runs the parser and goes to the OS more often but requires less rescheduling. - assertThat(sslFills.get(), Matchers.lessThan(70)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(70)); - - closeClient(client); - } - - @Test - public void testRequestWithBigContentWithRenegotiationInMiddleOfContentWithSplitBoundary() throws Exception - { - assumeJavaVersionSupportsTLSRenegotiations(); - - final SSLSocket client = newClient(); - final OutputStream clientOutput = client.getOutputStream(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - // Use a content that is larger than the TLS record which is 2^14 (around 16k) - byte[] data1 = new byte[80 * 1024]; - Arrays.fill(data1, (byte)'X'); - String content1 = new String(data1, StandardCharsets.UTF_8); - byte[] data2 = new byte[48 * 1024]; - Arrays.fill(data2, (byte)'Y'); - final String content2 = new String(data2, StandardCharsets.UTF_8); - - // Write only part of the body - automaticProxyFlow = proxy.startAutomaticFlow(); - clientOutput.write(( - "POST / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Content-Type: text/plain\r\n" + - "Content-Length: " + (content1.length() + content2.length()) + "\r\n" + - "\r\n" + - content1).getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - // Renegotiate - Future renegotiation = threadPool.submit(() -> - { - client.startHandshake(); - return null; - }); - - // Renegotiation Handshake - TLSRecord record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - byte[] bytes = record.getBytes(); - byte[] chunk1 = new byte[2 * bytes.length / 3]; - System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - byte[] chunk2 = new byte[bytes.length - chunk1.length]; - System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); - proxy.flushToServer(100, chunk1); - proxy.flushToServer(100, chunk2); - - // Renegotiation Handshake - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToClient(record); - - // Renegotiation Change Cipher - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType()); - proxy.flushToClient(record); - - // Renegotiation Handshake - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToClient(record); - - // Trigger a read to have the client write the final renegotiation steps - client.setSoTimeout(100); - - assertThrows(SocketTimeoutException.class, () -> client.getInputStream().read()); - - // Renegotiation Change Cipher - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType()); - bytes = record.getBytes(); - chunk1 = new byte[2 * bytes.length / 3]; - System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - chunk2 = new byte[bytes.length - chunk1.length]; - System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); - proxy.flushToServer(100, chunk1); - proxy.flushToServer(100, chunk2); - - // Renegotiation Handshake - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - bytes = record.getBytes(); - chunk1 = new byte[2 * bytes.length / 3]; - System.arraycopy(bytes, 0, chunk1, 0, chunk1.length); - chunk2 = new byte[bytes.length - chunk1.length]; - System.arraycopy(bytes, chunk1.length, chunk2, 0, chunk2.length); - proxy.flushToServer(100, chunk1); - // Do not write the second chunk now, but merge it with content, see below - - assertNull(renegotiation.get(5, TimeUnit.SECONDS)); - - // Write the rest of the request - Future request = threadPool.submit(() -> - { - clientOutput.write(content2.getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - return null; - }); - - // Three TLSRecords will be generated for the remainder of the content - // Merge the last chunk of the renegotiation with the first data record - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - byte[] dataBytes = record.getBytes(); - byte[] mergedBytes = new byte[chunk2.length + dataBytes.length]; - System.arraycopy(chunk2, 0, mergedBytes, 0, chunk2.length); - System.arraycopy(dataBytes, 0, mergedBytes, chunk2.length, dataBytes.length); - proxy.flushToServer(100, mergedBytes); - // Write the remaining 2 TLS records - for (int i = 0; i < 2; ++i) - { - // Application data - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToServer(record); - } - - assertNull(request.get(5, TimeUnit.SECONDS)); - - // Read response - // Application Data - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToClient(record); - - BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8)); - String line = reader.readLine(); - assertNotNull(line); - assertTrue(line.startsWith("HTTP/1.1 200 ")); - while ((line = reader.readLine()) != null) - { - if (line.trim().length() == 0) - break; - } - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - // The new HttpInput impl tends to call fill and parse more often than the previous one - // b/c HttpChannel.needContent() does a fill and parse before doing a fill interested; - // this runs the parser and goes to the OS more often but requires less rescheduling. - assertThat(sslFills.get(), Matchers.lessThan(80)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(120)); - - closeClient(client); - } - - @Test - public void testServerShutdownOutputClientDoesNotCloseServerCloses() throws Exception - { - final SSLSocket client = newClient(); - final OutputStream clientOutput = client.getOutputStream(); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - byte[] data = new byte[3 * 1024]; - Arrays.fill(data, (byte)'Y'); - String content = new String(data, StandardCharsets.UTF_8); - automaticProxyFlow = proxy.startAutomaticFlow(); - clientOutput.write(( - "POST / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Content-Type: text/plain\r\n" + - "Content-Length: " + content.length() + "\r\n" + - "Connection: close\r\n" + - "\r\n" + - content).getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - - BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8)); - String line = reader.readLine(); - assertNotNull(line); - assertTrue(line.startsWith("HTTP/1.1 200 ")); - while ((line = reader.readLine()) != null) - { - if (line.trim().length() == 0) - break; - } - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - // Check client is at EOF - assertEquals(-1, client.getInputStream().read()); - - // Client should close the socket, but let's hold it open. - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - // The server has shutdown the output since the client sent a Connection: close - // but the client does not close, so the server must idle timeout the endPoint. - - TimeUnit.MILLISECONDS.sleep(idleTimeout + idleTimeout / 2); - - assertFalse(serverEndPoint.get().isOpen()); - } - - @Test - public void testPlainText() throws Exception - { - final SSLSocket client = newClient(); - - threadPool.submit(() -> - { - client.startHandshake(); - return null; - }); - - // Instead of passing the Client Hello, we simulate plain text was passed in - proxy.flushToServer(0, "GET / HTTP/1.1\r\n".getBytes(StandardCharsets.UTF_8)); - - // We expect that the server sends the TLS Alert. - TLSRecord record = proxy.readFromServer(); - assertNotNull(record); - assertEquals(TLSRecord.Type.ALERT, record.getType()); - record = proxy.readFromServer(); - assertNull(record); - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(20)); - - client.close(); - } - - @Test - public void testRequestConcurrentWithIdleExpiration() throws Exception - { - final SSLSocket client = newClient(); - final OutputStream clientOutput = client.getOutputStream(); - final CountDownLatch latch = new CountDownLatch(1); - - idleHook = () -> - { - if (latch.getCount() == 0) - return; - try - { - // Send request - clientOutput.write(( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "\r\n").getBytes(StandardCharsets.UTF_8)); - clientOutput.flush(); - latch.countDown(); - } - catch (Exception x) - { - // Latch won't trigger and test will fail - x.printStackTrace(); - } - }; - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - client.startHandshake(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - assertTrue(latch.await(idleTimeout * 2, TimeUnit.MILLISECONDS)); - - // Be sure that the server sent an SSL close alert - TLSRecord record = proxy.readFromServer(); - assertNotNull(record); - assertEquals(TLSRecord.Type.ALERT, record.getType()); - - // Write the request to the server, to simulate a request - // concurrent with the SSL close alert - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.APPLICATION, record.getType()); - proxy.flushToServer(record, 0); - - // Check that we did not spin - TimeUnit.MILLISECONDS.sleep(500); - assertThat(sslFills.get(), Matchers.lessThan(20)); - assertThat(sslFlushes.get(), Matchers.lessThan(20)); - assertThat(httpParses.get(), Matchers.lessThan(50)); - - record = proxy.readFromServer(); - assertNull(record); - - TimeUnit.MILLISECONDS.sleep(200); - assertThat(((Dumpable)server.getConnectors()[0]).dump(), Matchers.not(Matchers.containsString("SCEP@"))); - } - - // TODO: Remove? We are on JDK 1.8+ now. - private void assumeJavaVersionSupportsTLSRenegotiations() - { - // Due to a security bug, TLS renegotiations were disabled in JDK 1.6.0_19-21 - // so we check the java version in order to avoid to fail the test. - String javaVersion = System.getProperty("java.version"); - Pattern regexp = Pattern.compile("1\\.6\\.0_(\\d{2})"); - Matcher matcher = regexp.matcher(javaVersion); - if (matcher.matches()) - { - String nano = matcher.group(1); - Assumptions.assumeTrue(Integer.parseInt(nano) > 21); - } - } - - private SSLSocket newClient() throws IOException, InterruptedException - { - return newClient(proxy); - } - - private SSLSocket newClient(SimpleProxy proxy) throws IOException, InterruptedException - { - SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", proxy.getPort()); - client.setUseClientMode(true); - assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS)); - return client; - } - - private void closeClient(SSLSocket client) throws Exception - { - client.close(); - - // Close Alert - TLSRecord record = proxy.readFromClient(); - proxy.flushToServer(record); - // Socket close - record = proxy.readFromClient(); - assertNull(record, String.valueOf(record)); - proxy.flushToServer(record); - - // Socket close - record = proxy.readFromServer(); - if (record != null) - { - assertEquals(record.getType(), Type.ALERT); - record = proxy.readFromServer(); - } - assertNull(record); - } -} diff --git a/jetty-core/jetty-alpn/jetty-alpn-client/pom.xml b/jetty-core/jetty-alpn/jetty-alpn-client/pom.xml index ade118019bd..3d55d94c5be 100644 --- a/jetty-core/jetty-alpn/jetty-alpn-client/pom.xml +++ b/jetty-core/jetty-alpn/jetty-alpn-client/pom.xml @@ -1,12 +1,12 @@ org.eclipse.jetty - jetty-alpn-parent - 11.0.10-SNAPSHOT + jetty-alpn + 12.0.0-SNAPSHOT 4.0.0 jetty-alpn-client - Jetty :: ALPN :: Client + Jetty Core :: ALPN :: Client ${project.groupId}.alpn.client org.eclipse.alpn.* diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/module-info.java b/jetty-core/jetty-alpn/jetty-alpn-client/src/main/java/module-info.java similarity index 100% rename from jetty-alpn/jetty-alpn-client/src/main/java/module-info.java rename to jetty-core/jetty-alpn/jetty-alpn-client/src/main/java/module-info.java diff --git a/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml index f10a054bb1d..a8b250f5369 100644 --- a/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml +++ b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/pom.xml @@ -3,13 +3,13 @@ org.eclipse.jetty - jetty-alpn-parent - 11.0.10-SNAPSHOT + jetty-alpn + 12.0.0-SNAPSHOT 4.0.0 jetty-alpn-conscrypt-client - Jetty :: ALPN :: Conscrypt Client Implementation + Jetty Core :: ALPN :: Conscrypt Client Implementation ${project.groupId}.alpn.conscrypt.client diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/src/main/java/module-info.java b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/main/java/module-info.java similarity index 100% rename from jetty-alpn/jetty-alpn-conscrypt-client/src/main/java/module-info.java rename to jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/main/java/module-info.java diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client similarity index 100% rename from jetty-alpn/jetty-alpn-conscrypt-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client rename to jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client diff --git a/jetty-alpn/jetty-alpn-conscrypt-client/src/test/resources/jetty-logging.properties b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-alpn/jetty-alpn-conscrypt-client/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/test/resources/jetty-logging.properties diff --git a/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml index 798ea1f4b21..f808be03a13 100644 --- a/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml +++ b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty - jetty-alpn-parent - 11.0.10-SNAPSHOT + jetty-alpn + 12.0.0-SNAPSHOT 4.0.0 jetty-alpn-conscrypt-server - Jetty :: ALPN :: Conscrypt Server Implementation + Jetty Core :: ALPN :: Conscrypt Server Implementation ${project.groupId}.alpn.conscrypt.server diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/src/main/java/module-info.java b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/main/java/module-info.java similarity index 100% rename from jetty-alpn/jetty-alpn-conscrypt-server/src/main/java/module-info.java rename to jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/main/java/module-info.java diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server similarity index 100% rename from jetty-alpn/jetty-alpn-conscrypt-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server rename to jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server diff --git a/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/test/java/org/eclipse/jetty/alpn/conscrypt/server/ConscryptHTTP2ServerTest.java b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/test/java/org/eclipse/jetty/alpn/conscrypt/server/ConscryptHTTP2ServerTest.java index 2ea507f2e47..067085038da 100644 --- a/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/test/java/org/eclipse/jetty/alpn/conscrypt/server/ConscryptHTTP2ServerTest.java +++ b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/test/java/org/eclipse/jetty/alpn/conscrypt/server/ConscryptHTTP2ServerTest.java @@ -18,8 +18,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.security.Security; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.conscrypt.OpenSSLProvider; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.client.HttpClient; @@ -28,14 +26,16 @@ import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.JavaVersion; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.AfterEach; @@ -54,7 +54,7 @@ public class ConscryptHTTP2ServerTest Security.addProvider(new OpenSSLProvider()); } - private Server server = new Server(); + private final Server server = new Server(); private SslContextFactory.Server newServerSslContextFactory() { @@ -105,13 +105,12 @@ public class ConscryptHTTP2ServerTest http2Connector.setPort(0); server.addConnector(http2Connector); - server.setHandler(new AbstractHandler() + server.setHandler(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - response.setStatus(200); - baseRequest.setHandled(true); + callback.succeeded(); } }); @@ -121,8 +120,7 @@ public class ConscryptHTTP2ServerTest @AfterEach public void stopServer() throws Exception { - if (server != null) - server.stop(); + server.stop(); } @Test diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/jetty-logging.properties b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/jetty-logging.properties diff --git a/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/keystore.p12 b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/keystore.p12 similarity index 100% rename from jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/keystore.p12 rename to jetty-core/jetty-alpn/jetty-alpn-conscrypt-server/src/test/resources/keystore.p12 diff --git a/jetty-core/jetty-alpn/jetty-alpn-java-client/pom.xml b/jetty-core/jetty-alpn/jetty-alpn-java-client/pom.xml index 4b03a97ee4c..a355b1bdc80 100644 --- a/jetty-core/jetty-alpn/jetty-alpn-java-client/pom.xml +++ b/jetty-core/jetty-alpn/jetty-alpn-java-client/pom.xml @@ -3,13 +3,13 @@ org.eclipse.jetty - jetty-alpn-parent - 11.0.10-SNAPSHOT + jetty-alpn + 12.0.0-SNAPSHOT 4.0.0 jetty-alpn-java-client - Jetty :: ALPN :: JDK9 Client Implementation + Jetty Core :: ALPN :: JDK9 Client Implementation ${project.groupId}.alpn.java.client diff --git a/jetty-alpn/jetty-alpn-java-client/src/main/java/module-info.java b/jetty-core/jetty-alpn/jetty-alpn-java-client/src/main/java/module-info.java similarity index 100% rename from jetty-alpn/jetty-alpn-java-client/src/main/java/module-info.java rename to jetty-core/jetty-alpn/jetty-alpn-java-client/src/main/java/module-info.java diff --git a/jetty-alpn/jetty-alpn-java-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client b/jetty-core/jetty-alpn/jetty-alpn-java-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client similarity index 100% rename from jetty-alpn/jetty-alpn-java-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client rename to jetty-core/jetty-alpn/jetty-alpn-java-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client diff --git a/jetty-alpn/jetty-alpn-java-client/src/test/resources/jetty-logging.properties b/jetty-core/jetty-alpn/jetty-alpn-java-client/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-alpn/jetty-alpn-java-client/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-alpn/jetty-alpn-java-client/src/test/resources/jetty-logging.properties diff --git a/jetty-core/jetty-alpn/jetty-alpn-java-server/pom.xml b/jetty-core/jetty-alpn/jetty-alpn-java-server/pom.xml index b16c1e85de7..84a52a4993b 100644 --- a/jetty-core/jetty-alpn/jetty-alpn-java-server/pom.xml +++ b/jetty-core/jetty-alpn/jetty-alpn-java-server/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty - jetty-alpn-parent - 11.0.10-SNAPSHOT + jetty-alpn + 12.0.0-SNAPSHOT 4.0.0 jetty-alpn-java-server - Jetty :: ALPN :: JDK9 Server Implementation + Jetty Core :: ALPN :: JDK9 Server Implementation ${project.groupId}.alpn.java.server diff --git a/jetty-alpn/jetty-alpn-java-server/src/main/java/module-info.java b/jetty-core/jetty-alpn/jetty-alpn-java-server/src/main/java/module-info.java similarity index 100% rename from jetty-alpn/jetty-alpn-java-server/src/main/java/module-info.java rename to jetty-core/jetty-alpn/jetty-alpn-java-server/src/main/java/module-info.java diff --git a/jetty-alpn/jetty-alpn-java-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server b/jetty-core/jetty-alpn/jetty-alpn-java-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server similarity index 100% rename from jetty-alpn/jetty-alpn-java-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server rename to jetty-core/jetty-alpn/jetty-alpn-java-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server diff --git a/jetty-core/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java b/jetty-core/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java index ad78e966936..b8fc14dc3b5 100644 --- a/jetty-core/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java +++ b/jetty-core/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java @@ -27,18 +27,17 @@ import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -88,12 +87,12 @@ public class JDK9ALPNTest @Test public void testClientNotSupportingALPNServerSpeaksDefaultProtocol() throws Exception { - startServer(new AbstractHandler() + startServer(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); + callback.succeeded(); } }); @@ -130,12 +129,12 @@ public class JDK9ALPNTest @Test public void testClientSupportingALPNServerSpeaksNegotiatedProtocol() throws Exception { - startServer(new AbstractHandler() + startServer(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); + callback.succeeded(); } }); @@ -175,12 +174,12 @@ public class JDK9ALPNTest @Test public void testClientSupportingALPNCannotNegotiateProtocol() throws Exception { - startServer(new AbstractHandler() + startServer(new Handler.Processor() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - jettyRequest.setHandled(true); + callback.succeeded(); } }); diff --git a/jetty-alpn/jetty-alpn-java-server/src/test/resources/jetty-logging.properties b/jetty-core/jetty-alpn/jetty-alpn-java-server/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-alpn/jetty-alpn-java-server/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-alpn/jetty-alpn-java-server/src/test/resources/jetty-logging.properties diff --git a/jetty-alpn/jetty-alpn-java-server/src/test/resources/keystore.p12 b/jetty-core/jetty-alpn/jetty-alpn-java-server/src/test/resources/keystore.p12 similarity index 100% rename from jetty-alpn/jetty-alpn-java-server/src/test/resources/keystore.p12 rename to jetty-core/jetty-alpn/jetty-alpn-java-server/src/test/resources/keystore.p12 diff --git a/jetty-core/jetty-alpn/jetty-alpn-server/pom.xml b/jetty-core/jetty-alpn/jetty-alpn-server/pom.xml index dc6a186beb1..b3181326f29 100644 --- a/jetty-core/jetty-alpn/jetty-alpn-server/pom.xml +++ b/jetty-core/jetty-alpn/jetty-alpn-server/pom.xml @@ -1,12 +1,12 @@ org.eclipse.jetty - jetty-alpn-parent - 11.0.10-SNAPSHOT + jetty-alpn + 12.0.0-SNAPSHOT 4.0.0 jetty-alpn-server - Jetty :: ALPN :: Server + Jetty Core :: ALPN :: Server ${project.groupId}.alpn.server org.eclipse.alpn.* diff --git a/jetty-alpn/jetty-alpn-server/src/main/java/module-info.java b/jetty-core/jetty-alpn/jetty-alpn-server/src/main/java/module-info.java similarity index 100% rename from jetty-alpn/jetty-alpn-server/src/main/java/module-info.java rename to jetty-core/jetty-alpn/jetty-alpn-server/src/main/java/module-info.java diff --git a/jetty-core/jetty-alpn/pom.xml b/jetty-core/jetty-alpn/pom.xml index fdba60e6fb6..017737297cc 100644 --- a/jetty-core/jetty-alpn/pom.xml +++ b/jetty-core/jetty-alpn/pom.xml @@ -1,13 +1,13 @@ org.eclipse.jetty - jetty-project - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 - jetty-alpn-parent + jetty-alpn pom - Jetty :: ALPN :: Parent + Jetty Core :: ALPN :: Parent jetty-alpn-server diff --git a/jetty-core/jetty-bom b/jetty-core/jetty-bom/pom.xml similarity index 54% rename from jetty-core/jetty-bom rename to jetty-core/jetty-bom/pom.xml index b51a3382b20..f11ed41abdf 100644 --- a/jetty-core/jetty-bom +++ b/jetty-core/jetty-bom/pom.xml @@ -3,13 +3,13 @@ org.eclipse.jetty - jetty-project - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT jetty-bom - Jetty :: BOM - Jetty BOM artifact + Jetty Core :: BOM + Jetty Core BOM artifact pom @@ -50,162 +50,137 @@ - - org.eclipse.jetty - apache-jsp - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - glassfish-jstl - 11.0.10-SNAPSHOT - - + org.eclipse.jetty jetty-alpn-client - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-alpn-java-client - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-alpn-java-server - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-alpn-conscrypt-client - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-alpn-conscrypt-server - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-alpn-server - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-annotations - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-ant - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-client - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-cdi - 10.0.3-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-deploy - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.fcgi fcgi-client - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.fcgi fcgi-server - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.gcloud jetty-gcloud-session-manager - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-home - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT zip org.eclipse.jetty jetty-home - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT tar.gz org.eclipse.jetty jetty-http - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.http2 http2-client - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.http2 http2-common - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.http2 http2-hpack - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.http2 http2-http-client-transport - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.http2 http2-server - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.http3 http3-client - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.http3 http3-common - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.http3 http3-http-client-transport - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.http3 http3-qpack - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.http3 http3-server - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-http-spi - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty infinispan-common - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty @@ -215,7 +190,7 @@ org.eclipse.jetty infinispan-remote-query - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty @@ -225,37 +200,27 @@ org.eclipse.jetty infinispan-embedded-query - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-hazelcast - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-io - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-jaas - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-jaspi - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-jmx - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-jndi - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty @@ -265,107 +230,52 @@ org.eclipse.jetty.memcached jetty-memcached-sessions - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-nosql - 11.0.10-SNAPSHOT - - - org.eclipse.jetty.osgi - jetty-osgi-alpn - 10.0.8-SNAPSHOT - - - org.eclipse.jetty.osgi - jetty-osgi-boot - 11.0.10-SNAPSHOT - - - org.eclipse.jetty.osgi - jetty-osgi-boot-jsp - 11.0.10-SNAPSHOT - - - org.eclipse.jetty.osgi - jetty-osgi-boot-warurl - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.quic quic-client - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.quic quic-common - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.quic quic-quiche-common - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.quic quic-quiche-jna - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.quic quic-server - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.osgi jetty-httpservice - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-plus - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-proxy - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-quickstart - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-rewrite - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-security - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-openid - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-server - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-servlet - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-servlets - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty @@ -375,97 +285,37 @@ org.eclipse.jetty jetty-unixdomain-server - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-unixsocket-common - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-unixsocket-client - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-unixsocket-server - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-util - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-util-ajax - 11.0.10-SNAPSHOT - - - org.eclipse.jetty - jetty-webapp - 11.0.10-SNAPSHOT - - - org.eclipse.jetty.websocket - websocket-jakarta-client - 11.0.10-SNAPSHOT - - - org.eclipse.jetty.websocket - websocket-jakarta-server - 11.0.10-SNAPSHOT - - - org.eclipse.jetty.websocket - websocket-jakarta-common - 11.0.10-SNAPSHOT - - - org.eclipse.jetty.websocket - websocket-jetty-api - 11.0.10-SNAPSHOT - - - org.eclipse.jetty.websocket - websocket-jetty-client - 11.0.10-SNAPSHOT - - - org.eclipse.jetty.websocket - websocket-jetty-common - 11.0.10-SNAPSHOT - - - org.eclipse.jetty.websocket - websocket-jetty-server - 11.0.10-SNAPSHOT - - - org.eclipse.jetty.websocket - websocket-servlet - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.websocket websocket-core-common - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.websocket websocket-core-client - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty.websocket websocket-core-server - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT org.eclipse.jetty jetty-xml - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT @@ -481,7 +331,7 @@ org.eclipse.jetty.quic quic-quiche-foreign-incubator - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT diff --git a/jetty-core/jetty-client/pom.xml b/jetty-core/jetty-client/pom.xml index fd2fc02c344..539e5663621 100644 --- a/jetty-core/jetty-client/pom.xml +++ b/jetty-core/jetty-client/pom.xml @@ -1,13 +1,13 @@ org.eclipse.jetty - jetty-project - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 jetty-client - Jetty :: Asynchronous HTTP Client + Jetty Core :: Asynchronous HTTP Client ${project.groupId}.client target/test-policy @@ -110,22 +110,11 @@ jetty-jmx true - - - org.eclipse.jetty.toolchain - jetty-jakarta-servlet-api - test - org.eclipse.jetty jetty-server test - - org.eclipse.jetty - jetty-security - test - org.awaitility awaitility @@ -151,10 +140,5 @@ jetty-slf4j-impl test - - org.eclipse.jetty.toolchain - jetty-test-helper - test - diff --git a/jetty-client/src/main/java/module-info.java b/jetty-core/jetty-client/src/main/java/module-info.java similarity index 100% rename from jetty-client/src/main/java/module-info.java rename to jetty-core/jetty-client/src/main/java/module-info.java diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java index 1515986a26f..7f400c93853 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java @@ -40,12 +40,10 @@ import java.util.function.Consumer; import java.util.function.LongConsumer; import java.util.function.Supplier; -import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.internal.RequestContentAdapter; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.client.util.PathRequestContent; import org.eclipse.jetty.http.HttpField; @@ -672,28 +670,6 @@ public class HttpRequest implements Request return this; } - @Override - public ContentProvider getContent() - { - if (content instanceof RequestContentAdapter) - return ((RequestContentAdapter)content).getContentProvider(); - return null; - } - - @Override - public Request content(ContentProvider content) - { - return content(content, null); - } - - @Override - public Request content(ContentProvider content, String contentType) - { - if (contentType != null) - headers.put(HttpHeader.CONTENT_TYPE, contentType); - return body(ContentProvider.toRequestContent(content)); - } - @Override public Content getBody() { diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java index efae0d57a98..79a412093e7 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java @@ -242,30 +242,6 @@ public interface Request */ Map getAttributes(); - /** - * @return the content provider of this request - * @deprecated use {@link #getBody()} instead - */ - @Deprecated - ContentProvider getContent(); - - /** - * @param content the content provider of this request - * @return this request object - * @deprecated use {@link #body(Content)} instead - */ - @Deprecated - Request content(ContentProvider content); - - /** - * @param content the content provider of this request - * @param contentType the content type - * @return this request object - * @deprecated use {@link #body(Content)} instead - */ - @Deprecated - Request content(ContentProvider content, String contentType); - /** * @return the request content of this request */ diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/package-info.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/api/package-info.java similarity index 100% rename from jetty-client/src/main/java/org/eclipse/jetty/client/api/package-info.java rename to jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/api/package-info.java diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java index 62955ab24bd..26a07e00f92 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/dynamic/HttpClientTransportDynamic.java @@ -55,9 +55,9 @@ import org.slf4j.LoggerFactory; * // Configure the clientConnector. * * // Prepare the application protocols. - * ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11; + * ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP; * HTTP2Client http2Client = new HTTP2Client(clientConnector); - * ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + * ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client); * * // Create the HttpClientTransportDynamic, preferring h2 over h1. * HttpClientTransport transport = new HttpClientTransportDynamic(clientConnector, h2, h1); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java similarity index 100% rename from jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java rename to jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/package-info.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/util/package-info.java similarity index 100% rename from jetty-client/src/main/java/org/eclipse/jetty/client/util/package-info.java rename to jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/util/package-info.java diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java index eed27ad1d92..f2663a54043 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java @@ -13,15 +13,11 @@ package org.eclipse.jetty.client; -import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; import org.eclipse.jetty.client.util.AsyncRequestContent; @@ -29,8 +25,13 @@ import org.eclipse.jetty.client.util.StringRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Blocking; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.thread.Invocable.InvocationType; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -46,23 +47,15 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest public void testClientConnectionCloseServerConnectionCloseClientClosesAfterExchange(Scenario scenario) throws Exception { byte[] data = new byte[128 * 1024]; - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor(InvocationType.BLOCKING) { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - - ServletInputStream input = request.getInputStream(); - while (true) - { - int read = input.read(); - if (read < 0) - break; - } + Content.consumeAll(request); response.setContentLength(data.length); - response.getOutputStream().write(data); + response.write(true, callback, ByteBuffer.wrap(data)); try { @@ -103,13 +96,11 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testClientConnectionCloseServerDoesNotRespondClientIdleTimeout(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); - request.startAsync(); // Do not respond. } }); @@ -147,24 +138,19 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest public void testClientConnectionCloseServerPartialResponseClientIdleTimeout(Scenario scenario) throws Exception { long idleTimeout = 1000; - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor(InvocationType.BLOCKING) { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); + Content.consumeAll(request); - ServletInputStream input = request.getInputStream(); - while (true) + try (Blocking.Callback block = Blocking.callback()) { - int read = input.read(); - if (read < 0) - break; + response.write(false, block, "Hello"); + block.block(); } - response.getOutputStream().print("Hello"); - response.flushBuffer(); - try { Thread.sleep(2 * idleTimeout); @@ -173,6 +159,8 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest { throw new InterruptedIOException(); } + + callback.succeeded(); } }); @@ -211,14 +199,17 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testClientConnectionCloseServerNoConnectionCloseClientCloses(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor(InvocationType.BLOCKING) { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); response.setContentLength(0); - response.flushBuffer(); + try (Blocking.Callback block = Blocking.callback()) + { + response.write(false, block); + block.block(); + } try { @@ -229,6 +220,7 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest { throw new InterruptedIOException(); } + callback.succeeded(); } }); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java index f1650106a44..f65390db1c0 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.client; -import java.io.IOException; import java.net.InetSocketAddress; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -25,9 +24,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; import java.util.stream.Stream; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Destination; @@ -40,15 +36,19 @@ import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.server.Content; import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.Blocking; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -134,41 +134,67 @@ public class ConnectionPoolTest @ParameterizedTest @MethodSource("pools") + @Disabled("fix this test") public void test(ConnectionPoolFactory factory) throws Exception { start(factory.factory, new EmptyServerHandler() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws Throwable { switch (HttpMethod.fromString(request.getMethod())) { - case GET: + case GET -> { - int contentLength = request.getIntHeader("X-Download"); + long contentLength = request.getHeaders().getLongField("X-Download"); if (contentLength > 0) { response.setContentLength(contentLength); - response.getOutputStream().write(new byte[contentLength]); + try (Blocking.Callback callback = _blocking.callback()) + { + response.write(true, callback, BufferUtil.allocate((int)contentLength)); + callback.block(); + } } - break; } - case POST: + case POST -> { - int contentLength = request.getContentLength(); + long contentLength = request.getContentLength(); if (contentLength > 0) response.setContentLength(contentLength); - IO.copy(request.getInputStream(), response.getOutputStream()); - break; - } - default: - { - throw new IllegalStateException(); + while (true) + { + Content content = request.readContent(); + if (content == null) + { + try (Blocking.Runnable block = _blocking.runnable()) + { + request.demandContent(block); + block.block(); + continue; + } + } + if (content instanceof Content.Error error) + throw error.getCause(); + + if (content.hasRemaining()) + { + try (Blocking.Callback callback = _blocking.callback()) + { + response.write(true, callback, content.getByteBuffer()); + callback.block(); + } + } + content.release(); + if (content.isLast()) + break; + } } + default -> throw new IllegalStateException(); } - if (Boolean.parseBoolean(request.getHeader("X-Close"))) - response.setHeader("Connection", "close"); + if (Boolean.parseBoolean(request.getHeaders().get("X-Close"))) + response.getHeaders().put("Connection", "close"); } }); @@ -204,12 +230,8 @@ public class ConnectionPoolTest HttpMethod method = random.nextBoolean() ? HttpMethod.GET : HttpMethod.POST; // Choose randomly whether to close the connection on the client or on the server. - boolean clientClose = false; - if (random.nextInt(100) < 1) - clientClose = true; - boolean serverClose = false; - if (random.nextInt(100) < 1) - serverClose = true; + boolean clientClose = random.nextInt(100) < 1; + boolean serverClose = random.nextInt(100) < 1; int maxContentLength = 64 * 1024; int contentLength = random.nextInt(maxContentLength) + 1; @@ -230,15 +252,13 @@ public class ConnectionPoolTest switch (method) { - case GET: - request.headers(fields -> fields.put("X-Download", String.valueOf(contentLength))); - break; - case POST: + case GET -> request.headers(fields -> fields.put("X-Download", String.valueOf(contentLength))); + case POST -> + { request.headers(fields -> fields.put(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength))); request.body(new BytesRequestContent(new byte[contentLength])); - break; - default: - throw new IllegalStateException(); + } + default -> throw new IllegalStateException(); } FutureResponseListener listener = new FutureResponseListener(request, contentLength); @@ -403,16 +423,9 @@ public class ConnectionPoolTest server.setHandler(new EmptyServerHandler() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws Exception { - try - { - barrier.await(); - } - catch (Exception x) - { - throw new ServletException(x); - } + barrier.await(); } }); server.start(); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ContentResponseTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ContentResponseTest.java index 8d209723c7a..cf7a593ec82 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ContentResponseTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ContentResponseTest.java @@ -13,17 +13,17 @@ package org.eclipse.jetty.client; -import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.Random; import java.util.concurrent.TimeUnit; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -39,13 +39,12 @@ public class ContentResponseTest extends AbstractHttpClientServerTest { final byte[] content = new byte[1024]; new Random().nextBytes(content); - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); - response.getOutputStream().write(content); + response.write(true, callback, ByteBuffer.wrap(content)); } }); @@ -66,14 +65,13 @@ public class ContentResponseTest extends AbstractHttpClientServerTest { final String content = "The quick brown fox jumped over the lazy dog"; final String mediaType = "text/plain"; - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); - response.setHeader(HttpHeader.CONTENT_TYPE.asString(), mediaType); - response.getOutputStream().write(content.getBytes("UTF-8")); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, mediaType); + response.write(true, callback, ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8))); } }); @@ -96,14 +94,13 @@ public class ContentResponseTest extends AbstractHttpClientServerTest final String mediaType = "text/plain"; final String encoding = "UTF-8"; final String contentType = mediaType + "; charset=" + encoding; - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - response.setHeader(HttpHeader.CONTENT_TYPE.asString(), contentType); - response.getOutputStream().write(content.getBytes(encoding)); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, contentType); + response.write(true, callback, ByteBuffer.wrap(content.getBytes(encoding))); } }); @@ -126,14 +123,13 @@ public class ContentResponseTest extends AbstractHttpClientServerTest final String mediaType = "text/plain"; final String encoding = "UTF-8"; final String contentType = mediaType + "; charset=\"" + encoding + "\""; - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - response.setHeader(HttpHeader.CONTENT_TYPE.asString(), contentType); - response.getOutputStream().write(content.getBytes(encoding)); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, contentType); + response.write(true, callback, ByteBuffer.wrap(content.getBytes(encoding))); } }); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/EmptyServerHandler.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/EmptyServerHandler.java index 32d71ee755c..effda3f800c 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/EmptyServerHandler.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/EmptyServerHandler.java @@ -13,24 +13,36 @@ package org.eclipse.jetty.client; -import java.io.IOException; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Blocking; +import org.eclipse.jetty.util.Callback; -public class EmptyServerHandler extends AbstractHandler +public class EmptyServerHandler extends Handler.Processor { - @Override - public final void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected Blocking.Shared _blocking = new Blocking.Shared(); + + public EmptyServerHandler() { - jettyRequest.setHandled(true); - service(target, jettyRequest, request, response); + super(InvocationType.BLOCKING); } - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + @Override + public void process(Request request, Response response, Callback callback) throws Exception + { + try + { + service(request, response); + callback.succeeded(); + } + catch (Throwable t) + { + callback.failed(t); + } + } + + protected void service(Request request, Response response) throws Throwable { } } diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java index 8615adccabe..4aee54de58f 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java @@ -13,24 +13,24 @@ package org.eclipse.jetty.client; -import java.io.IOException; import java.security.cert.CertificateException; import java.util.concurrent.ExecutionException; import javax.net.ssl.SSLHandshakeException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; -import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.util.Blocking; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; @@ -71,13 +71,15 @@ public class HostnameVerificationTest SslConnectionFactory ssl = new SslConnectionFactory(serverSslContextFactory, http.getProtocol()); connector = new ServerConnector(server, 1, 1, ssl, http); server.addConnector(connector); - server.setHandler(new DefaultHandler() + server.setHandler(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); - response.getWriter().write("foobar"); + try (Blocking.Callback blocker = Blocking.callback()) + { + response.write(true, blocker, "foobar"); + } } }); server.start(); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAuthenticationStoreTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAuthenticationStoreTest.java index 70eb6fdbe0b..d038deec0a6 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAuthenticationStoreTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpAuthenticationStoreTest.java @@ -27,7 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; public class HttpAuthenticationStoreTest { @Test - public void testFindAuthenticationWithDefaultHTTPPort() throws Exception + public void testFindAuthenticationWithDefaultHTTPPort() { AuthenticationStore store = new HttpAuthenticationStore(); @@ -50,7 +50,7 @@ public class HttpAuthenticationStoreTest } @Test - public void testFindAuthenticationResultWithDefaultHTTPPort() throws Exception + public void testFindAuthenticationResultWithDefaultHTTPPort() { AuthenticationStore store = new HttpAuthenticationStore(); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java index ab13f5a2fb2..0e0ef86bc85 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java @@ -13,11 +13,8 @@ package org.eclipse.jetty.client; -import java.io.ByteArrayOutputStream; -import java.io.IOException; +import java.io.OutputStream; import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -25,18 +22,10 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.LongConsumer; import java.util.zip.GZIPOutputStream; -import jakarta.servlet.AsyncContext; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.http.HttpChannelOverHTTP; -import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; -import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; -import org.eclipse.jetty.client.http.HttpReceiverOverHTTP; -import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.Blocking; import org.eclipse.jetty.util.Callback; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -53,12 +42,16 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { - ServletOutputStream output = response.getOutputStream(); - output.write(65); - output.flush(); - output.write(66); + try (Blocking.Callback blocker = _blocking.callback()) + { + response.write(false, blocker, "A"); + } + try (Blocking.Callback blocker = _blocking.callback()) + { + response.write(false, blocker, "A"); + } } }); @@ -107,6 +100,8 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest assertEquals(2, contentCount.get()); } + // TODO +/* @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) public void testConcurrentAsyncContent(Scenario scenario) throws Exception @@ -115,7 +110,7 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest startServer(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { ServletOutputStream output = response.getOutputStream(); output.write(new byte[1024]); @@ -201,17 +196,17 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest assertTrue(latch.await(5, TimeUnit.SECONDS)); } - +*/ @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) public void testAsyncContentAbort(Scenario scenario) throws Exception { - start(scenario, new EmptyServerHandler() + start(scenario, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - response.getOutputStream().write(new byte[1024]); + response.write(true, callback, ByteBuffer.wrap(new byte[1024])); } }); @@ -235,10 +230,11 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { - response.setHeader("Content-Encoding", "gzip"); - GZIPOutputStream gzip = new GZIPOutputStream(response.getOutputStream()); + response.getHeaders().put("Content-Encoding", "gzip"); + OutputStream outputStream = org.eclipse.jetty.server.Response.asOutputStream(response); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); gzip.write(new byte[1024]); gzip.finish(); } @@ -268,10 +264,11 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { - response.setHeader("Content-Encoding", "gzip"); - try (GZIPOutputStream gzip = new GZIPOutputStream(response.getOutputStream())) + response.getHeaders().put("Content-Encoding", "gzip"); + OutputStream outputStream = org.eclipse.jetty.server.Response.asOutputStream(response); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { gzip.write(new byte[1024]); } @@ -313,6 +310,8 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest assertTrue(resultLatch.await(5, TimeUnit.SECONDS)); } + // TODO +/* @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) public void testAsyncGzipContentAbortWhileDecodingWithDelayedDemand(Scenario scenario) throws Exception @@ -333,13 +332,13 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(0); asyncContextRef.set(asyncContext); - response.setHeader("Content-Encoding", "gzip"); + response.getHeaders().put("Content-Encoding", "gzip"); ServletOutputStream output = response.getOutputStream(); output.write(gzip1); output.flush(); @@ -396,4 +395,5 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest assertTrue(resultLatch.await(555, TimeUnit.SECONDS)); } +*/ } diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java index f0d6a4eb178..397bb4672a3 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java @@ -13,67 +13,21 @@ package org.eclipse.jetty.client; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.IntFunction; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.client.api.Authentication; -import org.eclipse.jetty.client.api.Authentication.HeaderInfo; -import org.eclipse.jetty.client.api.AuthenticationStore; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.api.Response.Listener; -import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.AbstractAuthentication; -import org.eclipse.jetty.client.util.AbstractRequestContent; -import org.eclipse.jetty.client.util.AsyncRequestContent; -import org.eclipse.jetty.client.util.BasicAuthentication; -import org.eclipse.jetty.client.util.DigestAuthentication; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.security.Authenticator; -import org.eclipse.jetty.security.ConstraintMapping; -import org.eclipse.jetty.security.ConstraintSecurityHandler; -import org.eclipse.jetty.security.HashLoginService; -import org.eclipse.jetty.security.LoginService; -import org.eclipse.jetty.security.authentication.BasicAuthenticator; -import org.eclipse.jetty.security.authentication.DigestAuthenticator; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.util.Attributes; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.URIUtil; -import org.eclipse.jetty.util.security.Constraint; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ArgumentsSource; -import static org.eclipse.jetty.client.api.Authentication.ANY_REALM; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalToIgnoringCase; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +// TODO +@Disabled public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest { + @Test + public void testNeedToUpdateThisTest() + { + fail("This test needs to be updated to use Core version of Basic Auth (when available)"); + } + /* private String realm = "TestRealm"; public void startBasic(Scenario scenario, Handler handler) throws Exception @@ -601,7 +555,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest // Always reply with a 401 to see if the client // can handle an infinite authentication loop. response.setStatus(HttpStatus.UNAUTHORIZED_401); - response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), authType); + response.getHeaders().put(HttpHeader.WWW_AUTHENTICATE, authType); } }); @@ -862,4 +816,5 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest } } } + */ } diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientChunkedContentTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientChunkedContentTest.java index 61ee9cbdf5b..6e34696ab4f 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientChunkedContentTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientChunkedContentTest.java @@ -19,7 +19,6 @@ import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -70,14 +69,10 @@ public class HttpClientChunkedContentTest final CountDownLatch completeLatch = new CountDownLatch(1); client.newRequest("localhost", server.getLocalPort()) .timeout(5, TimeUnit.SECONDS) - .send(new Response.CompleteListener() + .send(result -> { - @Override - public void onComplete(Result result) - { - resultRef.set(result); - completeLatch.countDown(); - } + resultRef.set(result); + completeLatch.countDown(); }); try (Socket socket = server.accept()) @@ -123,26 +118,18 @@ public class HttpClientChunkedContentTest final AtomicReference resultRef = new AtomicReference<>(); final CountDownLatch completeLatch = new CountDownLatch(1); client.newRequest("localhost", server.getLocalPort()) - .onResponseContentAsync(new Response.AsyncContentListener() + .onResponseContentAsync((response, content, callback) -> { - @Override - public void onContent(Response response, ByteBuffer content, Callback callback) - { - if (callbackRef.compareAndSet(null, callback)) - firstContentLatch.countDown(); - else - callback.succeeded(); - } + if (callbackRef.compareAndSet(null, callback)) + firstContentLatch.countDown(); + else + callback.succeeded(); }) .timeout(5, TimeUnit.SECONDS) - .send(new Response.CompleteListener() + .send(result -> { - @Override - public void onComplete(Result result) - { - resultRef.set(result); - completeLatch.countDown(); - } + resultRef.set(result); + completeLatch.countDown(); }); try (Socket socket = server.accept()) diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCorrelationDataTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCorrelationDataTest.java index f6a5ff8e6f9..b3c643ce386 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCorrelationDataTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCorrelationDataTest.java @@ -13,14 +13,11 @@ package org.eclipse.jetty.client; -import java.io.IOException; import java.util.concurrent.TimeUnit; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.server.Response; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -39,9 +36,9 @@ public class HttpClientCorrelationDataTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - assertEquals(correlationData, request.getHeader(correlationName)); + assertEquals(correlationData, request.getHeaders().get(correlationName)); } }); client.getRequestListeners().add(new Request.Listener.Adapter() diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java index 96dd0b056a2..4321165ff5b 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java @@ -19,9 +19,6 @@ import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpStatus; @@ -32,9 +29,10 @@ import org.eclipse.jetty.server.AbstractConnectionFactory; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; @@ -82,16 +80,15 @@ public class HttpClientCustomProxyTest { final String serverHost = "server"; final int status = HttpStatus.NO_CONTENT_204; - prepare(new AbstractHandler() + prepare(new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(Request request, Response response) { - baseRequest.setHandled(true); - if (serverHost.equals(request.getServerName())) + if (serverHost.equals(Request.getServerName(request))) response.setStatus(status); else - response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); + response.setStatus(HttpStatus.NOT_ACCEPTABLE_406); } }); @@ -107,7 +104,7 @@ public class HttpClientCustomProxyTest assertEquals(status, response.getStatus()); } - private class CAFEBABEProxy extends ProxyConfiguration.Proxy + private static class CAFEBABEProxy extends ProxyConfiguration.Proxy { private CAFEBABEProxy(Origin.Address address, boolean secure) { @@ -121,7 +118,7 @@ public class HttpClientCustomProxyTest } } - private class CAFEBABEClientConnectionFactory implements ClientConnectionFactory + private static class CAFEBABEClientConnectionFactory implements ClientConnectionFactory { private final ClientConnectionFactory connectionFactory; @@ -140,7 +137,7 @@ public class HttpClientCustomProxyTest } } - private class CAFEBABEConnection extends AbstractConnection implements Callback + private static class CAFEBABEConnection extends AbstractConnection implements Callback { private final ClientConnectionFactory connectionFactory; private final Map context; diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientGZIPTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientGZIPTest.java index c9015a1f597..7509f510ae5 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientGZIPTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientGZIPTest.java @@ -17,6 +17,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Random; @@ -24,9 +25,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.zip.GZIPOutputStream; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.util.InputStreamResponseListener; @@ -34,7 +32,9 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -56,10 +56,11 @@ public class HttpClientGZIPTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { - response.setHeader("Content-Encoding", "gzip"); - GZIPOutputStream gzipOutput = new GZIPOutputStream(response.getOutputStream()); + response.getHeaders().put("Content-Encoding", "gzip"); + + GZIPOutputStream gzipOutput = new GZIPOutputStream(org.eclipse.jetty.server.Response.asOutputStream(response)); gzipOutput.write(data); gzipOutput.finish(); } @@ -82,21 +83,19 @@ public class HttpClientGZIPTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { - response.setHeader("Content-Encoding", "gzip"); + response.getHeaders().put("Content-Encoding", "gzip"); ByteArrayOutputStream gzipData = new ByteArrayOutputStream(); GZIPOutputStream gzipOutput = new GZIPOutputStream(gzipData); gzipOutput.write(data); gzipOutput.finish(); - ServletOutputStream output = response.getOutputStream(); byte[] gzipBytes = gzipData.toByteArray(); for (byte gzipByte : gzipBytes) { - output.write(gzipByte); - output.flush(); + org.eclipse.jetty.server.Response.write(response, false, ByteBuffer.wrap(new byte[]{gzipByte})); sleep(100); } } @@ -115,12 +114,12 @@ public class HttpClientGZIPTest extends AbstractHttpClientServerTest public void testGZIPContentSentTwiceInOneWrite(Scenario scenario) throws Exception { final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - start(scenario, new EmptyServerHandler() + start(scenario, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - response.setHeader("Content-Encoding", "gzip"); + response.getHeaders().put("Content-Encoding", "gzip"); ByteArrayOutputStream gzipData = new ByteArrayOutputStream(); GZIPOutputStream gzipOutput = new GZIPOutputStream(gzipData); @@ -131,8 +130,7 @@ public class HttpClientGZIPTest extends AbstractHttpClientServerTest byte[] content = Arrays.copyOf(gzipBytes, 2 * gzipBytes.length); System.arraycopy(gzipBytes, 0, content, gzipBytes.length, gzipBytes.length); - ServletOutputStream output = response.getOutputStream(); - output.write(content); + response.write(true, callback, ByteBuffer.wrap(content)); } }); @@ -169,9 +167,9 @@ public class HttpClientGZIPTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { - response.setHeader("Content-Encoding", "gzip"); + response.getHeaders().put("Content-Encoding", "gzip"); ByteArrayOutputStream gzipData = new ByteArrayOutputStream(); GZIPOutputStream gzipOutput = new GZIPOutputStream(gzipData); @@ -182,14 +180,11 @@ public class HttpClientGZIPTest extends AbstractHttpClientServerTest byte[] chunk1 = Arrays.copyOfRange(gzipBytes, 0, gzipBytes.length - fragment); byte[] chunk2 = Arrays.copyOfRange(gzipBytes, gzipBytes.length - fragment, gzipBytes.length); - ServletOutputStream output = response.getOutputStream(); - output.write(chunk1); - output.flush(); + org.eclipse.jetty.server.Response.write(response, false, ByteBuffer.wrap(chunk1)); sleep(500); - output.write(chunk2); - output.flush(); + org.eclipse.jetty.server.Response.write(response, true, ByteBuffer.wrap(chunk2)); } }); @@ -205,14 +200,14 @@ public class HttpClientGZIPTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testGZIPContentCorrupted(Scenario scenario) throws Exception { - start(scenario, new EmptyServerHandler() + start(scenario, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - response.setHeader("Content-Encoding", "gzip"); + response.getHeaders().put("Content-Encoding", "gzip"); // Not gzipped, will cause the client to blow up. - response.getOutputStream().print("0123456789"); + response.write(true, callback, "0123456789"); } }); @@ -237,11 +232,11 @@ public class HttpClientGZIPTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { response.setContentType("text/plain;charset=" + StandardCharsets.US_ASCII.name()); - response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip"); - GZIPOutputStream gzip = new GZIPOutputStream(response.getOutputStream()); + response.getHeaders().put(HttpHeader.CONTENT_ENCODING, "gzip"); + GZIPOutputStream gzip = new GZIPOutputStream(org.eclipse.jetty.server.Response.asOutputStream(response)); gzip.write(content); gzip.finish(); } @@ -279,11 +274,11 @@ public class HttpClientGZIPTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { response.setContentType("text/plain;charset=" + StandardCharsets.US_ASCII.name()); - response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip"); - GZIPOutputStream gzip = new GZIPOutputStream(response.getOutputStream()); + response.getHeaders().put(HttpHeader.CONTENT_ENCODING, "gzip"); + GZIPOutputStream gzip = new GZIPOutputStream(org.eclipse.jetty.server.Response.asOutputStream(response)); gzip.write(content); gzip.finish(); } diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientIdleTimeoutTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientIdleTimeoutTest.java index feff9462a66..2884f191dc5 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientIdleTimeoutTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientIdleTimeoutTest.java @@ -13,20 +13,20 @@ package org.eclipse.jetty.client; +import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; +import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -68,19 +68,19 @@ public class HttpClientIdleTimeoutTest start(new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, Response response) { - Cookie[] cookies = request.getCookies(); - if (cookies == null || cookies.length == 0) + List cookies = Request.getCookies(request); + if (cookies == null || cookies.size() == 0) { // Send a cookie in the first response. - response.addCookie(new Cookie("name", "value")); + Response.addCookie(response, new HttpCookie("name", "value")); } else { // Verify that there is only one cookie, i.e. // that the request has not been normalized twice. - assertEquals(1, cookies.length); + assertEquals(1, cookies.size()); } } }); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyProtocolTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyProtocolTest.java index 52b3819c234..b26c9b25218 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyProtocolTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyProtocolTest.java @@ -13,14 +13,11 @@ package org.eclipse.jetty.client; -import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.concurrent.ThreadLocalRandom; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.http.HttpHeader; @@ -32,8 +29,10 @@ import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.ProxyConnectionFactory; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -86,13 +85,13 @@ public class HttpClientProxyProtocolTest @Test public void testClientProxyProtocolV1() throws Exception { - startServer(new EmptyServerHandler() + startServer(new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString()); - response.getOutputStream().print(request.getRemotePort()); + response.write(true, callback, String.valueOf(Request.getRemotePort(request))); } }); startClient(); @@ -124,13 +123,13 @@ public class HttpClientProxyProtocolTest @Test public void testClientProxyProtocolV2() throws Exception { - startServer(new EmptyServerHandler() + startServer(new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString()); - response.getOutputStream().print(request.getRemotePort()); + response.write(true, callback, String.valueOf(Request.getRemotePort(request))); } }); startClient(); @@ -165,21 +164,21 @@ public class HttpClientProxyProtocolTest int typeTLS = 0x20; String tlsVersion = "TLSv1.3"; byte[] tlsVersionBytes = tlsVersion.getBytes(StandardCharsets.US_ASCII); - startServer(new EmptyServerHandler() + startServer(new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - EndPoint endPoint = jettyRequest.getHttpChannel().getEndPoint(); + EndPoint endPoint = request.getConnectionMetaData().getConnection().getEndPoint(); assertTrue(endPoint instanceof ProxyConnectionFactory.ProxyEndPoint); ProxyConnectionFactory.ProxyEndPoint proxyEndPoint = (ProxyConnectionFactory.ProxyEndPoint)endPoint; - if (target.equals("/tls_version")) + if (request.getPathInContext().equals("/tls_version")) { assertNotNull(proxyEndPoint.getTLV(typeTLS)); assertEquals(tlsVersion, proxyEndPoint.getAttribute(ProxyConnectionFactory.TLS_VERSION)); } response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString()); - response.getOutputStream().print(request.getRemotePort()); + response.write(true, callback, String.valueOf(Request.getRemotePort(request))); } }); startClient(); @@ -219,13 +218,13 @@ public class HttpClientProxyProtocolTest @Test public void testProxyProtocolWrappingHTTPProxy() throws Exception { - startServer(new EmptyServerHandler() + startServer(new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString()); - response.getOutputStream().print(request.getRemotePort()); + response.write(true, callback, String.valueOf(Request.getRemotePort(request))); } }); startClient(); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java index d8ab7707a3b..fefe462df57 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java @@ -13,23 +13,21 @@ package org.eclipse.jetty.client; -import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.BasicAuthentication; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -43,18 +41,17 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest { final String serverHost = "server"; final int status = HttpStatus.NO_CONTENT_204; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - if (!URI.create(baseRequest.getHttpURI().toString()).isAbsolute()) - response.setStatus(HttpServletResponse.SC_USE_PROXY); - else if (serverHost.equals(request.getServerName())) + if (!URI.create(request.getHttpURI().toString()).isAbsolute()) + response.setStatus(HttpStatus.USE_PROXY_305); + else if (serverHost.equals(org.eclipse.jetty.server.Request.getServerName(request))) response.setStatus(status); else - response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); + response.setStatus(HttpStatus.NOT_ACCEPTABLE_406); } }); @@ -80,17 +77,16 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest final String serverHost = "server"; final String realm = "test_realm"; final int status = HttpStatus.NO_CONTENT_204; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - String authorization = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString()); + String authorization = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION.asString()); if (authorization == null) { response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407); - response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + realm + "\""); + response.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + realm + "\""); } else { @@ -162,19 +158,20 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest int serverPort = HttpScheme.HTTP.is(scenario.getScheme()) ? 80 : 443; String realm = "test_realm"; int status = HttpStatus.NO_CONTENT_204; - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback) { - baseRequest.setHandled(true); + String target = request.getPathInContext(); if (target.startsWith("/proxy")) { - String authorization = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString()); + String authorization = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION.asString()); if (authorization == null) { response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407); - response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + realm + "\""); + response.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + realm + "\""); + callback.succeeded(); } else { @@ -185,18 +182,21 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest if (credentials.equals(attempt)) { // Change also the host, to verify that proxy authentication works in this case too. - response.sendRedirect(scenario.getScheme() + "://127.0.0.1:" + serverPort + "/server"); + Response.sendRedirect(request, response, callback, scenario.getScheme() + "://127.0.0.1:" + serverPort + "/server"); + return; } } + callback.succeeded(); } } else if (target.startsWith("/server")) { response.setStatus(status); + callback.succeeded(); } else { - response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500); + Response.writeError(request, response, callback, HttpStatus.INTERNAL_SERVER_ERROR_500); } } }); @@ -254,25 +254,24 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest String proxyRealm = "proxyRealm"; String serverRealm = "serverRealm"; int status = HttpStatus.NO_CONTENT_204; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - String authorization = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString()); + String authorization = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION.asString()); if (authorization == null) { response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407); - response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + proxyRealm + "\""); + response.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + proxyRealm + "\""); } else { - authorization = request.getHeader(HttpHeader.AUTHORIZATION.asString()); + authorization = request.getHeaders().get(HttpHeader.AUTHORIZATION.asString()); if (authorization == null) { response.setStatus(HttpStatus.UNAUTHORIZED_401); - response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Basic realm=\"" + serverRealm + "\""); + response.getHeaders().put(HttpHeader.WWW_AUTHENTICATE, "Basic realm=\"" + serverRealm + "\""); } else { @@ -327,25 +326,24 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest String proxyRealm = "proxyRealm"; String serverRealm = "serverRealm"; int status = HttpStatus.NO_CONTENT_204; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - String authorization = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString()); + String authorization = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION.asString()); if (authorization == null) { response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407); - response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + proxyRealm + "\""); + response.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + proxyRealm + "\""); } else { - authorization = request.getHeader(HttpHeader.AUTHORIZATION.asString()); + authorization = request.getHeaders().get(HttpHeader.AUTHORIZATION.asString()); if (authorization == null) { response.setStatus(HttpStatus.UNAUTHORIZED_401); - response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Basic realm=\"" + serverRealm + "\""); + response.getHeaders().put(HttpHeader.WWW_AUTHENTICATE, "Basic realm=\"" + serverRealm + "\""); } else { diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java index 3bf406a5878..17262dd73ac 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java @@ -13,7 +13,8 @@ package org.eclipse.jetty.client; -import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.InetAddress; import java.net.URLDecoder; import java.net.UnknownHostException; @@ -27,9 +28,6 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; @@ -37,8 +35,11 @@ import org.eclipse.jetty.client.util.ByteBufferRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.toolchain.test.IO; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Fields; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.params.ParameterizedTest; @@ -271,10 +272,10 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { response.setStatus(303); - response.setHeader("Location", "ssh://localhost:" + connector.getLocalPort() + "/path"); + response.getHeaders().put("Location", "ssh://localhost:" + connector.getLocalPort() + "/path"); } }); @@ -439,23 +440,23 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest public void testRedirectWithCorruptedBody(Scenario scenario) throws Exception { byte[] bytes = "ok".getBytes(StandardCharsets.UTF_8); - start(scenario, new EmptyServerHandler() + start(scenario, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - if (target.startsWith("/redirect")) + if (request.getPathInContext().startsWith("/redirect")) { response.setStatus(HttpStatus.SEE_OTHER_303); - response.setHeader(HttpHeader.LOCATION.asString(), scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/ok"); + response.getHeaders().put(HttpHeader.LOCATION, scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/ok"); // Say that we send gzipped content, but actually don't. - response.setHeader(HttpHeader.CONTENT_ENCODING.asString(), "gzip"); - response.getOutputStream().write("redirect".getBytes(StandardCharsets.UTF_8)); + response.getHeaders().put(HttpHeader.CONTENT_ENCODING, "gzip"); + response.write(true, callback, ByteBuffer.wrap("redirect".getBytes(StandardCharsets.UTF_8))); } else { response.setStatus(HttpStatus.OK_200); - response.getOutputStream().write(bytes); + response.write(true, callback, ByteBuffer.wrap(bytes)); } } }); @@ -477,10 +478,10 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { response.setStatus(HttpStatus.SEE_OTHER_303); - response.setHeader(HttpHeader.LOCATION.asString(), request.getRequestURI()); + response.getHeaders().put(HttpHeader.LOCATION, request.getHttpURI().asString()); } }); @@ -499,13 +500,13 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { try { Thread.sleep(200); response.setStatus(HttpStatus.SEE_OTHER_303); - response.setHeader(HttpHeader.LOCATION.asString(), "/" + counter.getAndIncrement()); + response.getHeaders().put(HttpHeader.LOCATION, "/" + counter.getAndIncrement()); } catch (InterruptedException x) { @@ -534,48 +535,35 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { + String target = request.getPathInContext(); if ("/one".equals(target)) { response.setStatus(HttpStatus.SEE_OTHER_303); - response.setHeader(HttpHeader.LOCATION.asString(), scenario.getScheme() + "://127.0.0.1:" + connector.getLocalPort() + "/two"); + response.getHeaders().put(HttpHeader.LOCATION, scenario.getScheme() + "://127.0.0.1:" + connector.getLocalPort() + "/two"); } else if ("/two".equals(target)) { - try - { - // Send another request to "localhost", therefore reusing the - // connection used for the first request, it must timeout. - CountDownLatch latch = new CountDownLatch(1); - client.newRequest("localhost", connector.getLocalPort()) - .scheme(scenario.getScheme()) - .path("/three") - .timeout(timeout, TimeUnit.MILLISECONDS) - .send(result -> - { - if (result.getFailure() instanceof TimeoutException) - latch.countDown(); - }); - // Wait for the request to fail as it should. - assertTrue(latch.await(2 * timeout, TimeUnit.MILLISECONDS)); - } - catch (Throwable x) - { - throw new ServletException(x); - } + // Send another request to "localhost", therefore reusing the + // connection used for the first request, it must timeout. + CountDownLatch latch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scenario.getScheme()) + .path("/three") + .timeout(timeout, TimeUnit.MILLISECONDS) + .send(result -> + { + if (result.getFailure() instanceof TimeoutException) + latch.countDown(); + }); + // Wait for the request to fail as it should. + assertTrue(latch.await(2 * timeout, TimeUnit.MILLISECONDS)); } else if ("/three".equals(target)) { - try - { - // The third request must timeout. - Thread.sleep(2 * timeout); - } - catch (InterruptedException x) - { - throw new ServletException(x); - } + // The third request must timeout. + Thread.sleep(2 * timeout); } } }); @@ -597,31 +585,25 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { - try + String serverURI = scenario.getScheme() + "://localhost:" + connector.getLocalPort(); + String target = request.getPathInContext(); + if ("/one".equals(target)) { - String serverURI = scenario.getScheme() + "://localhost:" + connector.getLocalPort(); - if ("/one".equals(target)) - { - Thread.sleep(timeout); - response.setStatus(HttpStatus.SEE_OTHER_303); - response.setHeader(HttpHeader.LOCATION.asString(), serverURI + "/two"); - } - else if ("/two".equals(target)) - { - Thread.sleep(timeout); - response.setStatus(HttpStatus.SEE_OTHER_303); - response.setHeader(HttpHeader.LOCATION.asString(), serverURI + "/three"); - } - else if ("/three".equals(target)) - { - Thread.sleep(2 * timeout); - } + Thread.sleep(timeout); + response.setStatus(HttpStatus.SEE_OTHER_303); + response.getHeaders().put(HttpHeader.LOCATION, serverURI + "/two"); } - catch (InterruptedException x) + else if ("/two".equals(target)) { - throw new ServletException(x); + Thread.sleep(timeout); + response.setStatus(HttpStatus.SEE_OTHER_303); + response.getHeaders().put(HttpHeader.LOCATION, serverURI + "/three"); + } + else if ("/three".equals(target)) + { + Thread.sleep(2 * timeout); } } }); @@ -688,34 +670,38 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest private static class RedirectHandler extends EmptyServerHandler { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Exception { try { - String[] paths = target.split("/", 4); + Fields fields = Request.extractQueryParameters(request); + + String[] paths = request.getPathInContext().split("/", 4); int status = Integer.parseInt(paths[1]); response.setStatus(status); String host = paths[2]; String path = paths[3]; - boolean relative = Boolean.parseBoolean(request.getParameter("relative")); - String location = relative ? "" : request.getScheme() + "://" + host + ":" + request.getServerPort(); + boolean relative = Boolean.parseBoolean(fields.getValue("relative")); + String location = relative ? "" : request.getHttpURI().getScheme() + "://" + host + ":" + Request.getServerPort(request); location += "/" + path; - if (Boolean.parseBoolean(request.getParameter("decode"))) - location = URLDecoder.decode(location, "UTF-8"); + if (Boolean.parseBoolean(fields.getValue("decode"))) + location = URLDecoder.decode(location, StandardCharsets.UTF_8); - response.setHeader("Location", location); + response.getHeaders().put("Location", location); - if (Boolean.parseBoolean(request.getParameter("close"))) - response.setHeader("Connection", "close"); + if (Boolean.parseBoolean(fields.getValue("close"))) + response.getHeaders().put("Connection", "close"); } catch (NumberFormatException x) { response.setStatus(200); // Echo content back - IO.copy(request.getInputStream(), response.getOutputStream()); + InputStream inputStream = Request.asInputStream(request); + OutputStream outputStream = org.eclipse.jetty.server.Response.asOutputStream(response); + IO.copy(inputStream, outputStream); } } } diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java index d8ad678c430..0f05974306a 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java @@ -68,6 +68,7 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; @@ -1065,6 +1066,7 @@ public class HttpClientTLSTest } @Test + @Disabled("fix this test: investigate the difference between client and server bytes") public void testBytesInBytesOut() throws Exception { // Two connections will be closed: SslConnection and HttpConnection. diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index 9f852407399..c0576b903ff 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -25,14 +25,11 @@ import java.net.URI; import java.net.URLEncoder; import java.net.UnknownHostException; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; @@ -46,13 +43,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.LongConsumer; -import jakarta.servlet.AsyncContext; -import jakarta.servlet.DispatcherType; -import jakarta.servlet.ReadListener; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Destination; @@ -78,12 +68,15 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.internal.HttpChannelState; import org.eclipse.jetty.toolchain.test.Net; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.Promise; @@ -94,6 +87,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; @@ -183,13 +177,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testGETResponseWithContent(Scenario scenario) throws Exception { byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7}; - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - response.getOutputStream().write(data); - baseRequest.setHandled(true); + response.write(true, callback, ByteBuffer.wrap(data)); } }); @@ -208,30 +201,29 @@ public class HttpClientTest extends AbstractHttpClientServerTest { String paramName1 = "a"; String paramName2 = "b"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) throws Throwable { - response.setCharacterEncoding("UTF-8"); - ServletOutputStream output = response.getOutputStream(); - String paramValue1 = request.getParameter(paramName1); - output.write(paramValue1.getBytes(StandardCharsets.UTF_8)); - String paramValue2 = request.getParameter(paramName2); + response.setContentType("text/plain;charset=UTF-8"); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + String paramValue1 = fields.getValue(paramName1); + org.eclipse.jetty.server.Response.write(response, false, UTF_8.encode(paramValue1)); + String paramValue2 = fields.getValue(paramName2); assertEquals("", paramValue2); - output.write("empty".getBytes(StandardCharsets.UTF_8)); - baseRequest.setHandled(true); + org.eclipse.jetty.server.Response.write(response, true, UTF_8.encode("empty")); } }); String value1 = "\u20AC"; - String paramValue1 = URLEncoder.encode(value1, StandardCharsets.UTF_8); + String paramValue1 = URLEncoder.encode(value1, UTF_8); String query = paramName1 + "=" + paramValue1 + "&" + paramName2; ContentResponse response = client.GET(scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/?" + query); assertNotNull(response); assertEquals(200, response.getStatus()); - String content = new String(response.getContent(), StandardCharsets.UTF_8); + String content = new String(response.getContent(), UTF_8); assertEquals(value1 + "empty", content); } @@ -241,36 +233,36 @@ public class HttpClientTest extends AbstractHttpClientServerTest { String paramName1 = "a"; String paramName2 = "b"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) throws Throwable { - response.setCharacterEncoding("UTF-8"); - ServletOutputStream output = response.getOutputStream(); - String[] paramValues1 = request.getParameterValues(paramName1); + response.setContentType("text/plain;charset=UTF-8"); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + + List paramValues1 = fields.getValues(paramName1); for (String paramValue : paramValues1) { - output.write(paramValue.getBytes(StandardCharsets.UTF_8)); + org.eclipse.jetty.server.Response.write(response, false, UTF_8.encode(paramValue)); } - String paramValue2 = request.getParameter(paramName2); - output.write(paramValue2.getBytes(StandardCharsets.UTF_8)); - baseRequest.setHandled(true); + String paramValue2 = fields.getValue(paramName2); + org.eclipse.jetty.server.Response.write(response, true, UTF_8.encode(paramValue2)); } }); String value11 = "\u20AC"; String value12 = "\u20AA"; String value2 = "&"; - String paramValue11 = URLEncoder.encode(value11, StandardCharsets.UTF_8); - String paramValue12 = URLEncoder.encode(value12, StandardCharsets.UTF_8); - String paramValue2 = URLEncoder.encode(value2, StandardCharsets.UTF_8); + String paramValue11 = URLEncoder.encode(value11, UTF_8); + String paramValue12 = URLEncoder.encode(value12, UTF_8); + String paramValue2 = URLEncoder.encode(value2, UTF_8); String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2; ContentResponse response = client.GET(scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/?" + query); assertNotNull(response); assertEquals(200, response.getStatus()); - String content = new String(response.getContent(), StandardCharsets.UTF_8); + String content = new String(response.getContent(), UTF_8); assertEquals(value11 + value12 + value2, content); } @@ -280,18 +272,17 @@ public class HttpClientTest extends AbstractHttpClientServerTest { String paramName = "a"; String paramValue = "\u20AC"; - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - String value = request.getParameter(paramName); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + String value = fields.getValue(paramName); if (paramValue.equals(value)) { - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/plain"); - response.getOutputStream().print(value); + response.setContentType("text/plain;charset=UTF-8"); + response.write(true, callback, value); } } }); @@ -303,7 +294,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest assertNotNull(response); assertEquals(200, response.getStatus()); - assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8)); + assertEquals(paramValue, new String(response.getContent(), UTF_8)); } @ParameterizedTest @@ -312,19 +303,18 @@ public class HttpClientTest extends AbstractHttpClientServerTest { String paramName = "a"; String paramValue = "\u20AC"; - String encodedParamValue = URLEncoder.encode(paramValue, StandardCharsets.UTF_8); - start(scenario, new AbstractHandler() + String encodedParamValue = URLEncoder.encode(paramValue, UTF_8); + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - String value = request.getParameter(paramName); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + String value = fields.getValue(paramName); if (paramValue.equals(value)) { - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/plain"); - response.getOutputStream().print(value); + response.setContentType("text/plain;charset=UTF-8"); + response.write(true, callback, value); } } }); @@ -337,7 +327,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest assertNotNull(response); assertEquals(200, response.getStatus()); - assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8)); + assertEquals(paramValue, new String(response.getContent(), UTF_8)); } @ParameterizedTest @@ -347,19 +337,18 @@ public class HttpClientTest extends AbstractHttpClientServerTest byte[] content = {0, 1, 2, 3}; String paramName = "a"; String paramValue = "\u20AC"; - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - consume(request.getInputStream(), true); - String value = request.getParameter(paramName); + Content.consumeAll(request); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + String value = fields.getValue(paramName); if (paramValue.equals(value)) { - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/plain"); - response.getOutputStream().write(content); + response.setContentType("text/plain;charset=UTF-8"); + response.write(true, callback, ByteBuffer.wrap(content)); } } }); @@ -379,13 +368,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testPOSTWithContentNotifiesRequestContentListener(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) throws Throwable { - baseRequest.setHandled(true); - consume(request.getInputStream(), true); + Content.consumeAll(request); } }); @@ -410,13 +398,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testPOSTWithContentTracksProgress(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) throws Throwable { - baseRequest.setHandled(true); - consume(request.getInputStream(), true); + Content.consumeAll(request); } }); @@ -491,21 +478,19 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testQueuedRequestIsSentWhenPreviousRequestClosedConnection(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) { - if (target.endsWith("/one")) - baseRequest.getHttpChannel().getEndPoint().close(); - else - baseRequest.setHandled(true); + if (request.getPathInContext().endsWith("/one")) + request.getConnectionMetaData().getConnection().getEndPoint().close(); } }); client.setMaxConnectionsPerDestination(1); - try (StacklessLogging ignored = new StacklessLogging(org.eclipse.jetty.server.HttpChannel.class)) + try (StacklessLogging ignored = new StacklessLogging(HttpChannelState.class)) { CountDownLatch latch = new CountDownLatch(2); client.newRequest("localhost", connector.getLocalPort()) @@ -532,24 +517,15 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testExchangeIsCompleteOnlyWhenBothRequestAndResponseAreComplete(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) throws Throwable { - baseRequest.setHandled(true); response.setContentLength(0); - response.setStatus(200); - response.flushBuffer(); - - byte[] buffer = new byte[1024]; - InputStream in = request.getInputStream(); - while (true) - { - int read = in.read(buffer); - if (read < 0) - break; - } + response.setStatus(HttpStatus.OK_200); + org.eclipse.jetty.server.Response.write(response, true); + Content.consumeAll(request); } }); @@ -611,13 +587,15 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testExchangeIsCompleteWhenRequestFailsMidwayWithResponse(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) throws Throwable { // Echo back - IO.copy(request.getInputStream(), response.getOutputStream()); + InputStream inputStream = org.eclipse.jetty.server.Request.asInputStream(request); + OutputStream outputStream = org.eclipse.jetty.server.Response.asOutputStream(response); + IO.copy(inputStream, outputStream); } }); @@ -707,13 +685,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testHeaderProcessing(Scenario scenario) throws Exception { String headerName = "X-Header-Test"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) { - baseRequest.setHandled(true); - response.setHeader(headerName, "X"); + response.getHeaders().put(headerName, "X"); } }); @@ -765,13 +742,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testHEADWithResponseContentLength(Scenario scenario) throws Exception { int length = 1024; - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - response.getOutputStream().write(new byte[length]); + response.write(true, callback, ByteBuffer.wrap(new byte[length])); } }); @@ -870,13 +846,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testCustomUserAgent(Scenario scenario) throws Exception { String userAgent = "Test/1.0"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) { - baseRequest.setHandled(true); - ArrayList userAgents = Collections.list(request.getHeaders("User-Agent")); + List userAgents = request.getHeaders().getValuesList(HttpHeader.USER_AGENT); assertEquals(1, userAgents.size()); assertEquals(userAgent, userAgents.get(0)); } @@ -903,14 +878,13 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testUserAgentCanBeRemoved(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) { - baseRequest.setHandled(true); - ArrayList userAgents = Collections.list(request.getHeaders("User-Agent")); - if ("/ua".equals(target)) + List userAgents = request.getHeaders().getValuesList(HttpHeader.USER_AGENT); + if ("/ua".equals(request.getPathInContext())) assertEquals(1, userAgents.size()); else assertEquals(0, userAgents.size()); @@ -1112,13 +1086,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest { byte[] content = new byte[512]; new Random().nextBytes(content); - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - response.getOutputStream().write(content); + response.write(true, callback, ByteBuffer.wrap(content)); } }); @@ -1154,13 +1127,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testCustomHostHeader(Scenario scenario) throws Exception { String host = "localhost"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) { - baseRequest.setHandled(true); - assertEquals(host, request.getServerName()); + assertEquals(host, org.eclipse.jetty.server.Request.getServerName(request)); } }); @@ -1176,16 +1148,16 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testHTTP10WithKeepAliveAndContentLength(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - // Send the headers at this point, then write the content - byte[] content = "TEST".getBytes(StandardCharsets.UTF_8); + // Send the headers at this point, then write the content. + byte[] content = "TEST".getBytes(UTF_8); response.setContentLength(content.length); - response.flushBuffer(); - response.getOutputStream().write(content); + org.eclipse.jetty.server.Response.write(response, false); + response.write(true, callback, ByteBuffer.wrap(content)); } }); @@ -1204,14 +1176,14 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testHTTP10WithKeepAliveAndNoContentLength(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { // Send the headers at this point, then write the content - response.flushBuffer(); - response.getOutputStream().print("TEST"); + org.eclipse.jetty.server.Response.write(response, false); + response.write(true, callback, "TEST"); } }); @@ -1261,13 +1233,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testLongPollIsAbortedWhenClientIsStopped(Scenario scenario) throws Exception { CountDownLatch latch = new CountDownLatch(1); - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - request.startAsync(); + // Do not complete the callback. latch.countDown(); } }); @@ -1338,16 +1309,15 @@ public class HttpClientTest extends AbstractHttpClientServerTest byte[] data = new byte[length]; new Random().nextBytes(data); - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); // Send Connection: close to avoid that the server chunks the content with HTTP 1.1. if (version.compareTo(HttpVersion.HTTP_1_0) > 0) - response.setHeader("Connection", "close"); - response.getOutputStream().write(data); + response.getHeaders().put("Connection", "close"); + response.write(true, callback, ByteBuffer.wrap(data)); } }); @@ -1374,19 +1344,22 @@ public class HttpClientTest extends AbstractHttpClientServerTest { int maxRetries = 3; AtomicInteger requests = new AtomicInteger(); - start(scenario, new AbstractHandler() + CountDownLatch latch = new CountDownLatch(2); + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { + Content.consumeAll(request); int count = requests.incrementAndGet(); if (count == maxRetries) - baseRequest.setHandled(true); - consume(request.getInputStream(), true); + latch.countDown(); + else + response.setStatus(HttpStatus.NOT_FOUND_404); + callback.succeeded(); } }); - CountDownLatch latch = new CountDownLatch(1); new RetryListener(client, scenario.getScheme(), "localhost", connector.getLocalPort(), maxRetries) { @Override @@ -1403,14 +1376,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testCompleteNotInvokedUntilContentConsumed(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - ServletOutputStream output = response.getOutputStream(); - output.write(new byte[1024]); + response.write(true, callback, ByteBuffer.allocate(1024)); } }); @@ -1453,14 +1424,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testRequestSentOnlyAfterConnectionOpen(Scenario scenario) throws Exception { - startServer(scenario, new AbstractHandler() - { - @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) - { - baseRequest.setHandled(true); - } - }); + startServer(scenario, new EmptyServerHandler()); AtomicBoolean open = new AtomicBoolean(); ClientConnector clientConnector = new ClientConnector(); @@ -1530,7 +1494,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest "HTTP/1.0 200 OK\r\n" + "\r\n"; OutputStream output = socket.getOutputStream(); - output.write(httpResponse.getBytes(StandardCharsets.UTF_8)); + output.write(httpResponse.getBytes(UTF_8)); output.flush(); ContentResponse response = listener.get(5, TimeUnit.SECONDS); @@ -1557,7 +1521,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest "HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n" + "\r\n"; - output.write(httpResponse.getBytes(StandardCharsets.UTF_8)); + output.write(httpResponse.getBytes(UTF_8)); output.flush(); listener.get(5, TimeUnit.SECONDS); @@ -1570,13 +1534,13 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testIPv6HostWithHTTP10(Scenario scenario) throws Exception { Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable()); - start(scenario, new EmptyServerHandler() + start(scenario, new Handler.Processor() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { response.setContentType("text/plain"); - response.getOutputStream().print(request.getServerName()); + response.write(true, callback, org.eclipse.jetty.server.Request.getServerName(request)); } }); @@ -1648,13 +1612,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testHostWithHTTP10(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) { - baseRequest.setHandled(true); - assertThat(request.getHeader("Host"), Matchers.notNullValue()); + assertThat(request.getHeaders().get("Host"), Matchers.notNullValue()); } }); @@ -1702,7 +1665,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest "\r\n" + "No Content"; OutputStream output = socket.getOutputStream(); - output.write(httpResponse.getBytes(StandardCharsets.UTF_8)); + output.write(httpResponse.getBytes(UTF_8)); output.flush(); ContentResponse response = listener.get(5, TimeUnit.SECONDS); @@ -1725,7 +1688,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest "HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n" + "\r\n"; - output.write(httpResponse.getBytes(StandardCharsets.UTF_8)); + output.write(httpResponse.getBytes(UTF_8)); output.flush(); response = listener.get(5, TimeUnit.SECONDS); @@ -1740,14 +1703,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest { byte[] bytes = new byte[1024]; new Random().nextBytes(bytes); - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - ServletOutputStream output = response.getOutputStream(); - output.write(bytes); + response.write(true, callback, ByteBuffer.wrap(bytes)); } }); @@ -1832,7 +1793,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest try (Socket socket = server.accept()) { OutputStream output = socket.getOutputStream(); - output.write(bytesFromServer.getBytes(StandardCharsets.UTF_8)); + output.write(bytesFromServer.getBytes(UTF_8)); output.flush(); } @@ -1844,6 +1805,8 @@ public class HttpClientTest extends AbstractHttpClientServerTest } } + // TODO: move this test to Servlet tests. +/* @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) public void testHttpParserCloseWithAsyncReads(Scenario scenario) throws Exception @@ -1919,7 +1882,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest assertTrue(clientResultLatch.await(5, TimeUnit.SECONDS), "clientResultLatch didn't finish"); assertTrue(serverOnErrorLatch.await(5, TimeUnit.SECONDS), "serverOnErrorLatch didn't finish"); } - +*/ @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) public void testBindAddress(Scenario scenario) throws Exception @@ -1928,9 +1891,9 @@ public class HttpClientTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) { - assertEquals(bindAddress, request.getRemoteAddr()); + assertEquals(bindAddress, org.eclipse.jetty.server.Request.getRemoteAddr(request)); } }); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java index 4ffa3ce06ce..3879d9b7afb 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientURITest.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.client; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.InetSocketAddress; @@ -27,16 +26,13 @@ import java.util.Locale; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.toolchain.test.Net; import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.StringUtil; @@ -153,13 +149,12 @@ public class HttpClientURITest extends AbstractHttpClientServerTest public void testPath(Scenario scenario) throws Exception { final String path = "/path"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals(path, request.getRequestURI()); + assertEquals(path, request.getPathInContext()); } }); @@ -187,14 +182,13 @@ public class HttpClientURITest extends AbstractHttpClientServerTest String value = "1"; final String query = name + "=" + value; final String path = "/path"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals(path, request.getRequestURI()); - assertEquals(query, request.getQueryString()); + assertEquals(path, request.getPathInContext()); + assertEquals(query, request.getHttpURI().getQuery()); } }); @@ -225,14 +219,13 @@ public class HttpClientURITest extends AbstractHttpClientServerTest final String query = name + "=" + value; final String path = "/path"; String pathQuery = path + "?" + query; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals(path, request.getRequestURI()); - assertEquals(query, request.getQueryString()); + assertEquals(path, request.getPathInContext()); + assertEquals(query, request.getHttpURI().getQuery()); } }); @@ -265,14 +258,13 @@ public class HttpClientURITest extends AbstractHttpClientServerTest final String query = name1 + "=" + value1 + "&" + name2 + "=" + value2; final String path = "/path"; String pathQuery = path + "?" + query; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals(path, request.getRequestURI()); - assertEquals(query, request.getQueryString()); + assertEquals(path, request.getPathInContext()); + assertEquals(query, request.getHttpURI().getQuery()); } }); @@ -301,23 +293,23 @@ public class HttpClientURITest extends AbstractHttpClientServerTest { final String name1 = "a"; final String value1 = "\u20AC"; - final String encodedValue1 = URLEncoder.encode(value1, "UTF-8"); + final String encodedValue1 = URLEncoder.encode(value1, StandardCharsets.UTF_8); final String name2 = "b"; final String value2 = "\u00A5"; - String encodedValue2 = URLEncoder.encode(value2, "UTF-8"); + String encodedValue2 = URLEncoder.encode(value2, StandardCharsets.UTF_8); final String query = name1 + "=" + encodedValue1 + "&" + name2 + "=" + encodedValue2; final String path = "/path"; String pathQuery = path + "?" + query; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals(path, request.getRequestURI()); - assertEquals(query, request.getQueryString()); - assertEquals(value1, request.getParameter(name1)); - assertEquals(value2, request.getParameter(name2)); + assertEquals(path, request.getPathInContext()); + assertEquals(query, request.getHttpURI().getQuery()); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + assertEquals(value1, fields.getValue(name1)); + assertEquals(value2, fields.getValue(name2)); } }); @@ -347,14 +339,13 @@ public class HttpClientURITest extends AbstractHttpClientServerTest final String path = "/path"; final String query = "="; // Bogus query String pathQuery = path + "?" + query; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals(path, request.getRequestURI()); - assertEquals(query, request.getQueryString()); + assertEquals(path, request.getPathInContext()); + assertEquals(query, request.getHttpURI().getQuery()); } }); @@ -381,14 +372,13 @@ public class HttpClientURITest extends AbstractHttpClientServerTest final String path = "/path"; final String query = "=1"; // Bogus query String pathQuery = path + "?" + query; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals(path, request.getRequestURI()); - assertEquals(query, request.getQueryString()); + assertEquals(path, request.getPathInContext()); + assertEquals(query, request.getHttpURI().getQuery()); } }); @@ -414,14 +404,14 @@ public class HttpClientURITest extends AbstractHttpClientServerTest { final String name1 = "a"; final String name2 = "A"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals(name1, request.getParameter(name1)); - assertEquals(name2, request.getParameter(name2)); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + assertEquals(name1, fields.getValue(name1)); + assertEquals(name2, fields.getValue(name2)); } }); @@ -443,14 +433,14 @@ public class HttpClientURITest extends AbstractHttpClientServerTest final String rawValue = "Hello%20World"; final String rawQuery = name + "=" + rawValue; final String value = "Hello World"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals(rawQuery, request.getQueryString()); - assertEquals(value, request.getParameter(name)); + assertEquals(rawQuery, request.getHttpURI().getQuery()); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + assertEquals(value, fields.getValue(name)); } }); @@ -472,14 +462,14 @@ public class HttpClientURITest extends AbstractHttpClientServerTest final String rawValue = "Hello%20World"; final String rawQuery = name + "=" + rawValue; final String value = "Hello World"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals(rawQuery, request.getQueryString()); - assertEquals(value, request.getParameter(name)); + assertEquals(rawQuery, request.getHttpURI().getQuery()); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + assertEquals(value, fields.getValue(name)); } }); @@ -504,17 +494,17 @@ public class HttpClientURITest extends AbstractHttpClientServerTest final String rawQuery1 = name1 + "=" + rawValue1; final String value1 = "Hello World"; final String value2 = "alfa omega"; - final String encodedQuery2 = name2 + "=" + URLEncoder.encode(value2, "UTF-8"); + final String encodedQuery2 = name2 + "=" + URLEncoder.encode(value2, StandardCharsets.UTF_8); final String query = rawQuery1 + "&" + encodedQuery2; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals(query, request.getQueryString()); - assertEquals(value1, request.getParameter(name1)); - assertEquals(value2, request.getParameter(name2)); + assertEquals(query, request.getHttpURI().getQuery()); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + assertEquals(value1, fields.getValue(name1)); + assertEquals(value2, fields.getValue(name2)); } }); @@ -534,14 +524,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testSchemeIsCaseInsensitive(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() - { - @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - baseRequest.setHandled(true); - } - }); + start(scenario, new EmptyServerHandler()); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme().toUpperCase(Locale.ENGLISH)) @@ -555,14 +538,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testHostIsCaseInsensitive(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() - { - @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - baseRequest.setHandled(true); - } - }); + start(scenario, new EmptyServerHandler()); ContentResponse response = client.newRequest("LOCALHOST", connector.getLocalPort()) .scheme(scenario.getScheme()) @@ -576,14 +552,13 @@ public class HttpClientURITest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testAsteriskFormTarget(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); - assertEquals("*", target); - assertEquals("*", request.getPathInfo()); + assertEquals("*", request.getHttpURI().getPath()); + assertEquals("*", request.getPathInContext()); } }); @@ -610,7 +585,7 @@ public class HttpClientURITest extends AbstractHttpClientServerTest private ServerSocket serverSocket; private int port; - private IDNRedirectServer(final String location) throws IOException + private IDNRedirectServer(final String location) { this.location = location; } diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java index c5adf88b7cf..c34d4181a78 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java @@ -13,8 +13,6 @@ package org.eclipse.jetty.client; -import java.io.IOException; -import java.io.InputStream; import java.util.Map; import java.util.Random; import java.util.concurrent.CountDownLatch; @@ -22,17 +20,17 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.http.HttpChannelOverHTTP; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.http.HttpConnectionOverHTTP; import org.eclipse.jetty.client.util.BytesRequestContent; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.Content; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.Blocking; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.Test; @@ -54,24 +52,33 @@ public class HttpClientUploadDuringServerShutdownTest ServerConnector connector = new ServerConnector(server); connector.setPort(8888); server.addConnector(connector); - server.setHandler(new AbstractHandler() + server.setHandler(new EmptyServerHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(Request request, Response response) throws Throwable { - baseRequest.setHandled(true); - byte[] buffer = new byte[1024]; - InputStream input = request.getInputStream(); while (true) { - int read = input.read(buffer); - if (read < 0) - break; - long now = System.nanoTime(); - long sleep = TimeUnit.MICROSECONDS.toNanos(1); - while (System.nanoTime() < now + sleep) + Content content = request.readContent(); + if (content == null) { - // Wait. + try (Blocking.Runnable blocker = _blocking.runnable()) + { + request.demandContent(blocker); + } + } + else + { + if (content.hasRemaining()) + content.release(); + if (content.isLast()) + break; + long now = System.nanoTime(); + long sleep = TimeUnit.MICROSECONDS.toNanos(1); + while (System.nanoTime() < now + sleep) + { + Thread.onSpinWait(); + } } } } @@ -131,13 +138,12 @@ public class HttpClientUploadDuringServerShutdownTest Server server = new Server(serverThreads); ServerConnector connector = new ServerConnector(server); server.addConnector(connector); - server.setHandler(new AbstractHandler() + server.setHandler(new EmptyServerHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, Response response) { - baseRequest.setHandled(true); - endPointRef.set(baseRequest.getHttpChannel().getEndPoint()); + endPointRef.set(request.getConnectionMetaData().getConnection().getEndPoint()); serverLatch.countDown(); } }); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java index 6bdb1ab21ab..646f7712e8a 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java @@ -20,8 +20,6 @@ import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; @@ -31,7 +29,6 @@ import org.eclipse.jetty.client.util.ByteBufferRequestContent; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; import org.slf4j.Logger; @@ -321,13 +318,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testResponseWithConnectionCloseHeaderRemovesConnection(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) { - response.setHeader("Connection", "close"); - baseRequest.setHandled(true); + response.getHeaders().put("Connection", "close"); } }); @@ -368,13 +364,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest { try (StacklessLogging ignore = new StacklessLogging(HttpConnection.class)) { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response) { - response.setHeader("Connection", "close"); - baseRequest.setHandled(true); + response.getHeaders().put("Connection", "close"); // Don't read request content; this causes the server parser to be closed } }); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java index e6d53a2fcc2..d63d110a1cf 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java @@ -19,18 +19,15 @@ import java.net.URI; import java.util.Arrays; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.HttpCookieStore; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -41,10 +38,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +@Disabled("fix this test. the problem is on the server side, which caches the cookies so different requests return the same cookies") public class HttpCookieTest extends AbstractHttpClientServerTest { - private static final Cookie[] EMPTY_COOKIES = new Cookie[0]; - @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) public void testCookieIsStored(Scenario scenario) throws Exception @@ -54,9 +50,9 @@ public class HttpCookieTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { - response.addCookie(new Cookie(name, value)); + org.eclipse.jetty.server.Response.addCookie(response, new org.eclipse.jetty.http.HttpCookie(name, value)); } }); @@ -84,12 +80,12 @@ public class HttpCookieTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { - Cookie[] cookies = request.getCookies(); + List cookies = Request.getCookies(request); assertNotNull(cookies); - assertEquals(1, cookies.length); - Cookie cookie = cookies[0]; + assertEquals(1, cookies.size()); + org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0); assertEquals(name, cookie.getName()); assertEquals(value, cookie.getValue()); } @@ -113,7 +109,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { response.addHeader("Set-Cookie", ""); } @@ -147,12 +143,12 @@ public class HttpCookieTest extends AbstractHttpClientServerTest startServer(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { - Cookie[] cookies = request.getCookies(); + List cookies = Request.getCookies(request); assertNotNull(cookies); - assertEquals(1, cookies.length); - Cookie cookie = cookies[0]; + assertEquals(1, cookies.size()); + org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0); assertEquals(name, cookie.getName()); assertEquals(value, cookie.getValue()); } @@ -181,29 +177,28 @@ public class HttpCookieTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { - int r = request.getIntHeader(headerName); + String target = request.getPathInContext(); + int r = (int)request.getHeaders().getLongField(headerName); if ("/foo".equals(target) && r == 0) { - Cookie cookie = new Cookie(cookieName, cookieValue); - response.addCookie(cookie); + org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue); + org.eclipse.jetty.server.Response.addCookie(response, cookie); } else { - Cookie[] cookies = Optional.ofNullable(request.getCookies()).orElse(EMPTY_COOKIES); + List cookies = Request.getCookies(request); switch (target) { - case "/": - case "/foo": - case "/foo/bar": - assertEquals(1, cookies.length, target); - Cookie cookie = cookies[0]; + case "/", "/foo", "/foo/bar" -> + { + assertEquals(1, cookies.size(), target); + org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0); assertEquals(cookieName, cookie.getName(), target); assertEquals(cookieValue, cookie.getValue(), target); - break; - default: - fail("Unrecognized target: " + target); + } + default -> fail("Unrecognized target: " + target); } } } @@ -237,34 +232,29 @@ public class HttpCookieTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { - int r = request.getIntHeader(headerName); + String target = request.getPathInContext(); + int r = (int)request.getHeaders().getLongField(headerName); if ("/foo/bar".equals(target) && r == 0) { - Cookie cookie = new Cookie(cookieName, cookieValue); - response.addCookie(cookie); + org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue); + org.eclipse.jetty.server.Response.addCookie(response, cookie); } else { - Cookie[] cookies = Optional.ofNullable(request.getCookies()).orElse(EMPTY_COOKIES); + List cookies = Request.getCookies(request); switch (target) { - case "/": - case "/foo": - case "/foobar": - assertEquals(0, cookies.length, target); - break; - case "/foo/": - case "/foo/bar": - case "/foo/bar/baz": - assertEquals(1, cookies.length, target); - Cookie cookie = cookies[0]; + case "/", "/foo", "/foobar" -> assertEquals(0, cookies.size(), target); + case "/foo/", "/foo/bar", "/foo/bar/baz" -> + { + assertEquals(1, cookies.size(), target); + org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0); assertEquals(cookieName, cookie.getName(), target); assertEquals(cookieValue, cookie.getValue(), target); - break; - default: - fail("Unrecognized Target: " + target); + } + default -> fail("Unrecognized Target: " + target); } } } @@ -298,34 +288,29 @@ public class HttpCookieTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { - int r = request.getIntHeader(headerName); + String target = request.getPathInContext(); + int r = (int)request.getHeaders().getLongField(headerName); if ("/foo".equals(target) && r == 0) { - Cookie cookie = new Cookie(cookieName, cookieValue); - cookie.setPath("/foo/bar"); - response.addCookie(cookie); + org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, null, "/foo/bar"); + org.eclipse.jetty.server.Response.addCookie(response, cookie); } else { - Cookie[] cookies = Optional.ofNullable(request.getCookies()).orElse(EMPTY_COOKIES); + List cookies = Request.getCookies(request); switch (target) { - case "/": - case "/foo": - case "/foo/barbaz": - assertEquals(0, cookies.length, target); - break; - case "/foo/bar": - case "/foo/bar/": - assertEquals(1, cookies.length, target); - Cookie cookie = cookies[0]; + case "/", "/foo", "/foo/barbaz" -> assertEquals(0, cookies.size(), target); + case "/foo/bar", "/foo/bar/" -> + { + assertEquals(1, cookies.size(), target); + org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0); assertEquals(cookieName, cookie.getName(), target); assertEquals(cookieValue, cookie.getValue(), target); - break; - default: - fail("Unrecognized Target: " + target); + } + default -> fail("Unrecognized Target: " + target); } } } @@ -359,34 +344,29 @@ public class HttpCookieTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { - int r = request.getIntHeader(headerName); + String target = request.getPathInContext(); + int r = (int)request.getHeaders().getLongField(headerName); if ("/foo/bar".equals(target) && r == 0) { - Cookie cookie = new Cookie(cookieName, cookieValue); - cookie.setPath("/foo"); - response.addCookie(cookie); + org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, null, "/foo"); + org.eclipse.jetty.server.Response.addCookie(response, cookie); } else { - Cookie[] cookies = Optional.ofNullable(request.getCookies()).orElse(EMPTY_COOKIES); + List cookies = Request.getCookies(request); switch (target) { - case "/": - case "/foobar": - assertEquals(0, cookies.length, target); - break; - case "/foo": - case "/foo/": - case "/foo/bar": - assertEquals(1, cookies.length, target); - Cookie cookie = cookies[0]; + case "/", "/foobar" -> assertEquals(0, cookies.size(), target); + case "/foo", "/foo/", "/foo/bar" -> + { + assertEquals(1, cookies.size(), target); + org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0); assertEquals(cookieName, cookie.getName(), target); assertEquals(cookieValue, cookie.getValue(), target); - break; - default: - fail("Unrecognized Target: " + target); + } + default -> fail("Unrecognized Target: " + target); } } } @@ -421,35 +401,31 @@ public class HttpCookieTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { - int r = request.getIntHeader(headerName); + String target = request.getPathInContext(); + int r = (int)request.getHeaders().getLongField(headerName); if ("/foo".equals(target) && r == 0) { - Cookie cookie = new Cookie(cookieName, cookieValue1); - cookie.setPath("/foo"); - response.addCookie(cookie); - cookie = new Cookie(cookieName, cookieValue2); - cookie.setPath("/foo"); - response.addCookie(cookie); + org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue1, null, "/foo"); + org.eclipse.jetty.server.Response.addCookie(response, cookie); + cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue2, null, "/foo"); + org.eclipse.jetty.server.Response.addCookie(response, cookie); } else { - Cookie[] cookies = Optional.ofNullable(request.getCookies()).orElse(EMPTY_COOKIES); + List cookies = Request.getCookies(request); switch (target) { - case "/": - assertEquals(0, cookies.length, target); - break; - case "/foo": - case "/foo/bar": - assertEquals(1, cookies.length, target); - Cookie cookie = cookies[0]; + case "/" -> assertEquals(0, cookies.size(), target); + case "/foo", "/foo/bar" -> + { + assertEquals(1, cookies.size(), target); + org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0); assertEquals(cookieName, cookie.getName(), target); assertEquals(cookieValue2, cookie.getValue(), target); - break; - default: - fail("Unrecognized Target: " + target); + } + default -> fail("Unrecognized Target: " + target); } } } @@ -484,42 +460,38 @@ public class HttpCookieTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { - int r = request.getIntHeader(headerName); + String target = request.getPathInContext(); + int r = (int)request.getHeaders().getLongField(headerName); if ("/foo".equals(target) && r == 0) { - Cookie cookie = new Cookie(cookieName, cookieValue1); - cookie.setPath("/foo"); - response.addCookie(cookie); - cookie = new Cookie(cookieName, cookieValue2); - cookie.setPath("/bar"); - response.addCookie(cookie); + org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue1, null, "/foo"); + org.eclipse.jetty.server.Response.addCookie(response, cookie); + cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue2, null, "/bar"); + org.eclipse.jetty.server.Response.addCookie(response, cookie); } else { - Cookie[] cookies = Optional.ofNullable(request.getCookies()).orElse(EMPTY_COOKIES); + List cookies = Request.getCookies(request); switch (target) { - case "/": - assertEquals(0, cookies.length, target); - break; - case "/foo": - case "/foo/bar": - assertEquals(1, cookies.length, target); - Cookie cookie1 = cookies[0]; + case "/" -> assertEquals(0, cookies.size(), target); + case "/foo", "/foo/bar" -> + { + assertEquals(1, cookies.size(), target); + org.eclipse.jetty.http.HttpCookie cookie1 = cookies.get(0); assertEquals(cookieName, cookie1.getName(), target); assertEquals(cookieValue1, cookie1.getValue(), target); - break; - case "/bar": - case "/bar/foo": - assertEquals(1, cookies.length, target); - Cookie cookie2 = cookies[0]; + } + case "/bar", "/bar/foo" -> + { + assertEquals(1, cookies.size(), target); + org.eclipse.jetty.http.HttpCookie cookie2 = cookies.get(0); assertEquals(cookieName, cookie2.getName(), target); assertEquals(cookieValue2, cookie2.getValue(), target); - break; - default: - fail("Unrecognized Target: " + target); + } + default -> fail("Unrecognized Target: " + target); } } } @@ -554,45 +526,43 @@ public class HttpCookieTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { - int r = request.getIntHeader(headerName); + String target = request.getPathInContext(); + int r = (int)request.getHeaders().getLongField(headerName); if ("/foo".equals(target) && r == 0) { - Cookie cookie = new Cookie(cookieName, cookieValue1); - cookie.setPath("/foo"); - response.addCookie(cookie); - cookie = new Cookie(cookieName, cookieValue2); - cookie.setPath("/foo/bar"); - response.addCookie(cookie); + org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue1, null, "/foo"); + org.eclipse.jetty.server.Response.addCookie(response, cookie); + cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue2, null, "/foo/bar"); + org.eclipse.jetty.server.Response.addCookie(response, cookie); } else { - Cookie[] cookies = Optional.ofNullable(request.getCookies()).orElse(EMPTY_COOKIES); + List cookies = Request.getCookies(request); switch (target) { - case "/": - assertEquals(0, cookies.length, target); - break; - case "/foo": - assertEquals(1, cookies.length, target); - Cookie cookie = cookies[0]; + case "/" -> assertEquals(0, cookies.size(), target); + case "/foo" -> + { + assertEquals(1, cookies.size(), target); + org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0); assertEquals(cookieName, cookie.getName(), target); assertEquals(cookieValue1, cookie.getValue(), target); - break; - case "/foo/bar": - assertEquals(2, cookies.length, target); - Cookie cookie1 = cookies[0]; - Cookie cookie2 = cookies[1]; + } + case "/foo/bar" -> + { + assertEquals(2, cookies.size(), target); + org.eclipse.jetty.http.HttpCookie cookie1 = cookies.get(0); + org.eclipse.jetty.http.HttpCookie cookie2 = cookies.get(1); assertEquals(cookieName, cookie1.getName(), target); assertEquals(cookieName, cookie2.getName(), target); Set values = new HashSet<>(); values.add(cookie1.getValue()); values.add(cookie2.getValue()); assertThat(target, values, containsInAnyOrder(cookieValue1, cookieValue2)); - break; - default: - fail("Unrecognized Target: " + target); + } + default -> fail("Unrecognized Target: " + target); } } } @@ -626,34 +596,29 @@ public class HttpCookieTest extends AbstractHttpClientServerTest start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, org.eclipse.jetty.server.Response response) { - int r = request.getIntHeader(headerName); + String target = request.getPathInContext(); + int r = (int)request.getHeaders().getLongField(headerName); if ("/foo/bar".equals(target) && r == 0) { - Cookie cookie = new Cookie(cookieName, cookieValue); - cookie.setPath("/foo/"); - response.addCookie(cookie); + org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, null, "/foo/"); + org.eclipse.jetty.server.Response.addCookie(response, cookie); } else { - Cookie[] cookies = Optional.ofNullable(request.getCookies()).orElse(EMPTY_COOKIES); + List cookies = Request.getCookies(request); switch (target) { - case "/": - case "/foo": - case "/foobar": - assertEquals(0, cookies.length, target); - break; - case "/foo/": - case "/foo/bar": - assertEquals(1, cookies.length, target); - Cookie cookie = cookies[0]; + case "/", "/foo", "/foobar" -> assertEquals(0, cookies.size(), target); + case "/foo/", "/foo/bar" -> + { + assertEquals(1, cookies.size(), target); + org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0); assertEquals(cookieName, cookie.getName(), target); assertEquals(cookieValue, cookie.getValue(), target); - break; - default: - fail("Unrecognized Target: " + target); + } + default -> fail("Unrecognized Target: " + target); } } } diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java index 11de6a247a5..2d951541239 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java @@ -14,22 +14,22 @@ package org.eclipse.jetty.client; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import jakarta.servlet.DispatcherType; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.ByteBufferRequestContent; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.internal.HttpChannelState; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -232,23 +232,14 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testAbortOnCommitWithContent(Scenario scenario) throws Exception { - AtomicReference failure = new AtomicReference<>(); - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws IOException { - try - { - baseRequest.setHandled(true); - if (request.getDispatcherType() != DispatcherType.ERROR) - IO.copy(request.getInputStream(), response.getOutputStream()); - } - catch (IOException x) - { - failure.set(x); - throw x; - } + InputStream inputStream = org.eclipse.jetty.server.Request.asInputStream(request); + OutputStream outputStream = Response.asOutputStream(response); + IO.copy(inputStream, outputStream); } }); @@ -287,18 +278,19 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testAbortOnContent(Scenario scenario) throws Exception { - try (StacklessLogging ignore = new StacklessLogging(org.eclipse.jetty.server.HttpChannel.class)) + try (StacklessLogging ignore = new StacklessLogging(HttpChannelState.class)) { CountDownLatch serverLatch = new CountDownLatch(1); start(scenario, new EmptyServerHandler() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws Exception { try { - if (request.getDispatcherType() != DispatcherType.ERROR) - IO.copy(request.getInputStream(), response.getOutputStream()); + InputStream inputStream = org.eclipse.jetty.server.Request.asInputStream(request); + OutputStream outputStream = Response.asOutputStream(response); + IO.copy(inputStream, outputStream); } finally { @@ -346,21 +338,12 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest public void testInterrupt(Scenario scenario) throws Exception { long delay = 1000; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws Exception { - try - { - baseRequest.setHandled(true); - if (request.getDispatcherType() != DispatcherType.ERROR) - TimeUnit.MILLISECONDS.sleep(2 * delay); - } - catch (InterruptedException x) - { - throw new ServletException(x); - } + TimeUnit.MILLISECONDS.sleep(2 * delay); } }); @@ -390,21 +373,12 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest public void testAbortLongPoll(Scenario scenario) throws Exception { final long delay = 1000; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws Exception { - try - { - baseRequest.setHandled(true); - if (request.getDispatcherType() != DispatcherType.ERROR) - TimeUnit.MILLISECONDS.sleep(2 * delay); - } - catch (InterruptedException x) - { - throw new ServletException(x); - } + TimeUnit.MILLISECONDS.sleep(2 * delay); } }); @@ -452,21 +426,12 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest public void testAbortLongPollAsync(Scenario scenario) throws Exception { final long delay = 1000; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws Throwable { - try - { - baseRequest.setHandled(true); - if (request.getDispatcherType() != DispatcherType.ERROR) - TimeUnit.MILLISECONDS.sleep(2 * delay); - } - catch (InterruptedException x) - { - throw new ServletException(x); - } + TimeUnit.MILLISECONDS.sleep(2 * delay); } }); @@ -493,14 +458,15 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testAbortConversation(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback) { - baseRequest.setHandled(true); - if (!"/done".equals(request.getRequestURI())) - response.sendRedirect("/done"); + if ("/done".equals(request.getPathInContext())) + callback.succeeded(); + else + Response.sendRedirect(request, response, callback, "/done"); } }); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java index 9cbc37d8faf..65ed06d0296 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java @@ -14,17 +14,14 @@ package org.eclipse.jetty.client; import java.io.IOException; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.util.AsyncRequestContent; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -95,19 +92,15 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testAbortOnContent(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, Response response) { try { - baseRequest.setHandled(true); - OutputStream output = response.getOutputStream(); - output.write(1); - output.flush(); - output.write(2); - output.flush(); + Response.write(response, false, ByteBuffer.wrap(new byte[]{1})); + Response.write(response, false, ByteBuffer.wrap(new byte[]{2})); } catch (IOException ignored) { @@ -132,19 +125,15 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testAbortOnContentBeforeRequestTermination(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, Response response) { try { - baseRequest.setHandled(true); - OutputStream output = response.getOutputStream(); - output.write(1); - output.flush(); - output.write(2); - output.flush(); + Response.write(response, false, ByteBuffer.wrap(new byte[]{1})); + Response.write(response, false, ByteBuffer.wrap(new byte[]{2})); } catch (IOException ignored) { diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseConcurrentAbortTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseConcurrentAbortTest.java index 6b4840f8dfe..29d39449223 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseConcurrentAbortTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseConcurrentAbortTest.java @@ -13,21 +13,14 @@ package org.eclipse.jetty.client; -import java.io.IOException; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -49,14 +42,7 @@ public class HttpResponseConcurrentAbortTest extends AbstractHttpClientServerTes client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) - .onResponseBegin(new Response.BeginListener() - { - @Override - public void onBegin(Response response) - { - abort(response); - } - }) + .onResponseBegin(this::abort) .send(new TestResponseListener()); assertTrue(callbackLatch.await(5, TimeUnit.SECONDS)); assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); @@ -72,14 +58,10 @@ public class HttpResponseConcurrentAbortTest extends AbstractHttpClientServerTes client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) - .onResponseHeader(new Response.HeaderListener() + .onResponseHeader((response, field) -> { - @Override - public boolean onHeader(Response response, HttpField field) - { - abort(response); - return true; - } + abort(response); + return true; }) .send(new TestResponseListener()); assertTrue(callbackLatch.await(5, TimeUnit.SECONDS)); @@ -96,14 +78,7 @@ public class HttpResponseConcurrentAbortTest extends AbstractHttpClientServerTes client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) - .onResponseHeaders(new Response.HeadersListener() - { - @Override - public void onHeaders(Response response) - { - abort(response); - } - }) + .onResponseHeaders(this::abort) .send(new TestResponseListener()); assertTrue(callbackLatch.await(5, TimeUnit.SECONDS)); assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); @@ -115,28 +90,18 @@ public class HttpResponseConcurrentAbortTest extends AbstractHttpClientServerTes @ArgumentsSource(ScenarioProvider.class) public void testAbortOnContent(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(Request request, org.eclipse.jetty.server.Response response) throws Throwable { - baseRequest.setHandled(true); - OutputStream output = response.getOutputStream(); - output.write(1); - output.flush(); + org.eclipse.jetty.server.Response.write(response, false, ByteBuffer.wrap(new byte[]{1})); } }); client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) - .onResponseContent(new Response.ContentListener() - { - @Override - public void onContent(Response response, ByteBuffer content) - { - abort(response); - } - }) + .onResponseContent((response, content) -> abort(response)) .send(new TestResponseListener()); assertTrue(callbackLatch.await(5, TimeUnit.SECONDS)); assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java index c6a6e8415db..82e7fc86b40 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java @@ -13,21 +13,15 @@ package org.eclipse.jetty.client; -import java.io.IOException; -import java.io.InputStream; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; -import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; import org.eclipse.jetty.client.util.FormRequestContent; @@ -40,17 +34,20 @@ import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.ManagedSelector; import org.eclipse.jetty.io.NetworkTrafficListener; import org.eclipse.jetty.io.NetworkTrafficSocketChannelEndPoint; +import org.eclipse.jetty.server.Content; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.NetworkTrafficServerConnector; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Fields; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -132,14 +129,7 @@ public class NetworkTrafficListenerTest @Test public void testTrafficWithNoResponseContentOnNonPersistentConnection() throws Exception { - start(new AbstractHandler() - { - @Override - public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) - { - request.setHandled(true); - } - }); + start(new EmptyServerHandler()); AtomicReference serverIncoming = new AtomicReference<>(""); CountDownLatch serverIncomingLatch = new CountDownLatch(1); @@ -150,14 +140,14 @@ public class NetworkTrafficListenerTest @Override public void incoming(Socket socket, ByteBuffer bytes) { - serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, UTF_8)); serverIncomingLatch.countDown(); } @Override public void outgoing(Socket socket, ByteBuffer bytes) { - serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, UTF_8)); serverOutgoingLatch.countDown(); } }); @@ -171,14 +161,14 @@ public class NetworkTrafficListenerTest @Override public void outgoing(Socket socket, ByteBuffer bytes) { - clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, UTF_8)); clientOutgoingLatch.countDown(); } @Override public void incoming(Socket socket, ByteBuffer bytes) { - clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, UTF_8)); clientIncomingLatch.countDown(); } }); @@ -200,14 +190,12 @@ public class NetworkTrafficListenerTest public void testTrafficWithResponseContentOnPersistentConnection() throws Exception { String responseContent = "response_content" + END_OF_CONTENT; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException + public void process(Request request, Response response, Callback callback) { - request.setHandled(true); - ServletOutputStream output = servletResponse.getOutputStream(); - output.write(responseContent.getBytes(StandardCharsets.UTF_8)); + response.write(true, callback, UTF_8.encode(responseContent)); } }); @@ -220,14 +208,14 @@ public class NetworkTrafficListenerTest @Override public void incoming(Socket socket, ByteBuffer bytes) { - serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, UTF_8)); serverIncomingLatch.countDown(); } @Override public void outgoing(Socket socket, ByteBuffer bytes) { - serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, UTF_8)); serverOutgoingLatch.countDown(); } }); @@ -241,14 +229,14 @@ public class NetworkTrafficListenerTest @Override public void outgoing(Socket socket, ByteBuffer bytes) { - clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, UTF_8)); clientOutgoingLatch.countDown(); } @Override public void incoming(Socket socket, ByteBuffer bytes) { - clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, UTF_8)); clientIncomingLatch.countDown(); } }); @@ -271,17 +259,13 @@ public class NetworkTrafficListenerTest String responseContent = "response_content"; String responseChunk1 = responseContent.substring(0, responseContent.length() / 2); String responseChunk2 = responseContent.substring(responseContent.length() / 2); - start(new AbstractHandler() + start(new EmptyServerHandler() { @Override - public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException + protected void service(Request request, Response response) throws Throwable { - request.setHandled(true); - ServletOutputStream output = servletResponse.getOutputStream(); - output.write(responseChunk1.getBytes(StandardCharsets.UTF_8)); - output.flush(); - output.write(responseChunk2.getBytes(StandardCharsets.UTF_8)); - output.flush(); + Response.write(response, false, UTF_8.encode(responseChunk1)); + Response.write(response, false, UTF_8.encode(responseChunk2)); } }); @@ -294,14 +278,14 @@ public class NetworkTrafficListenerTest @Override public void incoming(Socket socket, ByteBuffer bytes) { - serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, UTF_8)); serverIncomingLatch.countDown(); } @Override public void outgoing(Socket socket, ByteBuffer bytes) { - serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, UTF_8)); if (serverOutgoing.get().endsWith("\r\n0\r\n\r\n")) serverOutgoingLatch.countDown(); } @@ -316,14 +300,14 @@ public class NetworkTrafficListenerTest @Override public void outgoing(Socket socket, ByteBuffer bytes) { - clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, UTF_8)); clientOutgoingLatch.countDown(); } @Override public void incoming(Socket socket, ByteBuffer bytes) { - clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, UTF_8)); if (clientIncoming.get().endsWith("\r\n0\r\n\r\n")) clientIncomingLatch.countDown(); } @@ -344,13 +328,12 @@ public class NetworkTrafficListenerTest public void testTrafficWithRequestContentWithResponseRedirectOnPersistentConnection() throws Exception { String location = "/redirect"; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException + public void process(Request request, Response response, Callback callback) { - request.setHandled(true); - servletResponse.sendRedirect(location); + Response.sendRedirect(request, response, callback, location); } }); @@ -363,14 +346,14 @@ public class NetworkTrafficListenerTest @Override public void incoming(Socket socket, ByteBuffer bytes) { - serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, UTF_8)); serverIncomingLatch.countDown(); } @Override public void outgoing(Socket socket, ByteBuffer bytes) { - serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, UTF_8)); serverOutgoingLatch.countDown(); } }); @@ -384,14 +367,14 @@ public class NetworkTrafficListenerTest @Override public void outgoing(Socket socket, ByteBuffer bytes) { - clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, UTF_8)); clientOutgoingLatch.countDown(); } @Override public void incoming(Socket socket, ByteBuffer bytes) { - clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, UTF_8)); clientIncomingLatch.countDown(); } }); @@ -416,23 +399,15 @@ public class NetworkTrafficListenerTest @Test public void testTrafficWithBigRequestContentOnPersistentConnection() throws Exception { - start(new AbstractHandler() + start(new EmptyServerHandler() { @Override - public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException + protected void service(Request request, Response response) throws Throwable { // Read and discard the request body to make the test more // reliable, otherwise there is a race between request body // upload and response download - InputStream input = servletRequest.getInputStream(); - byte[] buffer = new byte[4096]; - while (true) - { - int read = input.read(buffer); - if (read < 0) - break; - } - request.setHandled(true); + Content.consumeAll(request); } }); @@ -444,13 +419,13 @@ public class NetworkTrafficListenerTest @Override public void incoming(Socket socket, ByteBuffer bytes) { - serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, UTF_8)); } @Override public void outgoing(Socket socket, ByteBuffer bytes) { - serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, UTF_8)); serverOutgoingLatch.countDown(); } }); @@ -463,13 +438,13 @@ public class NetworkTrafficListenerTest @Override public void outgoing(Socket socket, ByteBuffer bytes) { - clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, UTF_8)); } @Override public void incoming(Socket socket, ByteBuffer bytes) { - clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8)); + clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, UTF_8)); clientIncomingLatch.countDown(); } }); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyConfigurationTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyConfigurationTest.java index c10f045d046..39fb2e84cb9 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyConfigurationTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ProxyConfigurationTest.java @@ -23,14 +23,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class ProxyConfigurationTest { @Test - public void testProxyMatchesWithoutIncludesWithoutExcludes() throws Exception + public void testProxyMatchesWithoutIncludesWithoutExcludes() { HttpProxy proxy = new HttpProxy("host", 0); assertTrue(proxy.matches(new Origin("http", "any", 0))); } @Test - public void testProxyMatchesWithOnlyExcludes() throws Exception + public void testProxyMatchesWithOnlyExcludes() { HttpProxy proxy = new HttpProxy("host", 0); proxy.getExcludedAddresses().add("1.2.3.4:5"); @@ -41,7 +41,7 @@ public class ProxyConfigurationTest } @Test - public void testProxyMatchesWithOnlyIncludes() throws Exception + public void testProxyMatchesWithOnlyIncludes() { HttpProxy proxy = new HttpProxy("host", 0); proxy.getIncludedAddresses().add("1.2.3.4:5"); @@ -52,7 +52,7 @@ public class ProxyConfigurationTest } @Test - public void testProxyMatchesWithIncludesAndExcludes() throws Exception + public void testProxyMatchesWithIncludesAndExcludes() { HttpProxy proxy = new HttpProxy("host", 0); proxy.getIncludedAddresses().add("1.2.3.4"); @@ -64,7 +64,7 @@ public class ProxyConfigurationTest } @Test - public void testProxyMatchesWithIncludesAndExcludesIPv6() throws Exception + public void testProxyMatchesWithIncludesAndExcludesIPv6() { Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable()); HttpProxy proxy = new HttpProxy("host", 0); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ServerConnectionCloseTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ServerConnectionCloseTest.java index f28995e2693..818ae143acb 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ServerConnectionCloseTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ServerConnectionCloseTest.java @@ -18,6 +18,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; +import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.api.ContentResponse; @@ -130,7 +131,7 @@ public class ServerConnectionCloseTest serverResponse += content; } - output.write(serverResponse.getBytes("UTF-8")); + output.write(serverResponse.getBytes(StandardCharsets.UTF_8)); output.flush(); if (shutdownOutput) socket.shutdownOutput(); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/TLSServerConnectionCloseTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/TLSServerConnectionCloseTest.java index 783c2ddf16e..d05e61a21d1 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/TLSServerConnectionCloseTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/TLSServerConnectionCloseTest.java @@ -18,6 +18,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; +import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; @@ -135,7 +136,7 @@ public class TLSServerConnectionCloseTest serverResponse += content; } - output.write(serverResponse.getBytes("UTF-8")); + output.write(serverResponse.getBytes(StandardCharsets.UTF_8)); output.flush(); switch (closeMode) diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ValidatingConnectionPoolTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ValidatingConnectionPoolTest.java index e5343845e57..6dc75609771 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ValidatingConnectionPoolTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ValidatingConnectionPoolTest.java @@ -13,13 +13,9 @@ package org.eclipse.jetty.client; -import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.FutureResponseListener; @@ -27,7 +23,7 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -71,25 +67,24 @@ public class ValidatingConnectionPoolTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testServerClosesConnectionAfterRedirectWithoutConnectionCloseHeader(Scenario scenario) throws Exception { - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws Throwable { - baseRequest.setHandled(true); - if (target.endsWith("/redirect")) + if (request.getPathInContext().endsWith("/redirect")) { response.setStatus(HttpStatus.TEMPORARY_REDIRECT_307); response.setContentLength(0); - response.setHeader(HttpHeader.LOCATION.asString(), scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/"); - response.flushBuffer(); - baseRequest.getHttpChannel().getEndPoint().shutdownOutput(); + response.getHeaders().put(HttpHeader.LOCATION, scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/"); + Response.write(response, false); + request.getConnectionMetaData().getConnection().getEndPoint().shutdownOutput(); } else { response.setStatus(HttpStatus.OK_200); response.setContentLength(0); - response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString()); + response.getHeaders().put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); } } }); @@ -106,15 +101,14 @@ public class ValidatingConnectionPoolTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testServerClosesConnectionAfterResponseWithQueuedRequestWithMaxConnectionsWithConnectionCloseHeader(Scenario scenario) throws Exception { - testServerClosesConnectionAfterResponseWithQueuedRequestWithMaxConnections(scenario, new AbstractHandler() + testServerClosesConnectionAfterResponseWithQueuedRequestWithMaxConnections(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) { - baseRequest.setHandled(true); response.setStatus(HttpStatus.OK_200); response.setContentLength(0); - response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString()); + response.getHeaders().put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); } }); } @@ -123,16 +117,15 @@ public class ValidatingConnectionPoolTest extends AbstractHttpClientServerTest @ArgumentsSource(ScenarioProvider.class) public void testServerClosesConnectionAfterResponseWithQueuedRequestWithMaxConnectionsWithoutConnectionCloseHeader(Scenario scenario) throws Exception { - testServerClosesConnectionAfterResponseWithQueuedRequestWithMaxConnections(scenario, new AbstractHandler() + testServerClosesConnectionAfterResponseWithQueuedRequestWithMaxConnections(scenario, new EmptyServerHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws Throwable { - baseRequest.setHandled(true); response.setStatus(HttpStatus.OK_200); response.setContentLength(0); - response.flushBuffer(); - baseRequest.getHttpChannel().getEndPoint().shutdownOutput(); + Response.write(response, false); + request.getConnectionMetaData().getConnection().getEndPoint().shutdownOutput(); } }); } diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java index 3618d26a34c..88392be70ee 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesTest.java @@ -331,7 +331,7 @@ public abstract class SslBytesTest } }); assertTrue(startLatch.await(5, TimeUnit.SECONDS)); - return new SslBytesServerTest.SimpleProxy.AutomaticFlow(stopLatch, clientToServer, serverToClient); + return new SslBytesTest.SimpleProxy.AutomaticFlow(stopLatch, clientToServer, serverToClient); } public boolean awaitClient(int time, TimeUnit unit) throws InterruptedException diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java index a4a351575ec..f03e540a5e2 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/InputStreamContentTest.java @@ -16,24 +16,23 @@ package org.eclipse.jetty.client.util; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.EmptyServerHandler; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Content; import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.params.ParameterizedTest; @@ -103,10 +102,10 @@ public class InputStreamContentTest start(new EmptyServerHandler() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws Exception { serverLatch.countDown(); - if (request.getInputStream().read() >= 0) + if (Content.readAllBytes(request).hasRemaining()) throw new IOException(); } }); @@ -178,9 +177,11 @@ public class InputStreamContentTest start(new EmptyServerHandler() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws Exception { - assertEquals(singleByteContent, request.getInputStream().read()); + ByteBuffer buffer = Content.readAllBytes(request); + assertTrue(buffer.hasRemaining()); + assertEquals(singleByteContent, buffer.get()); serverLatch.countDown(); } }); @@ -234,10 +235,10 @@ public class InputStreamContentTest start(new EmptyServerHandler() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + protected void service(org.eclipse.jetty.server.Request request, Response response) throws Exception { serverLatch.countDown(); - IO.copy(request.getInputStream(), IO.getNullStream()); + Content.consumeAll(request); } }); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentTest.java index 008501889c2..423845564c7 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentTest.java @@ -13,50 +13,23 @@ package org.eclipse.jetty.client.util; -import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Random; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import jakarta.servlet.MultipartConfigElement; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.Part; import org.eclipse.jetty.client.AbstractHttpClientServerTest; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.util.IO; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; -import static org.eclipse.jetty.toolchain.test.StackUtils.supply; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +// TODO // @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck +@Disabled public class MultiPartContentTest extends AbstractHttpClientServerTest { + @Test + public void testNeedToUpdateThisTest() + { + fail("This test needs to be updated to use Core version of multipart (when available)"); + } +/* @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) public void testEmptyMultiPart(Scenario scenario) throws Exception @@ -445,4 +418,5 @@ public class MultiPartContentTest extends AbstractHttpClientServerTest protected abstract void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; } +*/ } diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/SPNEGOAuthenticationTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/SPNEGOAuthenticationTest.java index 918a41d762a..2563f0723bb 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/SPNEGOAuthenticationTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/SPNEGOAuthenticationTest.java @@ -13,53 +13,23 @@ package org.eclipse.jetty.client.util; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer; import org.eclipse.jetty.client.AbstractHttpClientServerTest; -import org.eclipse.jetty.client.EmptyServerHandler; -import org.eclipse.jetty.client.api.Authentication; -import org.eclipse.jetty.client.api.AuthenticationStore; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.security.ConfigurableSpnegoLoginService; -import org.eclipse.jetty.security.ConstraintMapping; -import org.eclipse.jetty.security.ConstraintSecurityHandler; -import org.eclipse.jetty.security.HashLoginService; -import org.eclipse.jetty.security.authentication.AuthorizationService; -import org.eclipse.jetty.security.authentication.ConfigurableSpnegoAuthenticator; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.session.DefaultSessionIdManager; -import org.eclipse.jetty.server.session.SessionHandler; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.security.Constraint; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; +// TODO +@Disabled public class SPNEGOAuthenticationTest extends AbstractHttpClientServerTest { + @Test + public void testNeedToUpdateThisTest() + { + fail("This test needs to be updated to use Core version of SPNEGO (when available)"); + } + + /* private static final Logger LOG = LoggerFactory.getLogger(SPNEGOAuthenticationTest.class); static @@ -286,4 +256,5 @@ public class SPNEGOAuthenticationTest extends AbstractHttpClientServerTest // Authentication expired, but POSTs are allowed. assertEquals(1, requests.get()); } + */ } diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java index 3111be179fe..09681220af8 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java @@ -13,25 +13,25 @@ package org.eclipse.jetty.client.util; -import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.AbstractHttpClientServerTest; +import org.eclipse.jetty.client.EmptyServerHandler; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.FutureFormFields; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Fields; -import org.eclipse.jetty.util.IO; -import org.hamcrest.Matchers; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -48,19 +48,20 @@ public class TypedContentProviderTest extends AbstractHttpClientServerTest // @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck final String value3 = "\u20AC"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + protected void service(Request request, Response response) { - baseRequest.setHandled(true); assertEquals("POST", request.getMethod()); - assertEquals(MimeTypes.Type.FORM_ENCODED.asString(), request.getContentType()); - assertEquals(value1, request.getParameter(name1)); - String[] values = request.getParameterValues(name2); - assertNotNull(values); - assertEquals(2, values.length); - assertThat(values, Matchers.arrayContainingInAnyOrder(value2, value3)); + assertEquals(MimeTypes.Type.FORM_ENCODED.asString(), request.getHeaders().get(HttpHeader.CONTENT_TYPE)); + new FutureFormFields(request).whenComplete((fields, failure) -> + { + assertEquals(value1, fields.get(name1).getValue()); + List values = fields.get(name2).getValues(); + assertEquals(2, values.size()); + assertThat(values, containsInAnyOrder(value2, value3)); + }); } }); @@ -87,15 +88,14 @@ public class TypedContentProviderTest extends AbstractHttpClientServerTest final String content = FormRequestContent.convert(fields); final String contentType = "text/plain;charset=UTF-8"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(Request request, Response response) throws Throwable { - baseRequest.setHandled(true); assertEquals("POST", request.getMethod()); - assertEquals(contentType, request.getContentType()); - assertEquals(content, IO.toString(request.getInputStream())); + assertEquals(contentType, request.getHeaders().get(HttpHeader.CONTENT_TYPE)); + assertEquals(content, Content.readAll(request)); } }); @@ -115,15 +115,14 @@ public class TypedContentProviderTest extends AbstractHttpClientServerTest { final String content = "data"; - start(scenario, new AbstractHandler() + start(scenario, new EmptyServerHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + protected void service(Request request, Response response) throws Throwable { - baseRequest.setHandled(true); assertEquals("GET", request.getMethod()); - assertNotNull(request.getContentType()); - assertEquals(content, IO.toString(request.getInputStream())); + assertNotNull(request.getHeaders().get(HttpHeader.CONTENT_TYPE)); + assertEquals(content, Content.readAll(request)); } }); diff --git a/jetty-core/jetty-deploy/pom.xml b/jetty-core/jetty-deploy/pom.xml index c2ae4057fd6..c672ea39940 100644 --- a/jetty-core/jetty-deploy/pom.xml +++ b/jetty-core/jetty-deploy/pom.xml @@ -1,13 +1,13 @@ org.eclipse.jetty - jetty-project - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 jetty-deploy - Jetty :: Deployers + Jetty Core :: Deployers Jetty deployers @@ -21,7 +21,7 @@ maven-surefire-plugin - @{argLine} ${jetty.surefire.argLine} --add-modules org.eclipse.jetty.jmx --add-reads org.eclipse.jetty.deploy=org.eclipse.jetty.http,jetty.servlet.api + @{argLine} ${jetty.surefire.argLine} --add-modules org.eclipse.jetty.jmx --add-reads org.eclipse.jetty.deploy=org.eclipse.jetty.http @@ -31,7 +31,7 @@ org.eclipse.jetty - jetty-webapp + jetty-server org.eclipse.jetty diff --git a/jetty-core/jetty-deploy/src/main/config/etc/jetty-decorate.xml b/jetty-core/jetty-deploy/src/main/config/etc/jetty-decorate.xml index 3abfb16941b..73069bafa59 100644 --- a/jetty-core/jetty-deploy/src/main/config/etc/jetty-decorate.xml +++ b/jetty-core/jetty-deploy/src/main/config/etc/jetty-decorate.xml @@ -5,7 +5,7 @@ - + /etc/jetty-web-decorate.xml diff --git a/jetty-core/jetty-deploy/src/main/config/etc/jetty-deploy.xml b/jetty-core/jetty-deploy/src/main/config/etc/jetty-deploy.xml index a07afd3da92..73e8d86929f 100644 --- a/jetty-core/jetty-deploy/src/main/config/etc/jetty-deploy.xml +++ b/jetty-core/jetty-deploy/src/main/config/etc/jetty-deploy.xml @@ -38,7 +38,7 @@ - + diff --git a/jetty-core/jetty-deploy/src/main/config/etc/jetty-web-decorate.xml b/jetty-core/jetty-deploy/src/main/config/etc/jetty-web-decorate.xml index f91048c4b73..25ff88b5f60 100644 --- a/jetty-core/jetty-deploy/src/main/config/etc/jetty-web-decorate.xml +++ b/jetty-core/jetty-deploy/src/main/config/etc/jetty-web-decorate.xml @@ -1,13 +1,13 @@ - + - + - + diff --git a/jetty-core/jetty-deploy/src/main/config/modules/decorate.mod b/jetty-core/jetty-deploy/src/main/config/modules/decorate.mod index 3315ed0cbd3..fe95fbb3c27 100644 --- a/jetty-core/jetty-deploy/src/main/config/modules/decorate.mod +++ b/jetty-core/jetty-deploy/src/main/config/modules/decorate.mod @@ -4,9 +4,9 @@ Jetty setup to support Decoration of Listeners, Filters and Servlets within a deployed webapp. This module uses DecoratingListener to register an object set as a context attribute as a dynamic decorator. -This module sets the "org.eclipse.jetty.webapp.DecoratingListener" +This module sets the "org.eclipse.jetty.ee9.webapp.DecoratingListener" context attribute with the name of the context attribute that will be listened to. -By default the attribute is "org.eclipse.jetty.webapp.decorator". +By default the attribute is "org.eclipse.jetty.ee9.webapp.decorator". [depend] deploy diff --git a/jetty-core/jetty-deploy/src/main/config/modules/global-webapp-common.d/global-webapp-common.xml b/jetty-core/jetty-deploy/src/main/config/modules/global-webapp-common.d/global-webapp-common.xml index 7f28fd55ca5..2a933198d95 100644 --- a/jetty-core/jetty-deploy/src/main/config/modules/global-webapp-common.d/global-webapp-common.xml +++ b/jetty-core/jetty-deploy/src/main/config/modules/global-webapp-common.d/global-webapp-common.xml @@ -5,7 +5,7 @@ - + diff --git a/jetty-core/jetty-deploy/src/main/config/modules/global-webapp-common.d/webapp-common.xml b/jetty-core/jetty-deploy/src/main/config/modules/global-webapp-common.d/webapp-common.xml index 02e338e6c9c..a83fbd4f1e4 100644 --- a/jetty-core/jetty-deploy/src/main/config/modules/global-webapp-common.d/webapp-common.xml +++ b/jetty-core/jetty-deploy/src/main/config/modules/global-webapp-common.d/webapp-common.xml @@ -1,7 +1,7 @@ - + diff --git a/jetty-deploy/src/main/java/module-info.java b/jetty-core/jetty-deploy/src/main/java/module-info.java similarity index 95% rename from jetty-deploy/src/main/java/module-info.java rename to jetty-core/jetty-deploy/src/main/java/module-info.java index 5e5b4dfff8b..75d5423efd5 100644 --- a/jetty-deploy/src/main/java/module-info.java +++ b/jetty-core/jetty-deploy/src/main/java/module-info.java @@ -15,10 +15,9 @@ module org.eclipse.jetty.deploy { requires java.xml; requires org.eclipse.jetty.xml; + requires org.eclipse.jetty.server; requires org.slf4j; - requires transitive org.eclipse.jetty.webapp; - // Only required if using JMX. requires static org.eclipse.jetty.jmx; diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java index 65cfe23b62b..acf8a132780 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java @@ -14,7 +14,7 @@ package org.eclipse.jetty.deploy; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.Attributes; /** * The information about an App that is managed by the {@link DeploymentManager} @@ -23,6 +23,7 @@ public class App { private final DeploymentManager _manager; private final AppProvider _provider; + private final String _environment; private final String _originId; private ContextHandler _context; @@ -31,36 +32,20 @@ public class App * * @param manager the deployment manager * @param provider the app provider + * @param environment the name of the environment or null for the server environment. * @param originId the origin ID (The ID that the {@link AppProvider} knows * about) * @see App#getOriginId() * @see App#getContextPath() */ - public App(DeploymentManager manager, AppProvider provider, String originId) + public App(DeploymentManager manager, AppProvider provider, String environment, String originId) { _manager = manager; _provider = provider; + _environment = environment; _originId = originId; } - /** - * Create an App with specified Origin ID and archivePath - * - * @param manager the deployment manager - * @param provider the app provider - * @param originId the origin ID (The ID that the {@link AppProvider} knows - * about) - * @param context Some implementations of AppProvider might have to use an - * already created ContextHandler. - * @see App#getOriginId() - * @see App#getContextPath() - */ - public App(DeploymentManager manager, AppProvider provider, String originId, ContextHandler context) - { - this(manager, provider, originId); - _context = context; - } - /** * @return The deployment manager */ @@ -93,13 +78,14 @@ public class App { _context = getAppProvider().createContextHandler(this); - AttributesMap attributes = _manager.getContextAttributes(); + Attributes.Mapped attributes = _manager.getContextAttributes(); if (attributes != null && attributes.size() > 0) { // Merge the manager attributes under the existing attributes - attributes = new AttributesMap(attributes); - attributes.addAll(_context.getAttributes()); - _context.setAttributes(attributes); + for (String name : attributes.getAttributeNameSet()) + { + _context.setAttribute(name, attributes.getAttribute(name)); + } } } return _context; @@ -129,11 +115,12 @@ public class App */ public String getContextPath() { - if (this._context == null) - { - return null; - } - return this._context.getContextPath(); + return _context == null ? null : _context.getContextPath(); + } + + public String getEnvironment() + { + return _environment; } /** diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java index 5700493b375..dd182d5e2a5 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java @@ -34,7 +34,7 @@ import org.eclipse.jetty.deploy.graph.Node; import org.eclipse.jetty.deploy.graph.Path; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandlerCollection; -import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; @@ -125,10 +125,11 @@ public class DeploymentManager extends ContainerLifeCycle private final List _providers = new ArrayList(); private final AppLifeCycle _lifecycle = new AppLifeCycle(); private final Queue _apps = new ConcurrentLinkedQueue(); - private AttributesMap _contextAttributes = new AttributesMap(); + private Attributes.Mapped _contextAttributes = new Attributes.Mapped(); private ContextHandlerCollection _contexts; private boolean _useStandardBindings = true; private String _defaultLifeCycleGoal = AppLifeCycle.STARTED; + private String _defaultEnvironment = "ee9"; // TODO null or ee10? /** * Receive an app for processing. @@ -225,6 +226,16 @@ public class DeploymentManager extends ContainerLifeCycle _lifecycle.insertNode(edge, insertedNodeName); } + public String getDefaultEnvironment() + { + return _defaultEnvironment; + } + + public void setDefaultEnvironment(String defaultEnvironment) + { + _defaultEnvironment = defaultEnvironment; + } + @Override protected void doStart() throws Exception { @@ -384,7 +395,7 @@ public class DeploymentManager extends ContainerLifeCycle return _contextAttributes.getAttribute(name); } - public AttributesMap getContextAttributes() + public Attributes.Mapped getContextAttributes() { return _contextAttributes; } @@ -582,9 +593,10 @@ public class DeploymentManager extends ContainerLifeCycle _contextAttributes.setAttribute(name, value); } - public void setContextAttributes(AttributesMap contextAttributes) + public void setContextAttributes(Attributes contextAttributes) { - this._contextAttributes = contextAttributes; + this._contextAttributes.clearAttributes(); + this._contextAttributes.addAll(contextAttributes); } public void setContexts(ContextHandlerCollection contexts) diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/package-info.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/package-info.java similarity index 100% rename from jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/package-info.java rename to jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/package-info.java diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/package-info.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/package-info.java similarity index 100% rename from jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/package-info.java rename to jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/package-info.java diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/jmx/package-info.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/jmx/package-info.java similarity index 100% rename from jetty-deploy/src/main/java/org/eclipse/jetty/deploy/jmx/package-info.java rename to jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/jmx/package-info.java diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/package-info.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/package-info.java similarity index 100% rename from jetty-deploy/src/main/java/org/eclipse/jetty/deploy/package-info.java rename to jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/package-info.java diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java index e252826437d..e9b332346a4 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java @@ -124,7 +124,10 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements */ protected App createApp(String filename) { - return new App(_deploymentManager, this, filename); + // TODO otherways to work out the environment???? + String environment = getDeploymentManager().getDefaultEnvironment(); + + return new App(_deploymentManager, this, environment, filename); } @Override diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java index 1dfa06730d5..e6a38eaac40 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java @@ -15,6 +15,7 @@ package org.eclipse.jetty.deploy.providers; import java.io.File; import java.io.FilenameFilter; +import java.util.Arrays; import java.util.Locale; import org.eclipse.jetty.deploy.App; @@ -26,8 +27,8 @@ import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.component.Environment; import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.xml.XmlConfiguration; import org.slf4j.LoggerFactory; @@ -78,7 +79,7 @@ public class WebAppProvider extends ScanningAppProvider String lowerName = name.toLowerCase(Locale.ENGLISH); - Resource resource = Resource.newResource(new File(dir, name)); + Resource resource = Resource.newResource(new File(dir, name)); // TODO use paths if (getMonitoredResources().stream().anyMatch(resource::isSame)) return false; @@ -205,7 +206,7 @@ public class WebAppProvider extends ScanningAppProvider */ public void setConfigurationClasses(String[] configurations) { - _configurationClasses = configurations == null ? null : (String[])configurations.clone(); + _configurationClasses = configurations == null ? null : configurations.clone(); } @ManagedAttribute("configuration classes for webapps to be processed through") @@ -237,8 +238,9 @@ public class WebAppProvider extends ScanningAppProvider return _tempDirectory; } - protected void initializeWebAppContextDefaults(WebAppContext webapp) + protected void initializeWebAppContextDefaults(ContextHandler webapp) { + /* TODO if (_defaultsDescriptor != null) webapp.setDefaultsDescriptor(_defaultsDescriptor); webapp.setExtractWAR(_extractWars); @@ -248,76 +250,89 @@ public class WebAppProvider extends ScanningAppProvider if (_tempDirectory != null) { - /* Since the Temp Dir is really a context base temp directory, - * Lets set the Temp Directory in a way similar to how WebInfConfiguration does it, - * instead of setting the WebAppContext.setTempDirectory(File). - * If we used .setTempDirectory(File) all webapps will wind up in the - * same temp / work directory, overwriting each others work. - */ + // Since the Temp Dir is really a context base temp directory, + // Lets set the Temp Directory in a way similar to how WebInfConfiguration does it, + // instead of setting the WebAppContext.setTempDirectory(File). + // If we used .setTempDirectory(File) all webapps will wind up in the + // same temp / work directory, overwriting each others work. webapp.setAttribute(WebAppContext.BASETEMPDIR, _tempDirectory); } + */ } @Override public ContextHandler createContextHandler(final App app) throws Exception { - Resource resource = Resource.newResource(app.getOriginId()); - File file = resource.getFile(); - if (!resource.exists()) - throw new IllegalStateException("App resource does not exist " + resource); + Environment environment = getDeploymentManager().getServer().getEnvironment(app.getEnvironment()); - final String contextName = file.getName(); + if (LOG.isDebugEnabled()) + LOG.debug("createContextHandler {} in {}", app, environment); - // Resource aliases (after getting name) to ensure baseResource is not an alias - if (resource.isAlias()) + ClassLoader old = Thread.currentThread().getContextClassLoader(); + try { - file = new File(resource.getAlias()).toPath().toRealPath().toFile(); - resource = Resource.newResource(file); + Thread.currentThread().setContextClassLoader(environment.getClassLoader()); + + Resource resource = Resource.newResource(app.getOriginId()); + File file = resource.getFile(); if (!resource.exists()) throw new IllegalStateException("App resource does not exist " + resource); - } - // Handle a context XML file - if (resource.exists() && FileID.isXmlFile(file)) - { - XmlConfiguration xmlc = new XmlConfiguration(resource) + final String contextName = file.getName(); + + // Resource aliases (after getting name) to ensure baseResource is not an alias + if (resource.isAlias()) { - @Override - public void initializeDefaults(Object context) + file = new File(resource.getAlias()).toPath().toRealPath().toFile(); + resource = Resource.newResource(file); + if (!resource.exists()) + throw new IllegalStateException("App resource does not exist " + resource); + } + + // Handle a context XML file + if (resource.exists() && FileID.isXmlFile(file)) + { + XmlConfiguration xmlc = new XmlConfiguration(resource) { - super.initializeDefaults(context); + @Override + public void initializeDefaults(Object context) + { + super.initializeDefaults(context); - // If the XML created object is a ContextHandler - if (context instanceof ContextHandler) - // Initialize the context path prior to running context XML - initializeContextPath((ContextHandler)context, contextName, true); + // If the XML created object is a ContextHandler + if (context instanceof ContextHandler) + // Initialize the context path prior to running context XML + initializeContextPath((ContextHandler)context, contextName, true); - // If it is a webapp - if (context instanceof WebAppContext) - // initialize other defaults prior to running context XML - initializeWebAppContextDefaults((WebAppContext)context); - } - }; + if (context instanceof ContextHandler contextHandler) + initializeWebAppContextDefaults(contextHandler); + } + }; - getDeploymentManager().scope(xmlc, resource); + xmlc.getIdMap().put("Environment", environment); + getDeploymentManager().scope(xmlc, resource); + if (getConfigurationManager() != null) + xmlc.getProperties().putAll(getConfigurationManager().getProperties()); + return (ContextHandler)xmlc.configure(); + } + // Otherwise it must be a directory or an archive + else if (!file.isDirectory() && !FileID.isWebArchiveFile(file)) + { + throw new IllegalStateException("unable to create ContextHandler for " + app); + } - if (getConfigurationManager() != null) - xmlc.getProperties().putAll(getConfigurationManager().getProperties()); - return (ContextHandler)xmlc.configure(); + // Build the web application + ContextHandler webAppContext = null; // TODO new WebAppContext(); + webAppContext.setResourceBase(file.getAbsoluteFile().toPath()); + initializeContextPath(webAppContext, contextName, !file.isDirectory()); + initializeWebAppContextDefaults(webAppContext); + + return webAppContext; } - // Otherwise it must be a directory or an archive - else if (!file.isDirectory() && !FileID.isWebArchiveFile(file)) + finally { - throw new IllegalStateException("unable to create ContextHandler for " + app); + Thread.currentThread().setContextClassLoader(old); } - - // Build the web application - WebAppContext webAppContext = new WebAppContext(); - webAppContext.setWar(file.getAbsolutePath()); - initializeContextPath(webAppContext, contextName, !file.isDirectory()); - initializeWebAppContextDefaults(webAppContext); - - return webAppContext; } protected void initializeContextPath(ContextHandler context, String contextName, boolean stripExtension) @@ -342,7 +357,7 @@ public class WebAppProvider extends ScanningAppProvider { int dash = contextPath.indexOf('-'); String virtual = contextPath.substring(dash + 1); - context.setVirtualHosts(virtual.split(",")); + context.setVirtualHosts(Arrays.asList(virtual.split(","))); contextPath = URIUtil.SLASH; } @@ -352,15 +367,7 @@ public class WebAppProvider extends ScanningAppProvider // Set the display name and context Path context.setDisplayName(contextName); - if (context instanceof WebAppContext) - { - WebAppContext webAppContext = (WebAppContext)context; - webAppContext.setDefaultContextPath(contextPath); - } - else - { - context.setContextPath(contextPath); - } + context.setContextPath(contextPath); } @Override diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/package-info.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/package-info.java similarity index 100% rename from jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/package-info.java rename to jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/package-info.java diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/package-info.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/package-info.java similarity index 100% rename from jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/package-info.java rename to jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/package-info.java diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java index 617f9f94d50..ee32352df60 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java @@ -20,6 +20,7 @@ import org.eclipse.jetty.deploy.test.XmlConfiguredJetty; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -77,6 +78,7 @@ public class DeploymentManagerTest } @Test + @Disabled // TODO public void testXmlConfigured() throws Exception { XmlConfiguredJetty jetty = null; diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockAppProvider.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockAppProvider.java index 779cb62e888..5a8377b9592 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockAppProvider.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockAppProvider.java @@ -20,8 +20,6 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.component.AbstractLifeCycle; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.webapp.WebAppContext; public class MockAppProvider extends AbstractLifeCycle implements AppProvider { @@ -42,17 +40,16 @@ public class MockAppProvider extends AbstractLifeCycle implements AppProvider public void findWebapp(String name) { - App app = new App(deployMan, this, "mock-" + name); + App app = new App(deployMan, this, null, "mock-" + name); this.deployMan.addApp(app); } @Override public ContextHandler createContextHandler(App app) throws Exception { - WebAppContext context = new WebAppContext(); + ContextHandler contextHandler = new ContextHandler(); File war = new File(webappsDir, app.getOriginId().substring(5)); - context.setWar(Resource.newResource(Resource.toURL(war)).toString()); String path = war.getName(); @@ -74,8 +71,8 @@ public class MockAppProvider extends AbstractLifeCycle implements AppProvider if (path.endsWith("/") && path.length() > 0) path = path.substring(0, path.length() - 1); - context.setDefaultContextPath(path); + contextHandler.setContextPath(path); - return context; + return contextHandler; } } diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java index c7d76c110c5..0635ae62099 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderRuntimeUpdatesTest.java @@ -25,6 +25,7 @@ import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.util.resource.Resource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; @@ -35,6 +36,7 @@ import org.slf4j.LoggerFactory; * deployed webapps due to incoming changes identified by the {@link ScanningAppProvider}. */ @ExtendWith(WorkDirExtension.class) +@Disabled // TODO public class ScanningAppProviderRuntimeUpdatesTest { private static final Logger LOG = LoggerFactory.getLogger(ScanningAppProviderRuntimeUpdatesTest.class); @@ -118,7 +120,7 @@ public class ScanningAppProviderRuntimeUpdatesTest waitForDirectoryScan(); waitForDirectoryScan(); - jetty.assertWebAppContextsExists("/foo"); + jetty.assertContextHandlerExists("/foo"); } /** @@ -135,7 +137,7 @@ public class ScanningAppProviderRuntimeUpdatesTest waitForDirectoryScan(); waitForDirectoryScan(); - jetty.assertWebAppContextsExists("/foo"); + jetty.assertContextHandlerExists("/foo"); jetty.removeWebapp("foo.war"); jetty.removeWebapp("foo.xml"); @@ -143,7 +145,7 @@ public class ScanningAppProviderRuntimeUpdatesTest waitForDirectoryScan(); waitForDirectoryScan(); - jetty.assertNoWebAppContexts(); + jetty.assertNoContextHandlers(); } /** @@ -160,7 +162,7 @@ public class ScanningAppProviderRuntimeUpdatesTest waitForDirectoryScan(); waitForDirectoryScan(); - jetty.assertWebAppContextsExists("/foo"); + jetty.assertContextHandlerExists("/foo"); // Test that webapp response contains "-1" jetty.assertResponseContains("/foo/info", "FooServlet-1"); @@ -173,7 +175,7 @@ public class ScanningAppProviderRuntimeUpdatesTest // This should result in the existing foo.war being replaced with the new foo.war waitForDirectoryScan(); waitForDirectoryScan(); - jetty.assertWebAppContextsExists("/foo"); + jetty.assertContextHandlerExists("/foo"); // Test that webapp response contains "-2" jetty.assertResponseContains("/foo/info", "FooServlet-2"); diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderStartupTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderStartupTest.java index f638957fafb..443d62a33fd 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderStartupTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ScanningAppProviderStartupTest.java @@ -18,6 +18,7 @@ import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -57,9 +58,10 @@ public class ScanningAppProviderStartupTest } @Test + @Disabled // TODO public void testStartupContext() { // Check Server for Handlers - jetty.assertWebAppContextsExists("/foo"); + jetty.assertContextHandlerExists("/foo"); } } diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java index 13df6a7582a..8dcd08f20af 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java @@ -37,14 +37,14 @@ import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.PathAssert; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.xml.XmlConfiguration; import static org.hamcrest.MatcherAssert.assertThat; @@ -200,12 +200,12 @@ public class XmlConfiguredJetty return Collections.unmodifiableList(_xmlConfigurations); } - public void assertNoWebAppContexts() + public void assertNoContextHandlers() { - List contexts = getWebAppContexts(); + List contexts = getContextHandlers(); if (contexts.size() > 0) { - for (WebAppContext context : contexts) + for (ContextHandler context : contexts) { System.err.println("WebAppContext should not exist:\n" + context); } @@ -238,9 +238,9 @@ public class XmlConfiguredJetty assertThat(content, containsString(needle)); } - public void assertWebAppContextsExists(String... expectedContextPaths) + public void assertContextHandlerExists(String... expectedContextPaths) { - List contexts = getWebAppContexts(); + List contexts = getContextHandlers(); if (expectedContextPaths.length != contexts.size()) { System.err.println("## Expected Contexts"); @@ -249,7 +249,7 @@ public class XmlConfiguredJetty System.err.println(expected); } System.err.println("## Actual Contexts"); - for (WebAppContext context : contexts) + for (ContextHandler context : contexts) { System.err.printf("%s ## %s%n", context.getContextPath(), context); } @@ -259,7 +259,7 @@ public class XmlConfiguredJetty for (String expectedPath : expectedContextPaths) { boolean found = false; - for (WebAppContext context : contexts) + for (ContextHandler context : contexts) { if (context.getContextPath().equals(expectedPath)) { @@ -352,17 +352,17 @@ public class XmlConfiguredJetty return URI.create(uri.toString()); } - public List getWebAppContexts() + public List getContextHandlers() { - List contexts = new ArrayList<>(); - HandlerCollection handlers = (HandlerCollection)_server.getHandler(); - Handler[] children = handlers.getChildHandlers(); + List contexts = new ArrayList<>(); + ContextHandlerCollection handlers = (ContextHandlerCollection)_server.getHandler(); + List children = handlers.getHandlers(); for (Handler handler : children) { - if (handler instanceof WebAppContext) + if (handler instanceof ContextHandler) { - WebAppContext context = (WebAppContext)handler; + ContextHandler context = (ContextHandler)handler; contexts.add(context); } } diff --git a/jetty-core/jetty-deploy/src/test/resources/binding-test-contexts-1.xml b/jetty-core/jetty-deploy/src/test/resources/binding-test-contexts-1.xml index 6714b3b2316..98b637cacb8 100644 --- a/jetty-core/jetty-deploy/src/test/resources/binding-test-contexts-1.xml +++ b/jetty-core/jetty-deploy/src/test/resources/binding-test-contexts-1.xml @@ -7,7 +7,7 @@ -org.eclipse.jetty.jndi. -org.eclipse.jetty.plus.jaas. -org.eclipse.jetty.websocket. - -org.eclipse.jetty.servlet.DefaultServlet + -org.eclipse.jetty.ee9.servlet.DefaultServlet org.eclipse.jetty. org.eclipse.foo. @@ -20,7 +20,7 @@ org.apache.commons.logging org.eclipse.jetty.plus.jaas. org.eclipse.jetty.websocket - org.eclipse.jetty.servlet.DefaultServlet + org.eclipse.jetty.ee9.servlet.DefaultServlet @@ -33,7 +33,7 @@ - + /context-binding-test-1.xml @@ -44,7 +44,7 @@ - + /webapps 1 diff --git a/jetty-core/jetty-deploy/src/test/resources/context-binding-test-1.xml b/jetty-core/jetty-deploy/src/test/resources/context-binding-test-1.xml index 9e5215df02a..7cb6f2e94a1 100644 --- a/jetty-core/jetty-deploy/src/test/resources/context-binding-test-1.xml +++ b/jetty-core/jetty-deploy/src/test/resources/context-binding-test-1.xml @@ -1,6 +1,6 @@ - + org.eclipse.foo. diff --git a/jetty-core/jetty-deploy/src/test/resources/etc/realm.properties b/jetty-core/jetty-deploy/src/test/resources/etc/realm.properties index 492ffdb580a..cbf905de9fb 100644 --- a/jetty-core/jetty-deploy/src/test/resources/etc/realm.properties +++ b/jetty-core/jetty-deploy/src/test/resources/etc/realm.properties @@ -5,7 +5,7 @@ # : [, ...] # # Passwords may be clear text, obfuscated or checksummed. The class -# org.eclipse.jetty.util.security.Password should be used to generate obfuscated +# org.eclipse.util.Password should be used to generate obfuscated # passwords or password checksums # # If DIGEST Authentication is used, the password must be in a recoverable diff --git a/jetty-core/jetty-deploy/src/test/resources/etc/webdefault.xml b/jetty-core/jetty-deploy/src/test/resources/etc/webdefault.xml index 0b8145c8551..3cfe11a75aa 100644 --- a/jetty-core/jetty-deploy/src/test/resources/etc/webdefault.xml +++ b/jetty-core/jetty-deploy/src/test/resources/etc/webdefault.xml @@ -34,7 +34,7 @@ - org.eclipse.jetty.servlet.listener.ELContextCleaner + org.eclipse.jetty.ee9.servlet.listener.ELContextCleaner @@ -42,7 +42,7 @@ - org.eclipse.jetty.servlet.listener.IntrospectorCleaner + org.eclipse.jetty.ee9.servlet.listener.IntrospectorCleaner @@ -52,15 +52,15 @@ @@ -131,7 +131,7 @@ default - org.eclipse.jetty.servlet.DefaultServlet + org.eclipse.jetty.ee9.servlet.DefaultServlet aliases false diff --git a/jetty-core/jetty-deploy/src/test/resources/jetty-deploy-wars.xml b/jetty-core/jetty-deploy/src/test/resources/jetty-deploy-wars.xml index 8730443711b..770fedd1b6e 100644 --- a/jetty-core/jetty-deploy/src/test/resources/jetty-deploy-wars.xml +++ b/jetty-core/jetty-deploy/src/test/resources/jetty-deploy-wars.xml @@ -14,7 +14,7 @@ - + /webapps 1 /workish diff --git a/jetty-core/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml b/jetty-core/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml index c0a86628a4b..928a63c31ef 100644 --- a/jetty-core/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml +++ b/jetty-core/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml @@ -16,7 +16,7 @@ - + /webapps /etc/webdefault.xml 1 diff --git a/jetty-core/jetty-deploy/src/test/resources/jetty-logging.properties b/jetty-core/jetty-deploy/src/test/resources/jetty-logging.properties index ad1ad3a27ee..7b80290a89f 100644 --- a/jetty-core/jetty-deploy/src/test/resources/jetty-logging.properties +++ b/jetty-core/jetty-deploy/src/test/resources/jetty-logging.properties @@ -1,5 +1,5 @@ # Jetty Logging using jetty-slf4j-impl #org.eclipse.jetty.deploy.DeploymentTempDirTest.LEVEL=DEBUG #org.eclipse.jetty.deploy.LEVEL=DEBUG -#org.eclipse.jetty.webapp.LEVEL=DEBUG +#org.eclipse.jetty.ee9.webapp.LEVEL=DEBUG #org.eclipse.jetty.util.Scanner=DEBUG diff --git a/jetty-core/jetty-deploy/src/test/resources/webapps/badapp/badapp.xml b/jetty-core/jetty-deploy/src/test/resources/webapps/badapp/badapp.xml index 952aab492d1..50e55934ba2 100644 --- a/jetty-core/jetty-deploy/src/test/resources/webapps/badapp/badapp.xml +++ b/jetty-core/jetty-deploy/src/test/resources/webapps/badapp/badapp.xml @@ -1,7 +1,7 @@ - + /badapp /badapp.war true diff --git a/jetty-core/jetty-deploy/src/test/resources/webapps/foo.xml b/jetty-core/jetty-deploy/src/test/resources/webapps/foo.xml index 8b600c3bdc6..a373642f6a3 100644 --- a/jetty-core/jetty-deploy/src/test/resources/webapps/foo.xml +++ b/jetty-core/jetty-deploy/src/test/resources/webapps/foo.xml @@ -1,6 +1,6 @@ - + /foo /foo.war diff --git a/jetty-core/jetty-fcgi/fcgi-client/pom.xml b/jetty-core/jetty-fcgi/fcgi-client/pom.xml index a2399cd69e6..9803acffe6d 100644 --- a/jetty-core/jetty-fcgi/fcgi-client/pom.xml +++ b/jetty-core/jetty-fcgi/fcgi-client/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.fcgi - fcgi-parent - 11.0.10-SNAPSHOT + fcgi + 12.0.0-SNAPSHOT 4.0.0 fcgi-client - Jetty :: FastCGI :: Client + Jetty Core :: FastCGI :: Client ${project.groupId}.client diff --git a/jetty-fcgi/fcgi-client/src/main/java/module-info.java b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/module-info.java similarity index 83% rename from jetty-fcgi/fcgi-client/src/main/java/module-info.java rename to jetty-core/jetty-fcgi/fcgi-client/src/main/java/module-info.java index e3805a29ee8..f6034926558 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/module-info.java +++ b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/module-info.java @@ -19,6 +19,7 @@ module org.eclipse.jetty.fcgi.client exports org.eclipse.jetty.fcgi; exports org.eclipse.jetty.fcgi.client.http; - exports org.eclipse.jetty.fcgi.generator; - exports org.eclipse.jetty.fcgi.parser; + + exports org.eclipse.jetty.fcgi.generator to org.eclipse.jetty.fcgi.server; + exports org.eclipse.jetty.fcgi.parser to org.eclipse.jetty.fcgi.server; } diff --git a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/FCGI.java b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/FCGI.java index a4a32757593..83e70fe1b3e 100644 --- a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/FCGI.java +++ b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/FCGI.java @@ -25,17 +25,13 @@ public class FCGI public static Role from(int code) { - switch (code) + return switch (code) { - case 1: - return RESPONDER; - case 2: - return AUTHORIZER; - case 3: - return FILTER; - default: - throw new IllegalArgumentException(); - } + case 1 -> RESPONDER; + case 2 -> AUTHORIZER; + case 3 -> FILTER; + default -> throw new IllegalArgumentException(); + }; } public final int code; @@ -61,31 +57,20 @@ public class FCGI public static FrameType from(int code) { - switch (code) + return switch (code) { - case 1: - return BEGIN_REQUEST; - case 2: - return ABORT_REQUEST; - case 3: - return END_REQUEST; - case 4: - return PARAMS; - case 5: - return STDIN; - case 6: - return STDOUT; - case 7: - return STDERR; - case 8: - return DATA; - case 9: - return GET_VALUES; - case 10: - return GET_VALUES_RESULT; - default: - throw new IllegalArgumentException(); - } + case 1 -> BEGIN_REQUEST; + case 2 -> ABORT_REQUEST; + case 3 -> END_REQUEST; + case 4 -> PARAMS; + case 5 -> STDIN; + case 6 -> STDOUT; + case 7 -> STDERR; + case 8 -> DATA; + case 9 -> GET_VALUES; + case 10 -> GET_VALUES_RESULT; + default -> throw new IllegalArgumentException(); + }; } public final int code; diff --git a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index 99ab9f9067a..58c7b986fc0 100644 --- a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -11,403 +11,195 @@ // ======================================================================== // -package org.eclipse.jetty.fcgi.server; +package org.eclipse.jetty.fcgi.client.http; -import java.util.Locale; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicReference; +import java.nio.ByteBuffer; +import java.util.concurrent.TimeoutException; -import org.eclipse.jetty.fcgi.FCGI; -import org.eclipse.jetty.http.HostPortHttpField; +import org.eclipse.jetty.client.HttpChannel; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.HttpReceiver; +import org.eclipse.jetty.client.HttpSender; +import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.fcgi.generator.Flusher; +import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpInput; -import org.eclipse.jetty.server.HttpTransport; +import org.eclipse.jetty.io.IdleTimeout; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HttpChannelOverFCGI extends HttpChannel { private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverFCGI.class); - private static final HttpInput.Content EOF_CONTENT = new HttpInput.EofContent(); - private final Callback asyncFillCallback = new AsyncFillCallback(); - private final ServerFCGIConnection connection; - private final HttpFields.Mutable fields = HttpFields.build(); - private final Dispatcher dispatcher; - private HttpInput.Content normalContent; - private HttpInput.Content specialContent; - private String method; - private String path; - private String query; - private String version; - private HostPortHttpField hostPort; + private final HttpConnectionOverFCGI connection; + private final Flusher flusher; + private final HttpSenderOverFCGI sender; + private final HttpReceiverOverFCGI receiver; + private final FCGIIdleTimeout idle; + private int request; + private HttpVersion version; - public HttpChannelOverFCGI(ServerFCGIConnection connection, Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport) + public HttpChannelOverFCGI(final HttpConnectionOverFCGI connection, Flusher flusher, long idleTimeout) { - super(connector, configuration, endPoint, transport); + super(connection.getHttpDestination()); this.connection = connection; - this.dispatcher = new Dispatcher(connector.getServer().getThreadPool(), this); + this.flusher = flusher; + this.sender = new HttpSenderOverFCGI(this); + this.receiver = new HttpReceiverOverFCGI(this); + this.idle = new FCGIIdleTimeout(connection, idleTimeout); + } + + protected int getRequest() + { + return request; + } + + void setRequest(int request) + { + this.request = request; } @Override - public boolean onContent(HttpInput.Content content) + protected HttpSender getHttpSender() { - boolean result = super.onContent(content); - - HttpInput.Content special = this.specialContent; - Throwable failure = special == null ? null : special.getError(); - if (failure == null) - { - if (normalContent != null) - throw new IllegalStateException("onContent has unconsumed content"); - normalContent = content; - } - else - { - content.failed(failure); - } - - return result; + return sender; } @Override - public boolean needContent() + protected HttpReceiver getHttpReceiver() { - if (hasContent()) - { - if (LOG.isDebugEnabled()) - LOG.debug("needContent has immediate content {}", this); - return true; - } + return receiver; + } - parseAndFill(); + public boolean isFailed() + { + return sender.isFailed() || receiver.isFailed(); + } - if (hasContent()) - { - if (LOG.isDebugEnabled()) - LOG.debug("needContent has parsed content {}", this); - return true; - } + void receive() + { + connection.process(); + } - connection.getEndPoint().tryFillInterested(asyncFillCallback); + @Override + public void send(HttpExchange exchange) + { + version = exchange.getRequest().getVersion(); + idle.onOpen(); + sender.send(exchange); + } + + @Override + public void release() + { + connection.release(this); + } + + protected boolean responseBegin(int code, String reason) + { + idle.notIdle(); + HttpExchange exchange = getHttpExchange(); + if (exchange == null) + return false; + exchange.getResponse().version(version).status(code).reason(reason); + return receiver.responseBegin(exchange); + } + + protected boolean responseHeader(HttpField field) + { + HttpExchange exchange = getHttpExchange(); + return exchange != null && receiver.responseHeader(exchange, field); + } + + protected boolean responseHeaders() + { + idle.notIdle(); + HttpExchange exchange = getHttpExchange(); + return exchange != null && receiver.responseHeaders(exchange); + } + + protected boolean content(ByteBuffer buffer, Callback callback) + { + idle.notIdle(); + HttpExchange exchange = getHttpExchange(); + if (exchange != null) + return receiver.responseContent(exchange, buffer, callback); + callback.succeeded(); return false; } - private boolean hasContent() + protected boolean responseSuccess() { - return specialContent != null || normalContent != null; + HttpExchange exchange = getHttpExchange(); + return exchange != null && receiver.responseSuccess(exchange); + } + + protected boolean responseFailure(Throwable failure) + { + HttpExchange exchange = getHttpExchange(); + return exchange != null && receiver.responseFailure(failure); } @Override - public HttpInput.Content produceContent() + public void exchangeTerminated(HttpExchange exchange, Result result) { - if (!hasContent()) - parseAndFill(); + super.exchangeTerminated(exchange, result); + idle.onClose(); + HttpFields responseHeaders = result.getResponse().getHeaders(); + if (result.isFailed()) + connection.close(result.getFailure()); + else if (!connection.closeByHTTP(responseHeaders)) + release(); + } - if (!hasContent()) - return null; + protected void flush(Generator.Result... results) + { + flusher.flush(results); + } - HttpInput.Content content = normalContent; - if (content != null) + private class FCGIIdleTimeout extends IdleTimeout + { + private final HttpConnectionOverFCGI connection; + private boolean open; + + public FCGIIdleTimeout(HttpConnectionOverFCGI connection, long idleTimeout) + { + super(connection.getHttpDestination().getHttpClient().getScheduler()); + this.connection = connection; + setIdleTimeout(idleTimeout >= 0 ? idleTimeout : connection.getEndPoint().getIdleTimeout()); + } + + @Override + public void onOpen() + { + open = true; + notIdle(); + super.onOpen(); + } + + @Override + public void onClose() + { + super.onClose(); + open = false; + } + + @Override + protected void onIdleExpired(TimeoutException timeout) { if (LOG.isDebugEnabled()) - LOG.debug("produceContent produced {} {}", content, this); - normalContent = null; - return content; - } - content = specialContent; - if (LOG.isDebugEnabled()) - LOG.debug("produceContent produced special {} {}", content, this); - return content; - } - - private void parseAndFill() - { - if (LOG.isDebugEnabled()) - LOG.debug("parseAndFill {}", this); - connection.parseAndFill(); - } - - @Override - public boolean failAllContent(Throwable failure) - { - if (LOG.isDebugEnabled()) - LOG.debug("failing all content {}", this); - HttpInput.Content normal = normalContent; - if (normal != null) - normal.failed(failure); - HttpInput.Content special = specialContent; - if (special != null) - return special.isEof(); - while (true) - { - HttpInput.Content content = produceContent(); - if (content == null) - return false; - special = specialContent; - if (special != null) - return special.isEof(); - content.failed(failure); - } - } - - @Override - public boolean failed(Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("failed {}", this, x); - - HttpInput.Content special = specialContent; - Throwable error = special == null ? null : special.getError(); - if (error != null && error != x) - error.addSuppressed(x); - else - specialContent = new HttpInput.ErrorContent(x); - - return getRequest().getHttpInput().onContentProducible(); - } - - @Override - protected boolean eof() - { - if (LOG.isDebugEnabled()) - LOG.debug("received EOF"); - specialContent = EOF_CONTENT; - return getRequest().getHttpInput().onContentProducible(); - } - - protected void header(HttpField field) - { - String name = field.getName(); - String value = field.getValue(); - getRequest().setAttribute(name, value); - if (FCGI.Headers.REQUEST_METHOD.equalsIgnoreCase(name)) - method = value; - else if (FCGI.Headers.DOCUMENT_URI.equalsIgnoreCase(name)) - path = value; - else if (FCGI.Headers.QUERY_STRING.equalsIgnoreCase(name)) - query = value; - else if (FCGI.Headers.SERVER_PROTOCOL.equalsIgnoreCase(name)) - version = value; - else - processField(field); - } - - private void processField(HttpField field) - { - HttpField httpField = convertHeader(field); - if (httpField != null) - { - fields.add(httpField); - if (HttpHeader.HOST.is(httpField.getName())) - hostPort = (HostPortHttpField)httpField; - } - } - - public void onRequest() - { - String uri = path; - if (!StringUtil.isEmpty(query)) - uri += "?" + query; - // TODO https? - onRequest(new MetaData.Request(method, HttpScheme.HTTP.asString(), hostPort, uri, HttpVersion.fromString(version), fields, Long.MIN_VALUE)); - } - - private HttpField convertHeader(HttpField field) - { - String name = field.getName(); - if (name.startsWith("HTTP_")) - { - // Converts e.g. "HTTP_ACCEPT_ENCODING" to "Accept-Encoding" - String[] parts = name.split("_"); - StringBuilder httpName = new StringBuilder(); - for (int i = 1; i < parts.length; ++i) - { - if (i > 1) - httpName.append("-"); - String part = parts[i]; - httpName.append(Character.toUpperCase(part.charAt(0))); - httpName.append(part.substring(1).toLowerCase(Locale.ENGLISH)); - } - String headerName = httpName.toString(); - String value = field.getValue(); - if (HttpHeader.HOST.is(headerName)) - return new HostPortHttpField(value); - else - return new HttpField(headerName, value); - } - return null; - } - - protected void dispatch() - { - dispatcher.dispatch(); - } - - public boolean onIdleTimeout(Throwable timeout) - { - boolean handle = doOnIdleTimeout(timeout); - if (handle) - execute(this); - return !handle; - } - - private boolean doOnIdleTimeout(Throwable x) - { - boolean neverDispatched = getState().isIdle(); - HttpInput.Content normal = this.normalContent; - boolean waitingForContent = normal == null || normal.remaining() == 0; - if ((waitingForContent || neverDispatched) && specialContent == null) - { - x.addSuppressed(new Throwable("HttpInput idle timeout")); - specialContent = new HttpInput.ErrorContent(x); - return getRequest().getHttpInput().onContentProducible(); - } - return false; - } - - @Override - public void recycle() - { - super.recycle(); - HttpInput.Content normal = normalContent; - if (normal != null) - throw new AssertionError("unconsumed content: " + normal); - specialContent = null; - } - - @Override - public void onCompleted() - { - super.onCompleted(); - HttpInput input = getRequest().getHttpInput(); - boolean consumed = input.consumeAll(); - // Assume we don't arrive here from the connection's onFillable() (which already - // calls fillInterested()), because we dispatch() when all the headers are received. - // When the request/response is completed, we must arrange to call fillInterested(). - connection.onCompleted(consumed); - } - - private class AsyncFillCallback implements Callback - { - @Override - public void succeeded() - { - if (getRequest().getHttpInput().onContentProducible()) - handle(); + LOG.debug("Idle timeout for request {}", request); + connection.abort(timeout); } @Override - public void failed(Throwable x) + public boolean isOpen() { - if (HttpChannelOverFCGI.this.failed(x)) - handle(); - } - - @Override - public InvocationType getInvocationType() - { - return InvocationType.NON_BLOCKING; - } - } - - private static class Dispatcher implements Runnable - { - private final AtomicReference state = new AtomicReference<>(State.IDLE); - private final Executor executor; - private final Runnable runnable; - - private Dispatcher(Executor executor, Runnable runnable) - { - this.executor = executor; - this.runnable = runnable; - } - - public void dispatch() - { - while (true) - { - State current = state.get(); - if (LOG.isDebugEnabled()) - LOG.debug("Dispatching, state={}", current); - switch (current) - { - case IDLE: - { - if (!state.compareAndSet(current, State.DISPATCH)) - continue; - executor.execute(this); - return; - } - case DISPATCH: - case EXECUTE: - { - if (state.compareAndSet(current, State.SCHEDULE)) - return; - continue; - } - case SCHEDULE: - { - return; - } - default: - { - throw new IllegalStateException(); - } - } - } - } - - @Override - public void run() - { - while (true) - { - State current = state.get(); - if (LOG.isDebugEnabled()) - LOG.debug("Running, state={}", current); - switch (current) - { - case DISPATCH: - { - if (state.compareAndSet(current, State.EXECUTE)) - runnable.run(); - continue; - } - case EXECUTE: - { - if (state.compareAndSet(current, State.IDLE)) - return; - continue; - } - case SCHEDULE: - { - if (state.compareAndSet(current, State.DISPATCH)) - continue; - throw new IllegalStateException(); - } - default: - { - throw new IllegalStateException(); - } - } - } - } - - private enum State - { - IDLE, DISPATCH, EXECUTE, SCHEDULE + return open; } } } diff --git a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index e460a288664..598c532c932 100644 --- a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.fcgi.client.http; -import java.io.IOException; import java.util.List; import java.util.Map; @@ -86,7 +85,7 @@ public class HttpClientTransportOverFCGI extends AbstractConnectorHttpClientTran } @Override - public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException + public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) { HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); @SuppressWarnings("unchecked") @@ -97,12 +96,12 @@ public class HttpClientTransportOverFCGI extends AbstractConnectorHttpClientTran return customize(connection, context); } - protected HttpConnectionOverFCGI newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise) + protected org.eclipse.jetty.io.Connection newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise) { return new HttpConnectionOverFCGI(endPoint, destination, promise); } - protected void customize(Request request, HttpFields.Mutable fastCGIHeaders) + public void customize(Request request, HttpFields.Mutable fastCGIHeaders) { fastCGIHeaders.put(FCGI.Headers.DOCUMENT_ROOT, getScriptRoot()); } diff --git a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index 796483ef5ff..8dd042ff329 100644 --- a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -317,7 +317,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne private int acquireRequest() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { int last = requests.getLast(); int request = last + 1; @@ -328,7 +328,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne private void releaseRequest(int request) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { requests.removeFirstOccurrence(request); } @@ -445,7 +445,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne { switch (stream) { - case STD_OUT: + case STD_OUT -> { HttpChannelOverFCGI channel = HttpConnectionOverFCGI.this.channel; if (channel != null) @@ -457,17 +457,9 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne { noChannel(request); } - break; - } - case STD_ERR: - { - LOG.info(BufferUtil.toUTF8String(buffer)); - break; - } - default: - { - throw new IllegalArgumentException(); } + case STD_ERR -> LOG.info(BufferUtil.toUTF8String(buffer)); + default -> throw new IllegalArgumentException(); } return false; } diff --git a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java index d88e8f2a17e..49b2865f3a3 100644 --- a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java +++ b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java @@ -48,7 +48,7 @@ public class Flusher private void offer(Generator.Result result) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { queue.offer(result); } @@ -56,7 +56,7 @@ public class Flusher private Generator.Result poll() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { return queue.poll(); } diff --git a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java index 1cae12e42c3..72e5ae2d513 100644 --- a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java +++ b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java @@ -121,7 +121,7 @@ public class Generator public ByteBuffer[] getByteBuffers() { - return buffers.toArray(new ByteBuffer[buffers.size()]); + return buffers.toArray(new ByteBuffer[0]); } @Override diff --git a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java index 20102ff096d..999ce4161c8 100644 --- a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java +++ b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java @@ -51,16 +51,8 @@ public class ClientParser extends Parser } } - private class EndRequestListener implements Listener + private record EndRequestListener(Listener listener, StreamContentParser... streamParsers) implements Listener { - private final Listener listener; - private final StreamContentParser[] streamParsers; - - private EndRequestListener(Listener listener, StreamContentParser... streamParsers) - { - this.listener = listener; - this.streamParsers = streamParsers; - } @Override public void onBegin(int request, int code, String reason) diff --git a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java index 309d0c0836b..bce8007aaa9 100644 --- a/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java +++ b/jetty-core/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -82,7 +82,7 @@ public class ResponseContentParser extends StreamContentParser private static class ResponseParser implements HttpParser.ResponseHandler { private final HttpFields.Mutable fields = HttpFields.build(); - private ClientParser.Listener listener; + private final ClientParser.Listener listener; private final int request; private final FCGIHttpParser httpParser; private State state = State.HEADERS; diff --git a/jetty-fcgi/fcgi-client/src/test/resources/jetty-logging.properties b/jetty-core/jetty-fcgi/fcgi-client/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-fcgi/fcgi-client/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-fcgi/fcgi-client/src/test/resources/jetty-logging.properties diff --git a/jetty-core/jetty-fcgi/fcgi-server/pom.xml b/jetty-core/jetty-fcgi/fcgi-server/pom.xml index d6421e28f13..a512fccb5f1 100644 --- a/jetty-core/jetty-fcgi/fcgi-server/pom.xml +++ b/jetty-core/jetty-fcgi/fcgi-server/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.fcgi - fcgi-parent - 11.0.10-SNAPSHOT + fcgi + 12.0.0-SNAPSHOT 4.0.0 fcgi-server - Jetty :: FastCGI :: Server + Jetty Core :: FastCGI :: Server ${project.groupId}.server @@ -19,34 +19,24 @@ org.slf4j slf4j-api - + org.eclipse.jetty.fcgi fcgi-client - - org.eclipse.jetty - jetty-proxy - org.eclipse.jetty jetty-server - org.eclipse.jetty jetty-slf4j-impl test - - org.eclipse.jetty - jetty-servlet - test - org.eclipse.jetty.http2 http2-server @@ -57,11 +47,6 @@ jetty-alpn-java-server test - - org.eclipse.jetty - jetty-annotations - test - org.eclipse.jetty jetty-unixdomain-server diff --git a/jetty-fcgi/fcgi-server/src/main/java/module-info.java b/jetty-core/jetty-fcgi/fcgi-server/src/main/java/module-info.java similarity index 79% rename from jetty-fcgi/fcgi-server/src/main/java/module-info.java rename to jetty-core/jetty-fcgi/fcgi-server/src/main/java/module-info.java index f9ea05c49d0..65813325e51 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/module-info.java +++ b/jetty-core/jetty-fcgi/fcgi-server/src/main/java/module-info.java @@ -16,11 +16,7 @@ module org.eclipse.jetty.fcgi.server requires org.slf4j; requires transitive org.eclipse.jetty.fcgi.client; - requires transitive org.eclipse.jetty.proxy; - - // Only required if using the proxy features. - requires static jetty.servlet.api; + requires transitive org.eclipse.jetty.server; exports org.eclipse.jetty.fcgi.server; - exports org.eclipse.jetty.fcgi.server.proxy; } diff --git a/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java b/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java index b15004d6177..3e9ff77d31c 100644 --- a/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java +++ b/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnectionFactory.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.fcgi.server; +import org.eclipse.jetty.fcgi.server.internal.ServerFCGIConnection; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.AbstractConnectionFactory; diff --git a/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java b/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java new file mode 100644 index 00000000000..a448b6839d7 --- /dev/null +++ b/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java @@ -0,0 +1,379 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.fcgi.server.internal; + +import java.nio.ByteBuffer; +import java.util.Locale; + +import org.eclipse.jetty.fcgi.FCGI; +import org.eclipse.jetty.fcgi.generator.Flusher; +import org.eclipse.jetty.fcgi.generator.Generator; +import org.eclipse.jetty.fcgi.generator.ServerGenerator; +import org.eclipse.jetty.http.HostPortHttpField; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpStream; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.thread.Invocable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HttpStreamOverFCGI implements HttpStream +{ + private static final Logger LOG = LoggerFactory.getLogger(HttpStreamOverFCGI.class); + + private final Callback _demandCallback = new DemandCallback(); + private final HttpFields.Mutable _headers = HttpFields.build(); + private final ServerFCGIConnection _connection; + private final ServerGenerator _generator; + private final HttpChannel _httpChannel; + private final int _id; + private final long _nanoTime; + private String _method; + private HostPortHttpField hostPort; + private String _path; + private String _query; + private String _version; + private Content _content; + private boolean _committed; + private boolean _shutdown; + private boolean _aborted; + + public HttpStreamOverFCGI(ServerFCGIConnection connection, ServerGenerator generator, HttpChannel httpChannel, int id) + { + _connection = connection; + _generator = generator; + _httpChannel = httpChannel; + _id = id; + _nanoTime = System.nanoTime(); + } + + public HttpChannel getHttpChannel() + { + return _httpChannel; + } + + @Override + public String getId() + { + return String.valueOf(_id); + } + + @Override + public long getNanoTimeStamp() + { + return _nanoTime; + } + + public void onHeader(HttpField field) + { + String name = field.getName(); + String value = field.getValue(); + if (FCGI.Headers.REQUEST_METHOD.equalsIgnoreCase(name)) + _method = value; + else if (FCGI.Headers.DOCUMENT_URI.equalsIgnoreCase(name)) + _path = value; + else if (FCGI.Headers.QUERY_STRING.equalsIgnoreCase(name)) + _query = value; + else if (FCGI.Headers.SERVER_PROTOCOL.equalsIgnoreCase(name)) + _version = value; + else + processField(field); + } + + public void onHeaders() + { + String pathQuery = URIUtil.addPathQuery(_path, _query); + // TODO https? + MetaData.Request request = new MetaData.Request(_method, HttpScheme.HTTP.asString(), hostPort, pathQuery, HttpVersion.fromString(_version), _headers, Long.MIN_VALUE); + Runnable task = _httpChannel.onRequest(request); + _headers.forEach(field -> _httpChannel.getRequest().setAttribute(field.getName(), field.getValue())); + // TODO: here we just execute the task. + // However, we should really return all the way back to onFillable() + // and feed the Runnable to an ExecutionStrategy. + execute(task); + } + + private void processField(HttpField field) + { + HttpField httpField = convertHeader(field); + if (httpField != null) + { + _headers.add(httpField); + if (HttpHeader.HOST.is(httpField.getName())) + hostPort = (HostPortHttpField)httpField; + } + } + + private HttpField convertHeader(HttpField field) + { + String name = field.getName(); + if (name.startsWith("HTTP_")) + { + // Converts e.g. "HTTP_ACCEPT_ENCODING" to "Accept-Encoding" + String[] parts = name.split("_"); + StringBuilder httpName = new StringBuilder(); + for (int i = 1; i < parts.length; ++i) + { + if (i > 1) + httpName.append("-"); + String part = parts[i]; + httpName.append(Character.toUpperCase(part.charAt(0))); + httpName.append(part.substring(1).toLowerCase(Locale.ENGLISH)); + } + String headerName = httpName.toString(); + String value = field.getValue(); + if (HttpHeader.HOST.is(headerName)) + return new HostPortHttpField(value); + else + return new HttpField(headerName, value); + } + return null; + } + + @Override + public Content readContent() + { + if (_content == null) + _connection.parseAndFill(); + Content content = _content; + _content = Content.next(content); + return content; + } + + @Override + public void demandContent() + { + if (_content != null) + return; + + _connection.parseAndFill(); + + if (_content != null) + { + notifyContentAvailable(); + return; + } + + _connection.tryFillInterested(_demandCallback); + } + + private void notifyContentAvailable() + { + Runnable onContentAvailable = _httpChannel.onContentAvailable(); + if (onContentAvailable != null) + onContentAvailable.run(); + } + + public void onContent(Content content) + { + _content = content; + } + + public void onComplete() + { + _content = Content.last(_content); + } + + @Override + public void prepareResponse(HttpFields.Mutable headers) + { + // Nothing to do for FastCGI. + } + + @Override + public void send(MetaData.Request request, MetaData.Response response, boolean last, Callback callback, ByteBuffer... buffers) + { + if (buffers.length > 1) + throw new IllegalStateException(); + ByteBuffer content = buffers.length == 0 ? BufferUtil.EMPTY_BUFFER : buffers[0]; + + if (LOG.isDebugEnabled()) + LOG.debug("send {} {} l={}", this, request, last); + boolean head = HttpMethod.HEAD.is(request.getMethod()); + if (response != null) + { + commit(response, head, last, content, callback); + } + else + { + Flusher flusher = _connection.getFlusher(); + if (head) + { + if (last) + { + Generator.Result result = generateResponseContent(true, BufferUtil.EMPTY_BUFFER, callback); + flusher.flush(result); + } + else + { + // Skip content generation + callback.succeeded(); + } + } + else + { + Generator.Result result = generateResponseContent(last, content, callback); + flusher.flush(result); + } + + if (last && _shutdown) + flusher.shutdown(); + } + } + + private void commit(MetaData.Response info, boolean head, boolean last, ByteBuffer content, Callback callback) + { + if (LOG.isDebugEnabled()) + LOG.debug("commit {} {} l={}", this, info, last); + + _committed = true; + + boolean shutdown = _shutdown = info.getFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); + + Flusher flusher = _connection.getFlusher(); + if (head) + { + if (last) + { + Generator.Result headersResult = generateResponseHeaders(info, Callback.NOOP); + Generator.Result contentResult = generateResponseContent(true, BufferUtil.EMPTY_BUFFER, callback); + flusher.flush(headersResult, contentResult); + } + else + { + Generator.Result headersResult = generateResponseHeaders(info, callback); + flusher.flush(headersResult); + } + } + else + { + Generator.Result headersResult = generateResponseHeaders(info, Callback.NOOP); + Generator.Result contentResult = generateResponseContent(last, content, callback); + flusher.flush(headersResult, contentResult); + } + + if (last && shutdown) + flusher.shutdown(); + } + + private Generator.Result generateResponseHeaders(MetaData.Response info, Callback callback) + { + return _generator.generateResponseHeaders(_id, info.getStatus(), info.getReason(), info.getFields(), callback); + } + + private Generator.Result generateResponseContent(boolean last, ByteBuffer buffer, Callback callback) + { + return _generator.generateResponseContent(_id, buffer, last, _aborted, callback); + } + + @Override + public boolean isPushSupported() + { + return false; + } + + @Override + public void push(MetaData.Request request) + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isCommitted() + { + return _committed; + } + + @Override + public void succeeded() + { + _httpChannel.recycle(); + _connection.onCompleted(null); + } + + @Override + public void failed(Throwable x) + { + // TODO: should we do more? + _aborted = true; + _connection.onCompleted(x); + } + + @Override + public boolean isComplete() + { + // TODO + return false; + } + + @Override + public void setUpgradeConnection(Connection connection) + { + throw new UnsupportedOperationException(); + } + + @Override + public Connection upgrade() + { + return null; + } + + public boolean onIdleTimeout(Throwable timeout) + { + Runnable task = _httpChannel.onFailure(timeout); + if (task != null) + execute(task); + return false; + } + + private void execute(Runnable task) + { + _connection.getConnector().getExecutor().execute(task); + } + + private class DemandCallback implements Callback + { + @Override + public void succeeded() + { + notifyContentAvailable(); + } + + @Override + public void failed(Throwable x) + { + Runnable task = _httpChannel.onFailure(x); + if (task != null) + _connection.getConnector().getExecutor().execute(task); + } + + @Override + public InvocationType getInvocationType() + { + return Invocable.getInvocationType(_httpChannel); + } + } +} diff --git a/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java b/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java index 8c31d1e0a52..8fb6263e2e6 100644 --- a/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java +++ b/jetty-core/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java @@ -11,40 +11,53 @@ // ======================================================================== // -package org.eclipse.jetty.fcgi.server; +package org.eclipse.jetty.fcgi.server.internal; +import java.net.SocketAddress; import java.nio.ByteBuffer; +import java.util.Set; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.generator.Flusher; +import org.eclipse.jetty.fcgi.generator.ServerGenerator; import org.eclipse.jetty.fcgi.parser.ServerParser; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.AbstractConnection; +import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.RetainableByteBuffer; import org.eclipse.jetty.io.RetainableByteBufferPool; +import org.eclipse.jetty.server.ConnectionMetaData; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpInput; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.HostPort; +import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ServerFCGIConnection extends AbstractConnection +public class ServerFCGIConnection extends AbstractConnection implements ConnectionMetaData { private static final Logger LOG = LoggerFactory.getLogger(ServerFCGIConnection.class); + private final HttpChannel.Factory httpChannelFactory = new HttpChannel.DefaultFactory(); + private final Attributes attributes = new Lazy(); private final Connector connector; private final RetainableByteBufferPool networkByteBufferPool; private final boolean sendStatus200; private final Flusher flusher; private final HttpConfiguration configuration; private final ServerParser parser; + private final String id; private boolean useInputDirectByteBuffers; private boolean useOutputDirectByteBuffers; private RetainableByteBuffer networkBuffer; - private HttpChannelOverFCGI channel; + private HttpStreamOverFCGI stream; public ServerFCGIConnection(Connector connector, EndPoint endPoint, HttpConfiguration configuration, boolean sendStatus200) { @@ -55,6 +68,12 @@ public class ServerFCGIConnection extends AbstractConnection this.configuration = configuration; this.sendStatus200 = sendStatus200; this.parser = new ServerParser(new ServerListener()); + this.id = StringUtil.randomAlphaNumeric(16); + } + + Flusher getFlusher() + { + return flusher; } public boolean isUseInputDirectByteBuffers() @@ -77,6 +96,102 @@ public class ServerFCGIConnection extends AbstractConnection this.useOutputDirectByteBuffers = useOutputDirectByteBuffers; } + @Override + public String getId() + { + return id; + } + + @Override + public HttpConfiguration getHttpConfiguration() + { + return configuration; + } + + @Override + public HttpVersion getHttpVersion() + { + return HttpVersion.HTTP_1_1; + } + + @Override + public String getProtocol() + { + return "fcgi/1.0"; + } + + @Override + public Connection getConnection() + { + return this; + } + + @Override + public Connector getConnector() + { + return connector; + } + + @Override + public boolean isPersistent() + { + return true; + } + + @Override + public boolean isSecure() + { + return false; + } + + @Override + public SocketAddress getRemoteSocketAddress() + { + return getEndPoint().getRemoteSocketAddress(); + } + + @Override + public SocketAddress getLocalSocketAddress() + { + return getEndPoint().getLocalSocketAddress(); + } + + @Override + public HostPort getServerAuthority() + { + return ConnectionMetaData.getServerAuthority(configuration, this); + } + + @Override + public Object removeAttribute(String name) + { + return attributes.removeAttribute(name); + } + + @Override + public Object setAttribute(String name, Object attribute) + { + return attributes.setAttribute(name, attribute); + } + + @Override + public Object getAttribute(String name) + { + return attributes.getAttribute(name); + } + + @Override + public Set getAttributeNameSet() + { + return attributes.getAttributeNameSet(); + } + + @Override + public void clearAttributes() + { + attributes.clearAttributes(); + } + @Override public void onOpen() { @@ -135,12 +250,12 @@ public class ServerFCGIConnection extends AbstractConnection LOG.debug("parseAndFill {}", this); // This loop must run only until the request is completed. // See also HttpConnection.parseAndFillForContent(). - while (channel != null) + while (stream != null) { if (parse(networkBuffer.getBuffer())) return; // Check if the request was completed by the parsing. - if (channel == null) + if (stream == null) return; if (fillInputBuffer() <= 0) break; @@ -179,8 +294,8 @@ public class ServerFCGIConnection extends AbstractConnection @Override protected boolean onReadTimeout(Throwable timeout) { - if (channel != null) - return channel.onIdleTimeout(timeout); + if (stream != null) + return stream.onIdleTimeout(timeout); return true; } @@ -200,11 +315,13 @@ public class ServerFCGIConnection extends AbstractConnection flusher.shutdown(); } - void onCompleted(boolean fillMore) + void onCompleted(Throwable failure) { releaseInputBuffer(); - if (getEndPoint().isOpen() && fillMore) + if (failure == null) fillInterested(); + else + getFlusher().shutdown(); } private class ServerListener implements ServerParser.Listener @@ -213,10 +330,12 @@ public class ServerFCGIConnection extends AbstractConnection public void onStart(int request, FCGI.Role role, int flags) { // TODO: handle flags - if (channel != null) + if (stream != null) throw new UnsupportedOperationException("FastCGI Multiplexing"); - channel = new HttpChannelOverFCGI(ServerFCGIConnection.this, connector, configuration, getEndPoint(), - new HttpTransportOverFCGI(connector.getByteBufferPool(), isUseOutputDirectByteBuffers(), sendStatus200, flusher, request)); + HttpChannel channel = httpChannelFactory.newHttpChannel(ServerFCGIConnection.this); + ServerGenerator generator = new ServerGenerator(connector.getByteBufferPool(), isUseOutputDirectByteBuffers(), sendStatus200); + stream = new HttpStreamOverFCGI(ServerFCGIConnection.this, generator, channel, request); + channel.setHttpStream(stream); if (LOG.isDebugEnabled()) LOG.debug("Request {} start on {}", request, channel); } @@ -225,34 +344,34 @@ public class ServerFCGIConnection extends AbstractConnection public void onHeader(int request, HttpField field) { if (LOG.isDebugEnabled()) - LOG.debug("Request {} header {} on {}", request, field, channel); - if (channel != null) - channel.header(field); + LOG.debug("Request {} header {} on {}", request, field, stream); + if (stream != null) + stream.onHeader(field); } @Override public boolean onHeaders(int request) { if (LOG.isDebugEnabled()) - LOG.debug("Request {} headers on {}", request, channel); - if (channel != null) + LOG.debug("Request {} headers on {}", request, stream); + if (stream != null) { - channel.onRequest(); - channel.dispatch(); - // We have dispatched to the application, so we must stop the fill & parse loop. + stream.onHeaders(); + // We have dispatched to the application, + // so we must stop the fill & parse loop. return true; } return false; } @Override - public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType streamType, ByteBuffer buffer) { if (LOG.isDebugEnabled()) - LOG.debug("Request {} {} content {} on {}", request, stream, buffer, channel); - if (channel != null) + LOG.debug("Request {} {} content {} on {}", request, streamType, buffer, stream); + if (stream != null) { - channel.onContent(new FastCGIContent(buffer)); + stream.onContent(new FastCGIContent(buffer, false)); // Signal that the content is processed asynchronously, to ensure backpressure. return true; } @@ -263,14 +382,13 @@ public class ServerFCGIConnection extends AbstractConnection public void onEnd(int request) { if (LOG.isDebugEnabled()) - LOG.debug("Request {} end on {}", request, channel); - if (channel != null) + LOG.debug("Request {} end on {}", request, stream); + if (stream != null) { - channel.onContentComplete(); - channel.onRequestComplete(); - // Nulling out the channel signals that the + stream.onComplete(); + // Nulling out the stream signals that the // request is complete, see also parseAndFill(). - channel = null; + stream = null; } } @@ -278,33 +396,31 @@ public class ServerFCGIConnection extends AbstractConnection public void onFailure(int request, Throwable failure) { if (LOG.isDebugEnabled()) - LOG.debug("Request {} failure on {}: {}", request, channel, failure); - if (channel != null) - channel.onBadMessage(new BadMessageException(HttpStatus.BAD_REQUEST_400, null, failure)); - channel = null; + LOG.debug("Request {} failure on {}: {}", request, stream, failure); + if (stream != null) + stream.getHttpChannel().onFailure(new BadMessageException(HttpStatus.BAD_REQUEST_400, null, failure)); + stream = null; } - private class FastCGIContent extends HttpInput.Content + private class FastCGIContent extends Content.Abstract { - public FastCGIContent(ByteBuffer content) + private final ByteBuffer content; + + public FastCGIContent(ByteBuffer content, boolean last) { - super(content); + super(false, last); + this.content = content; networkBuffer.retain(); } @Override - public void succeeded() + public ByteBuffer getByteBuffer() { - release(); + return content; } @Override - public void failed(Throwable x) - { - release(); - } - - private void release() + public void release() { networkBuffer.release(); } diff --git a/jetty-core/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java b/jetty-core/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java index 095ffa2d9d6..82089f5d262 100644 --- a/jetty-core/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java +++ b/jetty-core/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java @@ -94,9 +94,8 @@ public abstract class AbstractHttpClientServerTest assertThat("Server BufferPool - unreleased", serverBufferPool.getLeakedResources(), Matchers.is(0L)); } - if ((clientBufferPool != null) && (clientBufferPool instanceof LeakTrackingByteBufferPool)) + if ((clientBufferPool != null) && (clientBufferPool instanceof LeakTrackingByteBufferPool pool)) { - LeakTrackingByteBufferPool pool = (LeakTrackingByteBufferPool)clientBufferPool; assertThat("Client BufferPool - leaked acquires", pool.getLeakedAcquires(), Matchers.is(0L)); assertThat("Client BufferPool - leaked releases", pool.getLeakedReleases(), Matchers.is(0L)); assertThat("Client BufferPool - leaked removes", pool.getLeakedRemoves(), Matchers.is(0L)); diff --git a/jetty-core/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-core/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java index fe78e9fe136..16b00ec4a66 100644 --- a/jetty-core/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java +++ b/jetty-core/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java @@ -13,12 +13,12 @@ package org.eclipse.jetty.fcgi.server; -import java.io.IOException; +import java.io.OutputStream; import java.net.URI; import java.net.URLEncoder; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -27,10 +27,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.GZIPOutputStream; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -40,13 +36,16 @@ import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.toolchain.test.IO; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.toolchain.test.Net; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Fields; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -59,7 +58,14 @@ public class HttpClientTest extends AbstractHttpClientServerTest @Test public void testGETResponseWithoutContent() throws Exception { - start(new EmptyServerHandler()); + start(new Handler.Processor() + { + @Override + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) + { + callback.succeeded(); + } + }); for (int i = 0; i < 2; ++i) { @@ -73,13 +79,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testGETResponseWithContent() throws Exception { byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7}; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - response.getOutputStream().write(data); - baseRequest.setHandled(true); + response.write(true, callback, ByteBuffer.wrap(data)); } }); @@ -101,17 +106,16 @@ public class HttpClientTest extends AbstractHttpClientServerTest { byte[] data = new byte[16 * 1024 * 1024]; new Random().nextBytes(data); - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { // Setting the Content-Length triggers the HTTP // content mode for response content parsing, // otherwise the RAW content mode is used. response.setContentLength(data.length); - response.getOutputStream().write(data); - baseRequest.setHandled(true); + response.write(true, callback, ByteBuffer.wrap(data)); } }); @@ -130,30 +134,29 @@ public class HttpClientTest extends AbstractHttpClientServerTest { String paramName1 = "a"; String paramName2 = "b"; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - response.setCharacterEncoding("UTF-8"); - ServletOutputStream output = response.getOutputStream(); - String paramValue1 = request.getParameter(paramName1); - output.write(paramValue1.getBytes(StandardCharsets.UTF_8)); - String paramValue2 = request.getParameter(paramName2); + response.setContentType("text/plain;charset=utf-8"); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + String paramValue1 = fields.getValue(paramName1); + org.eclipse.jetty.server.Response.write(response, false, UTF_8.encode(paramValue1)); + String paramValue2 = fields.getValue(paramName2); assertEquals("", paramValue2); - output.write("empty".getBytes(StandardCharsets.UTF_8)); - baseRequest.setHandled(true); + org.eclipse.jetty.server.Response.write(response, true, UTF_8.encode("empty")); } }); String value1 = "\u20AC"; - String paramValue1 = URLEncoder.encode(value1, StandardCharsets.UTF_8); + String paramValue1 = URLEncoder.encode(value1, UTF_8); String query = paramName1 + "=" + paramValue1 + "&" + paramName2; ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query); assertNotNull(response); assertEquals(200, response.getStatus()); - String content = new String(response.getContent(), StandardCharsets.UTF_8); + String content = new String(response.getContent(), UTF_8); assertEquals(value1 + "empty", content); } @@ -162,36 +165,35 @@ public class HttpClientTest extends AbstractHttpClientServerTest { String paramName1 = "a"; String paramName2 = "b"; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - response.setCharacterEncoding("UTF-8"); - ServletOutputStream output = response.getOutputStream(); - String[] paramValues1 = request.getParameterValues(paramName1); + response.setContentType("text/plain;charset=utf-8"); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + List paramValues1 = fields.getValues(paramName1); for (String paramValue : paramValues1) { - output.write(paramValue.getBytes(StandardCharsets.UTF_8)); + org.eclipse.jetty.server.Response.write(response, false, UTF_8.encode(paramValue)); } - String paramValue2 = request.getParameter(paramName2); - output.write(paramValue2.getBytes(StandardCharsets.UTF_8)); - baseRequest.setHandled(true); + String paramValue2 = fields.getValue(paramName2); + org.eclipse.jetty.server.Response.write(response, true, UTF_8.encode(paramValue2)); } }); String value11 = "\u20AC"; String value12 = "\u20AA"; String value2 = "&"; - String paramValue11 = URLEncoder.encode(value11, StandardCharsets.UTF_8); - String paramValue12 = URLEncoder.encode(value12, StandardCharsets.UTF_8); - String paramValue2 = URLEncoder.encode(value2, StandardCharsets.UTF_8); + String paramValue11 = URLEncoder.encode(value11, UTF_8); + String paramValue12 = URLEncoder.encode(value12, UTF_8); + String paramValue2 = URLEncoder.encode(value2, UTF_8); String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2; ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query); assertNotNull(response); assertEquals(200, response.getStatus()); - String content = new String(response.getContent(), StandardCharsets.UTF_8); + String content = new String(response.getContent(), UTF_8); assertEquals(value11 + value12 + value2, content); } @@ -200,18 +202,17 @@ public class HttpClientTest extends AbstractHttpClientServerTest { String paramName = "a"; String paramValue = "\u20AC"; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - String value = request.getParameter(paramName); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + String value = fields.getValue(paramName); if (paramValue.equals(value)) { - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/plain"); - response.getOutputStream().print(value); + response.setContentType("text/plain;charset=utf-8"); + response.write(true, callback, value); } } }); @@ -223,7 +224,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest assertNotNull(response); assertEquals(200, response.getStatus()); - assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8)); + assertEquals(paramValue, new String(response.getContent(), UTF_8)); } @Test @@ -231,31 +232,30 @@ public class HttpClientTest extends AbstractHttpClientServerTest { String paramName = "a"; String paramValue = "\u20AC"; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - String value = request.getParameter(paramName); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + String value = fields.getValue(paramName); if (paramValue.equals(value)) { - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/plain"); - response.getOutputStream().print(value); + response.setContentType("text/plain;charset=utf-8"); + response.write(true, callback, value); } } }); String uri = scheme + "://localhost:" + connector.getLocalPort() + - "/?" + paramName + "=" + URLEncoder.encode(paramValue, StandardCharsets.UTF_8); + "/?" + paramName + "=" + URLEncoder.encode(paramValue, UTF_8); ContentResponse response = client.POST(uri) .timeout(5, TimeUnit.SECONDS) .send(); assertNotNull(response); assertEquals(200, response.getStatus()); - assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8)); + assertEquals(paramValue, new String(response.getContent(), UTF_8)); } @Test @@ -263,18 +263,17 @@ public class HttpClientTest extends AbstractHttpClientServerTest { String paramName = "a"; String paramValue = "\u20AC"; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - String value = request.getParameter(paramName); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + String value = fields.getValue(paramName); if (paramValue.equals(value)) { - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/plain"); - response.getOutputStream().print(value); + response.setContentType("text/plain;charset=utf-8"); + response.write(true, callback, value); } } }); @@ -287,7 +286,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest assertNotNull(response); assertEquals(200, response.getStatus()); - assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8)); + assertEquals(paramValue, new String(response.getContent(), UTF_8)); } @Test @@ -296,18 +295,17 @@ public class HttpClientTest extends AbstractHttpClientServerTest byte[] content = {0, 1, 2, 3}; String paramName = "a"; String paramValue = "\u20AC"; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - String value = request.getParameter(paramName); + Fields fields = org.eclipse.jetty.server.Request.extractQueryParameters(request); + String value = fields.getValue(paramName); if (paramValue.equals(value)) { - response.setCharacterEncoding("UTF-8"); response.setContentType("application/octet-stream"); - IO.copy(request.getInputStream(), response.getOutputStream()); + Content.copy(request, response, callback); } } }); @@ -330,7 +328,14 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testPOSTWithContentNotifiesRequestContentListener() throws Exception { byte[] content = {0, 1, 2, 3}; - start(new EmptyServerHandler()); + start(new Handler.Processor() + { + @Override + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) + { + callback.succeeded(); + } + }); ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort()) .onRequestContent((request, buffer) -> @@ -351,7 +356,14 @@ public class HttpClientTest extends AbstractHttpClientServerTest @Test public void testPOSTWithContentTracksProgress() throws Exception { - start(new EmptyServerHandler()); + start(new Handler.Processor() + { + @Override + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) + { + callback.succeeded(); + } + }); AtomicInteger progress = new AtomicInteger(); ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort()) @@ -382,16 +394,17 @@ public class HttpClientTest extends AbstractHttpClientServerTest clientBufferPool = new MappedByteBufferPool.Tagged(); byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - response.setHeader("Content-Encoding", "gzip"); - GZIPOutputStream gzipOutput = new GZIPOutputStream(response.getOutputStream()); + response.getHeaders().put("Content-Encoding", "gzip"); + OutputStream outputStream = org.eclipse.jetty.server.Response.asOutputStream(response); + GZIPOutputStream gzipOutput = new GZIPOutputStream(outputStream); gzipOutput.write(data); gzipOutput.finish(); + callback.succeeded(); } }); @@ -408,20 +421,13 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testConnectionIdleTimeout() throws Exception { long idleTimeout = 1000; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - try - { - baseRequest.setHandled(true); - TimeUnit.MILLISECONDS.sleep(2 * idleTimeout); - } - catch (InterruptedException x) - { - throw new ServletException(x); - } + TimeUnit.MILLISECONDS.sleep(2 * idleTimeout); + callback.succeeded(); } }); @@ -453,7 +459,14 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testSendToIPv6Address() throws Exception { Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable()); - start(new EmptyServerHandler()); + start(new Handler.Processor() + { + @Override + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) + { + callback.succeeded(); + } + }); ContentResponse response = client.newRequest("[::1]", connector.getLocalPort()) .scheme(scheme) @@ -468,13 +481,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testHEADWithResponseContentLength() throws Exception { int length = 1024; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - response.getOutputStream().write(new byte[length]); + response.write(true, callback, ByteBuffer.wrap(new byte[length])); } }); @@ -505,14 +517,13 @@ public class HttpClientTest extends AbstractHttpClientServerTest public void testLongPollIsAbortedWhenClientIsStopped() throws Exception { CountDownLatch latch = new CountDownLatch(1); - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - request.startAsync(); latch.countDown(); + // Do not complete the callback. } }); @@ -536,20 +547,19 @@ public class HttpClientTest extends AbstractHttpClientServerTest @Test public void testEarlyEOF() throws Exception { - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); // Promise some content, then flush the headers, then fail to send the content. response.setContentLength(16); - response.flushBuffer(); + org.eclipse.jetty.server.Response.write(response, false); throw new NullPointerException("Explicitly thrown by test"); } }); - try (StacklessLogging ignore = new StacklessLogging(org.eclipse.jetty.server.HttpChannel.class)) + try (StacklessLogging ignore = new StacklessLogging(HttpChannel.class)) { assertThrows(ExecutionException.class, () -> client.newRequest("localhost", connector.getLocalPort()) @@ -575,14 +585,13 @@ public class HttpClientTest extends AbstractHttpClientServerTest { byte[] data = new byte[length]; new Random().nextBytes(data); - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) { - baseRequest.setHandled(true); - response.setHeader("Connection", "close"); - response.getOutputStream().write(data); + response.getHeaders().put("Connection", "close"); + response.write(true, callback, ByteBuffer.wrap(data)); } }); @@ -605,15 +614,13 @@ public class HttpClientTest extends AbstractHttpClientServerTest @Test public void testSmallAsyncContent() throws Exception { - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception { - ServletOutputStream output = response.getOutputStream(); - output.write(65); - output.flush(); - output.write(66); + org.eclipse.jetty.server.Response.write(response, false, UTF_8.encode("A")); + org.eclipse.jetty.server.Response.write(response, true, UTF_8.encode("B")); } }); diff --git a/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties b/jetty-core/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-fcgi/fcgi-server/src/test/resources/jetty-logging.properties diff --git a/jetty-core/jetty-fcgi/pom.xml b/jetty-core/jetty-fcgi/pom.xml index 994805bb38a..4e8997767d1 100644 --- a/jetty-core/jetty-fcgi/pom.xml +++ b/jetty-core/jetty-fcgi/pom.xml @@ -2,15 +2,15 @@ org.eclipse.jetty - jetty-project - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 org.eclipse.jetty.fcgi - fcgi-parent + fcgi pom - Jetty :: FastCGI :: Parent + Jetty Core :: FastCGI :: Parent fcgi-client diff --git a/jetty-core/jetty-http-spi/pom.xml b/jetty-core/jetty-http-spi/pom.xml index 0e4cdfabc84..02f4327fb61 100644 --- a/jetty-core/jetty-http-spi/pom.xml +++ b/jetty-core/jetty-http-spi/pom.xml @@ -1,16 +1,15 @@ org.eclipse.jetty - jetty-project - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 jetty-http-spi - Jetty :: Http Service Provider Interface + EE9 :: Jetty :: Http Service Provider Interface ${project.groupId}.http.spi org.eclipse.jetty.http.spi.* - true @@ -43,15 +42,6 @@ jetty-slf4j-impl test - - jakarta.ws.rs - jakarta.ws.rs-api - - - com.sun.xml.ws - jaxws-rt - test - @@ -75,6 +65,13 @@ + + org.jacoco + jacoco-maven-plugin + + true + + diff --git a/jetty-http-spi/src/main/java/module-info.java b/jetty-core/jetty-http-spi/src/main/java/module-info.java similarity index 100% rename from jetty-http-spi/src/main/java/module-info.java rename to jetty-core/jetty-http-spi/src/main/java/module-info.java diff --git a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java index 35b0d22dda3..128001137e1 100644 --- a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java +++ b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java @@ -13,8 +13,6 @@ package org.eclipse.jetty.http.spi; -import java.io.IOException; -import java.io.PrintWriter; import java.util.List; import java.util.Map; @@ -24,12 +22,11 @@ import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpPrincipal; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.Callback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +37,7 @@ public class HttpSpiContextHandler extends ContextHandler { public static final Logger LOG = LoggerFactory.getLogger(HttpSpiContextHandler.class); - private HttpContext _httpContext; + private final HttpContext _httpContext; private HttpHandler _httpHandler; @@ -48,68 +45,43 @@ public class HttpSpiContextHandler extends ContextHandler { this._httpContext = httpContext; this._httpHandler = httpHandler; + super.setHandler(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + try (HttpExchange jettyHttpExchange = request.isSecure() + ? new JettyHttpsExchange(_httpContext, request, response) + : new JettyHttpExchange(_httpContext, request, response)) + { + Authenticator auth = _httpContext.getAuthenticator(); + if (auth != null && handleAuthentication(request, response, callback, jettyHttpExchange, auth)) + return; + + _httpHandler.handle(jettyHttpExchange); + callback.succeeded(); + } + catch (Exception ex) + { + LOG.debug("Failed to handle", ex); + Response.writeError(request, response, callback, 500, null, ex); + } + } + }); } @Override - public void doScope(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException + public void setHandler(Handler handler) { - if (!target.startsWith(getContextPath())) - { - return; - } - - HttpExchange jettyHttpExchange; - if (baseRequest.isSecure()) - { - jettyHttpExchange = new JettyHttpsExchange(_httpContext, req, resp); - } - else - { - jettyHttpExchange = new JettyHttpExchange(_httpContext, req, resp); - } - - // TODO: add filters processing - - try - { - Authenticator auth = _httpContext.getAuthenticator(); - if (auth != null) - { - handleAuthentication(resp, jettyHttpExchange, auth); - } - else - { - _httpHandler.handle(jettyHttpExchange); - } - } - catch (Exception ex) - { - LOG.debug("Failed to handle", ex); - PrintWriter writer = new PrintWriter(jettyHttpExchange.getResponseBody()); - - resp.setStatus(500); - writer.println("

HTTP ERROR: 500

"); - writer.println("
INTERNAL_SERVER_ERROR
"); - writer.println("

RequestURI=" + StringUtil.sanitizeXmlString(req.getRequestURI()) + "

"); - - if (LOG.isDebugEnabled()) - { - writer.println("
");
-                ex.printStackTrace(writer);
-                writer.println("
"); - } - - baseRequest.getHttpChannel().getHttpConfiguration().writePoweredBy(writer, "

", "

"); - - writer.close(); - } - finally - { - baseRequest.setHandled(true); - } + throw new UnsupportedOperationException(); } - private void handleAuthentication(HttpServletResponse resp, HttpExchange httpExchange, Authenticator auth) throws IOException + private boolean handleAuthentication( + Request request, + Response response, + Callback callback, + HttpExchange httpExchange, + Authenticator auth) { Result result = auth.authenticate(httpExchange); if (result instanceof Authenticator.Failure) @@ -118,31 +90,35 @@ public class HttpSpiContextHandler extends ContextHandler for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) { for (String value : header.getValue()) - { - resp.addHeader(header.getKey(), value); - } + response.addHeader(header.getKey(), value); } - resp.sendError(rc); + Response.writeError(request, response, callback, rc); + return true; } - else if (result instanceof Authenticator.Retry) + + if (result instanceof Authenticator.Retry) { int rc = ((Authenticator.Retry)result).getResponseCode(); for (Map.Entry> header : httpExchange.getResponseHeaders().entrySet()) { for (String value : header.getValue()) { - resp.addHeader(header.getKey(), value); + response.addHeader(header.getKey(), value); } } - resp.setStatus(rc); - resp.flushBuffer(); + Response.writeError(request, response, callback, rc); + return true; } - else if (result instanceof Authenticator.Success) + + if (result instanceof Authenticator.Success) { HttpPrincipal principal = ((Authenticator.Success)result).getPrincipal(); ((JettyExchange)httpExchange).setPrincipal(principal); - _httpHandler.handle(httpExchange); + return false; } + + Response.writeError(request, response, callback, 500); + return true; } public HttpHandler getHttpHandler() diff --git a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpContext.java b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpContext.java index 6589cdd7ccf..f7c2fd9f1bd 100644 --- a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpContext.java +++ b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpContext.java @@ -29,22 +29,21 @@ import com.sun.net.httpserver.HttpServer; public class JettyHttpContext extends com.sun.net.httpserver.HttpContext { - private HttpSpiContextHandler _jettyContextHandler; + private final HttpSpiContextHandler _jettyContextHandler; - private HttpServer _server; + private final HttpServer _server; - private Map _attributes = new HashMap(); + private final Map _attributes = new HashMap(); - private List _filters = new ArrayList(); + private final List _filters = new ArrayList(); private Authenticator _authenticator; - protected JettyHttpContext(HttpServer server, String path, - HttpHandler handler) + protected JettyHttpContext(HttpServer server, String contextPath, HttpHandler handler) { this._server = server; _jettyContextHandler = new HttpSpiContextHandler(this, handler); - _jettyContextHandler.setContextPath(path); + _jettyContextHandler.setContextPath(contextPath); } protected HttpSpiContextHandler getJettyContextHandler() diff --git a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java index 330416e6597..0f2f9dcc530 100644 --- a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java +++ b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchange.java @@ -23,14 +23,14 @@ import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpPrincipal; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; public class JettyHttpExchange extends HttpExchange implements JettyExchange { - private JettyHttpExchangeDelegate _delegate; + private final JettyHttpExchangeDelegate _delegate; - public JettyHttpExchange(HttpContext jaxWsContext, HttpServletRequest req, HttpServletResponse resp) + public JettyHttpExchange(HttpContext jaxWsContext, Request req, Response resp) { super(); _delegate = new JettyHttpExchangeDelegate(jaxWsContext, req, resp); diff --git a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java index 785ceb2ef2b..5d54ff07270 100644 --- a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java +++ b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java @@ -17,9 +17,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.net.URI; -import java.net.URISyntaxException; -import java.util.Enumeration; import java.util.List; import java.util.Map; @@ -27,61 +26,52 @@ import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpPrincipal; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; /** * Jetty implementation of {@link com.sun.net.httpserver.HttpExchange} */ public class JettyHttpExchangeDelegate extends HttpExchange { + private final HttpContext _httpContext; - private HttpContext _httpContext; + private final Request _request; - private HttpServletRequest _req; + private final Response _response; - private HttpServletResponse _resp; - - private Headers _responseHeaders = new Headers(); + private final Headers _responseHeaders = new Headers(); private int _responseCode = 0; - private InputStream _is; + private InputStream _inputStream; - private OutputStream _os; + private OutputStream _outputStream; private HttpPrincipal _httpPrincipal; - JettyHttpExchangeDelegate(HttpContext jaxWsContext, HttpServletRequest req, HttpServletResponse resp) + JettyHttpExchangeDelegate(HttpContext httpSpiContext, Request request, Response response) { - this._httpContext = jaxWsContext; - this._req = req; - this._resp = resp; - try - { - this._is = req.getInputStream(); - this._os = resp.getOutputStream(); - } - catch (IOException ex) - { - throw new RuntimeException(ex); - } + this._httpContext = httpSpiContext; + this._request = request; + this._response = response; + this._inputStream = Content.asInputStream(request); + this._outputStream = Content.asOutputStream(response); } @Override public Headers getRequestHeaders() { Headers headers = new Headers(); - Enumeration en = _req.getHeaderNames(); - while (en.hasMoreElements()) + + for (HttpField field : _request.getHeaders()) { - String name = (String)en.nextElement(); - Enumeration en2 = _req.getHeaders(name); - while (en2.hasMoreElements()) - { - String value = (String)en2.nextElement(); - headers.add(name, value); - } + if (field.getValue() == null) + continue; + for (String value : field.getValues()) + headers.add(field.getName(), value); } return headers; } @@ -95,26 +85,13 @@ public class JettyHttpExchangeDelegate extends HttpExchange @Override public URI getRequestURI() { - try - { - String uriAsString = _req.getRequestURI(); - if (_req.getQueryString() != null) - { - uriAsString += "?" + _req.getQueryString(); - } - - return new URI(uriAsString); - } - catch (URISyntaxException ex) - { - throw new RuntimeException(ex); - } + return _request.getHttpURI().toURI(); } @Override public String getRequestMethod() { - return _req.getMethod(); + return _request.getMethod(); } @Override @@ -128,7 +105,7 @@ public class JettyHttpExchangeDelegate extends HttpExchange { try { - _resp.getOutputStream().close(); + _outputStream.close(); } catch (IOException ex) { @@ -139,13 +116,13 @@ public class JettyHttpExchangeDelegate extends HttpExchange @Override public InputStream getRequestBody() { - return _is; + return _inputStream; } @Override public OutputStream getResponseBody() { - return _os; + return _outputStream; } @Override @@ -160,20 +137,23 @@ public class JettyHttpExchangeDelegate extends HttpExchange for (String value : values) { - _resp.setHeader(name, value); + _response.setHeader(name, value); } } if (responseLength > 0) { - _resp.setHeader("content-length", "" + responseLength); + _response.setHeader("content-length", "" + responseLength); } - _resp.setStatus(rCode); + _response.setStatus(rCode); } @Override public InetSocketAddress getRemoteAddress() { - return new InetSocketAddress(_req.getRemoteAddr(), _req.getRemotePort()); + SocketAddress remote = _request.getConnectionMetaData().getRemoteSocketAddress(); + if (remote instanceof InetSocketAddress inet) + return inet; + return null; } @Override @@ -185,32 +165,35 @@ public class JettyHttpExchangeDelegate extends HttpExchange @Override public InetSocketAddress getLocalAddress() { - return new InetSocketAddress(_req.getLocalAddr(), _req.getLocalPort()); + SocketAddress local = _request.getConnectionMetaData().getLocalSocketAddress(); + if (local instanceof InetSocketAddress inet) + return inet; + return null; } @Override public String getProtocol() { - return _req.getProtocol(); + return _request.getConnectionMetaData().getProtocol(); } @Override public Object getAttribute(String name) { - return _req.getAttribute(name); + return _request.getAttribute(name); } @Override public void setAttribute(String name, Object value) { - _req.setAttribute(name, value); + _request.setAttribute(name, value); } @Override public void setStreams(InputStream i, OutputStream o) { - _is = i; - _os = o; + _inputStream = i; + _outputStream = o; } @Override diff --git a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java index c927af44785..0318127bbde 100644 --- a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java +++ b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpServer.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.Executor; @@ -41,19 +42,13 @@ import org.slf4j.LoggerFactory; public class JettyHttpServer extends com.sun.net.httpserver.HttpServer { private static final Logger LOG = LoggerFactory.getLogger(JettyHttpServer.class); - private final HttpConfiguration _httpConfiguration; - private final Server _server; - - private boolean _serverShared; - + private final boolean _serverShared; + private final Map _contexts = new HashMap<>(); + private final Map _connectors = new HashMap<>(); private InetSocketAddress _addr; - private Map _contexts = new HashMap<>(); - - private Map _connectors = new HashMap<>(); - public JettyHttpServer(Server server, boolean shared) { this(server, shared, new HttpConfiguration()); @@ -233,7 +228,7 @@ public class JettyHttpServer extends com.sun.net.httpserver.HttpServer JettyHttpContext context = new JettyHttpContext(this, path, httpHandler); HttpSpiContextHandler jettyContextHandler = context.getJettyContextHandler(); - ContextHandlerCollection chc = _server.getChildHandlerByClass(ContextHandlerCollection.class); + ContextHandlerCollection chc = _server.getDescendant(ContextHandlerCollection.class); if (chc == null) throw new RuntimeException("could not find ContextHandlerCollection, you must configure one"); @@ -270,10 +265,7 @@ public class JettyHttpServer extends com.sun.net.httpserver.HttpServer throw new RuntimeException("another context already bound to path " + path); } - Handler[] handlers = _server.getHandlers(); - if (handlers == null) - return; - + List handlers = _server.getHandlers(); for (Handler handler : handlers) { if (handler instanceof ContextHandler) @@ -293,7 +285,7 @@ public class JettyHttpServer extends com.sun.net.httpserver.HttpServer return; HttpSpiContextHandler handler = context.getJettyContextHandler(); - ContextHandlerCollection chc = _server.getChildHandlerByClass(ContextHandlerCollection.class); + ContextHandlerCollection chc = _server.getDescendant(ContextHandlerCollection.class); try { handler.stop(); diff --git a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpsExchange.java b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpsExchange.java index cda6eef40b3..41476aa8092 100644 --- a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpsExchange.java +++ b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpsExchange.java @@ -24,17 +24,17 @@ import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpPrincipal; import com.sun.net.httpserver.HttpsExchange; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; /** * */ public class JettyHttpsExchange extends HttpsExchange implements JettyExchange { - private JettyHttpExchangeDelegate _delegate; + private final JettyHttpExchangeDelegate _delegate; - public JettyHttpsExchange(HttpContext jaxWsContext, HttpServletRequest req, HttpServletResponse resp) + public JettyHttpsExchange(HttpContext jaxWsContext, Request req, Response resp) { super(); _delegate = new JettyHttpExchangeDelegate(jaxWsContext, req, resp); diff --git a/jetty-core/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestSPIServer.java b/jetty-core/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestSPIServer.java index 7a90944fd1b..b4029b4a241 100644 --- a/jetty-core/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestSPIServer.java +++ b/jetty-core/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestSPIServer.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.http.spi; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.URI; -import java.util.Iterator; import java.util.List; import java.util.Set; @@ -24,11 +23,11 @@ import com.sun.net.httpserver.BasicAuthenticator; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpServer; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.BasicAuthentication; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Server; import org.junit.jupiter.api.Test; @@ -67,11 +66,9 @@ public class TestSPIServer OutputStream responseBody = exchange.getResponseBody(); Headers requestHeaders = exchange.getRequestHeaders(); Set keySet = requestHeaders.keySet(); - Iterator iter = keySet.iterator(); - while (iter.hasNext()) + for (String key : keySet) { - String key = iter.next(); - List values = requestHeaders.get(key); + List values = requestHeaders.get(key); String s = key + " = " + values.toString() + "\n"; responseBody.write(s.getBytes()); } @@ -83,9 +80,7 @@ public class TestSPIServer @Override public boolean checkCredentials(String username, String password) { - if ("username".equals(username) && password.equals("password")) - return true; - return false; + return "username".equals(username) && password.equals("password"); } }); @@ -109,7 +104,7 @@ public class TestSPIServer Request request = client.newRequest("http://localhost:" + port + "/"); client.getAuthenticationStore().addAuthentication(new BasicAuthentication(URI.create("http://localhost:" + port), "Test", "username", "password")); ContentResponse response = request.send(); - assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + assertEquals(HttpStatus.OK_200, response.getStatus()); } finally { @@ -150,11 +145,9 @@ public class TestSPIServer OutputStream responseBody = exchange.getResponseBody(); Headers requestHeaders = exchange.getRequestHeaders(); Set keySet = requestHeaders.keySet(); - Iterator iter = keySet.iterator(); - while (iter.hasNext()) + for (String key : keySet) { - String key = iter.next(); - List values = requestHeaders.get(key); + List values = requestHeaders.get(key); String s = key + " = " + values.toString() + "\n"; responseBody.write(s.getBytes()); } @@ -166,9 +159,7 @@ public class TestSPIServer @Override public boolean checkCredentials(String username, String password) { - if ("username".equals(username) && password.equals("password")) - return true; - return false; + return "username".equals(username) && password.equals("password"); } }); @@ -186,7 +177,7 @@ public class TestSPIServer Request request = client.newRequest("http://localhost:" + port + "/"); client.getAuthenticationStore().addAuthentication(new BasicAuthentication(URI.create("http://localhost:" + port), "Test", "username", "password")); ContentResponse response = request.send(); - assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + assertEquals(HttpStatus.OK_200, response.getStatus()); } finally { diff --git a/tests/jetty-http-tools/pom.xml b/jetty-core/jetty-http-tools/pom.xml similarity index 78% rename from tests/jetty-http-tools/pom.xml rename to jetty-core/jetty-http-tools/pom.xml index 5b1c7ce3d05..1e8d5cfcf1d 100644 --- a/tests/jetty-http-tools/pom.xml +++ b/jetty-core/jetty-http-tools/pom.xml @@ -1,14 +1,14 @@ - org.eclipse.jetty.tests - tests-parent - 11.0.10-SNAPSHOT + org.eclipse.jetty + jetty-core + 12.0.0-SNAPSHOT 4.0.0 jetty-http-tools - Jetty Tests :: HTTP Utilities + Jetty :: HTTP Testing Utilities diff --git a/tests/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsContainsHeaderKey.java b/jetty-core/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsContainsHeaderKey.java similarity index 100% rename from tests/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsContainsHeaderKey.java rename to jetty-core/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsContainsHeaderKey.java diff --git a/tests/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsContainsHeaderValue.java b/jetty-core/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsContainsHeaderValue.java similarity index 100% rename from tests/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsContainsHeaderValue.java rename to jetty-core/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsContainsHeaderValue.java diff --git a/tests/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsHeaderValue.java b/jetty-core/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsHeaderValue.java similarity index 100% rename from tests/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsHeaderValue.java rename to jetty-core/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsHeaderValue.java diff --git a/tests/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsMatchers.java b/jetty-core/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsMatchers.java similarity index 100% rename from tests/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsMatchers.java rename to jetty-core/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsMatchers.java diff --git a/tests/jetty-http-tools/src/test/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsMatchersTest.java b/jetty-core/jetty-http-tools/src/test/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsMatchersTest.java similarity index 100% rename from tests/jetty-http-tools/src/test/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsMatchersTest.java rename to jetty-core/jetty-http-tools/src/test/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsMatchersTest.java diff --git a/jetty-core/jetty-http/pom.xml b/jetty-core/jetty-http/pom.xml index 36af987fedd..c3ec5511410 100644 --- a/jetty-core/jetty-http/pom.xml +++ b/jetty-core/jetty-http/pom.xml @@ -1,14 +1,14 @@ - jetty-project org.eclipse.jetty - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 jetty-http - Jetty :: Http Utility + Jetty Core :: Http Utility ${project.groupId}.http diff --git a/jetty-http/src/main/java/module-info.java b/jetty-core/jetty-http/src/main/java/module-info.java similarity index 89% rename from jetty-http/src/main/java/module-info.java rename to jetty-core/jetty-http/src/main/java/module-info.java index 20e0b7c90e6..06f8a8c1e34 100644 --- a/jetty-http/src/main/java/module-info.java +++ b/jetty-core/jetty-http/src/main/java/module-info.java @@ -23,5 +23,6 @@ module org.eclipse.jetty.http uses org.eclipse.jetty.http.HttpFieldPreEncoder; provides org.eclipse.jetty.http.HttpFieldPreEncoder with - org.eclipse.jetty.http.Http1FieldPreEncoder; + org.eclipse.jetty.http.Http10FieldPreEncoder, + org.eclipse.jetty.http.Http11FieldPreEncoder; } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/BadMessageException.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/BadMessageException.java index a9dad571766..088546f0026 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/BadMessageException.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/BadMessageException.java @@ -26,12 +26,12 @@ public class BadMessageException extends RuntimeException public BadMessageException() { - this(400, null); + this(400, null, null); } public BadMessageException(int code) { - this(code, null); + this(code, null, null); } public BadMessageException(String reason) @@ -49,6 +49,11 @@ public class BadMessageException extends RuntimeException this(code, reason, null); } + public BadMessageException(int code, Throwable cause) + { + this(code, null, cause); + } + public BadMessageException(int code, String reason, Throwable cause) { super(code + ": " + reason, cause); diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CachingContentFactory.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CachingContentFactory.java new file mode 100644 index 00000000000..be4ee311610 --- /dev/null +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CachingContentFactory.java @@ -0,0 +1,407 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.http; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.resource.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * HttpContent.ContentFactory implementation that wraps any other HttpContent.ContentFactory instance + * using it as a caching authority. + * Only HttpContent instances whose path is not a directory are cached. + * HttpContent instances returned by getContent() implement HttpContent.InMemory when a cached instance is found. + * + * TODO should directories (generated HTML) and not-found contents be cached? + * TODO this form of caching is done at a layer below the request processor (i.e.: done in the guts of the ResourceHandler) + * Consider if caching should rather be done at a layer above using a CachingHandler that would intercept Response.write() + * of a configured URI set, save that in a cache and serve that again. + */ +public class CachingContentFactory implements HttpContent.ContentFactory +{ + private static final Logger LOG = LoggerFactory.getLogger(CachingContentFactory.class); + + private final HttpContent.ContentFactory _authority; + private final boolean _useFileMappedBuffer; + private final ConcurrentMap _cache = new ConcurrentHashMap<>(); + private final AtomicLong _cachedSize = new AtomicLong(); + private int _maxCachedFileSize = 128 * 1024 * 1024; + private int _maxCachedFiles = 2048; + private int _maxCacheSize = 256 * 1024 * 1024; + + public CachingContentFactory(HttpContent.ContentFactory authority) + { + this(authority, false); + } + + public CachingContentFactory(HttpContent.ContentFactory authority, boolean useFileMappedBuffer) + { + _authority = authority; + _useFileMappedBuffer = useFileMappedBuffer; + } + + public long getCachedSize() + { + return _cachedSize.get(); + } + + public int getCachedFiles() + { + return _cache.size(); + } + + public int getMaxCachedFileSize() + { + return _maxCachedFileSize; + } + + public void setMaxCachedFileSize(int maxCachedFileSize) + { + _maxCachedFileSize = maxCachedFileSize; + shrinkCache(); + } + + public int getMaxCacheSize() + { + return _maxCacheSize; + } + + public void setMaxCacheSize(int maxCacheSize) + { + _maxCacheSize = maxCacheSize; + shrinkCache(); + } + + /** + * @return the max number of cached files. + */ + public int getMaxCachedFiles() + { + return _maxCachedFiles; + } + + /** + * @param maxCachedFiles the max number of cached files. + */ + public void setMaxCachedFiles(int maxCachedFiles) + { + _maxCachedFiles = maxCachedFiles; + shrinkCache(); + } + + public boolean isUseFileMappedBuffer() + { + return _useFileMappedBuffer; + } + + private void shrinkCache() + { + // While we need to shrink + while (_cache.size() > 0 && (_cache.size() > _maxCachedFiles || _cachedSize.get() > _maxCacheSize)) + { + // Scan the entire cache and generate an ordered list by last accessed time. + SortedSet sorted = new TreeSet<>((c1, c2) -> + { + if (c1._lastAccessed != c2._lastAccessed) + return Long.compare(c1._lastAccessed, c2._lastAccessed); + + if (c1._contentLengthValue < c2._contentLengthValue) + return -1; + + return c1._cacheKey.compareTo(c2._cacheKey); + }); + sorted.addAll(_cache.values()); + + // Invalidate least recently used first + for (CachingHttpContent content : sorted) + { + if (_cache.size() <= _maxCachedFiles && _cachedSize.get() <= _maxCacheSize) + break; + removeFromCache(content); + } + } + } + + private void removeFromCache(CachingHttpContent content) + { + if (content == _cache.remove(content._cacheKey)) + { + content.release(); + _cachedSize.addAndGet(-content.calculateSize()); + } + } + + public void flushCache() + { + for (CachingHttpContent content : _cache.values()) + { + removeFromCache(content); + } + } + + @Override + public HttpContent getContent(String path, int maxBuffer) throws IOException + { + // TODO load precompressed otherwise it is never served from cache + CachingHttpContent cachingHttpContent = _cache.get(path); + if (cachingHttpContent != null) + { + if (cachingHttpContent.isValid()) + return cachingHttpContent; + else + removeFromCache(cachingHttpContent); + } + HttpContent httpContent = _authority.getContent(path, maxBuffer); + // Do not cache directories or files that are too big + if (httpContent != null && !Files.isDirectory(httpContent.getPath()) && httpContent.getContentLengthValue() <= _maxCachedFileSize) + { + httpContent = cachingHttpContent = new CachingHttpContent(path, null, httpContent); + _cache.put(path, cachingHttpContent); + _cachedSize.addAndGet(cachingHttpContent.calculateSize()); + shrinkCache(); + } + return httpContent; + } + + private class CachingHttpContent implements HttpContent + { + private final HttpContent _delegate; + private final ByteBuffer _buffer; + private final FileTime _lastModifiedValue; + private final String _cacheKey; + private final String _etag; + private final long _contentLengthValue; + private final Map _precompressedContents; + private volatile long _lastAccessed; + + private CachingHttpContent(String key, String precalculatedEtag, HttpContent httpContent) throws IOException + { + _etag = precalculatedEtag; + _contentLengthValue = httpContent.getContentLengthValue(); // TODO getContentLengthValue() could return -1 + ByteBuffer byteBuffer; + + if (_useFileMappedBuffer) + { + // mmap the content into memory + byteBuffer = BufferUtil.toMappedBuffer(httpContent.getPath(), 0, _contentLengthValue); + } + else + { + // TODO use pool & check length limit + // load the content into memory + byteBuffer = ByteBuffer.allocateDirect((int)_contentLengthValue); + try (SeekableByteChannel channel = Files.newByteChannel(httpContent.getPath())) + { + // fill buffer + int read = 0; + while (read != _contentLengthValue) + read += channel.read(byteBuffer); + } + byteBuffer.flip(); + } + + // Load precompressed contents into memory. + Map precompressedContents = httpContent.getPrecompressedContents(); + if (precompressedContents != null) + { + _precompressedContents = new HashMap<>(); + for (Map.Entry entry : precompressedContents.entrySet()) + { + CompressedContentFormat format = entry.getKey(); + + // Rewrite the etag to be the content's one with the required suffix all within quotes. + String precompressedEtag = httpContent.getETagValue(); + boolean weak = false; + if (precompressedEtag.startsWith("W/")) + { + weak = true; + precompressedEtag = precompressedEtag.substring(2); + } + precompressedEtag = (weak ? "W/\"" : "\"") + QuotedStringTokenizer.unquote(precompressedEtag) + format.getEtagSuffix() + '"'; + + // The etag of the precompressed content must be the one of the non-compressed content, with the etag suffix appended. + _precompressedContents.put(format, new CachingHttpContent(key, precompressedEtag, entry.getValue())); + } + } + else + { + _precompressedContents = null; + } + + _cacheKey = key; + _buffer = byteBuffer; + _lastModifiedValue = Files.getLastModifiedTime(httpContent.getPath()); + _delegate = httpContent; + _lastAccessed = System.nanoTime(); + } + + long calculateSize() + { + long totalSize = _contentLengthValue; + if (_precompressedContents != null) + { + for (CachingHttpContent cachingHttpContent : _precompressedContents.values()) + { + totalSize += cachingHttpContent.calculateSize(); + } + } + return totalSize; + } + + @Override + public ByteBuffer getBuffer() + { + // TODO this should return a RetainableByteBuffer otherwise there is a race between + // threads serving the buffer while another thread invalidates it. That's going to + // be a lot of fun since RetainableByteBuffer is only meant to be acquired from a pool + // but the byte buffer here could be coming from a mmap'ed file. + return _buffer.slice(); + } + + public boolean isValid() + { + try + { + FileTime lastModifiedTime = Files.getLastModifiedTime(_delegate.getPath()); + if (lastModifiedTime.equals(_lastModifiedValue)) + { + _lastAccessed = System.nanoTime(); + return true; + } + } + catch (IOException e) + { + LOG.debug("unable to get delegate path' LastModifiedTime", e); + } + release(); + return false; + } + + @Override + public void release() + { + // TODO re-pool buffer and release precompressed contents + } + + @Override + public HttpField getContentType() + { + return _delegate.getContentType(); + } + + @Override + public String getContentTypeValue() + { + return _delegate.getContentTypeValue(); + } + + @Override + public String getCharacterEncoding() + { + return _delegate.getCharacterEncoding(); + } + + @Override + public MimeTypes.Type getMimeType() + { + return _delegate.getMimeType(); + } + + @Override + public HttpField getContentEncoding() + { + return _delegate.getContentEncoding(); + } + + @Override + public String getContentEncodingValue() + { + return _delegate.getContentEncodingValue(); + } + + @Override + public HttpField getContentLength() + { + return _delegate.getContentLength(); + } + + @Override + public long getContentLengthValue() + { + return _delegate.getContentLengthValue(); + } + + @Override + public HttpField getLastModified() + { + return _delegate.getLastModified(); + } + + @Override + public String getLastModifiedValue() + { + return _delegate.getLastModifiedValue(); + } + + @Override + public HttpField getETag() + { + String eTag = getETagValue(); + return eTag == null ? null : new HttpField(HttpHeader.ETAG, eTag); + } + + @Override + public String getETagValue() + { + if (_etag != null) + return _etag; + else + return _delegate.getETagValue(); + } + + @Override + public Path getPath() + { + return _delegate.getPath(); + } + + @Override + public Resource getResource() + { + return _delegate.getResource(); + } + + @Override + public Map getPrecompressedContents() + { + return _precompressedContents; + } + } +} diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCache.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCache.java new file mode 100644 index 00000000000..32358751520 --- /dev/null +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCache.java @@ -0,0 +1,118 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.http; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Cookie parser + *

Optimized stateful cookie parser. Cookies fields are added with the + * {@link #addCookieField(String)} method and parsed on the next subsequent + * call to {@link #getCookies(HttpFields)}. + * If the added fields are identical to those last added (as strings), then the + * cookies are not re parsed. + * + */ +public class CookieCache extends CookieCutter +{ + protected static final Logger LOG = LoggerFactory.getLogger(CookieCache.class); + protected final List _rawFields = new ArrayList<>(); + protected final List _cookieList = new ArrayList<>(); + private int _addedFields; + private boolean _parsed = false; + private boolean _set = false; + + public CookieCache() + { + this(CookieCompliance.RFC6265, null); + } + + public CookieCache(CookieCompliance compliance, ComplianceViolation.Listener complianceListener) + { + super(compliance, complianceListener); + } + + private void addCookieField(String rawField) + { + if (_set) + throw new IllegalStateException(); + + if (rawField == null) + return; + rawField = rawField.trim(); + if (rawField.length() == 0) + return; + + if (_rawFields.size() > _addedFields) + { + if (rawField.equals(_rawFields.get(_addedFields))) + { + _addedFields++; + return; + } + + while (_rawFields.size() > _addedFields) + { + _rawFields.remove(_addedFields); + } + } + _rawFields.add(_addedFields++, rawField); + _parsed = false; + } + + public List getCookies(HttpFields headers) + { + // TODO this could be done a lot better with a single iteration and not creating a new list etc. + _set = false; + _addedFields = 0; + for (HttpField field : headers) + { + if (HttpHeader.COOKIE.equals(field.getHeader())) + addCookieField(field.getValue()); + } + + while (_rawFields.size() > _addedFields) + { + _rawFields.remove(_addedFields); + _parsed = false; + } + + if (_parsed) + return _cookieList; + + parseFields(_rawFields); + _parsed = true; + return _cookieList; + } + + @Override + protected void addCookie(String name, String value, String domain, String path, int version, String comment) + { + try + { + // TODO probably should only do name & value now. Version is not longer a thing! + HttpCookie cookie = new HttpCookie(name, value, domain, path, -1, false, false, comment, version); + _cookieList.add(cookie); + } + catch (Exception e) + { + LOG.debug("Unable to add Cookie name={}, value={}, domain={}, path={}, version={}, comment={}", + name, value, domain, path, version, comment, e); + } + } +} diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCutter.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCutter.java index bdd08083c4f..adaaa62a4cc 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCutter.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCutter.java @@ -157,7 +157,7 @@ public abstract class CookieCutter try { - if (name.startsWith("$")) + if (name != null && name.startsWith("$")) { if (RESERVED_NAMES_NOT_DOLLAR_PREFIXED.isAllowedBy(_complianceMode)) { @@ -165,20 +165,10 @@ public abstract class CookieCutter String lowercaseName = name.toLowerCase(Locale.ENGLISH); switch (lowercaseName) { - case "$path": - cookiePath = value; - break; - case "$domain": - cookieDomain = value; - break; - case "$port": - cookieComment = "$port=" + value; - break; - case "$version": - cookieVersion = Integer.parseInt(value); - break; - default: - break; + case "$path" -> cookiePath = value; + case "$domain" -> cookieDomain = value; + case "$port" -> cookieComment = "$port=" + value; + case "$version" -> cookieVersion = Integer.parseInt(value); } } } @@ -188,10 +178,7 @@ public abstract class CookieCutter if (cookieName != null) { if (!reject) - { addCookie(cookieName, cookieValue, cookieDomain, cookiePath, cookieVersion, cookieComment); - reject = false; - } cookieDomain = null; cookiePath = null; cookieComment = null; diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/GZIPContentDecoder.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/GZIPContentDecoder.java index c3cfebd187a..d7074d84a5f 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/GZIPContentDecoder.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/GZIPContentDecoder.java @@ -39,7 +39,6 @@ public class GZIPContentDecoder implements Destroyable private final List _inflateds = new ArrayList<>(); private final ByteBufferPool _pool; private final int _bufferSize; - private final boolean _useDirectBuffers; private InflaterPool.Entry _inflaterEntry; private Inflater _inflater; private State _state; @@ -63,23 +62,12 @@ public class GZIPContentDecoder implements Destroyable this(new InflaterPool(0, true), pool, bufferSize); } - public GZIPContentDecoder(ByteBufferPool pool, int bufferSize, boolean useDirectBuffers) - { - this(new InflaterPool(0, true), pool, bufferSize, useDirectBuffers); - } - public GZIPContentDecoder(InflaterPool inflaterPool, ByteBufferPool pool, int bufferSize) - { - this(inflaterPool, pool, bufferSize, false); - } - - public GZIPContentDecoder(InflaterPool inflaterPool, ByteBufferPool pool, int bufferSize, boolean useDirectBuffers) { _inflaterEntry = inflaterPool.acquire(); _inflater = _inflaterEntry.get(); _bufferSize = bufferSize; _pool = pool; - _useDirectBuffers = useDirectBuffers; reset(); } @@ -221,13 +209,6 @@ public class GZIPContentDecoder implements Destroyable if (buffer == null) buffer = acquire(_bufferSize); - if (_inflater.needsInput()) - { - if (!compressed.hasRemaining()) - return; - _inflater.setInput(compressed); - } - try { int pos = BufferUtil.flipToFill(buffer); @@ -246,6 +227,12 @@ public class GZIPContentDecoder implements Destroyable if (decodedChunk(chunk)) return; } + else if (_inflater.needsInput()) + { + if (!compressed.hasRemaining()) + return; + _inflater.setInput(compressed); + } else if (_inflater.finished()) { _state = State.CRC; @@ -269,7 +256,7 @@ public class GZIPContentDecoder implements Destroyable { case ID: { - _value += (currByte & 0xFFL) << (8 * _size); + _value += (currByte & 0xFF) << 8 * _size; ++_size; if (_size == 2) { @@ -316,7 +303,7 @@ public class GZIPContentDecoder implements Destroyable } case EXTRA_LENGTH: { - _value += (currByte & 0xFFL) << (8 * _size); + _value += (currByte & 0xFF) << 8 * _size; ++_size; if (_size == 2) _state = State.EXTRA; @@ -370,7 +357,7 @@ public class GZIPContentDecoder implements Destroyable } case CRC: { - _value += (currByte & 0xFFL) << (8 * _size); + _value += (currByte & 0xFF) << 8 * _size; ++_size; if (_size == 4) { @@ -445,7 +432,7 @@ public class GZIPContentDecoder implements Destroyable */ public ByteBuffer acquire(int capacity) { - return _pool == null ? BufferUtil.allocate(capacity) : _pool.acquire(capacity, _useDirectBuffers); + return _pool == null ? BufferUtil.allocate(capacity) : _pool.acquire(capacity, false); } /** diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http10FieldPreEncoder.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http10FieldPreEncoder.java new file mode 100644 index 00000000000..2dcbc83c769 --- /dev/null +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http10FieldPreEncoder.java @@ -0,0 +1,26 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.http; + +/** + * + */ +public class Http10FieldPreEncoder extends Http1FieldPreEncoder +{ + @Override + public HttpVersion getHttpVersion() + { + return HttpVersion.HTTP_1_0; + } +} diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http11FieldPreEncoder.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http11FieldPreEncoder.java new file mode 100644 index 00000000000..c0d0cf6a117 --- /dev/null +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http11FieldPreEncoder.java @@ -0,0 +1,26 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.http; + +/** + * + */ +public class Http11FieldPreEncoder extends Http1FieldPreEncoder +{ + @Override + public HttpVersion getHttpVersion() + { + return HttpVersion.HTTP_1_1; + } +} diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http1FieldPreEncoder.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http1FieldPreEncoder.java index 6d2ce95148d..9fc30dd88dd 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http1FieldPreEncoder.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/Http1FieldPreEncoder.java @@ -20,15 +20,8 @@ import static java.nio.charset.StandardCharsets.ISO_8859_1; /** * */ -public class Http1FieldPreEncoder implements HttpFieldPreEncoder +public abstract class Http1FieldPreEncoder implements HttpFieldPreEncoder { - - @Override - public HttpVersion getHttpVersion() - { - return HttpVersion.HTTP_1_0; - } - @Override public byte[] getEncodedField(HttpHeader header, String headerString, String value) { diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java index 14103bcb0d9..26ad161a7cf 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpContent.java @@ -14,9 +14,8 @@ package org.eclipse.jetty.http; import java.io.IOException; -import java.io.InputStream; import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; +import java.nio.file.Path; import java.util.Map; import org.eclipse.jetty.http.MimeTypes.Type; @@ -32,6 +31,8 @@ import org.eclipse.jetty.util.resource.Resource; * reuse in from a cache). *

*/ +// TODO also review metadata (like getContentLengthValue and getLastModifiedValue) to check if they can be removed as those +// are available via the Path API public interface HttpContent { HttpField getContentType(); @@ -58,29 +59,27 @@ public interface HttpContent String getETagValue(); - ByteBuffer getIndirectBuffer(); - - ByteBuffer getDirectBuffer(); + // TODO rename? + Path getPath(); + // TODO getPath() is supposed to replace the following Resource getResource(); - InputStream getInputStream() throws IOException; - - ReadableByteChannel getReadableByteChannel() throws IOException; - - void release(); - Map getPrecompressedContents(); - public interface ContentFactory + ByteBuffer getBuffer(); + + void release(); + + interface ContentFactory { /** * @param path The path within the context to the resource - * @param maxBuffer The maximum buffer to allocated for this request. For cached content, a larger buffer may have - * previously been allocated and returned by the {@link HttpContent#getDirectBuffer()} or {@link HttpContent#getIndirectBuffer()} calls. + * @param maxBuffer The maximum buffer to allocated for this request. * @return A {@link HttpContent} * @throws IOException if unable to get content */ + // TODO maxBuffer is not needed anymore HttpContent getContent(String path, int maxBuffer) throws IOException; } } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java index c8d0747436d..e49ff1b7f36 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java @@ -23,7 +23,6 @@ import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -// TODO consider replacing this with java.net.HttpCookie (once it supports RFC6265) public class HttpCookie { private static final Logger LOG = LoggerFactory.getLogger(HttpCookie.class); @@ -50,9 +49,11 @@ public class HttpCookie public enum SameSite { - NONE("None"), STRICT("Strict"), LAX("Lax"); + NONE("None"), + STRICT("Strict"), + LAX("Lax"); - private String attributeValue; + private final String attributeValue; SameSite(String attributeValue) { @@ -486,6 +487,10 @@ public class HttpCookie } } + /** + * @deprecated We should not need to do this now + */ + @Deprecated public static String getCommentWithoutAttributes(String comment) { if (comment == null) @@ -503,6 +508,10 @@ public class HttpCookie return strippedComment.length() == 0 ? null : strippedComment; } + /** + * @deprecated We should not need to do this now + */ + @Deprecated public static String getCommentWithAttributes(String comment, boolean httpOnly, SameSite sameSite) { if (comment == null && sameSite == null) @@ -522,17 +531,10 @@ public class HttpCookie { switch (sameSite) { - case NONE: - builder.append(SAME_SITE_NONE_COMMENT); - break; - case STRICT: - builder.append(SAME_SITE_STRICT_COMMENT); - break; - case LAX: - builder.append(SAME_SITE_LAX_COMMENT); - break; - default: - throw new IllegalArgumentException(sameSite.toString()); + case NONE -> builder.append(SAME_SITE_NONE_COMMENT); + case STRICT -> builder.append(SAME_SITE_STRICT_COMMENT); + case LAX -> builder.append(SAME_SITE_LAX_COMMENT); + default -> throw new IllegalArgumentException(sameSite.toString()); } } @@ -556,4 +558,34 @@ public class HttpCookie return _cookie; } } + + /** + * Check that samesite is set on the cookie. If not, use a + * context default value, if one has been set. + * + * @param cookie the cookie to check + * @param attributes the context to check settings + * @return either the original cookie, or a new one that has the samesit default set + */ + public static HttpCookie checkSameSite(HttpCookie cookie, Attributes attributes) + { + if (cookie == null || cookie.getSameSite() != null) + return cookie; + + //sameSite is not set, use the default configured for the context, if one exists + SameSite contextDefault = HttpCookie.getSameSiteDefault(attributes); + if (contextDefault == null) + return cookie; //no default set + + return new HttpCookie(cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getMaxAge(), + cookie.isHttpOnly(), + cookie.isSecure(), + cookie.getComment(), + cookie.getVersion(), + contextDefault); + } } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java index b435d6268d1..09866c2fd26 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java @@ -138,23 +138,37 @@ public class HttpField * except if they are q=0, in which case the item itself is ignored. */ public boolean contains(String search) + { + return contains(_value, search); + } + + /** + * Look for a value in a possible multi valued field + * + * @param value The field value to search in. + * @param search Values to search for (case insensitive) + * @return True iff the value is contained in the field value entirely or + * as an element of a quoted comma separated list. List element parameters (eg qualities) are ignored, + * except if they are q=0, in which case the item itself is ignored. + */ + public static boolean contains(String value, String search) { if (search == null) - return _value == null; + return value == null; if (search.isEmpty()) return false; - if (_value == null) + if (value == null) return false; - if (search.equalsIgnoreCase(_value)) + if (search.equalsIgnoreCase(value)) return true; int state = 0; int match = 0; int param = 0; - for (int i = 0; i < _value.length(); i++) + for (int i = 0; i < value.length(); i++) { - char c = StringUtil.asciiToLowerCase(_value.charAt(i)); + char c = StringUtil.asciiToLowerCase(value.charAt(i)); switch (state) { case 0: // initial white space diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 3bb96ea6afd..e349a5cac31 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -25,52 +25,79 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.Spliterators; import java.util.function.BiFunction; import java.util.function.BiPredicate; +import java.util.function.Function; import java.util.function.ToIntFunction; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * Interface that represents on ordered collection of {@link HttpField}s. - * Both {@link Mutable} and {@link Immutable} implementations are available + * Both {@link MutableHttpFields} and {@link ImmutableHttpFields} implementations are available * via the static methods such as {@link #build()} and {@link #from(HttpField...)}. + * To implement this interface, only the {@link #iterator()} method + * need be implemented, however default implementations may be inefficient */ public interface HttpFields extends Iterable { + HttpField EXPIRES_01JAN1970 = new PreEncodedHttpField(HttpHeader.EXPIRES, DateGenerator.__01Jan1970); + HttpField CONNECTION_CLOSE = new PreEncodedHttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); + HttpField CONNECTION_KEEPALIVE = new PreEncodedHttpField(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()); + HttpFields EMPTY = build().asImmutable(); static Mutable build() { - return new Mutable(); + return new MutableHttpFields(); } static Mutable build(int capacity) { - return new Mutable(capacity); + return new MutableHttpFields(capacity); } static Mutable build(HttpFields fields) { - return new Mutable(fields); + return new MutableHttpFields(fields); } static Mutable build(HttpFields fields, HttpField replaceField) { - return new Mutable(fields, replaceField); + return new MutableHttpFields(fields, replaceField); } static Mutable build(HttpFields fields, EnumSet removeFields) { - return new Mutable(fields, removeFields); + return new MutableHttpFields(fields, removeFields); } - static Immutable from(HttpField... fields) + static ImmutableHttpFields from(HttpField... fields) { - return new Immutable(fields); + return new ImmutableHttpFields(fields); } - Immutable asImmutable(); + static ImmutableHttpFields from(HttpFields fields, Function mutation) + { + return new ImmutableHttpFields(fields.stream().map(mutation).filter(Objects::nonNull).toArray(HttpField[]::new)); + } + + default HttpFields asImmutable() + { + return HttpFields.build(this).asImmutable(); + } + + /** + * Efficiently take the fields as an Immutable that cannot be changed by any further mutations + * to this instance. + * @return An immutable version of the fields. + */ + default HttpFields takeAsImmutable() + { + return HttpFields.build(this).takeAsImmutable(); + } default String asString() { @@ -228,7 +255,23 @@ public interface HttpFields extends Iterable */ default long getDateField(String name) { - HttpField field = getField(name); + return parseDateField(getField(name)); + } + + /** + * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case + * of the field name is ignored. + * + * @param header the header + * @return the value of the field as a number of milliseconds since unix epoch + */ + default long getDateField(HttpHeader header) + { + return parseDateField(getField(header)); + } + + static long parseDateField(HttpField field) + { if (field == null) return -1; @@ -248,7 +291,16 @@ public interface HttpFields extends Iterable * @param index the field index * @return A Field value or null if the Field value has not been set */ - HttpField getField(int index); + default HttpField getField(int index) + { + int i = 0; + for (HttpField f: this) + { + if (i++ == index) + return f; + } + return null; + } default HttpField getField(HttpHeader header) { @@ -275,7 +327,9 @@ public interface HttpFields extends Iterable * _names for this request. * * @return an enumeration of field names + * @deprecated use {@link #getFieldNamesCollection()} */ + @Deprecated default Enumeration getFieldNames() { return Collections.enumeration(getFieldNamesCollection()); @@ -494,96 +548,27 @@ public interface HttpFields extends Iterable return !i.hasNext(); } - int size(); + default int size() + { + int s = 0; + for (HttpField f : this) + s++; + return s; + } - Stream stream(); + default Stream stream() + { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator(), 0), false); + } /** - * HTTP Fields. A collection of HTTP header and or Trailer fields. - * - *

This class is not synchronized as it is expected that modifications will only be performed by a - * single thread. - * - *

The cookie handling provided by this class is guided by the Servlet specification and RFC6265. + * A mutable HttpFields interface. + * To implement this interface, only the {@link #listIterator()} + * method needs to be implemented, however default implementations + * may not be efficient. */ - class Mutable implements Iterable, HttpFields + interface Mutable extends Iterable, HttpFields { - private HttpField[] _fields; - private int _size; - - /** - * Initialize an empty HttpFields. - */ - protected Mutable() - { - this(16); // Based on small sample of Chrome requests. - } - - /** - * Initialize an empty HttpFields. - * - * @param capacity the capacity of the http fields - */ - Mutable(int capacity) - { - _fields = new HttpField[capacity]; - } - - /** - * Initialize HttpFields from another. - * - * @param fields the fields to copy data from - */ - Mutable(HttpFields fields) - { - add(fields); - } - - /** - * Initialize HttpFields from another and replace a field - * - * @param fields the fields to copy data from - * @param replaceField the replacement field - */ - Mutable(HttpFields fields, HttpField replaceField) - { - _fields = new HttpField[fields.size() + 4]; - _size = 0; - boolean put = false; - for (HttpField f : fields) - { - if (replaceField.isSameName(f)) - { - if (!put) - _fields[_size++] = replaceField; - put = true; - } - else - { - _fields[_size++] = f; - } - } - if (!put) - _fields[_size++] = replaceField; - } - - /** - * Initialize HttpFields from another and remove fields - * - * @param fields the fields to copy data from - * @param removeFields the the fields to remove - */ - Mutable(HttpFields fields, EnumSet removeFields) - { - _fields = new HttpField[fields.size() + 4]; - _size = 0; - for (HttpField f : fields) - { - if (f.getHeader() == null || !removeFields.contains(f.getHeader())) - _fields[_size++] = f; - } - } - /** * Add to or set a field. If the field is allowed to have multiple values, add will add multiple * headers of the same name. @@ -592,14 +577,14 @@ public interface HttpFields extends Iterable * @param value the value of the field. * @return this builder */ - public Mutable add(String name, String value) + default Mutable add(String name, String value) { if (value != null) return add(new HttpField(name, value)); return this; } - public Mutable add(HttpHeader header, HttpHeaderValue value) + default Mutable add(HttpHeader header, HttpHeaderValue value) { return add(header, value.toString()); } @@ -612,7 +597,7 @@ public interface HttpFields extends Iterable * @param value the value of the field. * @return this builder */ - public Mutable add(HttpHeader header, String value) + default Mutable add(HttpHeader header, String value) { if (value == null) throw new IllegalArgumentException("null value"); @@ -621,44 +606,19 @@ public interface HttpFields extends Iterable return add(field); } - public Mutable add(HttpField field) + default Mutable add(HttpField field) { - if (field != null) - { - if (_size == _fields.length) - _fields = Arrays.copyOf(_fields, _size * 2); - _fields[_size++] = field; - } + ListIterator i = listIterator(); + while (i.hasNext()) + i.next(); + i.add(field); return this; } - public Mutable add(HttpFields fields) + default Mutable add(HttpFields fields) { - if (_fields == null) - _fields = new HttpField[fields.size() + 4]; - else if (_size + fields.size() >= _fields.length) - _fields = Arrays.copyOf(_fields, _size + fields.size() + 4); - - if (fields.size() == 0) - return this; - - if (fields instanceof Immutable) - { - Immutable b = (Immutable)fields; - System.arraycopy(b._fields, 0, _fields, _size, b._fields.length); - _size += b._fields.length; - } - else if (fields instanceof Mutable) - { - Mutable b = (Mutable)fields; - System.arraycopy(b._fields, 0, _fields, _size, b._size); - _size += b._size; - } - else - { - for (HttpField f : fields) - _fields[_size++] = f; - } + for (HttpField field : fields) + add(field); return this; } @@ -670,7 +630,7 @@ public interface HttpFields extends Iterable * @param values The value(s) to add * @return this builder */ - public Mutable addCSV(HttpHeader header, String... values) + default Mutable addCSV(HttpHeader header, String... values) { QuotedCSV existing = null; for (HttpField f : this) @@ -696,7 +656,7 @@ public interface HttpFields extends Iterable * @param values The value(s) to add * @return this builder */ - public Mutable addCSV(String name, String... values) + default Mutable addCSV(String name, String... values) { QuotedCSV existing = null; for (HttpField f : this) @@ -721,33 +681,33 @@ public interface HttpFields extends Iterable * @param date the field date value * @return this builder */ - public Mutable addDateField(String name, long date) + default Mutable addDateField(String name, long date) { add(name, DateGenerator.formatDate(date)); return this; } - @Override - public Immutable asImmutable() + default Mutable clear() { - return new Immutable(Arrays.copyOf(_fields, _size)); - } - - public Mutable clear() - { - _size = 0; + for (ListIterator i = listIterator(); i.hasNext(); ) + { + i.next(); + i.remove(); + } return this; } - /** Ensure that specific HttpField exists when the field may not exist or may + /** + * Ensure that specific HttpField exists when the field may not exist or may * exist and be multi valued. Multiple existing fields are merged into a * single field. + * * @param field The header to ensure is contained. The field is used - * directly if possible so {@link PreEncodedHttpField}s can be - * passed. If the value needs to be merged with existing values, - * then a new field is created. + * directly if possible so {@link PreEncodedHttpField}s can be + * passed. If the value needs to be merged with existing values, + * then a new field is created. */ - public void ensureField(HttpField field) + default void ensureField(HttpField field) { // Is the field value multi valued? if (field.getValue().indexOf(',') < 0) @@ -768,212 +728,27 @@ public interface HttpFields extends Iterable } } - /** - * Compute ensure field with a single value - * @param ensure The field to ensure exists - * @param fields The list of existing fields with the same header - */ - private static HttpField computeEnsure(HttpField ensure, List fields) - { - // If no existing fields return the ensure field - if (fields == null || fields.isEmpty()) - return ensure; - - String ensureValue = ensure.getValue(); - - // Handle a single existing field - if (fields.size() == 1) - { - // If the existing field contains the ensure value, return it, else append values. - HttpField f = fields.get(0); - return f.contains(ensureValue) - ? f - : new HttpField(ensure.getHeader(), ensure.getName(), f.getValue() + ", " + ensureValue); - } - - // Handle multiple existing fields - StringBuilder v = new StringBuilder(); - for (HttpField f : fields) - { - // Always append multiple fields into a single field value - if (v.length() > 0) - v.append(", "); - v.append(f.getValue()); - - // check if the ensure value is already contained - if (ensureValue != null && f.contains(ensureValue)) - ensureValue = null; - } - - // If the ensure value was not contained append it - if (ensureValue != null) - v.append(", ").append(ensureValue); - - return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); - } - - /** - * Compute ensure field with a multiple values - * @param ensure The field to ensure exists - * @param values The QuotedCSV parsed field values. - * @param fields The list of existing fields with the same header - */ - private static HttpField computeEnsure(HttpField ensure, String[] values, List fields) - { - // If no existing fields return the ensure field - if (fields == null || fields.isEmpty()) - return ensure; - - // Handle a single existing field - if (fields.size() == 1) - { - HttpField f = fields.get(0); - // check which ensured values are already contained - int ensured = values.length; - for (int i = 0; i < values.length; i++) - { - if (f.contains(values[i])) - { - ensured--; - values[i] = null; - } - } - - // if all ensured values contained return the existing field - if (ensured == 0) - return f; - // else if no ensured values contained append the entire ensured valued - if (ensured == values.length) - return new HttpField(ensure.getHeader(), ensure.getName(), - f.getValue() + ", " + ensure.getValue()); - // else append just the ensured values that are not contained - StringBuilder v = new StringBuilder(f.getValue()); - for (String value : values) - { - if (value != null) - v.append(", ").append(value); - } - return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); - } - - // Handle a multiple existing field - StringBuilder v = new StringBuilder(); - int ensured = values.length; - for (HttpField f : fields) - { - // Always append multiple fields into a single field value - if (v.length() > 0) - v.append(", "); - v.append(f.getValue()); - - // null out ensured values that are included - for (int i = 0; i < values.length; i++) - { - if (values[i] != null && f.contains(values[i])) - { - ensured--; - values[i] = null; - } - } - } - - // if no ensured values exist append them all - if (ensured == values.length) - v.append(", ").append(ensure.getValue()); - // else if some ensured values are missing, append them - else if (ensured > 0) - { - for (String value : values) - if (value != null) - v.append(", ").append(value); - } - - // return a merged header with missing ensured values added - return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); - } - @Override - public boolean equals(Object o) + default Iterator iterator() { - if (this == o) - return true; - if (!(o instanceof Mutable)) - return false; - - return isEqualTo((HttpFields)o); + return listIterator(); } - /** - * Get a Field by index. - * - * @param index the field index - * @return A Field value or null if the Field value has not been set - */ - @Override - public HttpField getField(int index) - { - if (index >= _size || index < 0) - throw new NoSuchElementException(); - return _fields[index]; - } + ListIterator listIterator(); - @Override - public int hashCode() - { - int hash = 0; - for (int i = _fields.length; i-- > 0; ) - hash ^= _fields[i].hashCode(); - return hash; - } - - @Override - public Iterator iterator() - { - return new Iterator<>() - { - int _index = 0; - - @Override - public boolean hasNext() - { - return _index < _size; - } - - @Override - public HttpField next() - { - return _fields[_index++]; - } - - @Override - public void remove() - { - if (_size == 0) - throw new IllegalStateException(); - Mutable.this.remove(--_index); - } - }; - } - - public ListIterator listIterator() - { - return new ListItr(); - } - - public Mutable put(HttpField field) + default Mutable put(HttpField field) { boolean put = false; - - for (int i = 0; i < _size; i++) + for (ListIterator i = listIterator(); i.hasNext(); ) { - HttpField f = _fields[i]; + HttpField f = i.next(); if (f.isSameName(field)) { if (put) - System.arraycopy(_fields, i + 1, _fields, i, _size-- - i-- - 1); + i.remove(); else { - _fields[i] = field; + i.set(field); put = true; } } @@ -990,16 +765,18 @@ public interface HttpFields extends Iterable * @param value the value of the field. If null the field is cleared. * @return this builder */ - public Mutable put(String name, String value) + default Mutable put(String name, String value) { - return (value == null) - ? remove(name) - : put(new HttpField(name, value)); + if (value == null) + return remove(name); + return put(new HttpField(name, value)); } - public Mutable put(HttpHeader header, HttpHeaderValue value) + default Mutable put(HttpHeader header, HttpHeaderValue value) { - return put(header, value.toString()); + if (value == null) + return remove(header); + return put(new HttpField(header, value.toString())); } /** @@ -1009,11 +786,11 @@ public interface HttpFields extends Iterable * @param value the value of the field. If null the field is cleared. * @return this builder */ - public Mutable put(HttpHeader header, String value) + default Mutable put(HttpHeader header, String value) { - return (value == null) - ? remove(header) - : put(new HttpField(header, value)); + if (value == null) + return remove(header); + return put(new HttpField(header, value)); } /** @@ -1023,15 +800,19 @@ public interface HttpFields extends Iterable * @param list the List value of the field. If null the field is cleared. * @return this builder */ - public Mutable put(String name, List list) + default Mutable put(String name, List list) { - Objects.requireNonNull(name, "name must not be null"); - Objects.requireNonNull(list, "list must not be null"); - remove(name); - for (String v : list) + Objects.requireNonNull(name); + Objects.requireNonNull(list); + boolean first = true; + for (String s : list) { - if (v != null) - add(name, v); + HttpField field = new HttpField(name, s); + if (first) + put(field); + else + add(field); + first = false; } return this; } @@ -1043,7 +824,7 @@ public interface HttpFields extends Iterable * @param date the field date value * @return this builder */ - public Mutable putDateField(HttpHeader name, long date) + default Mutable putDateField(HttpHeader name, long date) { return put(name, DateGenerator.formatDate(date)); } @@ -1055,7 +836,7 @@ public interface HttpFields extends Iterable * @param date the field date value * @return this builder */ - public Mutable putDateField(String name, long date) + default Mutable putDateField(String name, long date) { return put(name, DateGenerator.formatDate(date)); } @@ -1067,9 +848,9 @@ public interface HttpFields extends Iterable * @param value the field long value * @return this builder */ - public Mutable putLongField(HttpHeader name, long value) + default Mutable putLongField(HttpHeader name, long value) { - return put(name, Long.toString(value)); + return put(new HttpField.LongValueHttpField(name, value)); } /** @@ -1079,7 +860,7 @@ public interface HttpFields extends Iterable * @param value the field long value * @return this builder */ - public Mutable putLongField(String name, long value) + default Mutable putLongField(String name, long value) { return put(name, Long.toString(value)); } @@ -1159,9 +940,9 @@ public interface HttpFields extends Iterable * @param header the HTTP header * @param computeFn the compute function */ - public void computeField(HttpHeader header, BiFunction, HttpField> computeFn) + default void computeField(HttpHeader header, BiFunction, HttpField> computeFn) { - computeField(header, computeFn, (f, h) -> f.getHeader() == h); + put(computeFn.apply(header, stream().filter(f -> f.getHeader() == header).collect(Collectors.toList()))); } /** @@ -1171,12 +952,516 @@ public interface HttpFields extends Iterable * @param computeFn the compute function * @see #computeField(HttpHeader, BiFunction) */ + default void computeField(String name, BiFunction, HttpField> computeFn) + { + put(computeFn.apply(name, stream().filter(f -> f.is(name)).collect(Collectors.toList()))); + } + + /** + * Remove a field. + * + * @param header the field to remove + * @return this builder + */ + default Mutable remove(HttpHeader header) + { + for (ListIterator i = listIterator(); i.hasNext();) + { + HttpField f = i.next(); + if (f.getHeader() == header) + i.remove(); + } + return this; + } + + default Mutable remove(EnumSet fields) + { + for (ListIterator i = listIterator(); i.hasNext();) + { + HttpField f = i.next(); + HttpHeader h = f.getHeader(); + if (h != null && fields.contains(h)) + i.remove(); + } + return this; + } + + /** + * Remove a field. + * + * @param name the field to remove + * @return this builder + */ + default Mutable remove(String name) + { + for (ListIterator i = listIterator(); i.hasNext();) + { + HttpField f = i.next(); + if (f.is(name)) + i.remove(); + } + return this; + } + + static String formatCsvExcludingExisting(QuotedCSV existing, String... values) + { + // remove any existing values from the new values + boolean add = true; + if (existing != null && !existing.isEmpty()) + { + add = false; + + for (int i = values.length; i-- > 0; ) + { + String unquoted = QuotedCSV.unquote(values[i]); + if (existing.getValues().contains(unquoted)) + values[i] = null; + else + add = true; + } + } + + if (add) + { + StringBuilder value = new StringBuilder(); + for (String v : values) + { + if (v == null) + continue; + if (value.length() > 0) + value.append(", "); + value.append(v); + } + if (value.length() > 0) + return value.toString(); + } + + return null; + } + + /** + * Compute ensure field with a single value + * @param ensure The field to ensure exists + * @param fields The list of existing fields with the same header + */ + static HttpField computeEnsure(HttpField ensure, List fields) + { + // If no existing fields return the ensure field + if (fields == null || fields.isEmpty()) + return ensure; + + String ensureValue = ensure.getValue(); + + // Handle a single existing field + if (fields.size() == 1) + { + // If the existing field contains the ensure value, return it, else append values. + HttpField f = fields.get(0); + return f.contains(ensureValue) + ? f + : new HttpField(ensure.getHeader(), ensure.getName(), f.getValue() + ", " + ensureValue); + } + + // Handle multiple existing fields + StringBuilder v = new StringBuilder(); + for (HttpField f : fields) + { + // Always append multiple fields into a single field value + if (v.length() > 0) + v.append(", "); + v.append(f.getValue()); + + // check if the ensure value is already contained + if (ensureValue != null && f.contains(ensureValue)) + ensureValue = null; + } + + // If the ensure value was not contained append it + if (ensureValue != null) + v.append(", ").append(ensureValue); + + return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); + } + + /** + * Compute ensure field with a multiple values + * @param ensure The field to ensure exists + * @param values The QuotedCSV parsed field values. + * @param fields The list of existing fields with the same header + */ + static HttpField computeEnsure(HttpField ensure, String[] values, List fields) + { + // If no existing fields return the ensure field + if (fields == null || fields.isEmpty()) + return ensure; + + // Handle a single existing field + if (fields.size() == 1) + { + HttpField f = fields.get(0); + // check which ensured values are already contained + int ensured = values.length; + for (int i = 0; i < values.length; i++) + { + if (f.contains(values[i])) + { + ensured--; + values[i] = null; + } + } + + // if all ensured values contained return the existing field + if (ensured == 0) + return f; + // else if no ensured values contained append the entire ensured valued + if (ensured == values.length) + return new HttpField(ensure.getHeader(), ensure.getName(), + f.getValue() + ", " + ensure.getValue()); + // else append just the ensured values that are not contained + StringBuilder v = new StringBuilder(f.getValue()); + for (String value : values) + { + if (value != null) + v.append(", ").append(value); + } + return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); + } + + // Handle a multiple existing field + StringBuilder v = new StringBuilder(); + int ensured = values.length; + for (HttpField f : fields) + { + // Always append multiple fields into a single field value + if (v.length() > 0) + v.append(", "); + v.append(f.getValue()); + + // null out ensured values that are included + for (int i = 0; i < values.length; i++) + { + if (values[i] != null && f.contains(values[i])) + { + ensured--; + values[i] = null; + } + } + } + + // if no ensured values exist append them all + if (ensured == values.length) + v.append(", ").append(ensure.getValue()); + // else if some ensured values are missing, append them + else if (ensured > 0) + { + for (String value : values) + if (value != null) + v.append(", ").append(value); + } + + // return a merged header with missing ensured values added + return new HttpField(ensure.getHeader(), ensure.getName(), v.toString()); + } + } + + /** + * HTTP Fields. A collection of HTTP header and or Trailer fields. + * + *

This class is not synchronized as it is expected that modifications will only be performed by a + * single thread. + * + *

The cookie handling provided by this class is guided by the Servlet specification and RFC6265. + */ + class MutableHttpFields implements Mutable + { + private static final int INITIAL_SIZE = 16; + private static final int SIZE_INCREMENT = 4; + + private HttpField[] _fields; + private int _size; + + /** + * Initialize an empty HttpFields. + */ + protected MutableHttpFields() + { + this(INITIAL_SIZE); // Based on small sample of Chrome requests. + } + + /** + * Initialize an empty HttpFields. + * + * @param capacity the capacity of the http fields + */ + protected MutableHttpFields(int capacity) + { + _fields = new HttpField[capacity]; + } + + /** + * Initialize HttpFields from another. + * + * @param fields the fields to copy data from + */ + protected MutableHttpFields(HttpFields fields) + { + add(fields); + } + + /** + * Initialize HttpFields from another and replace a field + * + * @param fields the fields to copy data from + * @param replaceField the replacement field + */ + protected MutableHttpFields(HttpFields fields, HttpField replaceField) + { + _fields = new HttpField[fields.size() + SIZE_INCREMENT]; + _size = 0; + boolean put = false; + for (HttpField f : fields) + { + if (replaceField.isSameName(f)) + { + if (!put) + _fields[_size++] = replaceField; + put = true; + } + else + { + _fields[_size++] = f; + } + } + if (!put) + _fields[_size++] = replaceField; + } + + /** + * Initialize HttpFields from another and remove fields + * + * @param fields the fields to copy data from + * @param removeFields the the fields to remove + */ + protected MutableHttpFields(HttpFields fields, EnumSet removeFields) + { + _fields = new HttpField[fields.size() + SIZE_INCREMENT]; + _size = 0; + for (HttpField f : fields) + { + if (f.getHeader() == null || !removeFields.contains(f.getHeader())) + _fields[_size++] = f; + } + } + + @Override + public Mutable add(HttpField field) + { + if (field != null) + { + if (_fields == null) + _fields = new HttpField[INITIAL_SIZE]; + if (_size == _fields.length) + _fields = Arrays.copyOf(_fields, _size + SIZE_INCREMENT); + _fields[_size++] = field; + } + return this; + } + + @Override + public Mutable add(HttpFields fields) + { + if (_fields == null) + _fields = new HttpField[fields.size() + SIZE_INCREMENT]; + else if (_size + fields.size() >= _fields.length) + _fields = Arrays.copyOf(_fields, _size + fields.size() + SIZE_INCREMENT); + + if (fields.size() == 0) + return this; + + if (fields instanceof ImmutableHttpFields immutable) + { + System.arraycopy(immutable._fields, 0, _fields, _size, immutable._size); + _size += immutable._size; + } + else if (fields instanceof MutableHttpFields mutable) + { + System.arraycopy(mutable._fields, 0, _fields, _size, mutable._size); + _size += mutable._size; + } + else + { + for (HttpField f : fields) + _fields[_size++] = f; + } + return this; + } + + @Override + public HttpFields asImmutable() + { + return new ImmutableHttpFields(Arrays.copyOf(_fields, _size)); + } + + @Override + public HttpFields takeAsImmutable() + { + HttpField[] fields = _fields == null ? new HttpField[0] : _fields; + int size = _size; + _fields = null; + _size = 0; + + return new ImmutableHttpFields(fields, size); + } + + @Override + public Mutable clear() + { + _size = 0; + return this; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (!(o instanceof MutableHttpFields)) + return false; + + return isEqualTo((HttpFields)o); + } + + /** + * Get a Field by index. + * + * @param index the field index + * @return A Field value or null if the Field value has not been set + */ + @Override + public HttpField getField(int index) + { + if (index >= _size || index < 0) + throw new NoSuchElementException(); + return _fields[index]; + } + + @Override + public int hashCode() + { + int hash = 0; + for (int i = _fields.length; i-- > 0; ) + hash ^= _fields[i].hashCode(); + return hash; + } + + @Override + public Iterator iterator() + { + return new Iterator<>() + { + int _index = 0; + + @Override + public boolean hasNext() + { + return _index < _size; + } + + @Override + public HttpField next() + { + return _fields[_index++]; + } + + @Override + public void remove() + { + if (_size == 0) + throw new IllegalStateException(); + MutableHttpFields.this.remove(--_index); + } + }; + } + + @Override + public ListIterator listIterator() + { + return new ListItr(); + } + + @Override + public Mutable put(HttpField field) + { + boolean put = false; + + for (int i = 0; i < _size; i++) + { + HttpField f = _fields[i]; + if (f.isSameName(field)) + { + if (put) + System.arraycopy(_fields, i + 1, _fields, i, _size-- - i-- - 1); + else + { + _fields[i] = field; + put = true; + } + } + } + if (!put) + add(field); + return this; + } + + @Override + public Mutable put(String name, String value) + { + return (value == null) + ? remove(name) + : put(new HttpField(name, value)); + } + + @Override + public Mutable put(HttpHeader header, HttpHeaderValue value) + { + return put(header, value.toString()); + } + + @Override + public Mutable put(HttpHeader header, String value) + { + return (value == null) + ? remove(header) + : put(new HttpField(header, value)); + } + + @Override + public Mutable put(String name, List list) + { + Objects.requireNonNull(name); + Objects.requireNonNull(list); + remove(name); + for (String v : list) + { + if (v != null) + add(name, v); + } + return this; + } + + @Override + public void computeField(HttpHeader header, BiFunction, HttpField> computeFn) + { + computeField(header, computeFn, (f, h) -> f.getHeader() == h); + } + + @Override public void computeField(String name, BiFunction, HttpField> computeFn) { computeField(name, computeFn, HttpField::is); } - private void computeField(T header, BiFunction, HttpField> computeFn, BiPredicate matcher) + public void computeField(T header, BiFunction, HttpField> computeFn, BiPredicate matcher) { // Look for first occurrence int first = -1; @@ -1230,12 +1515,7 @@ public interface HttpFields extends Iterable _fields[first] = newField; } - /** - * Remove a field. - * - * @param name the field to remove - * @return this builder - */ + @Override public Mutable remove(HttpHeader name) { for (int i = 0; i < _size; i++) @@ -1247,6 +1527,7 @@ public interface HttpFields extends Iterable return this; } + @Override public Mutable remove(EnumSet fields) { for (int i = 0; i < _size; i++) @@ -1258,12 +1539,7 @@ public interface HttpFields extends Iterable return this; } - /** - * Remove a field. - * - * @param name the field to remove - * @return this builder - */ + @Override public Mutable remove(String name) { for (int i = 0; i < _size; i++) @@ -1299,42 +1575,6 @@ public interface HttpFields extends Iterable return asString(); } - private String formatCsvExcludingExisting(QuotedCSV existing, String... values) - { - // remove any existing values from the new values - boolean add = true; - if (existing != null && !existing.isEmpty()) - { - add = false; - - for (int i = values.length; i-- > 0; ) - { - String unquoted = QuotedCSV.unquote(values[i]); - if (existing.getValues().contains(unquoted)) - values[i] = null; - else - add = true; - } - } - - if (add) - { - StringBuilder value = new StringBuilder(); - for (String v : values) - { - if (v == null) - continue; - if (value.length() > 0) - value.append(", "); - value.append(v); - } - if (value.length() > 0) - return value.toString(); - } - - return null; - } - private class ListItr implements ListIterator { int _cursor; // index of next element to return @@ -1346,8 +1586,10 @@ public interface HttpFields extends Iterable if (field == null) return; - _fields = Arrays.copyOf(_fields, _fields.length + 1); - System.arraycopy(_fields, _cursor, _fields, _cursor + 1, _size++); + int last = _size++; + if (_fields.length < _size) + _fields = Arrays.copyOf(_fields, _fields.length + SIZE_INCREMENT); + System.arraycopy(_fields, _cursor, _fields, _cursor + 1, last - _cursor); _fields[_cursor++] = field; _current = -1; } @@ -1399,7 +1641,7 @@ public interface HttpFields extends Iterable { if (_current < 0) throw new IllegalStateException(); - Mutable.this.remove(_current); + MutableHttpFields.this.remove(_current); _cursor = _current; _current = -1; } @@ -1425,22 +1667,36 @@ public interface HttpFields extends Iterable * *

The cookie handling provided by this class is guided by the Servlet specification and RFC6265. */ - class Immutable implements HttpFields + class ImmutableHttpFields implements HttpFields { final HttpField[] _fields; + final int _size; /** * Initialize HttpFields from copy. * * @param fields the fields to copy data from */ - public Immutable(HttpField[] fields) + protected ImmutableHttpFields(HttpField[] fields) { + this(fields, fields.length); + } + + protected ImmutableHttpFields(HttpField[] fields, int size) + { + Objects.requireNonNull(fields); _fields = fields; + _size = size; } @Override - public Immutable asImmutable() + public HttpFields asImmutable() + { + return this; + } + + @Override + public HttpFields takeAsImmutable() { return this; } @@ -1450,7 +1706,7 @@ public interface HttpFields extends Iterable { if (this == o) return true; - if (!(o instanceof Immutable)) + if (!(o instanceof ImmutableHttpFields)) return false; return isEqualTo((HttpFields)o); @@ -1461,7 +1717,7 @@ public interface HttpFields extends Iterable { // default impl overridden for efficiency for (HttpField f : _fields) - if (f.is(header)) + if (f != null && f.is(header)) return f.getValue(); return null; } @@ -1471,7 +1727,7 @@ public interface HttpFields extends Iterable { // default impl overridden for efficiency for (HttpField f : _fields) - if (f.getHeader() == header) + if (f != null && f.getHeader() == header) return f.getValue(); return null; } @@ -1481,7 +1737,7 @@ public interface HttpFields extends Iterable { // default impl overridden for efficiency for (HttpField f : _fields) - if (f.getHeader() == header) + if (f != null && f.getHeader() == header) return f; return null; } @@ -1491,7 +1747,7 @@ public interface HttpFields extends Iterable { // default impl overridden for efficiency for (HttpField f : _fields) - if (f.is(name)) + if (f != null && f.is(name)) return f; return null; } @@ -1523,7 +1779,7 @@ public interface HttpFields extends Iterable @Override public boolean hasNext() { - return _index < _fields.length; + return _index < _size; } @Override @@ -1537,13 +1793,13 @@ public interface HttpFields extends Iterable @Override public int size() { - return _fields.length; + return _size; } @Override public Stream stream() { - return Arrays.stream(_fields); + return Arrays.stream(_fields).filter(Objects::nonNull); } @Override diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index 8a4b3ebab79..1adf5ef52e0 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -47,7 +47,7 @@ public class HttpGenerator public static final MetaData.Response CONTINUE_100_INFO = new MetaData.Response(HttpVersion.HTTP_1_1, 100, null, null, -1); public static final MetaData.Response PROGRESS_102_INFO = new MetaData.Response(HttpVersion.HTTP_1_1, 102, null, null, -1); public static final MetaData.Response RESPONSE_500_INFO = - new MetaData.Response(HttpVersion.HTTP_1_1, INTERNAL_SERVER_ERROR_500, null, HttpFields.build().put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE), 0); + new MetaData.Response(HttpVersion.HTTP_1_1, INTERNAL_SERVER_ERROR_500, null, HttpFields.build().add(HttpFields.CONNECTION_CLOSE), 0); // states public enum State @@ -171,6 +171,14 @@ public class HttpGenerator return Boolean.TRUE.equals(_persistent); } + /** + * @return true if known to be persistent + */ + public boolean isPersistent(HttpVersion version) + { + return version == HttpVersion.HTTP_1_1 ? !Boolean.FALSE.equals(_persistent) : Boolean.TRUE.equals(_persistent); + } + public boolean isWritten() { return _contentPrepared > 0; diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index a66ccee4da4..3a58071f3ab 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -1761,10 +1761,6 @@ public class HttpParser setState(State.CHUNK); break; - case SPACE: - setState(State.CHUNK_PARAMS); - break; - default: if (t.isHexDigit()) { @@ -1772,10 +1768,14 @@ public class HttpParser throw new BadMessageException(HttpStatus.PAYLOAD_TOO_LARGE_413); _chunkLength = _chunkLength * 16 + t.getHexDigit(); } - else + else if (t.getChar() == ';') { setState(State.CHUNK_PARAMS); } + else + { + throw new IllegalCharacterException(_state, t, buffer); + } } break; } @@ -1900,14 +1900,40 @@ public class HttpParser protected void setState(State state) { if (debugEnabled) - LOG.debug("{} --> {}", _state, state); + { + String info; + switch (state) + { + case SPACE1: + info = _requestHandler == null ? _version.asString() : _methodString; + break; + case SPACE2: + info = _requestHandler == null ? Integer.toString(_responseStatus) : _uri.toString(); + break; + case CONTENT_END: + case TRAILER: + info = Long.toString(_contentPosition); + break; + default: + info = null; + } + if (info == null) + LOG.debug("{} --> {}", _state, state); + else + LOG.debug("{} --> {}({})", _state, state, info); + } _state = state; } protected void setState(FieldState state) { if (debugEnabled) - LOG.debug("{}:{} --> {}", _state, _field != null ? _field : _headerString != null ? _headerString : _string, state); + { + if (state != FieldState.FIELD) + LOG.debug("{}:{} --> {}", _state, _fieldState, state); + else + LOG.debug("{}:{} --> {}({}: {})", _state, _fieldState, state, _field != null ? _field : _headerString, _valueString); + } _fieldState = state; } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java index 6181e7aa0e0..5ddff23f45c 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpTester.java @@ -273,7 +273,7 @@ public class HttpTester } } - public abstract static class Message extends HttpFields.Mutable implements HttpParser.HttpHandler + public abstract static class Message extends HttpFields.MutableHttpFields implements HttpParser.HttpHandler { boolean _earlyEOF; boolean _complete = false; diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java index 9423e66db9d..8870036d515 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java @@ -34,20 +34,21 @@ import org.eclipse.jetty.util.UrlEncoded; * via the static methods such as {@link #build()} and {@link #from(String)}. * * A URI such as - * {@code http://user@host:port/path;param1/%2e/info;param2?query#fragment} + * {@code http://user@host:port/path;param1/%2e/f%6fo%2fbar;param2?query#fragment} * is split into the following optional elements:

    *
  • {@link #getScheme()} - http:
  • *
  • {@link #getAuthority()} - //name@host:port
  • *
  • {@link #getHost()} - host
  • *
  • {@link #getPort()} - port
  • - *
  • {@link #getPath()} - /path;param1/%2e/info;param2
  • - *
  • {@link #getDecodedPath()} - /path/info
  • + *
  • {@link #getPath()} - /path;param1/%2e/f%6fo%2fbar;param2
  • + *
  • {@link #getCanonicalPath()} - /path/foo%2Fbar
  • + *
  • {@link #getDecodedPath()} - /path/foo/bar
  • *
  • {@link #getParam()} - param2
  • *
  • {@link #getQuery()} - query
  • *
  • {@link #getFragment()} - fragment
  • *
*

The path part of the URI is provided in both raw form ({@link #getPath()}) and - * decoded form ({@link #getDecodedPath}), which has: path parameters removed, + * decoded form ({@link #getCanonicalPath}), which has: path parameters removed, * percent encoded characters expanded and relative segments resolved. This approach * is somewhat contrary to RFC3986 * which no longer defines path parameters (removed after @@ -106,6 +107,18 @@ public interface HttpURI return new Mutable(uri); } + static Mutable build(String method, String uri) + { + if (HttpMethod.CONNECT.is(method)) + { + HostPort hostPort = new HostPort(uri); + return new Mutable(null, hostPort.getHost(), hostPort.getPort(), null); + } + if (uri.startsWith("/")) + return HttpURI.build().pathQuery(uri); + return HttpURI.build(uri); + } + static Immutable from(URI uri) { return new HttpURI.Mutable(uri).asImmutable(); @@ -125,6 +138,11 @@ public interface HttpURI return HttpURI.from(uri); } + static Immutable from(String scheme, HostPort hostPort, String pathQuery) + { + return new Mutable(scheme, hostPort.getHost(), hostPort.getPort(), pathQuery).asImmutable(); + } + static Immutable from(String scheme, String host, int port, String pathQuery) { return new Mutable(scheme, host, port, pathQuery).asImmutable(); @@ -138,13 +156,14 @@ public interface HttpURI String getDecodedPath(); + String getCanonicalPath(); + String getFragment(); String getHost(); /** - * Get a URI path parameter. Multiple and in segment parameters are ignored and only - * the last trailing parameter is returned. + * Get a URI path parameter. Only parameters from the last segment are returned. * @return The last path parameter or null */ String getParam(); @@ -258,7 +277,7 @@ public interface HttpURI private final String _query; private final String _fragment; private String _uri; - private String _decodedPath; + private String _canonicalPath; private final EnumSet _violations = EnumSet.noneOf(Violation.class); private Immutable(Mutable builder) @@ -272,7 +291,7 @@ public interface HttpURI _query = builder._query; _fragment = builder._fragment; _uri = builder._uri; - _decodedPath = builder._decodedPath; + _canonicalPath = builder._canonicalPath; _violations.addAll(builder._violations); } @@ -287,7 +306,7 @@ public interface HttpURI _query = null; _fragment = null; _uri = uri; - _decodedPath = null; + _canonicalPath = null; } @Override @@ -355,9 +374,15 @@ public interface HttpURI @Override public String getDecodedPath() { - if (_decodedPath == null && _path != null) - _decodedPath = URIUtil.canonicalPath(URIUtil.decodePath(_path)); - return _decodedPath; + return URIUtil.decodePath(getCanonicalPath()); + } + + @Override + public String getCanonicalPath() + { + if (_canonicalPath == null && _path != null) + _canonicalPath = URIUtil.canonicalPath(URIUtil.normalizePath(_path)); + return _canonicalPath; } @Override @@ -535,7 +560,7 @@ public interface HttpURI private String _query; private String _fragment; private String _uri; - private String _decodedPath; + private String _canonicalPath; private final EnumSet _violations = EnumSet.noneOf(Violation.class); private boolean _emptySegment; @@ -599,6 +624,10 @@ public interface HttpURI private Mutable(String scheme, String host, int port, String pathQuery) { + // TODO review if this should be here + if (port == HttpScheme.getDefaultPort(scheme)) + port = 0; + _uri = null; _scheme = scheme; @@ -660,7 +689,7 @@ public interface HttpURI _query = null; _fragment = null; _uri = null; - _decodedPath = null; + _canonicalPath = null; _emptySegment = false; _violations.clear(); return this; @@ -670,7 +699,7 @@ public interface HttpURI { _uri = null; _path = URIUtil.encodePath(path); - _decodedPath = path; + _canonicalPath = path; return this; } @@ -701,9 +730,15 @@ public interface HttpURI @Override public String getDecodedPath() { - if (_decodedPath == null && _path != null) - _decodedPath = URIUtil.canonicalPath(URIUtil.decodePath(_path)); - return _decodedPath; + return URIUtil.decodePath(getCanonicalPath()); + } + + @Override + public String getCanonicalPath() + { + if (_canonicalPath == null && _path != null) + _canonicalPath = URIUtil.canonicalPath(URIUtil.normalizePath(_path)); + return _canonicalPath; } @Override @@ -783,7 +818,7 @@ public interface HttpURI @Override public boolean isAbsolute() { - return _scheme != null && !_scheme.isEmpty(); + return StringUtil.isNotBlank(_scheme); } @Override @@ -824,10 +859,9 @@ public interface HttpURI public Mutable param(String param) { _param = param; - if (_path != null && _param != null && !_path.contains(_param)) - { + if (_path != null && _param != null) _path += ";" + _param; - } + _uri = null; return this; } @@ -840,7 +874,7 @@ public interface HttpURI { _uri = null; _path = path; - _decodedPath = null; + _canonicalPath = null; return this; } @@ -848,7 +882,7 @@ public interface HttpURI { _uri = null; _path = null; - _decodedPath = null; + _canonicalPath = null; _param = null; _query = null; if (pathQuery != null) @@ -910,7 +944,7 @@ public interface HttpURI _param = uri.getParam(); _query = uri.getQuery(); _uri = null; - _decodedPath = uri.getDecodedPath(); + _canonicalPath = uri.getCanonicalPath(); if (uri.hasAmbiguousSeparator()) _violations.add(Violation.AMBIGUOUS_PATH_SEPARATOR); if (uri.hasAmbiguousSegment()) @@ -1280,7 +1314,6 @@ public interface HttpURI break; case ';': // multiple parameters - mark = i + 1; break; default: break; @@ -1356,17 +1389,17 @@ public interface HttpURI if (!encodedPath && !dot) { if (_param == null) - _decodedPath = _path; + _canonicalPath = _path; else - _decodedPath = _path.substring(0, _path.length() - _param.length() - 1); + _canonicalPath = _path.substring(0, _path.length() - _param.length() - 1); } else if (_path != null) { // The RFC requires this to be canonical before decoding, but this can leave dot segments and dot dot segments // which are not canonicalized and could be used in an attempt to bypass security checks. - String decodedNonCanonical = URIUtil.decodePath(_path); - _decodedPath = URIUtil.canonicalPath(decodedNonCanonical); - if (_decodedPath == null) + String decodedNonCanonical = URIUtil.normalizePath(_path); + _canonicalPath = URIUtil.canonicalPath(decodedNonCanonical); + if (_canonicalPath == null) throw new IllegalArgumentException("Bad URI"); } } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java index 4feba15d065..5096eab41c9 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java @@ -69,6 +69,14 @@ public enum HttpVersion default: return null; } + case '3': + switch (bytes[position + 7]) + { + case '0': + return HTTP_3; + default: + return null; + } default: return null; } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java index 90dfd5c4149..4eda6e09482 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java @@ -19,6 +19,8 @@ import java.util.function.Supplier; public class MetaData implements Iterable { + private static final long UNKNOWN_CONTENT_LENGTH = Long.MIN_VALUE; + /** *

Returns whether the given HTTP request method and HTTP response status code * identify a successful HTTP CONNECT tunnel.

@@ -52,7 +54,8 @@ public class MetaData implements Iterable _httpVersion = version; _fields = fields == null ? null : fields.asImmutable(); - _contentLength = contentLength >= 0 ? contentLength : _fields == null ? -1 : _fields.getLongField(HttpHeader.CONTENT_LENGTH); + _contentLength = contentLength > UNKNOWN_CONTENT_LENGTH ? contentLength : _fields == null ? -1 : _fields.getLongField(HttpHeader.CONTENT_LENGTH); + _trailerSupplier = trailerSupplier; } @@ -123,7 +126,7 @@ public class MetaData implements Iterable public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields) { - this(method, uri, version, fields, Long.MIN_VALUE); + this(method, uri, version, fields, UNKNOWN_CONTENT_LENGTH); } public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields, long contentLength) @@ -204,7 +207,7 @@ public class MetaData implements Iterable { super(HttpMethod.CONNECT.asString(), HttpURI.build().scheme(scheme).host(authority == null ? null : authority.getHost()).port(authority == null ? -1 : authority.getPort()).pathQuery(path), - HttpVersion.HTTP_2, fields, Long.MIN_VALUE, null); + HttpVersion.HTTP_2, fields, UNKNOWN_CONTENT_LENGTH, null); _protocol = protocol; } @@ -222,7 +225,7 @@ public class MetaData implements Iterable public Response(HttpVersion version, int status, HttpFields fields) { - this(version, status, fields, Long.MIN_VALUE); + this(version, status, fields, UNKNOWN_CONTENT_LENGTH); } public Response(HttpVersion version, int status, HttpFields fields, long contentLength) diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java index f238be5e57a..025fd63e925 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java @@ -24,6 +24,7 @@ import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Properties; import java.util.Set; @@ -51,9 +52,42 @@ public class MimeTypes MULTIPART_BYTERANGES("multipart/byteranges"), MULTIPART_FORM_DATA("multipart/form-data"), - TEXT_HTML("text/html"), - TEXT_PLAIN("text/plain"), - TEXT_XML("text/xml"), + TEXT_HTML("text/html") + { + @Override + public HttpField getContentTypeField(Charset charset) + { + if (Objects.equals(charset, StandardCharsets.UTF_8)) + return TEXT_HTML_UTF_8.getContentTypeField(); + if (Objects.equals(charset, StandardCharsets.ISO_8859_1)) + return TEXT_HTML_8859_1.getContentTypeField(); + return super.getContentTypeField(charset); + } + }, + TEXT_PLAIN("text/plain") + { + @Override + public HttpField getContentTypeField(Charset charset) + { + if (Objects.equals(charset, StandardCharsets.UTF_8)) + return TEXT_PLAIN_UTF_8.getContentTypeField(); + if (Objects.equals(charset, StandardCharsets.ISO_8859_1)) + return TEXT_PLAIN_8859_1.getContentTypeField(); + return super.getContentTypeField(charset); + } + }, + TEXT_XML("text/xml") + { + @Override + public HttpField getContentTypeField(Charset charset) + { + if (Objects.equals(charset, StandardCharsets.UTF_8)) + return TEXT_XML_UTF_8.getContentTypeField(); + if (Objects.equals(charset, StandardCharsets.ISO_8859_1)) + return TEXT_XML_8859_1.getContentTypeField(); + return super.getContentTypeField(charset); + } + }, TEXT_JSON("text/json", StandardCharsets.UTF_8), APPLICATION_JSON("application/json", StandardCharsets.UTF_8), @@ -155,6 +189,13 @@ public class MimeTypes return _field; } + public HttpField getContentTypeField(Charset charset) + { + if (Objects.equals(_charset, charset)) + return _field; + return new HttpField(HttpHeader.CONTENT_TYPE, getContentTypeWithoutCharset(_string) + ";charset=" + charset.name()); + } + public Type getBaseType() { return _base; diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java index 4ef3f79af89..3a0969b9623 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java @@ -14,8 +14,7 @@ package org.eclipse.jetty.http; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; +import java.util.EnumMap; import java.util.ServiceLoader; import org.eclipse.jetty.util.TypeUtil; @@ -34,75 +33,48 @@ import org.slf4j.LoggerFactory; public class PreEncodedHttpField extends HttpField { private static final Logger LOG = LoggerFactory.getLogger(PreEncodedHttpField.class); - private static final HttpFieldPreEncoder[] __encoders; + private static final EnumMap __encoders = new EnumMap<>(HttpVersion.class); static { - List encoders = new ArrayList<>(); TypeUtil.serviceProviderStream(ServiceLoader.load(HttpFieldPreEncoder.class)).forEach(provider -> { try { HttpFieldPreEncoder encoder = provider.get(); - if (index(encoder.getHttpVersion()) >= 0) - encoders.add(encoder); + HttpFieldPreEncoder existing = __encoders.put(encoder.getHttpVersion(), encoder); + if (existing != null) + LOG.warn("multiple {} for {}", HttpFieldPreEncoder.class.getSimpleName(), encoder.getHttpVersion()); } catch (Error | RuntimeException e) { - LOG.debug("Unable to add HttpFieldPreEncoder", e); + if (LOG.isDebugEnabled()) + LOG.debug("unable to add {}", HttpFieldPreEncoder.class.getSimpleName(), e); } }); - LOG.debug("HttpField encoders loaded: {}", encoders); + if (LOG.isDebugEnabled()) + LOG.debug("loaded {} {}s", __encoders.size(), HttpFieldPreEncoder.class.getSimpleName()); - int size = 1; - for (HttpFieldPreEncoder e : encoders) - { - size = Math.max(size, index(e.getHttpVersion()) + 1); - } - __encoders = new HttpFieldPreEncoder[size]; - for (HttpFieldPreEncoder e : encoders) - { - int i = index(e.getHttpVersion()); - if (__encoders[i] == null) - __encoders[i] = e; - else - LOG.warn("multiple PreEncoders for {}", e.getHttpVersion()); - } - - // Always support HTTP1 - if (__encoders[0] == null) - __encoders[0] = new Http1FieldPreEncoder(); + // Always support HTTP1. + if (!__encoders.containsKey(HttpVersion.HTTP_1_0)) + __encoders.put(HttpVersion.HTTP_1_0, new Http10FieldPreEncoder()); + if (!__encoders.containsKey(HttpVersion.HTTP_1_1)) + __encoders.put(HttpVersion.HTTP_1_1, new Http11FieldPreEncoder()); } - private static int index(HttpVersion version) - { - switch (version) - { - case HTTP_1_0: - case HTTP_1_1: - return 0; - - case HTTP_2: - return 1; - - case HTTP_3: - return 2; - - default: - return -1; - } - } - - private final byte[][] _encodedField = new byte[__encoders.length][]; + private final EnumMap _encodedFields = new EnumMap<>(HttpVersion.class); public PreEncodedHttpField(HttpHeader header, String name, String value) { super(header, name, value); - for (int i = 0; i < __encoders.length; i++) + for (HttpFieldPreEncoder encoder : __encoders.values()) { - if (__encoders[i] != null) - _encodedField[i] = __encoders[i].getEncodedField(header, name, value); + HttpVersion version = encoder.getHttpVersion(); + _encodedFields.put(encoder.getHttpVersion(), + version == HttpVersion.HTTP_1_1 + ? _encodedFields.get(HttpVersion.HTTP_1_0) + : encoder.getEncodedField(header, name, value)); } } @@ -118,11 +90,11 @@ public class PreEncodedHttpField extends HttpField public void putTo(ByteBuffer bufferInFillMode, HttpVersion version) { - bufferInFillMode.put(_encodedField[index(version)]); + bufferInFillMode.put(_encodedFields.get(version)); } public int getEncodedLength(HttpVersion version) { - return _encodedField[index(version)].length; + return _encodedFields.get(version).length; } } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PrecompressedHttpContent.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PrecompressedHttpContent.java index 1c6d0859c4d..5f4f5121b59 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PrecompressedHttpContent.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PrecompressedHttpContent.java @@ -13,10 +13,8 @@ package org.eclipse.jetty.http; -import java.io.IOException; -import java.io.InputStream; import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; +import java.nio.file.Path; import java.util.Map; import org.eclipse.jetty.http.MimeTypes.Type; @@ -40,15 +38,9 @@ public class PrecompressedHttpContent implements HttpContent } @Override - public int hashCode() + public Path getPath() { - return _content.hashCode(); - } - - @Override - public boolean equals(Object obj) - { - return _content.equals(obj); + return _content.getPath(); } @Override @@ -66,7 +58,8 @@ public class PrecompressedHttpContent implements HttpContent @Override public String getETagValue() { - return _content.getResource().getWeakETag(_format.getEtagSuffix()); + //return _content.getResource().getWeakETag(_format.getEtagSuffix()); + return null; } @Override @@ -117,24 +110,6 @@ public class PrecompressedHttpContent implements HttpContent return _content.getMimeType(); } - @Override - public void release() - { - _content.release(); - } - - @Override - public ByteBuffer getIndirectBuffer() - { - return _precompressedContent.getIndirectBuffer(); - } - - @Override - public ByteBuffer getDirectBuffer() - { - return _precompressedContent.getDirectBuffer(); - } - @Override public HttpField getContentLength() { @@ -147,26 +122,15 @@ public class PrecompressedHttpContent implements HttpContent return _precompressedContent.getContentLengthValue(); } - @Override - public InputStream getInputStream() throws IOException - { - return _precompressedContent.getInputStream(); - } - - @Override - public ReadableByteChannel getReadableByteChannel() throws IOException - { - return _precompressedContent.getReadableByteChannel(); - } - @Override public String toString() { return String.format("%s@%x{e=%s,r=%s|%s,lm=%s|%s,ct=%s}", this.getClass().getSimpleName(), hashCode(), _format, - _content.getResource(), _precompressedContent.getResource(), - _content.getResource().lastModified(), _precompressedContent.getResource().lastModified(), + _content.getPath(), _precompressedContent.getPath(), +// _content.getResource().lastModified(), _precompressedContent.getResource().lastModified(), + 0L, 0L, getContentType()); } @@ -175,4 +139,16 @@ public class PrecompressedHttpContent implements HttpContent { return null; } + + @Override + public ByteBuffer getBuffer() + { + return _content.getBuffer(); + } + + @Override + public void release() + { + _content.release(); + } } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java index 2bbce2419fc..26c4a4bd00f 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/ResourceHttpContent.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; @@ -33,6 +34,7 @@ import org.eclipse.jetty.util.resource.Resource; public class ResourceHttpContent implements HttpContent { final Resource _resource; + final Path _path; final String _contentType; final int _maxBuffer; Map _precompressedContents; @@ -51,6 +53,7 @@ public class ResourceHttpContent implements HttpContent public ResourceHttpContent(final Resource resource, final String contentType, int maxBuffer, Map precompressedContents) { _resource = resource; + _path = resource.getPath(); _contentType = contentType; _maxBuffer = maxBuffer; if (precompressedContents == null) @@ -117,21 +120,6 @@ public class ResourceHttpContent implements HttpContent return lm >= 0 ? DateGenerator.formatDate(lm) : null; } - @Override - public ByteBuffer getDirectBuffer() - { - if (_resource.length() <= 0 || _maxBuffer > 0 && _resource.length() > _maxBuffer) - return null; - try - { - return BufferUtil.toBuffer(_resource, true); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - } - @Override public HttpField getETag() { @@ -144,21 +132,6 @@ public class ResourceHttpContent implements HttpContent return _resource.getWeakETag(); } - @Override - public ByteBuffer getIndirectBuffer() - { - if (_resource.length() <= 0 || _maxBuffer > 0 && _resource.length() > _maxBuffer) - return null; - try - { - return BufferUtil.toBuffer(_resource, false); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - } - @Override public HttpField getContentLength() { @@ -173,15 +146,9 @@ public class ResourceHttpContent implements HttpContent } @Override - public InputStream getInputStream() throws IOException + public Path getPath() { - return _resource.getInputStream(); - } - - @Override - public ReadableByteChannel getReadableByteChannel() throws IOException - { - return _resource.getReadableByteChannel(); + return _path; } @Override @@ -190,12 +157,6 @@ public class ResourceHttpContent implements HttpContent return _resource; } - @Override - public void release() - { - _resource.close(); - } - @Override public String toString() { @@ -207,4 +168,16 @@ public class ResourceHttpContent implements HttpContent { return _precompressedContents; } + + @Override + public ByteBuffer getBuffer() + { + return null; + } + + @Override + public void release() + { + _resource.close(); + } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/package-info.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/package-info.java similarity index 100% rename from jetty-http/src/main/java/org/eclipse/jetty/http/package-info.java rename to jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/package-info.java diff --git a/jetty-core/jetty-http/src/main/resources/META-INF/services/org.eclipse.jetty.http.HttpFieldPreEncoder b/jetty-core/jetty-http/src/main/resources/META-INF/services/org.eclipse.jetty.http.HttpFieldPreEncoder index 92640bd6b23..1d547dd1bce 100644 --- a/jetty-core/jetty-http/src/main/resources/META-INF/services/org.eclipse.jetty.http.HttpFieldPreEncoder +++ b/jetty-core/jetty-http/src/main/resources/META-INF/services/org.eclipse.jetty.http.HttpFieldPreEncoder @@ -1 +1,2 @@ -org.eclipse.jetty.http.Http1FieldPreEncoder \ No newline at end of file +org.eclipse.jetty.http.Http10FieldPreEncoder +org.eclipse.jetty.http.Http11FieldPreEncoder \ No newline at end of file diff --git a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/GZIPContentDecoderTest.java b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/GZIPContentDecoderTest.java index 93bbec26788..1a237767392 100644 --- a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/GZIPContentDecoderTest.java +++ b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/GZIPContentDecoderTest.java @@ -41,7 +41,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class GZIPContentDecoderTest { private ArrayByteBufferPool pool; - private final AtomicInteger buffers = new AtomicInteger(0); + private AtomicInteger buffers = new AtomicInteger(0); @BeforeEach public void before() @@ -132,26 +132,24 @@ public class GZIPContentDecoderTest { baos.write(read); } - assertEquals(data, baos.toString(StandardCharsets.UTF_8)); + assertEquals(data, new String(baos.toByteArray(), StandardCharsets.UTF_8)); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testNoBlocks(boolean useDirectBuffers) throws Exception + @Test + public void testNoBlocks() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); GZIPOutputStream output = new GZIPOutputStream(baos); output.close(); byte[] bytes = baos.toByteArray(); - GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048, useDirectBuffers); + GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048); ByteBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes)); assertEquals(0, decoded.remaining()); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testSmallBlock(boolean useDirectBuffers) throws Exception + @Test + public void testSmallBlock() throws Exception { String data = "0"; @@ -161,15 +159,14 @@ public class GZIPContentDecoderTest output.close(); byte[] bytes = baos.toByteArray(); - GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048, useDirectBuffers); + GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048); ByteBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes)); assertEquals(data, StandardCharsets.UTF_8.decode(decoded).toString()); decoder.release(decoded); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testSmallBlockWithGZIPChunkedAtBegin(boolean useDirectBuffers) throws Exception + @Test + public void testSmallBlockWithGZIPChunkedAtBegin() throws Exception { String data = "0"; @@ -185,7 +182,7 @@ public class GZIPContentDecoderTest byte[] bytes2 = new byte[bytes.length - bytes1.length]; System.arraycopy(bytes, bytes1.length, bytes2, 0, bytes2.length); - GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048, useDirectBuffers); + GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048); ByteBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes1)); assertEquals(0, decoded.capacity()); decoded = decoder.decode(ByteBuffer.wrap(bytes2)); @@ -193,9 +190,8 @@ public class GZIPContentDecoderTest decoder.release(decoded); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testSmallBlockWithGZIPChunkedAtEnd(boolean useDirectBuffers) throws Exception + @Test + public void testSmallBlockWithGZIPChunkedAtEnd() throws Exception { String data = "0"; @@ -211,7 +207,7 @@ public class GZIPContentDecoderTest byte[] bytes2 = new byte[bytes.length - bytes1.length]; System.arraycopy(bytes, bytes1.length, bytes2, 0, bytes2.length); - GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048, useDirectBuffers); + GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048); ByteBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes1)); assertEquals(data, StandardCharsets.UTF_8.decode(decoded).toString()); assertFalse(decoder.isFinished()); @@ -222,9 +218,8 @@ public class GZIPContentDecoderTest decoder.release(decoded); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testSmallBlockWithGZIPTrailerChunked(boolean useDirectBuffers) throws Exception + @Test + public void testSmallBlockWithGZIPTrailerChunked() throws Exception { String data = "0"; @@ -240,7 +235,7 @@ public class GZIPContentDecoderTest byte[] bytes2 = new byte[bytes.length - bytes1.length]; System.arraycopy(bytes, bytes1.length, bytes2, 0, bytes2.length); - GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048, useDirectBuffers); + GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048); ByteBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes1)); assertEquals(0, decoded.capacity()); decoder.release(decoded); @@ -249,9 +244,8 @@ public class GZIPContentDecoderTest decoder.release(decoded); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testTwoSmallBlocks(boolean useDirectBuffers) throws Exception + @Test + public void testTwoSmallBlocks() throws Exception { String data1 = "0"; ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -271,7 +265,7 @@ public class GZIPContentDecoderTest System.arraycopy(bytes1, 0, bytes, 0, bytes1.length); System.arraycopy(bytes2, 0, bytes, bytes1.length, bytes2.length); - GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048, useDirectBuffers); + GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048); ByteBuffer buffer = ByteBuffer.wrap(bytes); ByteBuffer decoded = decoder.decode(buffer); assertEquals(data1, StandardCharsets.UTF_8.decode(decoded).toString()); @@ -285,9 +279,8 @@ public class GZIPContentDecoderTest decoder.release(decoded); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testBigBlock(boolean useDirectBuffers) throws Exception + @Test + public void testBigBlock() throws Exception { String data = "0123456789ABCDEF"; for (int i = 0; i < 10; ++i) @@ -301,7 +294,7 @@ public class GZIPContentDecoderTest byte[] bytes = baos.toByteArray(); String result = ""; - GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048, useDirectBuffers); + GZIPContentDecoder decoder = new GZIPContentDecoder(pool, 2048); ByteBuffer buffer = ByteBuffer.wrap(bytes); while (buffer.hasRemaining()) { @@ -381,8 +374,11 @@ public class GZIPContentDecoderTest // Signed Integer Max static final long INT_MAX = Integer.MAX_VALUE; + // Unsigned Integer Max == 2^32 + static final long UINT_MAX = 0xFFFFFFFFL; + @ParameterizedTest - @ValueSource(longs = {INT_MAX, INT_MAX + 1}) + @ValueSource(longs = {INT_MAX, INT_MAX + 1 /* TODO too slow , UINT_MAX, UINT_MAX + 1 */ }) public void testLargeGzipStream(long origSize) throws IOException { // Size chosen for trade off between speed of I/O vs speed of Gzip diff --git a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index 659a64aeaa9..4e282c12358 100644 --- a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -26,11 +26,13 @@ import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jetty.util.BufferUtil; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import static org.hamcrest.MatcherAssert.assertThat; @@ -47,10 +49,79 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class HttpFieldsTest { - @Test - public void testPut() + public static Stream mutables() { - HttpFields.Mutable header = HttpFields.build() + return Stream.of( + HttpFields.build(), + HttpFields.build(0), + new HttpFields.Mutable() + { + private final HttpFields.Mutable fields = HttpFields.build(); + + @Override + public ListIterator listIterator() + { + return fields.listIterator(); + } + + @Override + public String toString() + { + return "DefaultMutableMethods"; + } + }, + new HttpFields.Mutable() + { + private final HttpFields.Mutable fields = HttpFields.build(); + + @Override + public ListIterator listIterator() + { + return fields.listIterator(); + } + + @Override + public String toString() + { + return "DefaultMutableMethods"; + } + + @Override + public HttpFields asImmutable() + { + HttpFields f = fields.asImmutable(); + return new HttpFields() + { + @Override + public Iterator iterator() + { + return f.iterator(); + } + }; + } + + @Override + public HttpFields takeAsImmutable() + { + HttpFields f = fields.takeAsImmutable(); + return new HttpFields() + { + @Override + public Iterator iterator() + { + return f.iterator(); + } + }; + } + } + ); + } + + @ParameterizedTest + @MethodSource("mutables") + public void testPut(HttpFields.Mutable header) + { + header .put("name0", "value:0") .put("name1", "value1"); @@ -60,10 +131,8 @@ public class HttpFieldsTest assertNull(header.get("name2")); int matches = 0; - Enumeration e = header.getFieldNames(); - while (e.hasMoreElements()) + for (String o : header.getFieldNamesCollection()) { - Object o = e.nextElement(); if ("name0".equals(o)) matches++; if ("name1".equals(o)) @@ -71,16 +140,26 @@ public class HttpFieldsTest } assertEquals(2, matches); - e = header.getValues("name0"); - assertTrue(e.hasMoreElements()); - assertEquals(e.nextElement(), "value:0"); - assertFalse(e.hasMoreElements()); + Enumeration values = header.getValues("name0"); + assertTrue(values.hasMoreElements()); + assertEquals(values.nextElement(), "value:0"); + assertFalse(values.hasMoreElements()); + + header.add("name0", "extra0"); + header.add("name1", "extra1"); + header.put(new HttpField("name0", "ZERO")); + header.put(new HttpField("name1", "ONE")); + assertThat(header.stream().map(HttpField::getValue).collect(Collectors.toList()), Matchers.contains("ZERO", "ONE")); + + header.put("name0", (String)null); + assertThat(header.stream().map(HttpField::getValue).collect(Collectors.toList()), Matchers.contains("ONE")); } - @Test - public void testPutTo() + @ParameterizedTest + @MethodSource("mutables") + public void testPutTo(HttpFields.Mutable header) { - HttpFields.Mutable header = HttpFields.build() + header .put("name0", "value0") .put("name1", "value:A") .add("name1", "value:B") @@ -121,10 +200,11 @@ public class HttpFieldsTest assertThrows(NoSuchElementException.class, () -> header.getField(2)); } - @Test - public void testMutable() + @ParameterizedTest + @MethodSource("mutables") + public void testMutable(HttpFields.Mutable mutable) { - HttpFields headers = HttpFields.build() + HttpFields headers = mutable .add(HttpHeader.ETAG, "tag") .add("name0", "value0") .add("name1", "value1").asImmutable(); @@ -142,10 +222,28 @@ public class HttpFieldsTest assertThat(headers.getField(3).getName(), is("name2")); } + @Test + public void testTakeImmutable() + { + HttpFields.Mutable mutable = HttpFields.build(); + HttpFields immutable = mutable.takeAsImmutable(); + assertThat(immutable.get(HttpHeader.HOST), nullValue()); + assertThat(immutable.size(), is(0)); + + immutable = mutable + .put("name0", "value0") + .put("name1", "value1").takeAsImmutable(); + + assertThat(mutable.get("name0"), nullValue()); + assertThat(mutable.get("name1"), nullValue()); + assertThat(immutable.get("name0"), is("value0")); + assertThat(immutable.get("name1"), is("value1")); + } + @Test public void testMap() { - Map map = new HashMap<>(); + Map map = new HashMap<>(); map.put(HttpFields.build().add("X", "1").add(HttpHeader.ETAG, "tag").asImmutable(), "1"); map.put(HttpFields.build().add("X", "2").add(HttpHeader.ETAG, "other").asImmutable(), "2"); @@ -179,11 +277,10 @@ public class HttpFieldsTest assertThrows(NoSuchElementException.class, () -> header.getField(2)); } - @Test - public void testGetKnown() + @ParameterizedTest + @MethodSource("mutables") + public void testGetKnown(HttpFields.Mutable header) { - HttpFields.Mutable header = HttpFields.build(); - header.put("Connection", "value0"); header.put(HttpHeader.ACCEPT, "value1"); @@ -197,11 +294,10 @@ public class HttpFieldsTest assertNull(header.get(HttpHeader.AGE)); } - @Test - public void testCRLF() + @ParameterizedTest + @MethodSource("mutables") + public void testCRLF(HttpFields.Mutable header) { - HttpFields.Mutable header = HttpFields.build(); - header.put("name0", "value\r\n0"); header.put("name\r\n1", "value1"); header.put("name:2", "value:\r\n2"); @@ -216,11 +312,10 @@ public class HttpFieldsTest assertThat(out, containsString("name?2: value: 2")); } - @Test - public void testCachedPut() + @ParameterizedTest + @MethodSource("mutables") + public void testCachedPut(HttpFields.Mutable header) { - HttpFields.Mutable header = HttpFields.build(); - header.put("Connection", "Keep-Alive"); header.put("tRansfer-EncOding", "CHUNKED"); header.put("CONTENT-ENCODING", "gZIP"); @@ -236,11 +331,10 @@ public class HttpFieldsTest assertThat(out, Matchers.containsString((HttpHeader.CONTENT_ENCODING + ": " + HttpHeaderValue.GZIP).toLowerCase(Locale.ENGLISH))); } - @Test - public void testRePut() + @ParameterizedTest + @MethodSource("mutables") + public void testRePut(HttpFields.Mutable header) { - HttpFields.Mutable header = HttpFields.build(); - header.put("name0", "value0"); header.put("name1", "xxxxxx"); header.put("name2", "value2"); @@ -257,10 +351,8 @@ public class HttpFieldsTest assertNull(header.get("name3")); int matches = 0; - Enumeration e = header.getFieldNames(); - while (e.hasMoreElements()) + for (String o : header.getFieldNamesCollection()) { - String o = e.nextElement(); if ("name0".equals(o)) matches++; if ("name1".equals(o)) @@ -270,16 +362,17 @@ public class HttpFieldsTest } assertEquals(3, matches); - e = header.getValues("name1"); + Enumeration e = header.getValues("name1"); assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "value1"); assertFalse(e.hasMoreElements()); } - @Test - public void testRemove() + @ParameterizedTest + @MethodSource("mutables") + public void testRemove(HttpFields.Mutable header) { - HttpFields.Mutable header = HttpFields.build(1) + header .put("name0", "value0") .add(HttpHeader.CONTENT_TYPE, "text") .add("name1", "WRONG") @@ -305,10 +398,8 @@ public class HttpFieldsTest assertNull(header.get("name3")); int matches = 0; - Enumeration e = header.getFieldNames(); - while (e.hasMoreElements()) + for (String o : header.getFieldNamesCollection()) { - Object o = e.nextElement(); if ("name0".equals(o)) matches++; if ("name1".equals(o)) @@ -318,10 +409,56 @@ public class HttpFieldsTest } assertEquals(2, matches); - e = header.getValues("name1"); + Enumeration e = header.getValues("name1"); assertFalse(e.hasMoreElements()); } + /** + *

+ * Test where multiple headers arrive with the same name, but + * those values are interleaved with other headers names. + *

+ */ + @Test + public void testRemoveInterleaved() + { + HttpFields originalHeaders = HttpFields.build() + .put(new HostPortHttpField("local")) + .add("Accept", "images/jpeg") + .add("Accept-Encoding", "Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0") + .add("Accept", "text/plain") + .add("Accept-Charset", "iso-8859-5, unicode-1-1;q=0.8") + .add("Accept", "*/*") + .add("Connection", "closed"); + + assertEquals(7, originalHeaders.size(), "Size of Original fields"); + + assertEquals("Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0", originalHeaders.get(HttpHeader.ACCEPT_ENCODING)); + assertEquals("iso-8859-5, unicode-1-1;q=0.8", originalHeaders.get(HttpHeader.ACCEPT_CHARSET)); + assertEquals("images/jpeg", originalHeaders.get(HttpHeader.ACCEPT), "Should have only gotten the first value?"); + assertEquals("images/jpeg, text/plain, */*", String.join(", ", originalHeaders.getValuesList(HttpHeader.ACCEPT)), "Should have gotten all of the values"); + + HttpFields tookImmutable = originalHeaders.takeAsImmutable(); + + assertEquals(7, tookImmutable.size(), "Size of (took as) Immutable fields"); + + assertEquals("Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0", tookImmutable.get(HttpHeader.ACCEPT_ENCODING)); + assertEquals("iso-8859-5, unicode-1-1;q=0.8", tookImmutable.get(HttpHeader.ACCEPT_CHARSET)); + assertEquals("images/jpeg", tookImmutable.get(HttpHeader.ACCEPT), "Should have only gotten the first value?"); + assertEquals("images/jpeg, text/plain, */*", String.join(", ", tookImmutable.getValuesList(HttpHeader.ACCEPT)), "Should have gotten all of the values"); + + // Lets remove "Accept" headers in a copy of the headers + HttpFields.Mutable headersCopy = HttpFields.build(tookImmutable); + + assertEquals(7, headersCopy.size(), "Size of Mutable fields"); + + // Attempt to remove all the "Accept" headers. + headersCopy.remove("Accept"); + + assertEquals("Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0", headersCopy.get(HttpHeader.ACCEPT_ENCODING)); + assertNull(headersCopy.get(HttpHeader.ACCEPT)); + } + @Test public void testAdd() { @@ -343,10 +480,8 @@ public class HttpFieldsTest assertNull(fields.get("name3")); int matches = 0; - Enumeration e = fields.getFieldNames(); - while (e.hasMoreElements()) + for (String o : fields.getFieldNamesCollection()) { - Object o = e.nextElement(); if ("name0".equals(o)) matches++; if ("name1".equals(o)) @@ -356,7 +491,7 @@ public class HttpFieldsTest } assertEquals(3, matches); - e = fields.getValues("name1"); + Enumeration e = fields.getValues("name1"); assertTrue(e.hasMoreElements()); assertEquals(e.nextElement(), "valueA"); assertTrue(e.hasMoreElements()); @@ -636,11 +771,10 @@ public class HttpFieldsTest assertEquals("Sun, 02 Dec 55 16:47:04 GMT", fields.get("Dancient")); } - @Test - public void testLongFields() + @ParameterizedTest + @MethodSource("mutables") + public void testLongFields(HttpFields.Mutable header) { - HttpFields.Mutable header = HttpFields.build(); - header.put("I1", "42"); header.put("I2", " 43 99"); header.put("I3", "-44"); @@ -665,11 +799,10 @@ public class HttpFieldsTest assertEquals("-47", header.get("I6")); } - @Test - public void testContains() + @ParameterizedTest + @MethodSource("mutables") + public void testContains(HttpFields.Mutable header) { - HttpFields.Mutable header = HttpFields.build(); - header.add("n0", ""); header.add("n1", ","); header.add("n2", ",,"); @@ -740,10 +873,10 @@ public class HttpFieldsTest @Test public void testAddHttpFields() { - HttpFields.Mutable fields = new HttpFields.Mutable(new HttpFields.Mutable()); + HttpFields.Mutable fields = HttpFields.build(); fields.add("One", "1"); - fields = new HttpFields.Mutable(fields); + fields = HttpFields.build(fields); fields.add(HttpFields.build().add("two", "2").add("three", "3")); fields.add(HttpFields.build().add("four", "4").add("five", "5").asImmutable()); @@ -823,10 +956,10 @@ public class HttpFieldsTest }); } - @Test - public void testIteration() + @ParameterizedTest + @MethodSource("mutables") + public void testIteration(HttpFields.Mutable header) { - HttpFields.Mutable header = HttpFields.build(); Iterator i = header.iterator(); assertThat(i.hasNext(), is(false)); @@ -951,7 +1084,7 @@ public class HttpFieldsTest // 1 existing cases fields.ensureField(new HttpField(HttpHeader.VARY, "one")); assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one")); -; + fields.ensureField(new HttpField(HttpHeader.VARY, "two")); assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Vary: one, two")); @@ -1019,7 +1152,7 @@ public class HttpFieldsTest // 1 existing cases fields.ensureField(new HttpField("Test", "one")); assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one")); - ; + fields.ensureField(new HttpField("Test", "two")); assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two")); @@ -1072,4 +1205,4 @@ public class HttpFieldsTest fields.ensureField(new HttpField("Test", "three, four")); assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two, three, four")); } -} \ No newline at end of file +} diff --git a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java index 75b369d2a83..fb81d27c13f 100644 --- a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +++ b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java @@ -949,17 +949,18 @@ public class HttpParserTest @Test public void testChunkParse() { - ByteBuffer buffer = BufferUtil.toBuffer( - "GET /chunk HTTP/1.0\r\n" + - "Header1: value1\r\n" + - "Transfer-Encoding: chunked\r\n" + - "\r\n" + - "a;\r\n" + - "0123456789\r\n" + - "1a\r\n" + - "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n" + - "0\r\n" + - "\r\n"); + ByteBuffer buffer = BufferUtil.toBuffer(""" + GET /chunk HTTP/1.0\r + Header1: value1\r + Transfer-Encoding: chunked\r + \r + a;\r + 0123456789\r + 1a\r + ABCDEFGHIJKLMNOPQRSTUVWXYZ\r + 0\r + \r + """); HttpParser.RequestHandler handler = new Handler(); HttpParser parser = new HttpParser(handler); parseAll(parser, buffer); @@ -977,19 +978,50 @@ public class HttpParserTest } @Test - public void testBadChunkParse() + public void testBadChunkLength() { - ByteBuffer buffer = BufferUtil.toBuffer( - "GET /chunk HTTP/1.0\r\n" + - "Header1: value1\r\n" + - "Transfer-Encoding: chunked, identity\r\n" + - "\r\n" + - "a;\r\n" + - "0123456789\r\n" + - "1a\r\n" + - "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n" + - "0\r\n" + - "\r\n"); + ByteBuffer buffer = BufferUtil.toBuffer(""" + GET /chunk HTTP/1.0\r + Header1: value1\r + Transfer-Encoding: chunked\r + \r + a;\r + 0123456789\r + xx\r + ABCDEFGHIJKLMNOPQRSTUVWXYZ\r + 0\r + \r + """); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser = new HttpParser(handler); + parseAll(parser, buffer); + + assertEquals("GET", _methodOrVersion); + assertEquals("/chunk", _uriOrStatus); + assertEquals("HTTP/1.0", _versionOrReason); + assertEquals(1, _headers); + assertEquals("Header1", _hdr[0]); + assertEquals("value1", _val[0]); + assertTrue(_headerCompleted); + assertTrue(_early); + assertFalse(_messageCompleted); + } + + @Test + public void testBadTransferEncoding() + { + ByteBuffer buffer = BufferUtil.toBuffer(""" + GET /chunk HTTP/1.0\r + Header1: value1\r + Transfer-Encoding: chunked, identity\r + \r + a;\r + 0123456789\r + 1a\r + ABCDEFGHIJKLMNOPQRSTUVWXYZ\r + 0\r + \r + """); HttpParser.RequestHandler handler = new Handler(); HttpParser parser = new HttpParser(handler); parseAll(parser, buffer); diff --git a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java index f3161dd0a4b..04fcae1800a 100644 --- a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java +++ b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpURITest.java @@ -57,7 +57,7 @@ public class HttpURITest assertThat(uri.getHost(), is("host")); assertThat(uri.getPort(), is(8888)); assertThat(uri.getPath(), is("/ignored/../p%61th;ignored/info;param")); - assertThat(uri.getDecodedPath(), is("/path/info")); + assertThat(uri.getCanonicalPath(), is("/path/info")); assertThat(uri.getParam(), is("param")); assertThat(uri.getQuery(), is("query=value")); assertThat(uri.getAuthority(), is("host:8888")); @@ -77,7 +77,7 @@ public class HttpURITest assertThat(uri.getHost(), is("[::1]")); assertThat(uri.getPort(), is(8080)); assertThat(uri.getPath(), is("/some%20encoded/evening;id=12345")); - assertThat(uri.getDecodedPath(), is("/some encoded/evening")); + assertThat(uri.getCanonicalPath(), is("/some encoded/evening")); assertThat(uri.getParam(), is("id=12345")); assertThat(uri.getQuery(), nullValue()); assertThat(uri.getAuthority(), is("[::1]:8080")); @@ -94,7 +94,7 @@ public class HttpURITest assertThat(uri.getHost(), is("host")); assertThat(uri.getPort(), is(8888)); assertThat(uri.getPath(), is("/ignored/../p%61th;ignored/info;param")); - assertThat(uri.getDecodedPath(), is("/path/info")); + assertThat(uri.getCanonicalPath(), is("/path/info")); assertThat(uri.getParam(), is("param")); assertThat(uri.getQuery(), is("query=value")); assertThat(uri.getFragment(), is("fragment")); @@ -163,6 +163,7 @@ public class HttpURITest uri = HttpURI.from("GET", "*"); assertThat(uri.getHost(), nullValue()); assertThat(uri.getPath(), is("*")); + assertThat(uri.toString(), is("*")); uri = HttpURI.from("GET", "/foo/bar"); assertThat(uri.getHost(), nullValue()); @@ -189,27 +190,27 @@ public class HttpURITest { HttpURI uri = HttpURI.from("/foo/bar"); assertEquals("/foo/bar", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); + assertEquals("/foo/bar", uri.getCanonicalPath()); assertNull(uri.getParam()); uri = HttpURI.from("/foo/bar;jsessionid=12345"); assertEquals("/foo/bar;jsessionid=12345", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); + assertEquals("/foo/bar", uri.getCanonicalPath()); assertEquals("jsessionid=12345", uri.getParam()); uri = HttpURI.from("/foo;abc=123/bar;jsessionid=12345"); assertEquals("/foo;abc=123/bar;jsessionid=12345", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); + assertEquals("/foo/bar", uri.getCanonicalPath()); assertEquals("jsessionid=12345", uri.getParam()); uri = HttpURI.from("/foo;abc=123/bar;jsessionid=12345?name=value"); assertEquals("/foo;abc=123/bar;jsessionid=12345", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); + assertEquals("/foo/bar", uri.getCanonicalPath()); assertEquals("jsessionid=12345", uri.getParam()); uri = HttpURI.from("/foo;abc=123/bar;jsessionid=12345#target"); assertEquals("/foo;abc=123/bar;jsessionid=12345", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); + assertEquals("/foo/bar", uri.getCanonicalPath()); assertEquals("jsessionid=12345", uri.getParam()); } @@ -220,49 +221,49 @@ public class HttpURITest HttpURI uri = builder.asImmutable(); assertEquals("/foo/bar", uri.toString()); assertEquals("/foo/bar", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); + assertEquals("/foo/bar", uri.getCanonicalPath()); uri = builder.scheme("http").asImmutable(); assertEquals("http:/foo/bar", uri.toString()); assertEquals("/foo/bar", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); + assertEquals("/foo/bar", uri.getCanonicalPath()); uri = builder.authority("host", 0).asImmutable(); assertEquals("http://host/foo/bar", uri.toString()); assertEquals("/foo/bar", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); + assertEquals("/foo/bar", uri.getCanonicalPath()); uri = builder.authority("host", 8888).asImmutable(); assertEquals("http://host:8888/foo/bar", uri.toString()); assertEquals("/foo/bar", uri.getPath()); - assertEquals("/foo/bar", uri.getDecodedPath()); + assertEquals("/foo/bar", uri.getCanonicalPath()); uri = builder.pathQuery("/f%30%30;p0/bar;p1;p2").asImmutable(); assertEquals("http://host:8888/f%30%30;p0/bar;p1;p2", uri.toString()); assertEquals("/f%30%30;p0/bar;p1;p2", uri.getPath()); - assertEquals("/f00/bar", uri.getDecodedPath()); - assertEquals("p2", uri.getParam()); + assertEquals("/f00/bar", uri.getCanonicalPath()); + assertEquals("p1;p2", uri.getParam()); assertNull(uri.getQuery()); uri = builder.pathQuery("/f%30%30;p0/bar;p1;p2?name=value").asImmutable(); assertEquals("http://host:8888/f%30%30;p0/bar;p1;p2?name=value", uri.toString()); assertEquals("/f%30%30;p0/bar;p1;p2", uri.getPath()); - assertEquals("/f00/bar", uri.getDecodedPath()); - assertEquals("p2", uri.getParam()); + assertEquals("/f00/bar", uri.getCanonicalPath()); + assertEquals("p1;p2", uri.getParam()); assertEquals("name=value", uri.getQuery()); uri = builder.pathQuery("/f%30%30;p0/bar;p1;p2").asImmutable(); assertEquals("http://host:8888/f%30%30;p0/bar;p1;p2", uri.toString()); assertEquals("/f%30%30;p0/bar;p1;p2", uri.getPath()); - assertEquals("/f00/bar", uri.getDecodedPath()); - assertEquals("p2", uri.getParam()); + assertEquals("/f00/bar", uri.getCanonicalPath()); + assertEquals("p1;p2", uri.getParam()); assertNull(uri.getQuery()); uri = builder.query("other=123456").asImmutable(); assertEquals("http://host:8888/f%30%30;p0/bar;p1;p2?other=123456", uri.toString()); assertEquals("/f%30%30;p0/bar;p1;p2", uri.getPath()); - assertEquals("/f00/bar", uri.getDecodedPath()); - assertEquals("p2", uri.getParam()); + assertEquals("/f00/bar", uri.getCanonicalPath()); + assertEquals("p1;p2", uri.getParam()); assertEquals("other=123456", uri.getQuery()); } @@ -299,34 +300,34 @@ public class HttpURITest public void testCanonicalDecoded() { HttpURI uri = HttpURI.from("/path/.info"); - assertEquals("/path/.info", uri.getDecodedPath()); + assertEquals("/path/.info", uri.getCanonicalPath()); uri = HttpURI.from("/path/./info"); - assertEquals("/path/info", uri.getDecodedPath()); + assertEquals("/path/info", uri.getCanonicalPath()); uri = HttpURI.from("/path/../info"); - assertEquals("/info", uri.getDecodedPath()); + assertEquals("/info", uri.getCanonicalPath()); uri = HttpURI.from("/./path/info."); - assertEquals("/path/info.", uri.getDecodedPath()); + assertEquals("/path/info.", uri.getCanonicalPath()); uri = HttpURI.from("./path/info/."); - assertEquals("path/info/", uri.getDecodedPath()); + assertEquals("path/info/", uri.getCanonicalPath()); uri = HttpURI.from("http://host/path/.info"); - assertEquals("/path/.info", uri.getDecodedPath()); + assertEquals("/path/.info", uri.getCanonicalPath()); uri = HttpURI.from("http://host/path/./info"); - assertEquals("/path/info", uri.getDecodedPath()); + assertEquals("/path/info", uri.getCanonicalPath()); uri = HttpURI.from("http://host/path/../info"); - assertEquals("/info", uri.getDecodedPath()); + assertEquals("/info", uri.getCanonicalPath()); uri = HttpURI.from("http://host/./path/info."); - assertEquals("/path/info.", uri.getDecodedPath()); + assertEquals("/path/info.", uri.getCanonicalPath()); uri = HttpURI.from("http:./path/info/."); - assertEquals("path/info/", uri.getDecodedPath()); + assertEquals("path/info/", uri.getCanonicalPath()); } public static Stream decodePathTests() @@ -334,132 +335,133 @@ public class HttpURITest return Arrays.stream(new Object[][] { // Simple path example - {"http://host/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, - {"//host/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, - {"/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"http://host/path/info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"//host/path/info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"/path/info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, // Scheme & host containing unusual valid characters - {"ht..tp://host/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, - {"ht1.2+..-3.4tp://127.0.0.1:8080/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, - {"http://h%2est/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, - {"http://h..est/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"ht..tp://host/path/info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"ht1.2+..-3.4tp://127.0.0.1:8080/path/info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"http://h%2est/path/info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"http://h..est/path/info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, // legal non ambiguous relative paths - {"http://host/../path/info", null, EnumSet.noneOf(Violation.class)}, - {"http://host/path/../info", "/info", EnumSet.noneOf(Violation.class)}, - {"http://host/path/./info", "/path/info", EnumSet.noneOf(Violation.class)}, - {"//host/path/../info", "/info", EnumSet.noneOf(Violation.class)}, - {"//host/path/./info", "/path/info", EnumSet.noneOf(Violation.class)}, - {"/path/../info", "/info", EnumSet.noneOf(Violation.class)}, - {"/path/./info", "/path/info", EnumSet.noneOf(Violation.class)}, - {"path/../info", "info", EnumSet.noneOf(Violation.class)}, - {"path/./info", "path/info", EnumSet.noneOf(Violation.class)}, + {"http://host/../path/info", null, null, EnumSet.noneOf(Violation.class)}, + {"http://host/path/../info", "/info", "/info", EnumSet.noneOf(Violation.class)}, + {"http://host/path/./info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"//host/path/../info", "/info", "/info", EnumSet.noneOf(Violation.class)}, + {"//host/path/./info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"/path/../info", "/info", "/info", EnumSet.noneOf(Violation.class)}, + {"/path/./info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"path/../info", "info", "info", EnumSet.noneOf(Violation.class)}, + {"path/./info", "path/info", "path/info", EnumSet.noneOf(Violation.class)}, // encoded paths - {"/f%6f%6F/bar", "/foo/bar", EnumSet.noneOf(Violation.class)}, - {"/f%u006f%u006F/bar", "/foo/bar", EnumSet.of(Violation.UTF16_ENCODINGS)}, - {"/f%u0001%u0001/bar", "/f\001\001/bar", EnumSet.of(Violation.UTF16_ENCODINGS)}, - {"/foo/%u20AC/bar", "/foo/\u20AC/bar", EnumSet.of(Violation.UTF16_ENCODINGS)}, + {"/f%6f%6F/bar", "/foo/bar", "/foo/bar", EnumSet.noneOf(Violation.class)}, + {"/f%u006f%u006F/bar", "/foo/bar", "/foo/bar", EnumSet.of(Violation.UTF16_ENCODINGS)}, + {"/f%u0001%u0001/bar", "/f%01%01/bar", "/f\001\001/bar", EnumSet.of(Violation.UTF16_ENCODINGS)}, + {"/foo/%u20AC/bar", "/foo/\u20AC/bar", "/foo/\u20AC/bar", EnumSet.of(Violation.UTF16_ENCODINGS)}, // illegal paths - {"//host/../path/info", null, EnumSet.noneOf(Violation.class)}, - {"/../path/info", null, EnumSet.noneOf(Violation.class)}, - {"../path/info", null, EnumSet.noneOf(Violation.class)}, - {"/path/%XX/info", null, EnumSet.noneOf(Violation.class)}, - {"/path/%2/F/info", null, EnumSet.noneOf(Violation.class)}, - {"/path/%/info", null, EnumSet.noneOf(Violation.class)}, - {"/path/%u000X/info", null, EnumSet.noneOf(Violation.class)}, - {"/path/Fo%u0000/info", null, EnumSet.noneOf(Violation.class)}, - {"/path/Fo%00/info", null, EnumSet.noneOf(Violation.class)}, - {"/path/Foo/info%u0000", null, EnumSet.noneOf(Violation.class)}, - {"/path/Foo/info%00", null, EnumSet.noneOf(Violation.class)}, - {"/path/%U20AC", null, EnumSet.noneOf(Violation.class)}, - {"%2e%2e/info", null, EnumSet.noneOf(Violation.class)}, - {"%u002e%u002e/info", null, EnumSet.noneOf(Violation.class)}, - {"%2e%2e;/info", null, EnumSet.noneOf(Violation.class)}, - {"%u002e%u002e;/info", null, EnumSet.noneOf(Violation.class)}, - {"%2e.", null, EnumSet.noneOf(Violation.class)}, - {"%u002e.", null, EnumSet.noneOf(Violation.class)}, - {".%2e", null, EnumSet.noneOf(Violation.class)}, - {".%u002e", null, EnumSet.noneOf(Violation.class)}, - {"%2e%2e", null, EnumSet.noneOf(Violation.class)}, - {"%u002e%u002e", null, EnumSet.noneOf(Violation.class)}, - {"%2e%u002e", null, EnumSet.noneOf(Violation.class)}, - {"%u002e%2e", null, EnumSet.noneOf(Violation.class)}, - {"..;/info", null, EnumSet.noneOf(Violation.class)}, - {"..;param/info", null, EnumSet.noneOf(Violation.class)}, + {"//host/../path/info", null, null, EnumSet.noneOf(Violation.class)}, + {"/../path/info", null, null, EnumSet.noneOf(Violation.class)}, + {"../path/info", null, null, EnumSet.noneOf(Violation.class)}, + {"/path/%XX/info", null, null, EnumSet.noneOf(Violation.class)}, + {"/path/%2/F/info", null, null, EnumSet.noneOf(Violation.class)}, + {"/path/%/info", null, null, EnumSet.noneOf(Violation.class)}, + {"/path/%u000X/info", null, null, EnumSet.noneOf(Violation.class)}, + {"/path/Fo%u0000/info", null, null, EnumSet.noneOf(Violation.class)}, + {"/path/Fo%00/info", null, null, EnumSet.noneOf(Violation.class)}, + {"/path/Foo/info%u0000", null, null, EnumSet.noneOf(Violation.class)}, + {"/path/Foo/info%00", null, null, EnumSet.noneOf(Violation.class)}, + {"/path/%U20AC", null, null, EnumSet.noneOf(Violation.class)}, + {"%2e%2e/info", null, null, EnumSet.noneOf(Violation.class)}, + {"%u002e%u002e/info", null, null, EnumSet.noneOf(Violation.class)}, + {"%2e%2e;/info", null, null, EnumSet.noneOf(Violation.class)}, + {"%u002e%u002e;/info", null, null, EnumSet.noneOf(Violation.class)}, + {"%2e.", null, null, EnumSet.noneOf(Violation.class)}, + {"%u002e.", null, null, EnumSet.noneOf(Violation.class)}, + {".%2e", null, null, EnumSet.noneOf(Violation.class)}, + {".%u002e", null, null, EnumSet.noneOf(Violation.class)}, + {"%2e%2e", null, null, EnumSet.noneOf(Violation.class)}, + {"%u002e%u002e", null, null, EnumSet.noneOf(Violation.class)}, + {"%2e%u002e", null, null, EnumSet.noneOf(Violation.class)}, + {"%u002e%2e", null, null, EnumSet.noneOf(Violation.class)}, + {"..;/info", null, null, EnumSet.noneOf(Violation.class)}, + {"..;param/info", null, null, EnumSet.noneOf(Violation.class)}, // ambiguous dot encodings - {"scheme://host/path/%2e/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"scheme:/path/%2e/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"/path/%2e/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"path/%2e/info/", "path/info/", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"/path/%2e%2e/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"/path/%2e%2e;/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/%2e%2e;param/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/%2e%2e;param;other/info;other", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"%2e/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"%u002e/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.UTF16_ENCODINGS)}, + {"scheme://host/path/%2e/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"scheme:/path/%2e/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"/path/%2e/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"path/%2e/info/", "path/info/", "path/info/", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"/path/%2e%2e/info", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"/path/%2e%2e;/info", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/%2e%2e;param/info", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/%2e%2e;param;other/info;other", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"%2e/info", "info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"%u002e/info", "info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.UTF16_ENCODINGS)}, - {"%2e", "", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"%u002e", "", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.UTF16_ENCODINGS)}, + {"%2e", "", "", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"%u002e", "", "", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.UTF16_ENCODINGS)}, // empty segment treated as ambiguous - {"/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"/foo//../bar", "/foo/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"/foo///../../../bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"/foo/./../bar", "/bar", EnumSet.noneOf(Violation.class)}, - {"/foo//./bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"foo/bar", "foo/bar", EnumSet.noneOf(Violation.class)}, - {"foo;/bar", "foo/bar", EnumSet.noneOf(Violation.class)}, - {";/bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {";?n=v", "", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"?n=v", "", EnumSet.noneOf(Violation.class)}, - {"#n=v", "", EnumSet.noneOf(Violation.class)}, - {"", "", EnumSet.noneOf(Violation.class)}, - {"http:/foo", "/foo", EnumSet.noneOf(Violation.class)}, + {"/foo//bar", "/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/foo//../bar", "/foo/bar", "/foo/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/foo///../../../bar", "/bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/foo/./../bar", "/bar", "/bar", EnumSet.noneOf(Violation.class)}, + {"/foo//./bar", "/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"foo/bar", "foo/bar", "foo/bar", EnumSet.noneOf(Violation.class)}, + {"foo;/bar", "foo/bar", "foo/bar", EnumSet.noneOf(Violation.class)}, + {";/bar", "/bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {";?n=v", "", "", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"?n=v", "", "", EnumSet.noneOf(Violation.class)}, + {"#n=v", "", "", EnumSet.noneOf(Violation.class)}, + {"", "", "", EnumSet.noneOf(Violation.class)}, + {"http:/foo", "/foo", "/foo", EnumSet.noneOf(Violation.class)}, // ambiguous parameter inclusions - {"/path/.;/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/.;param/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/..;/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/..;param/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, - {".;/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, - {".;param/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/.;/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/.;param/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/..;/info", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/..;param/info", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {".;/info", "info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {".;param/info", "info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, // ambiguous segment separators - {"/path/%2f/info", "/path///info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, - {"%2f/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, - {"%2F/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, - {"/path/%2f../info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, + {"/path/%2f/info", "/path/%2F/info", "/path///info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, + {"%2f/info", "%2F/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, + {"%2F/info", "%2F/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, + {"/path/%2f../info", "/path/%2F../info", "/path//../info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, // ambiguous encoding - {"/path/%25/info", "/path/%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, - {"/path/%u0025/info", "/path/%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING, Violation.UTF16_ENCODINGS)}, - {"%25/info", "%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, - {"/path/%25../info", "/path/%../info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, - {"/path/%u0025../info", "/path/%../info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING, Violation.UTF16_ENCODINGS)}, + {"/path/%25/info", "/path/%25/info", "/path/%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, + {"/path/%u0025/info", "/path/%25/info", "/path/%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING, Violation.UTF16_ENCODINGS)}, + {"%25/info", "%25/info", "%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, + {"/path/%25../info", "/path/%25../info", "/path/%../info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, + {"/path/%u0025../info", "/path/%25../info", "/path/%../info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING, Violation.UTF16_ENCODINGS)}, // combinations - {"/path/%2f/..;/info", "/path//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/%u002f/..;/info", "/path//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.UTF16_ENCODINGS)}, - {"/path/%2f/..;/%2e/info", "/path//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"/path/%2f/..;/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/%u002f/..;/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.UTF16_ENCODINGS)}, + {"/path/%2f/..;/%2e/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.AMBIGUOUS_PATH_SEGMENT)}, // Non ascii characters // @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck - {"http://localhost:9000/x\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", "/x\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", EnumSet.noneOf(Violation.class)}, - {"http://localhost:9000/\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", "/\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", EnumSet.noneOf(Violation.class)}, + {"http://localhost:9000/x\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", "/x\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", "/x\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", EnumSet.noneOf(Violation.class)}, + {"http://localhost:9000/\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", "/\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", "/\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32\uD83C\uDF32", EnumSet.noneOf(Violation.class)}, // @checkstyle-enable-check : AvoidEscapedUnicodeCharactersCheck }).map(Arguments::of); } @ParameterizedTest @MethodSource("decodePathTests") - public void testDecodedPath(String input, String decodedPath, EnumSet expected) + public void testDecodedPath(String input, String canonicalPath, String decodedPath, EnumSet expected) { try { HttpURI uri = HttpURI.from(input); + assertThat(uri.getCanonicalPath(), is(canonicalPath)); assertThat(uri.getDecodedPath(), is(decodedPath)); EnumSet ambiguous = EnumSet.copyOf(expected); ambiguous.retainAll(EnumSet.complementOf(EnumSet.of(Violation.UTF16_ENCODINGS))); @@ -485,97 +487,97 @@ public class HttpURITest return Arrays.stream(new Object[][] { // Simple path example - {"/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"/path/info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, // legal non ambiguous relative paths - {"/path/../info", "/info", EnumSet.noneOf(Violation.class)}, - {"/path/./info", "/path/info", EnumSet.noneOf(Violation.class)}, - {"path/../info", "info", EnumSet.noneOf(Violation.class)}, - {"path/./info", "path/info", EnumSet.noneOf(Violation.class)}, + {"/path/../info", "/info", "/info", EnumSet.noneOf(Violation.class)}, + {"/path/./info", "/path/info", "/path/info", EnumSet.noneOf(Violation.class)}, + {"path/../info", "info", "info", EnumSet.noneOf(Violation.class)}, + {"path/./info", "path/info", "path/info", EnumSet.noneOf(Violation.class)}, // illegal paths - {"/../path/info", null, null}, - {"../path/info", null, null}, - {"/path/%XX/info", null, null}, - {"/path/%2/F/info", null, null}, - {"%2e%2e/info", null, null}, - {"%2e%2e;/info", null, null}, - {"%2e.", null, null}, - {".%2e", null, null}, - {"%2e%2e", null, null}, - {"..;/info", null, null}, - {"..;param/info", null, null}, + {"/../path/info", null, null, null}, + {"../path/info", null, null, null}, + {"/path/%XX/info", null, null, null}, + {"/path/%2/F/info", null, null, null}, + {"%2e%2e/info", null, null, null}, + {"%2e%2e;/info", null, null, null}, + {"%2e.", null, null, null}, + {".%2e", null, null, null}, + {"%2e%2e", null, null, null}, + {"..;/info", null, null, null}, + {"..;param/info", null, null, null}, // ambiguous dot encodings - {"/path/%2e/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"path/%2e/info/", "path/info/", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"/path/%2e%2e/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"/path/%2e%2e;/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/%2e%2e;param/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/%2e%2e;param;other/info;other", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"%2e/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"%2e", "", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"/path/%2e/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"path/%2e/info/", "path/info/", "path/info/", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"/path/%2e%2e/info", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"/path/%2e%2e;/info", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/%2e%2e;param/info", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/%2e%2e;param;other/info;other", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"%2e/info", "info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"%2e", "", "", EnumSet.of(Violation.AMBIGUOUS_PATH_SEGMENT)}, // empty segment treated as ambiguous - {"/", "/", EnumSet.noneOf(Violation.class)}, - {"/#", "/", EnumSet.noneOf(Violation.class)}, - {"/path", "/path", EnumSet.noneOf(Violation.class)}, - {"/path/", "/path/", EnumSet.noneOf(Violation.class)}, - {"//", "//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"/foo//", "/foo//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"//foo/bar", "//foo/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"/foo?bar", "/foo", EnumSet.noneOf(Violation.class)}, - {"/foo#bar", "/foo", EnumSet.noneOf(Violation.class)}, - {"/foo;bar", "/foo", EnumSet.noneOf(Violation.class)}, - {"/foo/?bar", "/foo/", EnumSet.noneOf(Violation.class)}, - {"/foo/#bar", "/foo/", EnumSet.noneOf(Violation.class)}, - {"/foo/;param", "/foo/", EnumSet.noneOf(Violation.class)}, - {"/foo/;param/bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"/foo//bar//", "/foo//bar//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"//foo//bar//", "//foo//bar//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"/foo//../bar", "/foo/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"/foo///../../../bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"/foo/./../bar", "/bar", EnumSet.noneOf(Violation.class)}, - {"/foo//./bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"foo/bar", "foo/bar", EnumSet.noneOf(Violation.class)}, - {"foo;/bar", "foo/bar", EnumSet.noneOf(Violation.class)}, - {";/bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {";?n=v", "", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, - {"?n=v", "", EnumSet.noneOf(Violation.class)}, - {"#n=v", "", EnumSet.noneOf(Violation.class)}, - {"", "", EnumSet.noneOf(Violation.class)}, + {"/", "/", "/", EnumSet.noneOf(Violation.class)}, + {"/#", "/", "/", EnumSet.noneOf(Violation.class)}, + {"/path", "/path", "/path", EnumSet.noneOf(Violation.class)}, + {"/path/", "/path/", "/path/", EnumSet.noneOf(Violation.class)}, + {"//", "//", "//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/foo//", "/foo//", "/foo//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/foo//bar", "/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"//foo/bar", "//foo/bar", "//foo/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/foo?bar", "/foo", "/foo", EnumSet.noneOf(Violation.class)}, + {"/foo#bar", "/foo", "/foo", EnumSet.noneOf(Violation.class)}, + {"/foo;bar", "/foo", "/foo", EnumSet.noneOf(Violation.class)}, + {"/foo/?bar", "/foo/", "/foo/", EnumSet.noneOf(Violation.class)}, + {"/foo/#bar", "/foo/", "/foo/", EnumSet.noneOf(Violation.class)}, + {"/foo/;param", "/foo/", "/foo/", EnumSet.noneOf(Violation.class)}, + {"/foo/;param/bar", "/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/foo//bar", "/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/foo//bar//", "/foo//bar//", "/foo//bar//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"//foo//bar//", "//foo//bar//", "//foo//bar//", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/foo//../bar", "/foo/bar", "/foo/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/foo///../../../bar", "/bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/foo/./../bar", "/bar", "/bar", EnumSet.noneOf(Violation.class)}, + {"/foo//./bar", "/foo//bar", "/foo//bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"foo/bar", "foo/bar", "foo/bar", EnumSet.noneOf(Violation.class)}, + {"foo;/bar", "foo/bar", "foo/bar", EnumSet.noneOf(Violation.class)}, + {";/bar", "/bar", "/bar", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {";?n=v", "", "", EnumSet.of(Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"?n=v", "", "", EnumSet.noneOf(Violation.class)}, + {"#n=v", "", "", EnumSet.noneOf(Violation.class)}, + {"", "", "", EnumSet.noneOf(Violation.class)}, // ambiguous parameter inclusions - {"/path/.;/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/.;param/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/..;/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/..;param/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, - {".;/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, - {".;param/info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/.;/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/.;param/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/..;/info", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/..;param/info", "/info", "/info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {".;/info", "info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, + {".;param/info", "info", "info", EnumSet.of(Violation.AMBIGUOUS_PATH_PARAMETER)}, // ambiguous segment separators - {"/path/%2f/info", "/path///info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, - {"%2f/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, - {"%2F/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, - {"/path/%2f../info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, + {"/path/%2f/info", "/path/%2F/info", "/path///info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, + {"%2f/info", "%2F/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, + {"%2F/info", "%2F/info", "//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, + {"/path/%2f../info", "/path/%2F../info", "/path//../info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR)}, // ambiguous encoding - {"/path/%25/info", "/path/%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, - {"%25/info", "%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, - {"/path/%25../info", "/path/%../info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, + {"/path/%25/info", "/path/%25/info", "/path/%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, + {"%25/info", "%25/info", "%/info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, + {"/path/%25../info", "/path/%25../info", "/path/%../info", EnumSet.of(Violation.AMBIGUOUS_PATH_ENCODING)}, // combinations - {"/path/%2f/..;/info", "/path//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER)}, - {"/path/%2f/..;/%2e/info", "/path//info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.AMBIGUOUS_PATH_SEGMENT)}, - {"/path/%2f/%25/..;/%2e//info", "/path////info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_ENCODING, Violation.AMBIGUOUS_EMPTY_SEGMENT)}, + {"/path/%2f/..;/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER)}, + {"/path/%2f/..;/%2e/info", "/path/info", "/path/info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.AMBIGUOUS_PATH_SEGMENT)}, + {"/path/%2f/%25/..;/%2e//info", "/path/%2F//info", "/path////info", EnumSet.of(Violation.AMBIGUOUS_PATH_SEPARATOR, Violation.AMBIGUOUS_PATH_PARAMETER, Violation.AMBIGUOUS_PATH_SEGMENT, Violation.AMBIGUOUS_PATH_ENCODING, Violation.AMBIGUOUS_EMPTY_SEGMENT)}, }).map(Arguments::of); } @ParameterizedTest @MethodSource("testPathQueryTests") - public void testPathQuery(String input, String decodedPath, EnumSet expected) + public void testPathQuery(String input, String canonicalPath, String decodedPath, EnumSet expected) { // If expected is null then it is a bad URI and should throw. if (expected == null) @@ -585,6 +587,7 @@ public class HttpURITest } HttpURI uri = HttpURI.build().pathQuery(input); + assertThat(uri.getCanonicalPath(), is(canonicalPath)); assertThat(uri.getDecodedPath(), is(decodedPath)); assertThat(uri.isAmbiguous(), is(!expected.isEmpty())); assertThat(uri.hasAmbiguousEmptySegment(), is(expected.contains(Violation.AMBIGUOUS_EMPTY_SEGMENT))); @@ -683,6 +686,9 @@ public class HttpURITest Arguments.of("/?x=y", null, null, null, "/", null, "x=y", null), Arguments.of("/?abc=test", null, null, null, "/", null, "abc=test", null), + // Encoded delimiters + Arguments.of("/path%2finfo%3fa=?query", null, null, null, "/path%2finfo%3fa=", null, "query", null), + // Simple path with fragment Arguments.of("/#fragment", null, null, null, "/", null, null, "fragment"), @@ -766,9 +772,8 @@ public class HttpURITest catch (URISyntaxException ignore) { // Ignore, as URI is invalid anyway + return; } - assumeTrue(javaUri != null, "Skipping, not a valid input URI: " + input); - HttpURI httpUri = HttpURI.from(javaUri); assertThat("[" + input + "] .scheme", httpUri.getScheme(), is(scheme)); @@ -824,4 +829,13 @@ public class HttpURITest HttpURI httpURI = HttpURI.build(input); assertThat("[" + input + "] .query", httpURI.getQuery(), is(expectedQuery)); } + + @Test + public void testKnownPort() + { + assertThat(HttpURI.from("http", "server", 80, "/path").toString(), is("http://server/path")); + assertThat(HttpURI.from("http", "server", 8888, "/path").toString(), is("http://server:8888/path")); + assertThat(HttpURI.from("https", "server", 443, "/path").toString(), is("https://server/path")); + assertThat(HttpURI.from("https", "server", 8443, "/path").toString(), is("https://server:8443/path")); + } } diff --git a/jetty-core/jetty-http2/http2-client/pom.xml b/jetty-core/jetty-http2/http2-client/pom.xml index a68d762aa69..a111b59d912 100644 --- a/jetty-core/jetty-http2/http2-client/pom.xml +++ b/jetty-core/jetty-http2/http2-client/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.http2 - http2-parent - 11.0.10-SNAPSHOT + http2 + 12.0.0-SNAPSHOT 4.0.0 http2-client - Jetty :: HTTP2 :: Client + Jetty Core :: HTTP2 :: Client ${project.groupId}.client @@ -41,42 +41,6 @@ org.slf4j slf4j-api
- - - org.eclipse.jetty - jetty-slf4j-impl - test - - - org.eclipse.jetty.toolchain - jetty-test-helper - test - - - org.eclipse.jetty - jetty-server - test - - - org.eclipse.jetty - jetty-servlet - test - - - org.eclipse.jetty - jetty-servlets - test - - - org.eclipse.jetty - jetty-proxy - test - - - org.eclipse.jetty.http2 - http2-server - test -
diff --git a/jetty-http2/http2-client/src/main/java/module-info.java b/jetty-core/jetty-http2/http2-client/src/main/java/module-info.java similarity index 100% rename from jetty-http2/http2-client/src/main/java/module-info.java rename to jetty-core/jetty-http2/http2-client/src/main/java/module-info.java diff --git a/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java b/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java index d8ac8861dd4..e024179e57e 100644 --- a/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java +++ b/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java @@ -19,14 +19,15 @@ import java.util.Map; import java.util.concurrent.Executor; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.HTTP2Connection; import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.client.internal.HTTP2ClientSession; import org.eclipse.jetty.http2.frames.PrefaceFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; -import org.eclipse.jetty.http2.generator.Generator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.HTTP2Connection; +import org.eclipse.jetty.http2.internal.generator.Generator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.Connection; diff --git a/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/internal/HTTP2ClientSession.java b/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/internal/HTTP2ClientSession.java index 31eb77a94a0..51da678a07f 100644 --- a/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/internal/HTTP2ClientSession.java +++ b/jetty-core/jetty-http2/http2-client/src/main/java/org/eclipse/jetty/http2/client/internal/HTTP2ClientSession.java @@ -11,19 +11,19 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.client.internal; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.CloseState; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; -import org.eclipse.jetty.http2.generator.Generator; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.generator.Generator; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.thread.Scheduler; diff --git a/jetty-core/jetty-http2/http2-common/pom.xml b/jetty-core/jetty-http2/http2-common/pom.xml index 7ca6a49f39e..0b71ab3b1ef 100644 --- a/jetty-core/jetty-http2/http2-common/pom.xml +++ b/jetty-core/jetty-http2/http2-common/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.http2 - http2-parent - 11.0.10-SNAPSHOT + http2 + 12.0.0-SNAPSHOT 4.0.0 http2-common - Jetty :: HTTP2 :: Common + Jetty Core :: HTTP2 :: Common ${project.groupId}.common diff --git a/jetty-http2/http2-common/src/main/java/module-info.java b/jetty-core/jetty-http2/http2-common/src/main/java/module-info.java similarity index 63% rename from jetty-http2/http2-common/src/main/java/module-info.java rename to jetty-core/jetty-http2/http2-common/src/main/java/module-info.java index 169acaf1e80..c033ff1bbe7 100644 --- a/jetty-http2/http2-common/src/main/java/module-info.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/module-info.java @@ -21,6 +21,8 @@ module org.eclipse.jetty.http2.common exports org.eclipse.jetty.http2.api; exports org.eclipse.jetty.http2.api.server; exports org.eclipse.jetty.http2.frames; - exports org.eclipse.jetty.http2.generator; - exports org.eclipse.jetty.http2.parser; + + exports org.eclipse.jetty.http2.internal to org.eclipse.jetty.http2.client, org.eclipse.jetty.http2.http.client.transport, org.eclipse.jetty.http2.server; + exports org.eclipse.jetty.http2.internal.generator to org.eclipse.jetty.http2.client, org.eclipse.jetty.http2.http.client.transport, org.eclipse.jetty.http2.server; + exports org.eclipse.jetty.http2.internal.parser to org.eclipse.jetty.http2.client, org.eclipse.jetty.http2.http.client.transport, org.eclipse.jetty.http2.server; } diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java index 63c474d5804..ccfbe1a3be3 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/AbstractFlowControlStrategy.java @@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory; @ManagedObject public abstract class AbstractFlowControlStrategy implements FlowControlStrategy, Dumpable { - protected static final Logger LOG = LoggerFactory.getLogger(FlowControlStrategy.class); + private static final Logger LOG = LoggerFactory.getLogger(AbstractFlowControlStrategy.class); private final AtomicLong sessionStall = new AtomicLong(); private final AtomicLong sessionStallTime = new AtomicLong(); diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java index c8d0c27bdb9..39d105af54e 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/BufferingFlowControlStrategy.java @@ -23,6 +23,8 @@ import org.eclipse.jetty.util.Atomics; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** *

A flow control strategy that accumulates updates and emits window control @@ -60,6 +62,8 @@ import org.eclipse.jetty.util.annotation.ManagedObject; @ManagedObject public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy { + private static final Logger LOG = LoggerFactory.getLogger(BufferingFlowControlStrategy.class); + private final AtomicInteger maxSessionRecvWindow = new AtomicInteger(DEFAULT_WINDOW_SIZE); private final AtomicInteger sessionLevel = new AtomicInteger(); private final Map streamLevels = new ConcurrentHashMap<>(); diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java index 96327c53a99..ead82f83a1b 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/IStream.java @@ -48,6 +48,8 @@ public interface IStream extends Stream, Attachable, Closeable */ public Listener getListener(); + public Data readData(); + /** * @param listener the {@link org.eclipse.jetty.http2.api.Stream.Listener} associated with this stream * @see #getListener() @@ -200,4 +202,26 @@ public interface IStream extends Stream, Attachable, Closeable return frames; } } + + public static final class Data + { + private final DataFrame frame; + private final Runnable complete; + + public Data(DataFrame frame, Runnable complete) + { + this.frame = frame; + this.complete = complete; + } + + public DataFrame frame() + { + return frame; + } + + public void complete() + { + complete.run(); + } + } } diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/RateControl.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/RateControl.java index 9af8824a5d0..010e3ad1c16 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/RateControl.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/RateControl.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2; import org.eclipse.jetty.io.EndPoint; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/SimpleFlowControlStrategy.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/SimpleFlowControlStrategy.java index 6383a605d16..576a14cc1ae 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/SimpleFlowControlStrategy.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/SimpleFlowControlStrategy.java @@ -19,9 +19,13 @@ import java.util.List; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.util.Callback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SimpleFlowControlStrategy extends AbstractFlowControlStrategy { + private static final Logger LOG = LoggerFactory.getLogger(SimpleFlowControlStrategy.class); + public SimpleFlowControlStrategy() { this(DEFAULT_WINDOW_SIZE); diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/WindowRateControl.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/WindowRateControl.java index 63bfc7ca174..deda5c6daae 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/WindowRateControl.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/WindowRateControl.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2; import java.time.Duration; import java.util.Queue; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/GoAwayFrame.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/GoAwayFrame.java index 6966a09848b..ed451aaff51 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/GoAwayFrame.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/GoAwayFrame.java @@ -15,7 +15,7 @@ package org.eclipse.jetty.http2.frames; import java.nio.charset.StandardCharsets; -import org.eclipse.jetty.http2.ErrorCode; +import org.eclipse.jetty.http2.internal.ErrorCode; public class GoAwayFrame extends Frame { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ResetFrame.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ResetFrame.java index 01b3f13fbc5..103e919b384 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ResetFrame.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/ResetFrame.java @@ -13,7 +13,7 @@ package org.eclipse.jetty.http2.frames; -import org.eclipse.jetty.http2.ErrorCode; +import org.eclipse.jetty.http2.internal.ErrorCode; public class ResetFrame extends Frame { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/ErrorCode.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/ErrorCode.java index feb7307e892..ec696a762bb 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/ErrorCode.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/ErrorCode.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2; +package org.eclipse.jetty.http2.internal; import java.util.HashMap; import java.util.Locale; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/Flags.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/Flags.java index 509662eafd0..225a7508bd6 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/Flags.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/Flags.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2; +package org.eclipse.jetty.http2.internal; public interface Flags { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Channel.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Channel.java index 6fbd70e2ea4..4abce8dafdb 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Channel.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Channel.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2; +package org.eclipse.jetty.http2.internal; import java.util.function.Consumer; @@ -51,10 +51,17 @@ public interface HTTP2Channel public Runnable onTrailer(HeadersFrame frame); + // TODO: review the signature because the serialization done by HttpChannel.onError() + // is now failing the callback which fails the HttpStream, which should decide whether + // to reset the HTTP/2 stream, so we may not need the boolean return type. public boolean onTimeout(Throwable failure, Consumer consumer); + // TODO: can it be simplified? The callback seems to only be succeeded, which + // means it can be converted into a Runnable which may just be the return type + // so we can get rid of the Callback parameter. public Runnable onFailure(Throwable failure, Callback callback); + // TODO: is this needed? public boolean isIdle(); } } diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Connection.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Connection.java index f52a0855471..9c46af3f63f 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Connection.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Connection.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2; +package org.eclipse.jetty.http2.internal; import java.io.IOException; import java.nio.ByteBuffer; @@ -20,8 +20,9 @@ import java.util.Queue; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicLong; +import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.frames.DataFrame; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Flusher.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Flusher.java index a09c69c0e72..66212b9f181 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Flusher.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Flusher.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2; +package org.eclipse.jetty.http2.internal; import java.io.IOException; import java.nio.ByteBuffer; @@ -25,6 +25,8 @@ import java.util.List; import java.util.Queue; import java.util.Set; +import org.eclipse.jetty.http2.FlowControlStrategy; +import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Session.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Session.java index 75da3d93327..2d5d464d25b 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Session.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Session.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2; +package org.eclipse.jetty.http2.internal; import java.io.IOException; import java.net.InetSocketAddress; @@ -38,6 +38,10 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.CloseState; +import org.eclipse.jetty.http2.FlowControlStrategy; +import org.eclipse.jetty.http2.ISession; +import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; @@ -54,9 +58,9 @@ import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.StreamFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; -import org.eclipse.jetty.http2.generator.Generator; import org.eclipse.jetty.http2.hpack.HpackException; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.generator.Generator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.CyclicTimeouts; import org.eclipse.jetty.io.EndPoint; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Stream.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Stream.java index 9188898a992..ed32f9066f4 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Stream.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Stream.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2; +package org.eclipse.jetty.http2.internal; import java.io.EOFException; import java.io.IOException; @@ -29,6 +29,9 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.CloseState; +import org.eclipse.jetty.http2.ISession; +import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.FailureFrame; @@ -350,6 +353,22 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts. return listener; } + @Override + public Data readData() + { + DataEntry dataEntry; + try (AutoLock l = lock.lock()) + { + if (dataQueue == null || dataQueue.isEmpty()) + return null; + dataEntry = dataQueue.poll(); + } + DataFrame frame = dataEntry.frame; + if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED)) + session.removeStream(this); + return new Data(frame, () -> dataEntry.callback.succeeded()); + } + @Override public void setListener(Listener listener) { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2StreamEndPoint.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2StreamEndPoint.java index 256c54adb2c..1b43a8dc3a8 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2StreamEndPoint.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2StreamEndPoint.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2; +package org.eclipse.jetty.http2.internal; import java.io.IOException; import java.net.InetSocketAddress; @@ -25,6 +25,7 @@ import java.util.Deque; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/DataGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/DataGenerator.java index 718d277854b..5de967599a5 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/DataGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/DataGenerator.java @@ -11,14 +11,14 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/FrameGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/FrameGenerator.java index 05ce1dc7020..0c0b6bfe4b1 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/FrameGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/FrameGenerator.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/Generator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/Generator.java index e8411820263..aa7d3928125 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/Generator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/Generator.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.Frame; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/GoAwayGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/GoAwayGenerator.java index f22e87ccf83..8468d29db22 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/GoAwayGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/GoAwayGenerator.java @@ -11,15 +11,15 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; import java.util.Arrays; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.GoAwayFrame; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/HeaderGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/HeaderGenerator.java index 83fc73c2436..1d1592c19e9 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/HeaderGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/HeaderGenerator.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/HeadersGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/HeadersGenerator.java index b05ab946984..c1afa0a46ee 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/HeadersGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/HeadersGenerator.java @@ -11,18 +11,18 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PriorityFrame; import org.eclipse.jetty.http2.hpack.HpackEncoder; import org.eclipse.jetty.http2.hpack.HpackException; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/NoOpGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/NoOpGenerator.java index 08655c6374c..388bb582cbc 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/NoOpGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/NoOpGenerator.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.io.ByteBufferPool; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PingGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PingGenerator.java index 0fb8e445aee..fd12c31433d 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PingGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PingGenerator.java @@ -11,14 +11,14 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.PingFrame; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PrefaceGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PrefaceGenerator.java index 9ccf8eb8b54..b2cddd59286 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PrefaceGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PrefaceGenerator.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PriorityGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PriorityGenerator.java index 42b260b7e95..8ddb988de0c 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PriorityGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PriorityGenerator.java @@ -11,14 +11,14 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.PriorityFrame; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PushPromiseGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PushPromiseGenerator.java index e1988da07b6..a100a5d173a 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PushPromiseGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/PushPromiseGenerator.java @@ -11,17 +11,17 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.hpack.HpackEncoder; import org.eclipse.jetty.http2.hpack.HpackException; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/ResetGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/ResetGenerator.java index 74a3351750d..ed55d23d5cd 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/ResetGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/ResetGenerator.java @@ -11,14 +11,14 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/SettingsGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/SettingsGenerator.java index 9658f0b6be5..e31fe3f5756 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/SettingsGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/SettingsGenerator.java @@ -11,15 +11,15 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; import java.util.Map; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/WindowUpdateGenerator.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/WindowUpdateGenerator.java index 6c47a70230d..960dcd20bb9 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/WindowUpdateGenerator.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/generator/WindowUpdateGenerator.java @@ -11,14 +11,14 @@ // ======================================================================== // -package org.eclipse.jetty.http2.generator; +package org.eclipse.jetty.http2.internal.generator; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/BodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/BodyParser.java index ad4ae9f2395..6b5cb318016 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/BodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/BodyParser.java @@ -11,12 +11,10 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; @@ -26,6 +24,8 @@ import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.util.BufferUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ContinuationBodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ContinuationBodyParser.java index 2e66b79557e..7ec64d869b7 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ContinuationBodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ContinuationBodyParser.java @@ -11,15 +11,15 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.ContinuationFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.Flags; public class ContinuationBodyParser extends BodyParser { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/DataBodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/DataBodyParser.java index c578360761c..c4c4b617a1f 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/DataBodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/DataBodyParser.java @@ -11,12 +11,12 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.util.BufferUtil; public class DataBodyParser extends BodyParser diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/GoAwayBodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/GoAwayBodyParser.java index c2d6cec0c63..51ef7d124da 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/GoAwayBodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/GoAwayBodyParser.java @@ -11,12 +11,12 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.frames.GoAwayFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; public class GoAwayBodyParser extends BodyParser { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderBlockFragments.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderBlockFragments.java index 9d0275084cf..8e4e892002c 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderBlockFragments.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderBlockFragments.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderBlockParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderBlockParser.java index 6cfcc87d557..8ab18235422 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderBlockParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderBlockParser.java @@ -11,15 +11,15 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.hpack.HpackDecoder; import org.eclipse.jetty.http2.hpack.HpackException; +import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.slf4j.Logger; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderParser.java index 3688e501a35..fcbca4ee3ac 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeaderParser.java @@ -11,10 +11,11 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; +import org.eclipse.jetty.http2.RateControl; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeadersBodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeadersBodyParser.java index d7423be2c50..3eba6b516b6 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeadersBodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/HeadersBodyParser.java @@ -11,16 +11,16 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PriorityFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.util.BufferUtil; public class HeadersBodyParser extends BodyParser diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/Parser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/Parser.java index b576d520cb1..e5fd3fd74a1 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/Parser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/Parser.java @@ -11,13 +11,12 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; import java.util.function.UnaryOperator; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.Flags; +import org.eclipse.jetty.http2.RateControl; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; @@ -30,6 +29,8 @@ import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; import org.eclipse.jetty.http2.hpack.HpackDecoder; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.io.ByteBufferPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PingBodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PingBodyParser.java index f7deac1abd9..854c0763217 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PingBodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PingBodyParser.java @@ -11,13 +11,13 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.PingFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.Flags; public class PingBodyParser extends BodyParser { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PrefaceParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PrefaceParser.java index eb7df4b1177..8869a223cd6 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PrefaceParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PrefaceParser.java @@ -11,12 +11,12 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.frames.PrefaceFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; import org.eclipse.jetty.util.BufferUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PriorityBodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PriorityBodyParser.java index 4a54388e237..81715d056b8 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PriorityBodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PriorityBodyParser.java @@ -11,12 +11,12 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.frames.PriorityFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; public class PriorityBodyParser extends BodyParser { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PushPromiseBodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PushPromiseBodyParser.java index 8704ee42a58..2031a679414 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PushPromiseBodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/PushPromiseBodyParser.java @@ -11,14 +11,14 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.frames.PushPromiseFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.Flags; public class PushPromiseBodyParser extends BodyParser { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ResetBodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ResetBodyParser.java index d0b2d9f8bf6..ad47dd762c9 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ResetBodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ResetBodyParser.java @@ -11,12 +11,12 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; public class ResetBodyParser extends BodyParser { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ServerParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ServerParser.java index 44dfacb71af..d7dbfa98095 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ServerParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/ServerParser.java @@ -11,13 +11,14 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.Flags; +import org.eclipse.jetty.http2.RateControl; import org.eclipse.jetty.http2.frames.FrameType; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.Flags; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.slf4j.Logger; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/SettingsBodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/SettingsBodyParser.java index 70a3711b99b..074201c4210 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/SettingsBodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/SettingsBodyParser.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; import java.util.Collections; @@ -19,10 +19,11 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.Flags; +import org.eclipse.jetty.http2.RateControl; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.Flags; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/UnknownBodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/UnknownBodyParser.java index dc61efe9b81..78096154b61 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/UnknownBodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/UnknownBodyParser.java @@ -11,12 +11,12 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.frames.UnknownFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; public class UnknownBodyParser extends BodyParser { diff --git a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/WindowUpdateBodyParser.java b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/WindowUpdateBodyParser.java index cc695a54635..82836765b59 100644 --- a/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/WindowUpdateBodyParser.java +++ b/jetty-core/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/internal/parser/WindowUpdateBodyParser.java @@ -11,12 +11,12 @@ // ======================================================================== // -package org.eclipse.jetty.http2.parser; +package org.eclipse.jetty.http2.internal.parser; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; public class WindowUpdateBodyParser extends BodyParser { diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java index b7a12cfe346..1c68695415c 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ContinuationParseTest.java @@ -24,11 +24,11 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.Flags; -import org.eclipse.jetty.http2.generator.HeaderGenerator; -import org.eclipse.jetty.http2.generator.HeadersGenerator; import org.eclipse.jetty.http2.hpack.HpackEncoder; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.Flags; +import org.eclipse.jetty.http2.internal.generator.HeaderGenerator; +import org.eclipse.jetty.http2.internal.generator.HeadersGenerator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java index 8a0c12fbaa7..23f17926dc6 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java @@ -19,9 +19,9 @@ import java.util.List; import java.util.Random; import java.util.function.UnaryOperator; -import org.eclipse.jetty.http2.generator.DataGenerator; -import org.eclipse.jetty.http2.generator.HeaderGenerator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.generator.DataGenerator; +import org.eclipse.jetty.http2.internal.generator.HeaderGenerator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.util.BufferUtil; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java index ef47307f3c1..b52e9e35e52 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/FrameFloodTest.java @@ -20,10 +20,10 @@ import java.util.function.UnaryOperator; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.Flags; +import org.eclipse.jetty.http2.WindowRateControl; import org.eclipse.jetty.http2.hpack.HpackEncoder; -import org.eclipse.jetty.http2.parser.Parser; -import org.eclipse.jetty.http2.parser.WindowRateControl; +import org.eclipse.jetty.http2.internal.Flags; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java index 09ad6fde5e7..df27cdc3417 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java @@ -19,9 +19,9 @@ import java.util.List; import java.util.Random; import java.util.function.UnaryOperator; -import org.eclipse.jetty.http2.generator.GoAwayGenerator; -import org.eclipse.jetty.http2.generator.HeaderGenerator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.generator.GoAwayGenerator; +import org.eclipse.jetty.http2.internal.generator.HeaderGenerator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java index 9c52afc35bc..90f89f05cd0 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java @@ -24,10 +24,10 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.generator.HeaderGenerator; -import org.eclipse.jetty.http2.generator.HeadersGenerator; import org.eclipse.jetty.http2.hpack.HpackEncoder; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.generator.HeaderGenerator; +import org.eclipse.jetty.http2.internal.generator.HeadersGenerator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersTooLargeParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersTooLargeParseTest.java index 365a5f88eb7..141176f4efd 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersTooLargeParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersTooLargeParseTest.java @@ -22,12 +22,12 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.generator.HeaderGenerator; -import org.eclipse.jetty.http2.generator.HeadersGenerator; import org.eclipse.jetty.http2.hpack.HpackEncoder; import org.eclipse.jetty.http2.hpack.HpackException; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.generator.HeaderGenerator; +import org.eclipse.jetty.http2.internal.generator.HeadersGenerator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/MaxFrameSizeParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/MaxFrameSizeParseTest.java index d95d7aac4c2..478a6f8e4f5 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/MaxFrameSizeParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/MaxFrameSizeParseTest.java @@ -17,8 +17,8 @@ import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.UnaryOperator; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java index b824d0c9013..aeaf2552a09 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java @@ -19,9 +19,9 @@ import java.util.List; import java.util.Random; import java.util.function.UnaryOperator; -import org.eclipse.jetty.http2.generator.HeaderGenerator; -import org.eclipse.jetty.http2.generator.PingGenerator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.generator.HeaderGenerator; +import org.eclipse.jetty.http2.internal.generator.PingGenerator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java index da00c2eb659..8d048fbaa00 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java @@ -18,9 +18,9 @@ import java.util.ArrayList; import java.util.List; import java.util.function.UnaryOperator; -import org.eclipse.jetty.http2.generator.HeaderGenerator; -import org.eclipse.jetty.http2.generator.PriorityGenerator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.generator.HeaderGenerator; +import org.eclipse.jetty.http2.internal.generator.PriorityGenerator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java index f33afafa6cc..812d4ff2f89 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java @@ -24,10 +24,10 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.generator.HeaderGenerator; -import org.eclipse.jetty.http2.generator.PushPromiseGenerator; import org.eclipse.jetty.http2.hpack.HpackEncoder; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.generator.HeaderGenerator; +import org.eclipse.jetty.http2.internal.generator.PushPromiseGenerator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java index c04b17b2874..5bbbb44c109 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java @@ -18,9 +18,9 @@ import java.util.ArrayList; import java.util.List; import java.util.function.UnaryOperator; -import org.eclipse.jetty.http2.generator.HeaderGenerator; -import org.eclipse.jetty.http2.generator.ResetGenerator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.generator.HeaderGenerator; +import org.eclipse.jetty.http2.internal.generator.ResetGenerator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java index 3099ba45d76..a5689245051 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java @@ -22,10 +22,10 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.UnaryOperator; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.generator.HeaderGenerator; -import org.eclipse.jetty.http2.generator.SettingsGenerator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.generator.HeaderGenerator; +import org.eclipse.jetty.http2.internal.generator.SettingsGenerator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/UnknownParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/UnknownParseTest.java index b670fffb2be..bd55440be64 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/UnknownParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/UnknownParseTest.java @@ -19,8 +19,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.UnaryOperator; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java index 3ed7846bba7..f87496558c9 100644 --- a/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java +++ b/jetty-core/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java @@ -18,9 +18,9 @@ import java.util.ArrayList; import java.util.List; import java.util.function.UnaryOperator; -import org.eclipse.jetty.http2.generator.HeaderGenerator; -import org.eclipse.jetty.http2.generator.WindowUpdateGenerator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.generator.HeaderGenerator; +import org.eclipse.jetty.http2.internal.generator.WindowUpdateGenerator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.junit.jupiter.api.Test; diff --git a/jetty-http2/http2-common/src/test/resources/jetty-logging.properties b/jetty-core/jetty-http2/http2-common/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-http2/http2-common/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-http2/http2-common/src/test/resources/jetty-logging.properties diff --git a/jetty-core/jetty-http2/http2-hpack/pom.xml b/jetty-core/jetty-http2/http2-hpack/pom.xml index 423727f2c33..e378afd937f 100644 --- a/jetty-core/jetty-http2/http2-hpack/pom.xml +++ b/jetty-core/jetty-http2/http2-hpack/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.http2 - http2-parent - 11.0.10-SNAPSHOT + http2 + 12.0.0-SNAPSHOT 4.0.0 http2-hpack - Jetty :: HTTP2 :: HPACK + Jetty Core :: HTTP2 :: HPACK ${project.groupId}.hpack @@ -51,7 +51,7 @@ - org.eclipse.jetty.tests + org.eclipse.jetty jetty-http-tools test diff --git a/jetty-http2/http2-hpack/src/main/java/module-info.java b/jetty-core/jetty-http2/http2-hpack/src/main/java/module-info.java similarity index 94% rename from jetty-http2/http2-hpack/src/main/java/module-info.java rename to jetty-core/jetty-http2/http2-hpack/src/main/java/module-info.java index 0e6ba0a53f4..eeb5a8af136 100644 --- a/jetty-http2/http2-hpack/src/main/java/module-info.java +++ b/jetty-core/jetty-http2/http2-hpack/src/main/java/module-info.java @@ -18,6 +18,7 @@ module org.eclipse.jetty.http2.hpack requires transitive org.eclipse.jetty.http; exports org.eclipse.jetty.http2.hpack; + exports org.eclipse.jetty.http2.hpack.internal; provides org.eclipse.jetty.http.HttpFieldPreEncoder with org.eclipse.jetty.http2.hpack.HpackFieldPreEncoder; diff --git a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java index 0e86dc0325d..8f7543d4e0a 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java +++ b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java @@ -24,6 +24,9 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.http2.hpack.internal.Huffman; +import org.eclipse.jetty.http2.hpack.internal.NBitInteger; +import org.eclipse.jetty.http2.hpack.internal.StaticTableHttpField; import org.eclipse.jetty.util.Index; import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; @@ -39,7 +42,7 @@ import org.slf4j.LoggerFactory; */ public class HpackContext { - public static final Logger LOG = LoggerFactory.getLogger(HpackContext.class); + private static final Logger LOG = LoggerFactory.getLogger(HpackContext.class); private static final String EMPTY = ""; public static final String[][] STATIC_TABLE = { diff --git a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java index 3d056b7e4bb..712009e3ef4 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java +++ b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java @@ -20,6 +20,10 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpTokens; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.hpack.HpackContext.Entry; +import org.eclipse.jetty.http2.hpack.internal.AuthorityHttpField; +import org.eclipse.jetty.http2.hpack.internal.Huffman; +import org.eclipse.jetty.http2.hpack.internal.MetaDataBuilder; +import org.eclipse.jetty.http2.hpack.internal.NBitInteger; import org.eclipse.jetty.util.BufferUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,8 +34,8 @@ import org.slf4j.LoggerFactory; */ public class HpackDecoder { - public static final Logger LOG = LoggerFactory.getLogger(HpackDecoder.class); - public static final HttpField.LongValueHttpField CONTENT_LENGTH_0 = + private static final Logger LOG = LoggerFactory.getLogger(HpackDecoder.class); + private static final HttpField.LongValueHttpField CONTENT_LENGTH_0 = new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH, 0L); private final HpackContext _context; diff --git a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java index ca6cfdd364d..200c47a0263 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java +++ b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java @@ -31,6 +31,8 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http2.hpack.HpackContext.Entry; import org.eclipse.jetty.http2.hpack.HpackContext.StaticEntry; +import org.eclipse.jetty.http2.hpack.internal.Huffman; +import org.eclipse.jetty.http2.hpack.internal.NBitInteger; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; diff --git a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackException.java b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackException.java index bde36b21ed7..5a3abc7f4f7 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackException.java +++ b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackException.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.http2.hpack; -@SuppressWarnings("serial") public abstract class HpackException extends Exception { HpackException(String messageFormat, Object... args) @@ -30,7 +29,7 @@ public abstract class HpackException extends Exception */ public static class StreamException extends HpackException { - StreamException(String messageFormat, Object... args) + public StreamException(String messageFormat, Object... args) { super(messageFormat, args); } @@ -43,7 +42,7 @@ public abstract class HpackException extends Exception */ public static class SessionException extends HpackException { - SessionException(String messageFormat, Object... args) + public SessionException(String messageFormat, Object... args) { super(messageFormat, args); } diff --git a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java index 94ba15c5e6a..1fa9d484dc2 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java +++ b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackFieldPreEncoder.java @@ -18,6 +18,8 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.http.HttpFieldPreEncoder; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http2.hpack.internal.Huffman; +import org.eclipse.jetty.http2.hpack.internal.NBitInteger; import org.eclipse.jetty.util.BufferUtil; /** diff --git a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/AuthorityHttpField.java b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/AuthorityHttpField.java index 1261316042b..dfbf44030b6 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/AuthorityHttpField.java +++ b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/AuthorityHttpField.java @@ -11,14 +11,12 @@ // ======================================================================== // -package org.eclipse.jetty.http2.hpack; +package org.eclipse.jetty.http2.hpack.internal; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http2.hpack.HpackContext; -/** - * - */ public class AuthorityHttpField extends HostPortHttpField { public static final String AUTHORITY = HpackContext.STATIC_TABLE[1][0]; diff --git a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/Huffman.java b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/Huffman.java index 03a562b4f00..7af0afe2f18 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/Huffman.java +++ b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/Huffman.java @@ -11,10 +11,11 @@ // ======================================================================== // -package org.eclipse.jetty.http2.hpack; +package org.eclipse.jetty.http2.hpack.internal; import java.nio.ByteBuffer; +import org.eclipse.jetty.http2.hpack.HpackException; import org.eclipse.jetty.util.Utf8StringBuilder; public class Huffman diff --git a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/MetaDataBuilder.java b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/MetaDataBuilder.java index 9b8b123c805..88a699dd1fa 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/MetaDataBuilder.java +++ b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/MetaDataBuilder.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.hpack; +package org.eclipse.jetty.http2.hpack.internal; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpField; @@ -21,6 +21,7 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.hpack.HpackException; import org.eclipse.jetty.http2.hpack.HpackException.SessionException; public class MetaDataBuilder @@ -42,7 +43,7 @@ public class MetaDataBuilder /** * @param maxHeadersSize The maximum size of the headers, expressed as total name and value characters. */ - protected MetaDataBuilder(int maxHeadersSize) + public MetaDataBuilder(int maxHeadersSize) { _maxSize = maxHeadersSize; } @@ -79,9 +80,8 @@ public class MetaDataBuilder if (_size > _maxSize) throw new HpackException.SessionException("Header size %d > %d", _size, _maxSize); - if (field instanceof StaticTableHttpField) + if (field instanceof StaticTableHttpField staticField) { - StaticTableHttpField staticField = (StaticTableHttpField)field; switch (header) { case C_STATUS: @@ -196,7 +196,7 @@ public class MetaDataBuilder } } - protected void streamException(String messageFormat, Object... args) + public void streamException(String messageFormat, Object... args) { HpackException.StreamException stream = new HpackException.StreamException(messageFormat, args); if (_streamException == null) diff --git a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/NBitInteger.java b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/NBitInteger.java index 5dcef68da0c..b655b62f498 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/NBitInteger.java +++ b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/NBitInteger.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.hpack; +package org.eclipse.jetty.http2.hpack.internal; import java.nio.ByteBuffer; diff --git a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/StaticTableHttpField.java b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/StaticTableHttpField.java index 187c2b22567..c6a50ac5a7e 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/StaticTableHttpField.java +++ b/jetty-core/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/StaticTableHttpField.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.hpack; +package org.eclipse.jetty.http2.hpack.internal; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; diff --git a/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java b/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java index af956e5752a..6e128f399d9 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java +++ b/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java @@ -17,6 +17,8 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http2.hpack.HpackContext.Entry; +import org.eclipse.jetty.http2.hpack.internal.Huffman; +import org.eclipse.jetty.http2.hpack.internal.NBitInteger; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java b/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java index 17713ef347c..f7cca49bb4a 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java +++ b/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java @@ -23,7 +23,8 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.hpack.HpackException.CompressionException; import org.eclipse.jetty.http2.hpack.HpackException.SessionException; import org.eclipse.jetty.http2.hpack.HpackException.StreamException; -import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.http2.hpack.internal.MetaDataBuilder; +import org.eclipse.jetty.util.StringUtil; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; @@ -61,7 +62,7 @@ public class HpackDecoderTest // First request String encoded = "828684410f7777772e6578616d706c652e636f6d"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); MetaData.Request request = (MetaData.Request)decoder.decode(buffer); @@ -73,7 +74,7 @@ public class HpackDecoderTest // Second request encoded = "828684be58086e6f2d6361636865"; - buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); request = (MetaData.Request)decoder.decode(buffer); @@ -88,7 +89,7 @@ public class HpackDecoderTest // Third request encoded = "828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565"; - buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); request = (MetaData.Request)decoder.decode(buffer); @@ -109,7 +110,7 @@ public class HpackDecoderTest // First request String encoded = "828684418cf1e3c2e5f23a6ba0ab90f4ff"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); MetaData.Request request = (MetaData.Request)decoder.decode(buffer); @@ -121,7 +122,7 @@ public class HpackDecoderTest // Second request encoded = "828684be5886a8eb10649cbf"; - buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); request = (MetaData.Request)decoder.decode(buffer); @@ -142,7 +143,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "8682418cF1E3C2E5F23a6bA0Ab90F4Ff841f0822426173696320515778685a475270626a70766347567549484e6c633246745a513d3d"; - byte[] bytes = TypeUtil.fromHexString(encoded); + byte[] bytes = StringUtil.fromHexString(encoded); byte[] array = new byte[bytes.length + 1]; System.arraycopy(bytes, 0, array, 1, bytes.length); ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice(); @@ -165,7 +166,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "8286418cf1e3c2e5f23a6ba0ab90f4ff84"; - byte[] bytes = TypeUtil.fromHexString(encoded); + byte[] bytes = StringUtil.fromHexString(encoded); byte[] array = new byte[bytes.length + 1]; System.arraycopy(bytes, 0, array, 1, bytes.length); ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice(); @@ -184,7 +185,7 @@ public class HpackDecoderTest { // Response encoded by nghttpx String encoded = "886196C361Be940b6a65B6850400B8A00571972e080a62D1Bf5f87497cA589D34d1f9a0f0d0234327690Aa69D29aFcA954D3A5358980Ae112e0f7c880aE152A9A74a6bF3"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(4096, 8192); MetaData.Response response = (MetaData.Response)decoder.decode(buffer); @@ -203,7 +204,7 @@ public class HpackDecoderTest public void testResize() throws Exception { String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(4096, 8192); MetaData metaData = decoder.decode(buffer); assertThat(metaData.getFields().get(HttpHeader.HOST), is("localhost0")); @@ -225,7 +226,7 @@ public class HpackDecoderTest */ String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f20"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(4096, 8192); try { @@ -242,7 +243,7 @@ public class HpackDecoderTest public void testTooBigToIndex() throws Exception { String encoded = "3f610f17FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(128, 8192); MetaData metaData = decoder.decode(buffer); @@ -255,7 +256,7 @@ public class HpackDecoderTest public void testUnknownIndex() throws Exception { String encoded = "BE"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(128, 8192); @@ -445,7 +446,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "83" + "49509F"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); MetaData.Request request = (MetaData.Request)decoder.decode(buffer); @@ -463,7 +464,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "84" + "49509FFF"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("Bad termination")); } @@ -475,7 +476,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "83" + "495090"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("Incorrect padding")); @@ -488,7 +489,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "87" + "497FFFFFFF427F"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("EOS in content")); @@ -500,7 +501,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "81" + "FE"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("Bad termination")); @@ -512,7 +513,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "82868441" + "82" + "FFFE"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); CompressionException ex = assertThrows(CompressionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("Bad termination")); @@ -524,7 +525,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "00000130"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); SessionException ex = assertThrows(SessionException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("Header size 0")); } @@ -535,7 +536,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "00016800"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); MetaData metaData = decoder.decode(buffer); assertThat(metaData.getFields().size(), is(1)); assertThat(metaData.getFields().get("h"), is("")); @@ -547,7 +548,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "0001480130"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); StreamException ex = assertThrows(StreamException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("Uppercase header")); } @@ -558,7 +559,7 @@ public class HpackDecoderTest HpackDecoder decoder = new HpackDecoder(4096, 8192); String encoded = "0001200130"; - ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); StreamException ex = assertThrows(StreamException.class, () -> decoder.decode(buffer)); assertThat(ex.getMessage(), Matchers.containsString("Illegal header")); } diff --git a/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java b/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java index dc4bacea8a7..497b363228e 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java +++ b/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java @@ -18,8 +18,9 @@ import java.nio.ByteBuffer; import java.util.Locale; import java.util.stream.Stream; +import org.eclipse.jetty.http2.hpack.internal.Huffman; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.StringUtil; import org.hamcrest.Matchers; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -50,7 +51,7 @@ public class HuffmanTest @MethodSource("data") public void testDecode(String specSection, String hex, String expected) throws Exception { - byte[] encoded = TypeUtil.fromHexString(hex); + byte[] encoded = StringUtil.fromHexString(hex); String decoded = Huffman.decode(ByteBuffer.wrap(encoded)); assertEquals(expected, decoded, specSection); } @@ -63,7 +64,7 @@ public class HuffmanTest int pos = BufferUtil.flipToFill(buf); Huffman.encode(buf, expected); BufferUtil.flipToFlush(buf, pos); - String encoded = TypeUtil.toHexString(BufferUtil.toArray(buf)).toLowerCase(Locale.ENGLISH); + String encoded = StringUtil.toHexString(BufferUtil.toArray(buf)).toLowerCase(Locale.ENGLISH); assertEquals(hex, encoded, specSection); assertEquals(hex.length() / 2, Huffman.octetsNeeded(expected)); } diff --git a/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/NBitIntegerTest.java b/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/NBitIntegerTest.java index cc8f30221f2..21d45e4ca16 100644 --- a/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/NBitIntegerTest.java +++ b/jetty-core/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/NBitIntegerTest.java @@ -15,8 +15,9 @@ package org.eclipse.jetty.http2.hpack; import java.nio.ByteBuffer; +import org.eclipse.jetty.http2.hpack.internal.NBitInteger; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.StringUtil; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -80,7 +81,7 @@ public class NBitIntegerTest buf.put((byte)0x00); NBitInteger.encode(buf, n, i); BufferUtil.flipToFlush(buf, p); - String r = TypeUtil.toHexString(BufferUtil.toArray(buf)); + String r = StringUtil.toHexString(BufferUtil.toArray(buf)); assertEquals(expected, r); assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitInteger.octectsNeeded(n, i)); @@ -120,7 +121,7 @@ public class NBitIntegerTest public void testDecode(int n, int expected, String encoded) { - ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString(encoded)); buf.position(n == 8 ? 0 : 1); assertEquals(expected, NBitInteger.decode(buf, n)); } @@ -135,7 +136,7 @@ public class NBitIntegerTest NBitInteger.encode(buf, 5, 10); BufferUtil.flipToFlush(buf, p); - String r = TypeUtil.toHexString(BufferUtil.toArray(buf)); + String r = StringUtil.toHexString(BufferUtil.toArray(buf)); assertEquals("77Ea", r); } @@ -143,7 +144,7 @@ public class NBitIntegerTest @Test public void testDecodeExampleD11() { - ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("77EaFF")); + ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString("77EaFF")); buf.position(2); assertEquals(10, NBitInteger.decode(buf, 5)); @@ -159,7 +160,7 @@ public class NBitIntegerTest NBitInteger.encode(buf, 5, 1337); BufferUtil.flipToFlush(buf, p); - String r = TypeUtil.toHexString(BufferUtil.toArray(buf)); + String r = StringUtil.toHexString(BufferUtil.toArray(buf)); assertEquals("881f9a0a", r); } @@ -167,7 +168,7 @@ public class NBitIntegerTest @Test public void testDecodeExampleD12() { - ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("881f9a0aff")); + ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString("881f9a0aff")); buf.position(2); assertEquals(1337, NBitInteger.decode(buf, 5)); @@ -183,7 +184,7 @@ public class NBitIntegerTest NBitInteger.encode(buf, 8, 42); BufferUtil.flipToFlush(buf, p); - String r = TypeUtil.toHexString(BufferUtil.toArray(buf)); + String r = StringUtil.toHexString(BufferUtil.toArray(buf)); assertEquals("88Ff2a", r); } @@ -191,7 +192,7 @@ public class NBitIntegerTest @Test public void testDecodeExampleD13() { - ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("882aFf")); + ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString("882aFf")); buf.position(1); assertEquals(42, NBitInteger.decode(buf, 8)); diff --git a/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties b/jetty-core/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties diff --git a/jetty-core/jetty-http2/http2-http-client-transport/pom.xml b/jetty-core/jetty-http2/http2-http-client-transport/pom.xml index 177b018c63c..8c8088b7d46 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/pom.xml +++ b/jetty-core/jetty-http2/http2-http-client-transport/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.http2 - http2-parent - 11.0.10-SNAPSHOT + http2 + 12.0.0-SNAPSHOT 4.0.0 http2-http-client-transport - Jetty :: HTTP2 :: HTTP Client Transport + Jetty Core :: HTTP2 :: HTTP Client Transport ${project.groupId}.client.http @@ -31,37 +31,6 @@ org.slf4j slf4j-api - - - org.eclipse.jetty - jetty-alpn-java-server - test - - - org.eclipse.jetty - jetty-slf4j-impl - test - - - org.eclipse.jetty.toolchain - jetty-test-helper - test - - - org.eclipse.jetty - jetty-server - test - - - org.eclipse.jetty.http2 - http2-server - test - - - org.awaitility - awaitility - test - diff --git a/jetty-http2/http2-http-client-transport/src/main/java/module-info.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/module-info.java similarity index 100% rename from jetty-http2/http2-http-client-transport/src/main/java/module-info.java rename to jetty-core/jetty-http2/http2-http-client-transport/src/main/java/module-info.java diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java index 885382329c0..5a7957e7bed 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java @@ -25,6 +25,8 @@ import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; import org.eclipse.jetty.client.http.HttpClientConnectionFactory; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.HTTP2ClientConnectionFactory; +import org.eclipse.jetty.http2.client.http.internal.HTTPSessionListenerPromise; +import org.eclipse.jetty.http2.client.http.internal.HttpConnectionOverHTTP2; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.ssl.SslClientConnectionFactory; diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java index 97ebfc397ea..90f4e2de1e2 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java @@ -22,6 +22,7 @@ import java.util.Map; import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory; import org.eclipse.jetty.client.AbstractHttpClientTransport; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpConnection; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.MultiplexConnectionPool; @@ -32,6 +33,8 @@ import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.HTTP2ClientConnectionFactory; +import org.eclipse.jetty.http2.client.http.internal.HTTPSessionListenerPromise; +import org.eclipse.jetty.http2.client.http.internal.HttpConnectionOverHTTP2; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.EndPoint; @@ -165,12 +168,12 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport return factory.newConnection(endPoint, context); } - protected HttpConnectionOverHTTP2 newHttpConnection(HttpDestination destination, Session session) + protected HttpConnection newHttpConnection(HttpDestination destination, Session session) { return new HttpConnectionOverHTTP2(destination, session); } - protected void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame) + protected void onClose(HttpConnection connection, GoAwayFrame frame) { connection.close(); } @@ -185,11 +188,11 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport @Override protected HttpConnectionOverHTTP2 newHttpConnection(HttpDestination destination, Session session) { - return HttpClientTransportOverHTTP2.this.newHttpConnection(destination, session); + return (HttpConnectionOverHTTP2)HttpClientTransportOverHTTP2.this.newHttpConnection(destination, session); } @Override - void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame) + public void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame) { HttpClientTransportOverHTTP2.this.onClose(connection, frame); } diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/ClientHTTP2StreamEndPoint.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/ClientHTTP2StreamEndPoint.java index 394bcdebf55..1692df2a5d7 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/ClientHTTP2StreamEndPoint.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/ClientHTTP2StreamEndPoint.java @@ -11,12 +11,12 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.client.http.internal; -import org.eclipse.jetty.http2.HTTP2Channel; -import org.eclipse.jetty.http2.HTTP2StreamEndPoint; import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.internal.HTTP2Channel; +import org.eclipse.jetty.http2.internal.HTTP2StreamEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.util.Callback; import org.slf4j.Logger; diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HTTPSessionListenerPromise.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HTTPSessionListenerPromise.java index c00845cd7ca..1a906e9cbe5 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HTTPSessionListenerPromise.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HTTPSessionListenerPromise.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.client.http.internal; import java.nio.channels.ClosedChannelException; import java.util.Map; @@ -21,18 +21,18 @@ import java.util.concurrent.atomic.AtomicMarkableReference; import org.eclipse.jetty.client.HttpClientTransport; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.api.Connection; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.internal.HTTP2Session; import org.eclipse.jetty.util.Promise; -class HTTPSessionListenerPromise extends Session.Listener.Adapter implements Promise +public class HTTPSessionListenerPromise extends Session.Listener.Adapter implements Promise { private final AtomicMarkableReference connection = new AtomicMarkableReference<>(null, false); private final Map context; - HTTPSessionListenerPromise(Map context) + public HTTPSessionListenerPromise(Map context) { this.context = context; } @@ -91,7 +91,7 @@ class HTTPSessionListenerPromise extends Session.Listener.Adapter implements Pro onClose(connection, frame); } - void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame) + public void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame) { connection.close(); } diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpChannelOverHTTP2.java similarity index 97% rename from jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java rename to jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpChannelOverHTTP2.java index 38ca30b601d..9785262c536 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpChannelOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpChannelOverHTTP2.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.client.http.internal; import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpDestination; @@ -19,8 +19,6 @@ import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpReceiver; import org.eclipse.jetty.client.HttpSender; import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.HTTP2Channel; import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; @@ -28,6 +26,8 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Channel; import org.eclipse.jetty.util.Callback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpConnectionOverHTTP2.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpConnectionOverHTTP2.java index cdfd03c7545..d3945e7c818 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpConnectionOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpConnectionOverHTTP2.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.client.http.internal; import java.nio.channels.AsynchronousCloseException; import java.util.Iterator; @@ -36,11 +36,11 @@ import org.eclipse.jetty.client.SendFailure; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Session; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.thread.Sweeper; import org.slf4j.Logger; diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpReceiverOverHTTP2.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpReceiverOverHTTP2.java index b44b95efe96..e12c6817af3 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpReceiverOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpReceiverOverHTTP2.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.client.http.internal; import java.io.IOException; import java.nio.ByteBuffer; @@ -33,14 +33,14 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.HTTP2Channel; import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Channel; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; diff --git a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpSenderOverHTTP2.java b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpSenderOverHTTP2.java index e9c7325e18d..0f4adf2dc0f 100644 --- a/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpSenderOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/internal/HttpSenderOverHTTP2.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.client.http.internal; import java.nio.ByteBuffer; import java.util.function.Supplier; diff --git a/jetty-core/jetty-http2/http2-server/pom.xml b/jetty-core/jetty-http2/http2-server/pom.xml index be37c5d441c..1731587ca13 100644 --- a/jetty-core/jetty-http2/http2-server/pom.xml +++ b/jetty-core/jetty-http2/http2-server/pom.xml @@ -2,56 +2,19 @@ org.eclipse.jetty.http2 - http2-parent - 11.0.10-SNAPSHOT + http2 + 12.0.0-SNAPSHOT 4.0.0 http2-server - Jetty :: HTTP2 :: Server + Jetty Core :: HTTP2 :: Server ${project.groupId}.server 28888 - - - - maven-surefire-plugin - - - @{argLine} ${jetty.surefire.argLine} --add-reads org.eclipse.jetty.http2.server=jetty.servlet.api --add-modules jetty.servlet.api - - - - - org.mortbay.jetty - h2spec-maven-plugin - - org.eclipse.jetty.http2.server.H2SpecServer - ${skipTests} - org.eclipse.jetty.h2spec - true - ${project.build.directory}/h2spec-reports - true - - 3.5 - Sends invalid connection preface - - - - - h2spec - test - - h2spec - - - - - - - org.eclipse.jetty.http2 @@ -65,16 +28,6 @@ org.slf4j slf4j-api - - org.eclipse.jetty - jetty-servlet - test - - - org.eclipse.jetty - jetty-servlets - test - org.eclipse.jetty jetty-alpn-server @@ -85,40 +38,6 @@ jetty-slf4j-impl test - - org.eclipse.jetty.toolchain - jetty-test-helper - test - - - - run-spec-server - - - - org.codehaus.mojo - exec-maven-plugin - - - spec-server-run - test - - org.eclipse.jetty.http2.server.H2SpecServer - test - - ${manual.test.port} - - - - java - - - - - - - - diff --git a/jetty-core/jetty-http2/http2-server/src/main/config/etc/jetty-http2.xml b/jetty-core/jetty-http2/http2-server/src/main/config/etc/jetty-http2.xml index 78904fd4bba..bd0226dd58d 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/config/etc/jetty-http2.xml +++ b/jetty-core/jetty-http2/http2-server/src/main/config/etc/jetty-http2.xml @@ -11,7 +11,7 @@ - + diff --git a/jetty-core/jetty-http2/http2-server/src/main/config/etc/jetty-http2c.xml b/jetty-core/jetty-http2/http2-server/src/main/config/etc/jetty-http2c.xml index a13cf3970f6..3cd4a8c2082 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/config/etc/jetty-http2c.xml +++ b/jetty-core/jetty-http2/http2-server/src/main/config/etc/jetty-http2c.xml @@ -11,7 +11,7 @@ - + diff --git a/jetty-http2/http2-server/src/main/java/module-info.java b/jetty-core/jetty-http2/http2-server/src/main/java/module-info.java similarity index 100% rename from jetty-http2/http2-server/src/main/java/module-info.java rename to jetty-core/jetty-http2/http2-server/src/main/java/module-info.java diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java index ae180edf9e1..634fdec17c3 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java @@ -25,16 +25,18 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.http2.BufferingFlowControlStrategy; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.HTTP2Connection; import org.eclipse.jetty.http2.ISession; +import org.eclipse.jetty.http2.RateControl; +import org.eclipse.jetty.http2.WindowRateControl; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.http2.generator.Generator; -import org.eclipse.jetty.http2.parser.RateControl; -import org.eclipse.jetty.http2.parser.ServerParser; -import org.eclipse.jetty.http2.parser.WindowRateControl; +import org.eclipse.jetty.http2.internal.HTTP2Connection; +import org.eclipse.jetty.http2.internal.generator.Generator; +import org.eclipse.jetty.http2.internal.parser.ServerParser; +import org.eclipse.jetty.http2.server.internal.HTTP2ServerConnection; +import org.eclipse.jetty.http2.server.internal.HTTP2ServerSession; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.RetainableByteBufferPool; @@ -51,6 +53,15 @@ import org.eclipse.jetty.util.component.LifeCycle; @ManagedObject public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConnectionFactory { + private static boolean isProtocolSupported(String protocol) + { + return switch (protocol) + { + case "h2", "h2c" -> true; + default -> false; + }; + } + private final HTTP2SessionContainer sessionContainer = new HTTP2SessionContainer(); private final HttpConfiguration httpConfiguration; private int maxDynamicTableSize = 4096; @@ -77,7 +88,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne super(protocols); for (String p : protocols) { - if (!HTTP2ServerConnection.isSupportedProtocol(p)) + if (!isProtocolSupported(p)) throw new IllegalArgumentException("Unsupported HTTP2 Protocol variant: " + p); } addBean(sessionContainer); @@ -282,7 +293,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne RetainableByteBufferPool retainableByteBufferPool = RetainableByteBufferPool.findOrAdapt(connector, connector.getByteBufferPool()); - HTTP2Connection connection = new HTTP2ServerConnection(retainableByteBufferPool, connector.getExecutor(), + HTTP2Connection connection = new HTTP2ServerConnection(retainableByteBufferPool, connector, endPoint, httpConfiguration, parser, session, getInputBufferSize(), listener); connection.setUseInputDirectByteBuffers(isUseInputDirectByteBuffers()); connection.setUseOutputDirectByteBuffers(isUseOutputDirectByteBuffers()); @@ -292,7 +303,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne protected abstract ServerSessionListener newSessionListener(Connector connector, EndPoint endPoint); - protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener, RateControl rateControl) + private ServerParser newServerParser(Connector connector, ServerParser.Listener listener, RateControl rateControl) { return new ServerParser(connector.getByteBufferPool(), listener, getMaxDynamicTableSize(), getHttpConfiguration().getRequestHeaderSize(), rateControl); } diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java index 7c7d1e680cb..a53d3d1394a 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java @@ -16,6 +16,7 @@ package org.eclipse.jetty.http2.server; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.MetaData.Request; +import org.eclipse.jetty.http2.server.internal.HTTP2ServerConnection; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.server.ConnectionFactory; @@ -51,11 +52,6 @@ public class HTTP2CServerConnectionFactory extends HTTP2ServerConnectionFactory public HTTP2CServerConnectionFactory(@Name("config") HttpConfiguration httpConfiguration, @Name("protocols") String... protocols) { super(httpConfiguration, protocols); - for (String p : protocols) - { - if (!HTTP2ServerConnection.isSupportedProtocol(p)) - throw new IllegalArgumentException("Unsupported HTTP2 Protocol variant: " + p); - } } @Override diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java index 89260f9c15a..725794b5744 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.http2.server; import java.util.Map; import java.util.concurrent.TimeoutException; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.HTTP2Cipher; import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; @@ -27,6 +26,8 @@ import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.server.internal.HTTP2ServerConnection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.QuietException; @@ -56,7 +57,7 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF @Override protected ServerSessionListener newSessionListener(Connector connector, EndPoint endPoint) { - return new HTTPServerSessionListener(connector, endPoint); + return new HTTPServerSessionListener(endPoint); } @Override @@ -71,16 +72,14 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF protected class HTTPServerSessionListener extends ServerSessionListener.Adapter implements Stream.Listener { - private final Connector connector; private final EndPoint endPoint; - public HTTPServerSessionListener(Connector connector, EndPoint endPoint) + public HTTPServerSessionListener(EndPoint endPoint) { - this.connector = connector; this.endPoint = endPoint; } - protected HTTP2ServerConnection getConnection() + private HTTP2ServerConnection getConnection() { return (HTTP2ServerConnection)endPoint.getConnection(); } @@ -94,7 +93,7 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF @Override public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { - getConnection().onNewStream(connector, (IStream)stream, frame); + getConnection().onNewStream((IStream)stream, frame); return this; } diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java index d9f35cec776..019e67b4e3c 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java @@ -11,28 +11,24 @@ // ======================================================================== // -package org.eclipse.jetty.http2.server; +package org.eclipse.jetty.http2.server.internal; -import java.io.Closeable; import java.io.IOException; -import java.util.ArrayDeque; +import java.net.SocketAddress; import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.Objects; -import java.util.Queue; -import java.util.concurrent.Executor; +import java.util.Set; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData.Request; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.HTTP2Channel; -import org.eclipse.jetty.http2.HTTP2Connection; import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -40,57 +36,42 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PrefaceFrame; -import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.http2.parser.ServerParser; -import org.eclipse.jetty.http2.parser.SettingsBodyParser; +import org.eclipse.jetty.http2.internal.HTTP2Channel; +import org.eclipse.jetty.http2.internal.HTTP2Connection; +import org.eclipse.jetty.http2.internal.parser.ServerParser; +import org.eclipse.jetty.http2.internal.parser.SettingsBodyParser; +import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.RetainableByteBufferPool; +import org.eclipse.jetty.io.ssl.SslConnection; +import org.eclipse.jetty.server.ConnectionMetaData; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.TypeUtil; -import org.eclipse.jetty.util.thread.AutoLock; +import org.eclipse.jetty.util.HostPort; +import org.eclipse.jetty.util.StringUtil; -public class HTTP2ServerConnection extends HTTP2Connection +public class HTTP2ServerConnection extends HTTP2Connection implements ConnectionMetaData { - /** - * @param protocol An HTTP2 protocol variant - * @return True if the protocol version is supported - */ - public static boolean isSupportedProtocol(String protocol) - { - switch (protocol) - { - case "h2": - case "h2-17": - case "h2-16": - case "h2-15": - case "h2-14": - case "h2c": - case "h2c-17": - case "h2c-16": - case "h2c-15": - case "h2c-14": - return true; - default: - return false; - } - } - - private final AutoLock lock = new AutoLock(); - private final Queue channels = new ArrayDeque<>(); + private final HttpChannel.Factory httpChannelFactory = new HttpChannel.DefaultFactory(); + private final Attributes attributes = new Lazy(); private final List upgradeFrames = new ArrayList<>(); + private final Connector connector; private final ServerSessionListener listener; private final HttpConfiguration httpConfig; - private boolean recycleHttpChannels = true; + private final String id; - public HTTP2ServerConnection(RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endPoint, HttpConfiguration httpConfig, ServerParser parser, ISession session, int inputBufferSize, ServerSessionListener listener) + public HTTP2ServerConnection(RetainableByteBufferPool retainableByteBufferPool, Connector connector, EndPoint endPoint, HttpConfiguration httpConfig, ServerParser parser, ISession session, int inputBufferSize, ServerSessionListener listener) { - super(retainableByteBufferPool, executor, endPoint, parser, session, inputBufferSize); + super(retainableByteBufferPool, connector.getExecutor(), endPoint, parser, session, inputBufferSize); + this.connector = connector; this.listener = listener; this.httpConfig = httpConfig; + this.id = StringUtil.randomAlphaNumeric(16); } @Override @@ -99,16 +80,6 @@ public class HTTP2ServerConnection extends HTTP2Connection return (ServerParser)super.getParser(); } - public boolean isRecycleHttpChannels() - { - return recycleHttpChannels; - } - - public void setRecycleHttpChannels(boolean recycleHttpChannels) - { - this.recycleHttpChannels = recycleHttpChannels; - } - @Override public void onOpen() { @@ -134,12 +105,16 @@ public class HTTP2ServerConnection extends HTTP2Connection } } - public void onNewStream(Connector connector, IStream stream, HeadersFrame frame) + public void onNewStream(IStream stream, HeadersFrame frame) { if (LOG.isDebugEnabled()) LOG.debug("Processing {} on {}", frame, stream); - HttpChannelOverHTTP2 channel = provideHttpChannel(connector, stream); - Runnable task = channel.onRequest(frame); + + HttpChannel httpChannel = httpChannelFactory.newHttpChannel(this); + HttpStreamOverHTTP2 httpStream = new HttpStreamOverHTTP2(this, httpChannel, stream); + httpChannel.setHttpStream(httpStream); + stream.setAttachment(httpStream); + Runnable task = httpStream.onRequest(frame); if (task != null) offerTask(task, false); } @@ -148,6 +123,7 @@ public class HTTP2ServerConnection extends HTTP2Connection { if (LOG.isDebugEnabled()) LOG.debug("Processing {} on {}", frame, stream); + HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment(); if (channel != null) { @@ -227,14 +203,32 @@ public class HTTP2ServerConnection extends HTTP2Connection callback.succeeded(); } - public void push(Connector connector, IStream stream, MetaData.Request request) + public void push(IStream stream, MetaData.Request request) { if (LOG.isDebugEnabled()) LOG.debug("Processing push {} on {}", request, stream); - HttpChannelOverHTTP2 channel = provideHttpChannel(connector, stream); - Runnable task = channel.onPushRequest(request); + + HttpChannel httpChannel = httpChannelFactory.newHttpChannel(this); + HttpStreamOverHTTP2 httpStream = new HttpStreamOverHTTP2(this, httpChannel, stream); + httpChannel.setHttpStream(httpStream); + Runnable task = httpStream.onPushRequest(request); if (task != null) - offerTask(task, true); + offerTask(task, false); + } + +/* + private final AutoLock lock = new AutoLock(); + private final Queue channels = new ArrayDeque<>(); + private boolean recycleHttpChannels = true; + + public boolean isRecycleHttpChannels() + { + return recycleHttpChannels; + } + + public void setRecycleHttpChannels(boolean recycleHttpChannels) + { + this.recycleHttpChannels = recycleHttpChannels; } private HttpChannelOverHTTP2 provideHttpChannel(Connector connector, IStream stream) @@ -289,6 +283,7 @@ public class HTTP2ServerConnection extends HTTP2Connection return null; } } +*/ public boolean upgrade(Request request, HttpFields.Mutable responseFields) { @@ -305,7 +300,7 @@ public class HTTP2ServerConnection extends HTTP2Connection final byte[] settings = Base64.getUrlDecoder().decode(value == null ? "" : value); if (LOG.isDebugEnabled()) - LOG.debug("{} {}: {}", this, HttpHeader.HTTP2_SETTINGS, TypeUtil.toHexString(settings)); + LOG.debug("{} {}: {}", this, HttpHeader.HTTP2_SETTINGS, StringUtil.toHexString(settings)); SettingsFrame settingsFrame = SettingsBodyParser.parseBody(BufferUtil.toBuffer(settings)); if (settingsFrame == null) @@ -331,6 +326,7 @@ public class HTTP2ServerConnection extends HTTP2Connection return true; } +/* protected class ServerHttpChannelOverHTTP2 extends HttpChannelOverHTTP2 implements Closeable { public ServerHttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP2 transport) @@ -377,4 +373,101 @@ public class HTTP2ServerConnection extends HTTP2Connection consumeInput(); } } +*/ + + @Override + public String getId() + { + return id; + } + + @Override + public HttpConfiguration getHttpConfiguration() + { + return httpConfig; + } + + @Override + public HttpVersion getHttpVersion() + { + return HttpVersion.HTTP_2; + } + + @Override + public String getProtocol() + { + return getHttpVersion().asString(); + } + + @Override + public Connection getConnection() + { + return this; + } + + @Override + public Connector getConnector() + { + return connector; + } + + @Override + public boolean isPersistent() + { + return true; + } + + @Override + public boolean isSecure() + { + return getEndPoint() instanceof SslConnection.DecryptedEndPoint; + } + + @Override + public SocketAddress getRemoteSocketAddress() + { + return getEndPoint().getRemoteSocketAddress(); + } + + @Override + public SocketAddress getLocalSocketAddress() + { + return getEndPoint().getLocalSocketAddress(); + } + + @Override + public HostPort getServerAuthority() + { + return ConnectionMetaData.getServerAuthority(httpConfig, this); + } + + @Override + public Object getAttribute(String name) + { + return attributes.getAttribute(name); + } + + @Override + public Object setAttribute(String name, Object attribute) + { + return attributes.setAttribute(name, attribute); + } + + @Override + public Object removeAttribute(String name) + { + return attributes.removeAttribute(name); + } + + @Override + public Set getAttributeNameSet() + { + return attributes.getAttributeNameSet(); + } + + @Override + public void clearAttributes() + { + attributes.clearAttributes(); + } } diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerSession.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerSession.java index 7d499c5f35f..d0777ec7758 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerSession.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerSession.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.server; +package org.eclipse.jetty.http2.server.internal; import java.util.Collections; import java.util.List; @@ -19,9 +19,7 @@ import java.util.Map; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.CloseState; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; @@ -32,8 +30,10 @@ import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; -import org.eclipse.jetty.http2.generator.Generator; -import org.eclipse.jetty.http2.parser.ServerParser; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.generator.Generator; +import org.eclipse.jetty.http2.internal.parser.ServerParser; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.thread.Scheduler; diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpChannelOverHTTP2.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpChannelOverHTTP2.java new file mode 100644 index 00000000000..ea253848532 --- /dev/null +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpChannelOverHTTP2.java @@ -0,0 +1,760 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.http2.server.internal; + +public class HttpChannelOverHTTP2 +{ +// +// extends HttpChannel implements Closeable, WriteFlusher.Listener, HTTP2Channel.Server +//{ +// private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverHTTP2.class); +// private static final HttpField SERVER_VERSION = new PreEncodedHttpField(HttpHeader.SERVER, HttpConfiguration.SERVER_VERSION); +// private static final HttpField POWERED_BY = new PreEncodedHttpField(HttpHeader.X_POWERED_BY, HttpConfiguration.SERVER_VERSION); +// +// private boolean _expect100Continue; +// private boolean _delayedUntilContent; +// private boolean _useOutputDirectByteBuffers; +// private final ContentDemander _contentDemander; +// +// public HttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP2 transport) +// { +// super(connector, configuration, endPoint, transport); +// _contentDemander = new ContentDemander(); +// } +// +// protected IStream getStream() +// { +// return getHttpTransport().getStream(); +// } +// +// @Override +// public boolean isUseOutputDirectByteBuffers() +// { +// return _useOutputDirectByteBuffers; +// } +// +// public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) +// { +// _useOutputDirectByteBuffers = useOutputDirectByteBuffers; +// } +// +// @Override +// public boolean isExpecting100Continue() +// { +// return _expect100Continue; +// } +// +// @Override +// public void setIdleTimeout(long timeoutMs) +// { +// getStream().setIdleTimeout(timeoutMs); +// } +// +// @Override +// public long getIdleTimeout() +// { +// return getStream().getIdleTimeout(); +// } +// +// @Override +// public void onFlushed(long bytes) throws IOException +// { +// getResponse().getHttpOutput().onFlushed(bytes); +// } +// +// public Runnable onRequest(HeadersFrame frame) +// { +// try +// { +// MetaData.Request request = (MetaData.Request)frame.getMetaData(); +// HttpFields fields = request.getFields(); +// +// _expect100Continue = fields.contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()); +// +// HttpFields.Mutable response = getResponse().getHttpFields(); +// if (getHttpConfiguration().getSendServerVersion()) +// response.add(SERVER_VERSION); +// if (getHttpConfiguration().getSendXPoweredBy()) +// response.add(POWERED_BY); +// +// onRequest(request); +// +// boolean endStream = frame.isEndStream(); +// if (endStream) +// { +// onContentComplete(); +// onRequestComplete(); +// } +// +// boolean connect = request instanceof MetaData.ConnectRequest; +// _delayedUntilContent = getHttpConfiguration().isDelayDispatchUntilContent() && +// !endStream && !_expect100Continue && !connect; +// +// // Delay the demand of DATA frames for CONNECT with :protocol +// // or for normal requests expecting 100 continue. +// if (connect) +// { +// if (request.getProtocol() == null) +// _contentDemander.demand(false); +// } +// else +// { +// if (_delayedUntilContent) +// _contentDemander.demand(false); +// } +// +// if (LOG.isDebugEnabled()) +// { +// Stream stream = getStream(); +// LOG.debug("HTTP2 Request #{}/{}, delayed={}:{}{} {} {}{}{}", +// stream.getId(), Integer.toHexString(stream.getSession().hashCode()), +// _delayedUntilContent, System.lineSeparator(), +// request.getMethod(), request.getURI(), request.getHttpVersion(), +// System.lineSeparator(), fields); +// } +// +// return _delayedUntilContent ? null : this; +// } +// catch (BadMessageException x) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("onRequest", x); +// onBadMessage(x); +// return null; +// } +// catch (Throwable x) +// { +// onBadMessage(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, null, x)); +// return null; +// } +// } +// +// public Runnable onPushRequest(MetaData.Request request) +// { +// try +// { +// onRequest(request); +// getRequest().setAttribute("org.eclipse.jetty.pushed", Boolean.TRUE); +// onContentComplete(); +// onRequestComplete(); +// +// if (LOG.isDebugEnabled()) +// { +// Stream stream = getStream(); +// LOG.debug("HTTP2 PUSH Request #{}/{}:{}{} {} {}{}{}", +// stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(), +// request.getMethod(), request.getURI(), request.getHttpVersion(), +// System.lineSeparator(), request.getFields()); +// } +// +// return this; +// } +// catch (BadMessageException x) +// { +// onBadMessage(x); +// return null; +// } +// catch (Throwable x) +// { +// onBadMessage(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, null, x)); +// return null; +// } +// } +// +// @Override +// public HttpTransportOverHTTP2 getHttpTransport() +// { +// return (HttpTransportOverHTTP2)super.getHttpTransport(); +// } +// +// @Override +// public void recycle() +// { +// super.recycle(); +// getHttpTransport().recycle(); +// _expect100Continue = false; +// _delayedUntilContent = false; +// // The content demander must be the very last thing to be recycled +// // to make sure any pending demanding content gets cleared off. +// _contentDemander.recycle(); +// } +// +// @Override +// protected void commit(MetaData.Response info) +// { +// super.commit(info); +// if (LOG.isDebugEnabled()) +// { +// Stream stream = getStream(); +// LOG.debug("HTTP2 Commit Response #{}/{}:{}{} {} {}{}{}", +// stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(), info.getHttpVersion(), info.getStatus(), info.getReason(), +// System.lineSeparator(), info.getFields()); +// } +// } +// +// @Override +// public Runnable onData(DataFrame frame, Callback callback) +// { +// ByteBuffer buffer = frame.getData(); +// int length = buffer.remaining(); +// HttpInput.Content content = new HttpInput.Content(buffer) +// { +// @Override +// public boolean isEof() +// { +// return frame.isEndStream(); +// } +// +// @Override +// public void succeeded() +// { +// callback.succeeded(); +// } +// +// @Override +// public void failed(Throwable x) +// { +// callback.failed(x); +// } +// +// @Override +// public InvocationType getInvocationType() +// { +// return callback.getInvocationType(); +// } +// }; +// boolean needed = _contentDemander.onContent(content); +// boolean handle = onContent(content); +// +// boolean endStream = frame.isEndStream(); +// if (endStream) +// { +// boolean handleContent = onContentComplete(); +// // This will generate EOF -> must happen before onContentProducible. +// boolean handleRequest = onRequestComplete(); +// handle |= handleContent | handleRequest; +// } +// +// boolean woken = needed && getRequest().getHttpInput().onContentProducible(); +// handle |= woken; +// if (LOG.isDebugEnabled()) +// { +// Stream stream = getStream(); +// LOG.debug("HTTP2 Request #{}/{}: {} bytes of {} content, woken: {}, needed: {}, handle: {}", +// stream.getId(), +// Integer.toHexString(stream.getSession().hashCode()), +// length, +// endStream ? "last" : "some", +// woken, +// needed, +// handle); +// } +// +// boolean wasDelayed = _delayedUntilContent; +// _delayedUntilContent = false; +// return handle || wasDelayed ? this : null; +// } +// +// /** +// * Demanding content is a marker content that is used to remember that a demand was +// * registered into the stream. The {@code needed} flag indicates if the demand originated +// * from a call to {@link #produceContent()} when false or {@link #needContent()} +// * when true, as {@link HttpInput#onContentProducible()} must only be called +// * only when {@link #needContent()} was called. +// * Instances of this class must never escape the scope of this channel impl, +// * so {@link #produceContent()} must never return one. +// */ +// private static final class DemandingContent extends HttpInput.SpecialContent +// { +// private final boolean needed; +// +// private DemandingContent(boolean needed) +// { +// this.needed = needed; +// } +// } +// +// private static final HttpInput.Content EOF = new HttpInput.EofContent(); +// private static final HttpInput.Content DEMANDING_NEEDED = new DemandingContent(true); +// private static final HttpInput.Content DEMANDING_NOT_NEEDED = new DemandingContent(false); +// +// private class ContentDemander +// { +// private final AtomicReference _content = new AtomicReference<>(); +// +// public void recycle() +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("recycle {}", this); +// HttpInput.Content c = _content.getAndSet(null); +// if (c != null && !c.isSpecial()) +// throw new AssertionError("unconsumed content: " + c); +// } +// +// public HttpInput.Content poll() +// { +// while (true) +// { +// HttpInput.Content c = _content.get(); +// if (LOG.isDebugEnabled()) +// LOG.debug("poll, content = {}", c); +// if (c == null || c.isSpecial() || _content.compareAndSet(c, c.isEof() ? EOF : null)) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("returning current content"); +// return c; +// } +// } +// } +// +// public boolean demand(boolean needed) +// { +// while (true) +// { +// HttpInput.Content c = _content.get(); +// if (LOG.isDebugEnabled()) +// LOG.debug("demand({}), content = {}", needed, c); +// if (c instanceof DemandingContent) +// { +// if (needed && !((DemandingContent)c).needed) +// { +// if (!_content.compareAndSet(c, DEMANDING_NEEDED)) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("already demanding but switched needed flag to true"); +// continue; +// } +// } +// if (LOG.isDebugEnabled()) +// LOG.debug("already demanding, returning false"); +// return false; +// } +// if (c != null) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("content available, returning true"); +// return true; +// } +// if (_content.compareAndSet(null, needed ? DEMANDING_NEEDED : DEMANDING_NOT_NEEDED)) +// { +// IStream stream = getStream(); +// if (stream == null) +// { +// _content.set(null); +// if (LOG.isDebugEnabled()) +// LOG.debug("no content available, switched to demanding but stream is now null"); +// return false; +// } +// if (LOG.isDebugEnabled()) +// LOG.debug("no content available, demanding stream {}", stream); +// stream.demand(1); +// c = _content.get(); +// boolean hasContent = !(c instanceof DemandingContent) && c != null; +// if (LOG.isDebugEnabled()) +// LOG.debug("has content now? {}", hasContent); +// return hasContent; +// } +// } +// } +// +// public boolean onContent(HttpInput.Content content) +// { +// while (true) +// { +// HttpInput.Content c = _content.get(); +// if (LOG.isDebugEnabled()) +// LOG.debug("content delivered by stream: {}, current content: {}", content, c); +// if (c instanceof DemandingContent) +// { +// if (_content.compareAndSet(c, content)) +// { +// boolean needed = ((DemandingContent)c).needed; +// if (LOG.isDebugEnabled()) +// LOG.debug("replacing demand content with {} succeeded; returning {}", content, needed); +// return needed; +// } +// } +// else if (c == null) +// { +// if (!content.isSpecial()) +// { +// // This should never happen, consider as a bug. +// content.failed(new IllegalStateException("Non special content without demand : " + content)); +// return false; +// } +// if (_content.compareAndSet(null, content)) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("replacing null content with {} succeeded", content); +// return false; +// } +// } +// else if (c.isEof() && content.isEof() && content.isEmpty()) +// { +// content.succeeded(); +// return true; +// } +// else if (content.getError() != null) +// { +// if (c.getError() != null) +// { +// if (c.getError() != content.getError()) +// c.getError().addSuppressed(content.getError()); +// return true; +// } +// if (_content.compareAndSet(c, content)) +// { +// c.failed(content.getError()); +// if (LOG.isDebugEnabled()) +// LOG.debug("replacing current content with {} succeeded", content); +// return true; +// } +// } +// else if (c.getError() != null && content.remaining() == 0) +// { +// content.succeeded(); +// return true; +// } +// else +// { +// // This should never happen, consider as a bug. +// content.failed(new IllegalStateException("Cannot overwrite exiting content " + c + " with " + content)); +// return false; +// } +// } +// } +// +// public boolean onTimeout(Throwable failure) +// { +// while (true) +// { +// HttpInput.Content c = _content.get(); +// if (LOG.isDebugEnabled()) +// LOG.debug("onTimeout with current content: {} and failure = {}", c, failure); +// if (!(c instanceof DemandingContent)) +// return false; +// if (_content.compareAndSet(c, new HttpInput.ErrorContent(failure))) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("replacing current content with error succeeded"); +// return true; +// } +// } +// } +// +// public void eof() +// { +// while (true) +// { +// HttpInput.Content c = _content.get(); +// if (LOG.isDebugEnabled()) +// LOG.debug("eof with current content: {}", c); +// if (c instanceof DemandingContent) +// { +// if (_content.compareAndSet(c, EOF)) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("replacing current content with special EOF succeeded"); +// return; +// } +// } +// else if (c == null) +// { +// if (_content.compareAndSet(null, EOF)) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("replacing null content with special EOF succeeded"); +// return; +// } +// } +// else if (c.isEof()) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("current content already is EOF"); +// return; +// } +// else if (c.remaining() == 0) +// { +// if (_content.compareAndSet(c, EOF)) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("replacing current content with special EOF succeeded"); +// return; +// } +// } +// else +// { +// // EOF may arrive with HEADERS frame (e.g. a trailer) that is not flow controlled, so we need to wrap the existing content. +// // Covered by HttpTrailersTest.testRequestTrailersWithContent. +// HttpInput.Content content = new HttpInput.WrappingContent(c, true); +// if (_content.compareAndSet(c, content)) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("replacing current content with {} succeeded", content); +// return; +// } +// } +// } +// } +// +// public boolean failContent(Throwable failure) +// { +// while (true) +// { +// HttpInput.Content c = _content.get(); +// if (LOG.isDebugEnabled()) +// LOG.debug("failing current content {} with {} {}", c, failure, this); +// if (c == null) +// return false; +// if (c.isSpecial()) +// return c.isEof(); +// if (_content.compareAndSet(c, null)) +// { +// c.failed(failure); +// if (LOG.isDebugEnabled()) +// LOG.debug("replacing current content with null succeeded"); +// return false; +// } +// } +// } +// +// @Override +// public String toString() +// { +// return getClass().getSimpleName() + "@" + hashCode() + " _content=" + _content; +// } +// } +// +// @Override +// public boolean needContent() +// { +// boolean hasContent = _contentDemander.demand(true); +// if (LOG.isDebugEnabled()) +// LOG.debug("needContent has content? {}", hasContent); +// return hasContent; +// } +// +// @Override +// public HttpInput.Content produceContent() +// { +// HttpInput.Content content = null; +// if (_contentDemander.demand(false)) +// content = _contentDemander.poll(); +// if (LOG.isDebugEnabled()) +// LOG.debug("produceContent produced {}", content); +// return content; +// } +// +// @Override +// public boolean failAllContent(Throwable failure) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("failing all content with {} {}", failure, this); +// IStream stream = getStream(); +// boolean atEof = stream == null || stream.failAllData(failure); +// atEof |= _contentDemander.failContent(failure); +// if (LOG.isDebugEnabled()) +// LOG.debug("failed all content, reached EOF? {}", atEof); +// return atEof; +// } +// +// @Override +// public boolean failed(Throwable x) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("failed " + x); +// +// _contentDemander.onContent(new HttpInput.ErrorContent(x)); +// +// return getRequest().getHttpInput().onContentProducible(); +// } +// +// @Override +// protected boolean eof() +// { +// _contentDemander.eof(); +// return false; +// } +// +// @Override +// public Runnable onTrailer(HeadersFrame frame) +// { +// HttpFields trailers = frame.getMetaData().getFields(); +// if (trailers.size() > 0) +// onTrailers(trailers); +// +// if (LOG.isDebugEnabled()) +// { +// Stream stream = getStream(); +// LOG.debug("HTTP2 Request #{}/{}, trailers:{}{}", +// stream.getId(), Integer.toHexString(stream.getSession().hashCode()), +// System.lineSeparator(), trailers); +// } +// +// // This will generate EOF -> need to call onContentProducible. +// boolean handle = onRequestComplete(); +// boolean woken = getRequest().getHttpInput().onContentProducible(); +// handle |= woken; +// +// boolean wasDelayed = _delayedUntilContent; +// _delayedUntilContent = false; +// return handle || wasDelayed ? this : null; +// } +// +// @Override +// public boolean isIdle() +// { +// return getState().isIdle(); +// } +// +// @Override +// public boolean onTimeout(Throwable failure, Consumer consumer) +// { +// final boolean delayed = _delayedUntilContent; +// _delayedUntilContent = false; +// +// boolean reset = isIdle(); +// if (reset) +// consumeInput(); +// +// getHttpTransport().onStreamTimeout(failure); +// +// failure.addSuppressed(new Throwable("HttpInput idle timeout")); +// _contentDemander.onTimeout(failure); +// boolean needed = getRequest().getHttpInput().onContentProducible(); +// +// if (needed || delayed) +// { +// consumer.accept(this::handleWithContext); +// reset = false; +// } +// +// return reset; +// } +// +// @Override +// public Runnable onFailure(Throwable failure, Callback callback) +// { +// getHttpTransport().onStreamFailure(failure); +// boolean handle = failed(failure); +// consumeInput(); +// return new FailureTask(failure, callback, handle); +// } +// +// protected void consumeInput() +// { +// getRequest().getHttpInput().consumeAll(); +// } +// +// private void handleWithContext() +// { +// ContextHandler context = getState().getContextHandler(); +// if (context != null) +// context.handle(getRequest(), this); +// else +// handle(); +// } +// +// /** +// * If the associated response has the Expect header set to 100 Continue, +// * then accessing the input stream indicates that the handler/servlet +// * is ready for the request body and thus a 100 Continue response is sent. +// * +// * @throws IOException if the InputStream cannot be created +// */ +// @Override +// public void continue100(int available) throws IOException +// { +// // If the client is expecting 100 CONTINUE, then send it now. +// // TODO: consider using an AtomicBoolean ? +// if (isExpecting100Continue()) +// { +// _expect100Continue = false; +// +// // is content missing? +// if (available == 0) +// { +// if (getResponse().isCommitted()) +// throw new IOException("Committed before 100 Continues"); +// +// boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false); +// if (!committed) +// throw new IOException("Concurrent commit while trying to send 100-Continue"); +// } +// } +// } +// +// @Override +// public boolean isTunnellingSupported() +// { +// return true; +// } +// +// @Override +// public EndPoint getTunnellingEndPoint() +// { +// return new ServerHTTP2StreamEndPoint(getStream()); +// } +// +// @Override +// public void close() +// { +// abort(new IOException("Unexpected close")); +// } +// +// @Override +// public String toString() +// { +// IStream stream = getStream(); +// long streamId = stream == null ? -1 : stream.getId(); +// return String.format("%s#%d", super.toString(), streamId); +// } +// +// private class FailureTask implements Runnable +// { +// private final Throwable failure; +// private final Callback callback; +// private final boolean handle; +// +// public FailureTask(Throwable failure, Callback callback, boolean handle) +// { +// this.failure = failure; +// this.callback = callback; +// this.handle = handle; +// } +// +// @Override +// public void run() +// { +// try +// { +// if (handle) +// handleWithContext(); +// else if (getHttpConfiguration().isNotifyRemoteAsyncErrors()) +// getState().asyncError(failure); +// callback.succeeded(); +// } +// catch (Throwable x) +// { +// callback.failed(x); +// } +// } +// +// @Override +// public String toString() +// { +// return String.format("%s@%x[%s]", getClass().getName(), hashCode(), failure); +// } +// } +} diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java new file mode 100644 index 00000000000..3b2ea29d8d8 --- /dev/null +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java @@ -0,0 +1,562 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.http2.server.internal; + +import java.nio.ByteBuffer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.IStream; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.PushPromiseFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Channel; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpStream; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Promise; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server +{ + private static final Logger LOG = LoggerFactory.getLogger(HttpStreamOverHTTP2.class); + + private final HTTP2ServerConnection _connection; + private final HttpChannel _httpChannel; + private final IStream _stream; + private final long _nanoTimeStamp; + private Content _content; + private MetaData.Response _metaData; + private boolean committed; + private boolean _demand; + + public HttpStreamOverHTTP2(HTTP2ServerConnection connection, HttpChannel httpChannel, IStream stream) + { + _connection = connection; + _httpChannel = httpChannel; + _stream = stream; + _nanoTimeStamp = System.nanoTime(); + } + + @Override + public String getId() + { + return String.valueOf(_stream.getId()); + } + + @Override + public long getNanoTimeStamp() + { + return _nanoTimeStamp; + } + + public Runnable onRequest(HeadersFrame frame) + { + try + { + MetaData.Request request = (MetaData.Request)frame.getMetaData(); + + Runnable handler = _httpChannel.onRequest(request); + + if (frame.isEndStream()) + _content = Content.EOF; + + HttpFields fields = request.getFields(); + + // TODO: handle 100 continue. +// _expect100Continue = fields.contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()); + + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP2 request #{}/{}, {} {} {}{}{}", + _stream.getId(), Integer.toHexString(_stream.getSession().hashCode()), + request.getMethod(), request.getURI(), request.getHttpVersion(), + System.lineSeparator(), fields); + } + + return handler; + } + catch (BadMessageException x) + { + if (LOG.isDebugEnabled()) + LOG.debug("onRequest", x); + onBadMessage(x); + return null; + } + catch (Throwable x) + { + onBadMessage(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, null, x)); + return null; + } + } + + private void onBadMessage(BadMessageException x) + { + // TODO + } + + @Override + public Content readContent() + { + while (true) + { + Content content = _content; + _content = Content.next(content); + if (content != null) + return content; + + IStream.Data data = _stream.readData(); + if (data == null) + return null; + + _content = newContent(data.frame(), data::complete); + } + } + + @Override + public void demandContent() + { + if (!_demand) + { + _demand = true; + _stream.demand(1); + } + } + + @Override + public Runnable onData(DataFrame frame, Callback callback) + { + _demand = false; + _content = newContent(frame, callback::succeeded); + + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP2 Request #{}/{}: {} bytes of {} content", + _stream.getId(), + Integer.toHexString(_stream.getSession().hashCode()), + frame.remaining(), + frame.isEndStream() ? "last" : "some"); + } + + return _httpChannel.onContentAvailable(); + } + + private Content.Abstract newContent(DataFrame frame, Runnable complete) + { + return new Content.Abstract(false, frame.isEndStream()) + { + @Override + public ByteBuffer getByteBuffer() + { + return frame.getData(); + } + + @Override + public void release() + { + complete.run(); + } + }; + } + + @Override + public Runnable onTrailer(HeadersFrame frame) + { + HttpFields trailers = frame.getMetaData().getFields().asImmutable(); + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP2 Request #{}/{}, trailer:{}{}", + _stream.getId(), Integer.toHexString(_stream.getSession().hashCode()), + System.lineSeparator(), trailers); + } + _content = new Content.Trailers(trailers); + return _httpChannel.onContentAvailable(); + } + + @Override + public void prepareResponse(HttpFields.Mutable headers) + { + // Nothing to do here. + } + + @Override + public void send(MetaData.Request request, MetaData.Response response, boolean last, Callback callback, ByteBuffer... buffers) + { + if (buffers.length > 1) + throw new IllegalStateException(); + + ByteBuffer content = buffers.length == 0 ? BufferUtil.EMPTY_BUFFER : buffers[0]; + if (response != null) + sendHeaders(request, response, content, last, callback); + else + sendContent(request, content, last, callback); + } + + private void sendHeaders(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean last, Callback callback) + { + _metaData = response; + + HeadersFrame headersFrame; + DataFrame dataFrame = null; + HeadersFrame trailersFrame = null; + + boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod()); + boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest; + int status = response.getStatus(); + boolean interimResponse = status == HttpStatus.CONTINUE_100 || status == HttpStatus.PROCESSING_102; + int streamId = _stream.getId(); + if (interimResponse) + { + // Must not commit interim responses. + if (hasContent) + { + callback.failed(new IllegalStateException("Interim response cannot have content")); + return; + } + headersFrame = new HeadersFrame(streamId, _metaData, null, false); + } + else + { + committed = true; + if (last) + { + long realContentLength = BufferUtil.length(content); + long contentLength = response.getContentLength(); + if (contentLength < 0) + { + _metaData = new MetaData.Response( + response.getHttpVersion(), + response.getStatus(), + response.getReason(), + response.getFields(), + realContentLength, + response.getTrailerSupplier() + ); + } + else if (hasContent && contentLength != realContentLength) + { + callback.failed(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, String.format("Incorrect Content-Length %d!=%d", contentLength, realContentLength))); + return; + } + } + + if (hasContent) + { + headersFrame = new HeadersFrame(streamId, _metaData, null, false); + if (last) + { + HttpFields trailers = retrieveTrailers(); + if (trailers == null) + { + dataFrame = new DataFrame(streamId, content, true); + } + else + { + dataFrame = new DataFrame(streamId, content, false); + trailersFrame = new HeadersFrame(streamId, new MetaData(HttpVersion.HTTP_2, trailers), null, true); + } + } + else + { + dataFrame = new DataFrame(streamId, content, false); + } + } + else + { + if (last) + { + if (isTunnel(request, _metaData)) + { + headersFrame = new HeadersFrame(streamId, _metaData, null, false); + } + else + { + HttpFields trailers = retrieveTrailers(); + if (trailers == null) + { + headersFrame = new HeadersFrame(streamId, _metaData, null, true); + } + else + { + headersFrame = new HeadersFrame(streamId, _metaData, null, false); + trailersFrame = new HeadersFrame(streamId, new MetaData(HttpVersion.HTTP_2, trailers), null, true); + } + } + } + else + { + headersFrame = new HeadersFrame(streamId, _metaData, null, false); + } + } + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP2 Response #{}/{}:{}{} {}{}{}", + _stream.getId(), Integer.toHexString(_stream.getSession().hashCode()), + System.lineSeparator(), HttpVersion.HTTP_2, _metaData.getStatus(), + System.lineSeparator(), _metaData.getFields()); + } + _stream.send(new IStream.FrameList(headersFrame, dataFrame, trailersFrame), callback); + } + + private void sendContent(MetaData.Request request, ByteBuffer content, boolean last, Callback callback) + { + boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod()); + boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest; + if (hasContent || (last && !isTunnel(request, _metaData))) + { + if (last) + { + HttpFields trailers = retrieveTrailers(); + if (trailers == null) + { + sendDataFrame(content, true, true, callback); + } + else + { + if (hasContent) + { + SendTrailers sendTrailers = new SendTrailers(callback, trailers); + sendDataFrame(content, true, false, sendTrailers); + } + else + { + sendTrailersFrame(new MetaData(HttpVersion.HTTP_2, trailers), callback); + } + } + } + else + { + sendDataFrame(content, false, false, callback); + } + } + else + { + callback.succeeded(); + } + } + + private HttpFields retrieveTrailers() + { + Supplier supplier = _metaData.getTrailerSupplier(); + if (supplier == null) + return null; + HttpFields trailers = supplier.get(); + if (trailers == null) + return null; + return trailers.size() == 0 ? null : trailers; + } + + private boolean isTunnel(MetaData.Request request, MetaData.Response response) + { + return MetaData.isTunnel(request.getMethod(), response.getStatus()); + } + + @Override + public boolean isPushSupported() + { + return _stream.getSession().isPushEnabled(); + } + + @Override + public void push(MetaData.Request request) + { + if (!_stream.getSession().isPushEnabled()) + { + if (LOG.isDebugEnabled()) + LOG.debug("HTTP/2 push disabled for {}", request); + return; + } + + if (LOG.isDebugEnabled()) + LOG.debug("HTTP/2 push {}", request); + + _stream.push(new PushPromiseFrame(_stream.getId(), request), new Promise<>() + { + @Override + public void succeeded(Stream pushStream) + { + _connection.push((IStream)pushStream, request); + } + + @Override + public void failed(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("Could not HTTP/2 push {}", request, x); + } + }, new Stream.Listener.Adapter()); // TODO: handle reset from the client ? + } + + public Runnable onPushRequest(MetaData.Request request) + { + try + { + Runnable task = _httpChannel.onRequest(request); + _httpChannel.getRequest().setAttribute("org.eclipse.jetty.pushed", Boolean.TRUE); + + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP/2 push request #{}/{}:{}{} {} {}{}{}", + _stream.getId(), Integer.toHexString(_stream.getSession().hashCode()), System.lineSeparator(), + request.getMethod(), request.getURI(), request.getHttpVersion(), + System.lineSeparator(), request.getFields()); + } + + return task; + } + catch (BadMessageException x) + { + onBadMessage(x); + return null; + } + catch (Throwable x) + { + onBadMessage(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, null, x)); + return null; + } + } + + private void sendDataFrame(ByteBuffer content, boolean lastContent, boolean endStream, Callback callback) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP2 Response #{}/{}: {} content bytes{}", + _stream.getId(), Integer.toHexString(_stream.getSession().hashCode()), + content.remaining(), lastContent ? " (last chunk)" : ""); + } + DataFrame frame = new DataFrame(_stream.getId(), content, endStream); + _stream.data(frame, callback); + } + + private void sendTrailersFrame(MetaData metaData, Callback callback) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP2 Response #{}/{}: trailers", + _stream.getId(), Integer.toHexString(_stream.getSession().hashCode())); + } + + HeadersFrame frame = new HeadersFrame(_stream.getId(), metaData, null, true); + _stream.headers(frame, callback); + } + + @Override + public boolean isCommitted() + { + return committed; + } + + @Override + public boolean isComplete() + { + // TODO + return false; + } + + @Override + public void setUpgradeConnection(Connection connection) + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isIdle() + { + // TODO: is this necessary? + return false; + } + + @Override + public Connection upgrade() + { + // TODO + throw new UnsupportedOperationException(); + } + + @Override + public boolean onTimeout(Throwable failure, Consumer consumer) + { + Runnable runnable = _httpChannel.onFailure(failure); + if (runnable != null) + consumer.accept(runnable); + return !_httpChannel.isRequestHandled(); + } + + @Override + public Runnable onFailure(Throwable failure, Callback callback) + { + Runnable runnable = _httpChannel.onFailure(failure); + return () -> + { + if (runnable != null) + runnable.run(); + callback.succeeded(); + }; + } + + @Override + public void succeeded() + { + _httpChannel.recycle(); + + // If the stream is not closed, it is still reading the request content. + // Send a reset to the other end so that it stops sending data. + if (!_stream.isClosed()) + { + if (LOG.isDebugEnabled()) + LOG.debug("HTTP2 response #{}/{}: unconsumed request content, resetting stream", _stream.getId(), Integer.toHexString(_stream.getSession().hashCode())); + _stream.reset(new ResetFrame(_stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + } + } + + @Override + public void failed(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("HTTP2 response #{}/{} aborted", _stream.getId(), Integer.toHexString(_stream.getSession().hashCode())); + _stream.reset(new ResetFrame(_stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + } + + private class SendTrailers extends Callback.Nested + { + private final HttpFields trailers; + + private SendTrailers(Callback callback, HttpFields trailers) + { + super(callback); + this.trailers = trailers; + } + + @Override + public void succeeded() + { + sendTrailersFrame(new MetaData(HttpVersion.HTTP_2, trailers), getCallback()); + } + } +} diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpTransportOverHTTP2.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpTransportOverHTTP2.java index b1cc39b88a3..a7a5d435441 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpTransportOverHTTP2.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpTransportOverHTTP2.java @@ -11,578 +11,550 @@ // ======================================================================== // -package org.eclipse.jetty.http2.server; +package org.eclipse.jetty.http2.server.internal; -import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import org.eclipse.jetty.http.BadMessageException; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.IStream; -import org.eclipse.jetty.http2.api.Stream; -import org.eclipse.jetty.http2.frames.DataFrame; -import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.frames.PushPromiseFrame; -import org.eclipse.jetty.http2.frames.ResetFrame; -import org.eclipse.jetty.io.Connection; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpTransport; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.Promise; -import org.eclipse.jetty.util.thread.AutoLock; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class HttpTransportOverHTTP2 implements HttpTransport +public class HttpTransportOverHTTP2 { - private static final Logger LOG = LoggerFactory.getLogger(HttpTransportOverHTTP2.class); - - private final AtomicBoolean commit = new AtomicBoolean(); - private final TransportCallback transportCallback = new TransportCallback(); - private final Connector connector; - private final HTTP2ServerConnection connection; - private IStream stream; - private MetaData.Response metaData; - - public HttpTransportOverHTTP2(Connector connector, HTTP2ServerConnection connection) - { - this.connector = connector; - this.connection = connection; - } - - public IStream getStream() - { - return stream; - } - - public void setStream(IStream stream) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} setStream {}", this, stream.getId()); - this.stream = stream; - } - - public void recycle() - { - this.stream = null; - commit.set(false); - } - - @Override - public void send(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback) - { - if (response != null) - sendHeaders(request, response, content, lastContent, callback); - else - sendContent(request, content, lastContent, callback); - } - - public void sendHeaders(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback) - { - metaData = response; - - HeadersFrame headersFrame; - DataFrame dataFrame = null; - HeadersFrame trailersFrame = null; - - boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod()); - boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest; - int status = response.getStatus(); - boolean interimResponse = status == HttpStatus.CONTINUE_100 || status == HttpStatus.PROCESSING_102; - if (interimResponse) - { - // Must not commit interim responses. - if (hasContent) - { - callback.failed(new IllegalStateException("Interim response cannot have content")); - return; - } - headersFrame = new HeadersFrame(stream.getId(), metaData, null, false); - } - else - { - if (commit.compareAndSet(false, true)) - { - if (lastContent) - { - long realContentLength = BufferUtil.length(content); - long contentLength = response.getContentLength(); - if (contentLength < 0) - { - metaData = new MetaData.Response( - response.getHttpVersion(), - response.getStatus(), - response.getReason(), - response.getFields(), - realContentLength, - response.getTrailerSupplier() - ); - } - else if (hasContent && contentLength != realContentLength) - { - callback.failed(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, String.format("Incorrect Content-Length %d!=%d", contentLength, realContentLength))); - return; - } - } - - if (hasContent) - { - headersFrame = new HeadersFrame(stream.getId(), metaData, null, false); - if (lastContent) - { - HttpFields trailers = retrieveTrailers(); - if (trailers == null) - { - dataFrame = new DataFrame(stream.getId(), content, true); - } - else - { - dataFrame = new DataFrame(stream.getId(), content, false); - trailersFrame = new HeadersFrame(stream.getId(), new MetaData(HttpVersion.HTTP_2, trailers), null, true); - } - } - else - { - dataFrame = new DataFrame(stream.getId(), content, false); - } - } - else - { - if (lastContent) - { - if (isTunnel(request, metaData)) - { - headersFrame = new HeadersFrame(stream.getId(), metaData, null, false); - } - else - { - HttpFields trailers = retrieveTrailers(); - if (trailers == null) - { - headersFrame = new HeadersFrame(stream.getId(), metaData, null, true); - } - else - { - headersFrame = new HeadersFrame(stream.getId(), metaData, null, false); - trailersFrame = new HeadersFrame(stream.getId(), new MetaData(HttpVersion.HTTP_2, trailers), null, true); - } - } - } - else - { - headersFrame = new HeadersFrame(stream.getId(), metaData, null, false); - } - } - } - else - { - callback.failed(new IllegalStateException("committed")); - return; - } - } - - HeadersFrame hf = headersFrame; - DataFrame df = dataFrame; - HeadersFrame tf = trailersFrame; - - transportCallback.send(callback, true, c -> - { - if (LOG.isDebugEnabled()) - { - LOG.debug("HTTP2 Response #{}/{}:{}{} {}{}{}", - stream.getId(), Integer.toHexString(stream.getSession().hashCode()), - System.lineSeparator(), HttpVersion.HTTP_2, metaData.getStatus(), - System.lineSeparator(), metaData.getFields()); - } - stream.send(new IStream.FrameList(hf, df, tf), c); - }); - } - - public void sendContent(MetaData.Request request, ByteBuffer content, boolean lastContent, Callback callback) - { - boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod()); - boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest; - if (hasContent || (lastContent && !isTunnel(request, metaData))) - { - if (lastContent) - { - HttpFields trailers = retrieveTrailers(); - if (trailers == null) - { - transportCallback.send(callback, false, c -> - sendDataFrame(content, true, true, c)); - } - else - { - SendTrailers sendTrailers = new SendTrailers(callback, trailers); - if (hasContent) - { - transportCallback.send(sendTrailers, false, c -> - sendDataFrame(content, true, false, c)); - } - else - { - sendTrailers.succeeded(); - } - } - } - else - { - transportCallback.send(callback, false, c -> - sendDataFrame(content, false, false, c)); - } - } - else - { - callback.succeeded(); - } - } - - private HttpFields retrieveTrailers() - { - Supplier supplier = metaData.getTrailerSupplier(); - if (supplier == null) - return null; - HttpFields trailers = supplier.get(); - if (trailers == null) - return null; - return trailers.size() == 0 ? null : trailers; - } - - private boolean isTunnel(MetaData.Request request, MetaData.Response response) - { - return MetaData.isTunnel(request.getMethod(), response.getStatus()); - } - - @Override - public boolean isPushSupported() - { - return stream.getSession().isPushEnabled(); - } - - @Override - public void push(final MetaData.Request request) - { - if (!stream.getSession().isPushEnabled()) - { - if (LOG.isDebugEnabled()) - LOG.debug("HTTP/2 Push disabled for {}", request); - return; - } - - if (LOG.isDebugEnabled()) - LOG.debug("HTTP/2 Push {}", request); - - stream.push(new PushPromiseFrame(stream.getId(), request), new Promise<>() - { - @Override - public void succeeded(Stream pushStream) - { - connection.push(connector, (IStream)pushStream, request); - } - - @Override - public void failed(Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("Could not push {}", request, x); - } - }, new Stream.Listener.Adapter()); // TODO: handle reset from the client ? - } - - private void sendDataFrame(ByteBuffer content, boolean lastContent, boolean endStream, Callback callback) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("HTTP2 Response #{}/{}: {} content bytes{}", - stream.getId(), Integer.toHexString(stream.getSession().hashCode()), - content.remaining(), lastContent ? " (last chunk)" : ""); - } - DataFrame frame = new DataFrame(stream.getId(), content, endStream); - stream.data(frame, callback); - } - - private void sendTrailersFrame(MetaData metaData, Callback callback) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("HTTP2 Response #{}/{}: trailers", - stream.getId(), Integer.toHexString(stream.getSession().hashCode())); - } - - HeadersFrame frame = new HeadersFrame(stream.getId(), metaData, null, true); - stream.headers(frame, callback); - } - - public void onStreamFailure(Throwable failure) - { - transportCallback.abort(failure); - } - - public boolean onStreamTimeout(Throwable failure) - { - return transportCallback.idleTimeout(failure); - } - - /** - * @return true if error sent, false if upgraded or aborted. - */ - boolean prepareUpgrade() - { - HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttachment(); - Request request = channel.getRequest(); - if (request.getHttpInput().hasContent()) - return channel.sendErrorOrAbort("Unexpected content in CONNECT request"); - - Connection connection = (Connection)request.getAttribute(UPGRADE_CONNECTION_ATTRIBUTE); - if (connection == null) - return channel.sendErrorOrAbort("No UPGRADE_CONNECTION_ATTRIBUTE available"); - - EndPoint endPoint = connection.getEndPoint(); - endPoint.upgrade(connection); - stream.setAttachment(endPoint); - - // Only now that we have switched the attachment, we can demand DATA frames to process them. - stream.demand(1); - - if (LOG.isDebugEnabled()) - LOG.debug("Upgrading to {}", connection); - - return false; - } - - @Override - public void onCompleted() - { - Object attachment = stream.getAttachment(); - if (attachment instanceof HttpChannelOverHTTP2) - { - // If the stream is not closed, it is still reading the request content. - // Send a reset to the other end so that it stops sending data. - if (!stream.isClosed()) - { - if (LOG.isDebugEnabled()) - LOG.debug("HTTP2 Response #{}: unconsumed request content, resetting stream", stream.getId()); - stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); - } - - // Consume the existing queued data frames to - // avoid stalling the session flow control. - HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)attachment; - channel.consumeInput(); - } - } - - @Override - public void abort(Throwable failure) - { - IStream stream = this.stream; - if (LOG.isDebugEnabled()) - LOG.debug("HTTP2 Response #{}/{} aborted", stream == null ? -1 : stream.getId(), - stream == null ? -1 : Integer.toHexString(stream.getSession().hashCode())); - if (stream != null) - stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); - } - - /** - *

Callback that controls sends initiated by the transport, by eventually - * notifying a nested callback.

- *

There are 3 sources of concurrency after a send is initiated:

- *
    - *
  • the completion of the send operation, either success or failure
  • - *
  • an asynchronous failure coming from the read side such as a stream - * being reset, or the connection being closed
  • - *
  • an asynchronous idle timeout
  • - *
- * - * @see State - */ - private class TransportCallback implements Callback - { - private final AutoLock _lock = new AutoLock(); - private State _state = State.IDLE; - private Callback _callback; - private boolean _commit; - private Throwable _failure; - - private void reset(Throwable failure) - { - assert _lock.isHeldByCurrentThread(); - _state = failure != null ? State.FAILED : State.IDLE; - _callback = null; - _commit = false; - _failure = failure; - } - - private void send(Callback callback, boolean commit, Consumer sendFrame) - { - Throwable failure = sending(callback, commit); - if (failure == null) - sendFrame.accept(this); - else - callback.failed(failure); - } - - private void abort(Throwable failure) - { - failed(failure); - } - - private Throwable sending(Callback callback, boolean commit) - { - try (AutoLock l = _lock.lock()) - { - switch (_state) - { - case IDLE: - { - _state = State.SENDING; - _callback = callback; - _commit = commit; - return null; - } - case FAILED: - { - return _failure; - } - default: - { - return new IllegalStateException("Invalid transport state: " + _state); - } - } - } - } - - @Override - public void succeeded() - { - Callback callback; - boolean commit; - try (AutoLock l = _lock.lock()) - { - if (_state != State.SENDING) - { - // This thread lost the race to succeed the current - // send, as other threads likely already failed it. - return; - } - callback = _callback; - commit = _commit; - reset(null); - } - if (LOG.isDebugEnabled()) - LOG.debug("HTTP2 Response #{}/{} {} success", - stream.getId(), Integer.toHexString(stream.getSession().hashCode()), - commit ? "commit" : "flush"); - callback.succeeded(); - } - - @Override - public void failed(Throwable failure) - { - Callback callback; - boolean commit; - try (AutoLock l = _lock.lock()) - { - if (_state != State.SENDING) - { - reset(failure); - return; - } - callback = _callback; - commit = _commit; - reset(failure); - } - if (LOG.isDebugEnabled()) - LOG.debug("HTTP2 Response #{}/{} {} failure", - stream.getId(), Integer.toHexString(stream.getSession().hashCode()), - commit ? "commit" : "flush", - failure); - callback.failed(failure); - } - - private boolean idleTimeout(Throwable failure) - { - Callback callback = null; - try (AutoLock l = _lock.lock()) - { - // Ignore idle timeouts if not writing, - // as the application may be suspended. - if (_state == State.SENDING) - { - callback = _callback; - reset(failure); - } - } - boolean timeout = callback != null; - if (LOG.isDebugEnabled()) - LOG.debug("HTTP2 Response #{}/{} idle timeout {}", - stream.getId(), Integer.toHexString(stream.getSession().hashCode()), - timeout ? "expired" : "ignored", - failure); - if (timeout) - callback.failed(failure); - return timeout; - } - - @Override - public InvocationType getInvocationType() - { - Callback callback; - try (AutoLock l = _lock.lock()) - { - callback = _callback; - } - return callback != null ? callback.getInvocationType() : Callback.super.getInvocationType(); - } - } - - /** - *

Send states for {@link TransportCallback}.

- * - * @see TransportCallback - */ - private enum State - { - /** - *

No send initiated or in progress.

- */ - IDLE, - /** - *

A send is initiated and possibly in progress.

- */ - SENDING, - /** - *

The terminal state indicating failure of the send.

- */ - FAILED - } - - private class SendTrailers extends Callback.Nested - { - private final HttpFields trailers; - - private SendTrailers(Callback callback, HttpFields trailers) - { - super(callback); - this.trailers = trailers; - } - - @Override - public void succeeded() - { - transportCallback.send(getCallback(), false, c -> - sendTrailersFrame(new MetaData(HttpVersion.HTTP_2, trailers), c)); - } - } +// implements HttpTransport +//{ +// private static final Logger LOG = LoggerFactory.getLogger(HttpTransportOverHTTP2.class); +// +// private final AtomicBoolean commit = new AtomicBoolean(); +// private final TransportCallback transportCallback = new TransportCallback(); +// private final Connector connector; +// private final HTTP2ServerConnection connection; +// private IStream stream; +// private MetaData.Response metaData; +// +// public HttpTransportOverHTTP2(Connector connector, HTTP2ServerConnection connection) +// { +// this.connector = connector; +// this.connection = connection; +// } +// +// public IStream getStream() +// { +// return stream; +// } +// +// public void setStream(IStream stream) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("{} setStream {}", this, stream.getId()); +// this.stream = stream; +// } +// +// public void recycle() +// { +// this.stream = null; +// commit.set(false); +// } +// +// @Override +// public void send(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback) +// { +// if (response != null) +// sendHeaders(request, response, content, lastContent, callback); +// else +// sendContent(request, content, lastContent, callback); +// } +// +// public void sendHeaders(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback) +// { +// metaData = response; +// +// HeadersFrame headersFrame; +// DataFrame dataFrame = null; +// HeadersFrame trailersFrame = null; +// +// boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod()); +// boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest; +// int status = response.getStatus(); +// boolean interimResponse = status == HttpStatus.CONTINUE_100 || status == HttpStatus.PROCESSING_102; +// if (interimResponse) +// { +// // Must not commit interim responses. +// if (hasContent) +// { +// callback.failed(new IllegalStateException("Interim response cannot have content")); +// return; +// } +// headersFrame = new HeadersFrame(stream.getId(), metaData, null, false); +// } +// else +// { +// if (commit.compareAndSet(false, true)) +// { +// if (lastContent) +// { +// long realContentLength = BufferUtil.length(content); +// long contentLength = response.getContentLength(); +// if (contentLength < 0) +// { +// metaData = new MetaData.Response( +// response.getHttpVersion(), +// response.getStatus(), +// response.getReason(), +// response.getFields(), +// realContentLength, +// response.getTrailerSupplier() +// ); +// } +// else if (hasContent && contentLength != realContentLength) +// { +// callback.failed(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, String.format("Incorrect Content-Length %d!=%d", contentLength, realContentLength))); +// return; +// } +// } +// +// if (hasContent) +// { +// headersFrame = new HeadersFrame(stream.getId(), metaData, null, false); +// if (lastContent) +// { +// HttpFields trailers = retrieveTrailers(); +// if (trailers == null) +// { +// dataFrame = new DataFrame(stream.getId(), content, true); +// } +// else +// { +// dataFrame = new DataFrame(stream.getId(), content, false); +// trailersFrame = new HeadersFrame(stream.getId(), new MetaData(HttpVersion.HTTP_2, trailers), null, true); +// } +// } +// else +// { +// dataFrame = new DataFrame(stream.getId(), content, false); +// } +// } +// else +// { +// if (lastContent) +// { +// if (isTunnel(request, metaData)) +// { +// headersFrame = new HeadersFrame(stream.getId(), metaData, null, false); +// } +// else +// { +// HttpFields trailers = retrieveTrailers(); +// if (trailers == null) +// { +// headersFrame = new HeadersFrame(stream.getId(), metaData, null, true); +// } +// else +// { +// headersFrame = new HeadersFrame(stream.getId(), metaData, null, false); +// trailersFrame = new HeadersFrame(stream.getId(), new MetaData(HttpVersion.HTTP_2, trailers), null, true); +// } +// } +// } +// else +// { +// headersFrame = new HeadersFrame(stream.getId(), metaData, null, false); +// } +// } +// } +// else +// { +// callback.failed(new IllegalStateException("committed")); +// return; +// } +// } +// +// HeadersFrame hf = headersFrame; +// DataFrame df = dataFrame; +// HeadersFrame tf = trailersFrame; +// +// transportCallback.send(callback, true, c -> +// { +// if (LOG.isDebugEnabled()) +// { +// LOG.debug("HTTP2 Response #{}/{}:{}{} {}{}{}", +// stream.getId(), Integer.toHexString(stream.getSession().hashCode()), +// System.lineSeparator(), HttpVersion.HTTP_2, metaData.getStatus(), +// System.lineSeparator(), metaData.getFields()); +// } +// stream.send(new IStream.FrameList(hf, df, tf), c); +// }); +// } +// +// public void sendContent(MetaData.Request request, ByteBuffer content, boolean lastContent, Callback callback) +// { +// boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod()); +// boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest; +// if (hasContent || (lastContent && !isTunnel(request, metaData))) +// { +// if (lastContent) +// { +// HttpFields trailers = retrieveTrailers(); +// if (trailers == null) +// { +// transportCallback.send(callback, false, c -> +// sendDataFrame(content, true, true, c)); +// } +// else +// { +// SendTrailers sendTrailers = new SendTrailers(callback, trailers); +// if (hasContent) +// { +// transportCallback.send(sendTrailers, false, c -> +// sendDataFrame(content, true, false, c)); +// } +// else +// { +// sendTrailers.succeeded(); +// } +// } +// } +// else +// { +// transportCallback.send(callback, false, c -> +// sendDataFrame(content, false, false, c)); +// } +// } +// else +// { +// callback.succeeded(); +// } +// } +// +// private HttpFields retrieveTrailers() +// { +// Supplier supplier = metaData.getTrailerSupplier(); +// if (supplier == null) +// return null; +// HttpFields trailers = supplier.get(); +// if (trailers == null) +// return null; +// return trailers.size() == 0 ? null : trailers; +// } +// +// private boolean isTunnel(MetaData.Request request, MetaData.Response response) +// { +// return MetaData.isTunnel(request.getMethod(), response.getStatus()); +// } +// +// @Override +// public boolean isPushSupported() +// { +// return stream.getSession().isPushEnabled(); +// } +// +// @Override +// public void push(final MetaData.Request request) +// { +// if (!stream.getSession().isPushEnabled()) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("HTTP/2 Push disabled for {}", request); +// return; +// } +// +// if (LOG.isDebugEnabled()) +// LOG.debug("HTTP/2 Push {}", request); +// +// stream.push(new PushPromiseFrame(stream.getId(), request), new Promise<>() +// { +// @Override +// public void succeeded(Stream pushStream) +// { +// connection.push(connector, (IStream)pushStream, request); +// } +// +// @Override +// public void failed(Throwable x) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("Could not push {}", request, x); +// } +// }, new Stream.Listener.Adapter()); // TODO: handle reset from the client ? +// } +// +// private void sendDataFrame(ByteBuffer content, boolean lastContent, boolean endStream, Callback callback) +// { +// if (LOG.isDebugEnabled()) +// { +// LOG.debug("HTTP2 Response #{}/{}: {} content bytes{}", +// stream.getId(), Integer.toHexString(stream.getSession().hashCode()), +// content.remaining(), lastContent ? " (last chunk)" : ""); +// } +// DataFrame frame = new DataFrame(stream.getId(), content, endStream); +// stream.data(frame, callback); +// } +// +// private void sendTrailersFrame(MetaData metaData, Callback callback) +// { +// if (LOG.isDebugEnabled()) +// { +// LOG.debug("HTTP2 Response #{}/{}: trailers", +// stream.getId(), Integer.toHexString(stream.getSession().hashCode())); +// } +// +// HeadersFrame frame = new HeadersFrame(stream.getId(), metaData, null, true); +// stream.headers(frame, callback); +// } +// +// public void onStreamFailure(Throwable failure) +// { +// transportCallback.abort(failure); +// } +// +// public boolean onStreamTimeout(Throwable failure) +// { +// return transportCallback.idleTimeout(failure); +// } +// +// /** +// * @return true if error sent, false if upgraded or aborted. +// */ +// boolean prepareUpgrade() +// { +// HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttachment(); +// Request request = channel.getRequest(); +// if (request.getHttpInput().hasContent()) +// return channel.sendErrorOrAbort("Unexpected content in CONNECT request"); +// +// Connection connection = (Connection)request.getAttribute(UPGRADE_CONNECTION_ATTRIBUTE); +// if (connection == null) +// return channel.sendErrorOrAbort("No UPGRADE_CONNECTION_ATTRIBUTE available"); +// +// EndPoint endPoint = connection.getEndPoint(); +// endPoint.upgrade(connection); +// stream.setAttachment(endPoint); +// +// // Only now that we have switched the attachment, we can demand DATA frames to process them. +// stream.demand(1); +// +// if (LOG.isDebugEnabled()) +// LOG.debug("Upgrading to {}", connection); +// +// return false; +// } +// +// @Override +// public void onCompleted() +// { +// Object attachment = stream.getAttachment(); +// if (attachment instanceof HttpChannelOverHTTP2) +// { +// // If the stream is not closed, it is still reading the request content. +// // Send a reset to the other end so that it stops sending data. +// if (!stream.isClosed()) +// { +// if (LOG.isDebugEnabled()) +// LOG.debug("HTTP2 Response #{}: unconsumed request content, resetting stream", stream.getId()); +// stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); +// } +// +// // Consume the existing queued data frames to +// // avoid stalling the session flow control. +// HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)attachment; +// channel.consumeInput(); +// } +// } +// +// @Override +// public void abort(Throwable failure) +// { +// IStream stream = this.stream; +// if (LOG.isDebugEnabled()) +// LOG.debug("HTTP2 Response #{}/{} aborted", stream == null ? -1 : stream.getId(), +// stream == null ? -1 : Integer.toHexString(stream.getSession().hashCode())); +// if (stream != null) +// stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); +// } +// +// /** +// *

Callback that controls sends initiated by the transport, by eventually +// * notifying a nested callback.

+// *

There are 3 sources of concurrency after a send is initiated:

+// *
    +// *
  • the completion of the send operation, either success or failure
  • +// *
  • an asynchronous failure coming from the read side such as a stream +// * being reset, or the connection being closed
  • +// *
  • an asynchronous idle timeout
  • +// *
+// * +// * @see State +// */ +// private class TransportCallback implements Callback +// { +// private final AutoLock _lock = new AutoLock(); +// private State _state = State.IDLE; +// private Callback _callback; +// private boolean _commit; +// private Throwable _failure; +// +// private void reset(Throwable failure) +// { +// assert _lock.isHeldByCurrentThread(); +// _state = failure != null ? State.FAILED : State.IDLE; +// _callback = null; +// _commit = false; +// _failure = failure; +// } +// +// private void send(Callback callback, boolean commit, Consumer sendFrame) +// { +// Throwable failure = sending(callback, commit); +// if (failure == null) +// sendFrame.accept(this); +// else +// callback.failed(failure); +// } +// +// private void abort(Throwable failure) +// { +// failed(failure); +// } +// +// private Throwable sending(Callback callback, boolean commit) +// { +// try (AutoLock l = _lock.lock()) +// { +// switch (_state) +// { +// case IDLE: +// { +// _state = State.SENDING; +// _callback = callback; +// _commit = commit; +// return null; +// } +// case FAILED: +// { +// return _failure; +// } +// default: +// { +// return new IllegalStateException("Invalid transport state: " + _state); +// } +// } +// } +// } +// +// @Override +// public void succeeded() +// { +// Callback callback; +// boolean commit; +// try (AutoLock l = _lock.lock()) +// { +// if (_state != State.SENDING) +// { +// // This thread lost the race to succeed the current +// // send, as other threads likely already failed it. +// return; +// } +// callback = _callback; +// commit = _commit; +// reset(null); +// } +// if (LOG.isDebugEnabled()) +// LOG.debug("HTTP2 Response #{}/{} {} success", +// stream.getId(), Integer.toHexString(stream.getSession().hashCode()), +// commit ? "commit" : "flush"); +// callback.succeeded(); +// } +// +// @Override +// public void failed(Throwable failure) +// { +// Callback callback; +// boolean commit; +// try (AutoLock l = _lock.lock()) +// { +// if (_state != State.SENDING) +// { +// reset(failure); +// return; +// } +// callback = _callback; +// commit = _commit; +// reset(failure); +// } +// if (LOG.isDebugEnabled()) +// LOG.debug("HTTP2 Response #{}/{} {} failure", +// stream.getId(), Integer.toHexString(stream.getSession().hashCode()), +// commit ? "commit" : "flush", +// failure); +// callback.failed(failure); +// } +// +// private boolean idleTimeout(Throwable failure) +// { +// Callback callback = null; +// try (AutoLock l = _lock.lock()) +// { +// // Ignore idle timeouts if not writing, +// // as the application may be suspended. +// if (_state == State.SENDING) +// { +// callback = _callback; +// reset(failure); +// } +// } +// boolean timeout = callback != null; +// if (LOG.isDebugEnabled()) +// LOG.debug("HTTP2 Response #{}/{} idle timeout {}", +// stream.getId(), Integer.toHexString(stream.getSession().hashCode()), +// timeout ? "expired" : "ignored", +// failure); +// if (timeout) +// callback.failed(failure); +// return timeout; +// } +// +// @Override +// public InvocationType getInvocationType() +// { +// Callback callback; +// try (AutoLock l = _lock.lock()) +// { +// callback = _callback; +// } +// return callback != null ? callback.getInvocationType() : Callback.super.getInvocationType(); +// } +// } +// +// /** +// *

Send states for {@link TransportCallback}.

+// * +// * @see TransportCallback +// */ +// private enum State +// { +// /** +// *

No send initiated or in progress.

+// */ +// IDLE, +// /** +// *

A send is initiated and possibly in progress.

+// */ +// SENDING, +// /** +// *

The terminal state indicating failure of the send.

+// */ +// FAILED +// } +// +// private class SendTrailers extends Callback.Nested +// { +// private final HttpFields trailers; +// +// private SendTrailers(Callback callback, HttpFields trailers) +// { +// super(callback); +// this.trailers = trailers; +// } +// +// @Override +// public void succeeded() +// { +// transportCallback.send(getCallback(), false, c -> +// sendTrailersFrame(new MetaData(HttpVersion.HTTP_2, trailers), c)); +// } +// } } diff --git a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java index 5a37f48366a..90cd5b2c6f1 100644 --- a/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java +++ b/jetty-core/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java @@ -11,15 +11,15 @@ // ======================================================================== // -package org.eclipse.jetty.http2.server; +package org.eclipse.jetty.http2.server.internal; import java.util.function.Consumer; -import org.eclipse.jetty.http2.HTTP2Channel; -import org.eclipse.jetty.http2.HTTP2StreamEndPoint; import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.internal.HTTP2Channel; +import org.eclipse.jetty.http2.internal.HTTP2StreamEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.util.Callback; import org.slf4j.Logger; diff --git a/jetty-core/jetty-http2/jetty-http2-tests/pom.xml b/jetty-core/jetty-http2/jetty-http2-tests/pom.xml new file mode 100644 index 00000000000..73b9b97db86 --- /dev/null +++ b/jetty-core/jetty-http2/jetty-http2-tests/pom.xml @@ -0,0 +1,114 @@ + + + + org.eclipse.jetty.http2 + http2 + 12.0.0-SNAPSHOT + + + 4.0.0 + jetty-http2-tests + Jetty Core :: HTTP2 :: Tests + + + + + org.mortbay.jetty + h2spec-maven-plugin + + org.eclipse.jetty.http2.tests.H2SpecServer + ${skipTests} + org.eclipse.jetty.h2spec + true + ${project.build.directory}/h2spec-reports + + 3.5 - Sends invalid connection preface + + + + + h2spec + test + + h2spec + + + + + + + + + + run-spec-server + + + + org.codehaus.mojo + exec-maven-plugin + + + spec-server-run + test + + org.eclipse.jetty.http2.tests.H2SpecServer + test + + ${manual.test.port} + + + + java + + + + + + + + + + + + org.eclipse.jetty + jetty-alpn-java-server + test + + + org.eclipse.jetty + jetty-client + test + + + org.eclipse.jetty + jetty-slf4j-impl + test + + + org.eclipse.jetty + jetty-server + test + + + org.eclipse.jetty.http2 + http2-client + test + + + org.eclipse.jetty.http2 + http2-http-client-transport + test + + + org.eclipse.jetty.http2 + http2-server + test + + + org.awaitility + awaitility + test + + + + diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractServerTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractServerTest.java index df098c361b5..aa6fd997ee3 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractServerTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractServerTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.server; +package org.eclipse.jetty.http2.tests; import java.io.IOException; import java.io.InputStream; @@ -19,23 +19,23 @@ import java.net.Socket; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; -import jakarta.servlet.http.HttpServlet; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.api.server.ServerSessionListener; -import org.eclipse.jetty.http2.generator.Generator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.generator.Generator; +import org.eclipse.jetty.http2.internal.parser.Parser; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; @@ -47,11 +47,10 @@ public class AbstractServerTest protected Server server; protected String path; - protected void startServer(HttpServlet servlet) throws Exception + protected void startServer(Handler handler) throws Exception { prepareServer(new HTTP2ServerConnectionFactory(new HttpConfiguration())); - ServletContextHandler context = new ServletContextHandler(server, "/"); - context.addServlet(new ServletHolder(servlet), path); + server.setHandler(handler); server.start(); } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractTest.java index 0e158545497..45c4c95dabc 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AbstractTest.java @@ -11,13 +11,13 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.net.InetSocketAddress; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import jakarta.servlet.http.HttpServlet; +import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpScheme; @@ -26,43 +26,40 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.server.ServerSessionListener; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; +import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.FuturePromise; +import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; public class AbstractTest { - protected ServerConnector connector; - protected String servletPath = "/test"; - protected HTTP2Client client; protected Server server; + protected ServerConnector connector; + protected HTTP2Client http2Client; + protected HttpClient httpClient; - protected void start(HttpServlet servlet) throws Exception + protected void start(Handler handler) throws Exception { HTTP2CServerConnectionFactory connectionFactory = new HTTP2CServerConnectionFactory(new HttpConfiguration()); connectionFactory.setInitialSessionRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); connectionFactory.setInitialStreamRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); prepareServer(connectionFactory); - ServletContextHandler context = new ServletContextHandler(server, "/", true, false); - context.addServlet(new ServletHolder(servlet), servletPath + "/*"); - customizeContext(context); + server.setHandler(handler); server.start(); prepareClient(); - client.start(); - } - - protected void customizeContext(ServletContextHandler context) - { + httpClient.start(); } protected void start(ServerSessionListener listener) throws Exception @@ -80,7 +77,7 @@ public class AbstractTest server.start(); prepareClient(); - client.start(); + httpClient.start(); } protected void prepareServer(ConnectionFactory... connectionFactories) @@ -94,43 +91,44 @@ public class AbstractTest protected void prepareClient() { - client = new HTTP2Client(); + ClientConnector connector = new ClientConnector(); QueuedThreadPool clientExecutor = new QueuedThreadPool(); clientExecutor.setName("client"); - client.setExecutor(clientExecutor); - client.setInitialSessionRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); - client.setInitialStreamRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); + connector.setExecutor(clientExecutor); + http2Client = new HTTP2Client(connector); + http2Client.setInitialSessionRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); + http2Client.setInitialStreamRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); + HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(http2Client); + httpClient = new HttpClient(transport); } - protected Session newClient(Session.Listener listener) throws Exception + protected Session newClientSession(Session.Listener listener) throws Exception { String host = "localhost"; int port = connector.getLocalPort(); InetSocketAddress address = new InetSocketAddress(host, port); FuturePromise promise = new FuturePromise<>(); - client.connect(address, listener, promise); + http2Client.connect(address, listener, promise); return promise.get(5, TimeUnit.SECONDS); } protected MetaData.Request newRequest(String method, HttpFields fields) { - return newRequest(method, "", fields); + return newRequest(method, "/", fields); } - protected MetaData.Request newRequest(String method, String pathInfo, HttpFields fields) + protected MetaData.Request newRequest(String method, String path, HttpFields fields) { String host = "localhost"; int port = connector.getLocalPort(); String authority = host + ":" + port; - return new MetaData.Request(method, HttpScheme.HTTP.asString(), new HostPortHttpField(authority), servletPath + pathInfo, HttpVersion.HTTP_2, fields, -1); + return new MetaData.Request(method, HttpScheme.HTTP.asString(), new HostPortHttpField(authority), path, HttpVersion.HTTP_2, fields, -1); } @AfterEach public void dispose() throws Exception { - if (client != null) - client.stop(); - if (server != null) - server.stop(); + LifeCycle.stop(httpClient); + LifeCycle.stop(server); } } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncIOTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncIOTest.java index 4a09c4a52ac..573980ce508 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncIOTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncIOTest.java @@ -11,75 +11,49 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import jakarta.servlet.AsyncContext; -import jakarta.servlet.ReadListener; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.WriteListener; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.server.HttpOutput; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; public class AsyncIOTest extends AbstractTest { @Test public void testLastContentAvailableBeforeService() throws Exception { - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + public void process(Request request, Response response, Callback callback) throws Exception { // Wait for the data to fully arrive. sleep(1000); - - final AsyncContext asyncContext = request.startAsync(); - asyncContext.setTimeout(0); - request.getInputStream().setReadListener(new EmptyReadListener() - { - @Override - public void onDataAvailable() throws IOException - { - ServletInputStream input = request.getInputStream(); - while (input.isReady()) - { - int read = input.read(); - if (read < 0) - break; - } - if (input.isFinished()) - asyncContext.complete(); - } - }); + Content.consumeAll(request); + callback.succeeded(); } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, false); @@ -103,33 +77,17 @@ public class AsyncIOTest extends AbstractTest @Test public void testLastContentAvailableAfterServiceReturns() throws Exception { - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + public void process(Request request, Response response, Callback callback) throws Exception { - final AsyncContext asyncContext = request.startAsync(); - asyncContext.setTimeout(0); - request.getInputStream().setReadListener(new EmptyReadListener() - { - @Override - public void onDataAvailable() throws IOException - { - ServletInputStream input = request.getInputStream(); - while (input.isReady()) - { - int read = input.read(); - if (read < 0) - break; - } - if (input.isFinished()) - asyncContext.complete(); - } - }); + Content.consumeAll(request); + callback.succeeded(); } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, false); @@ -154,13 +112,16 @@ public class AsyncIOTest extends AbstractTest } @Test - public void testSomeContentAvailableAfterServiceReturns() throws Exception + @Disabled("port to ee9") + public void testSomeContentAvailableAfterServiceReturns() { + fail(); +/* final AtomicInteger count = new AtomicInteger(); - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + public void process(Request request, Response response, Callback callback) throws Exception { final AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(0); @@ -212,11 +173,15 @@ public class AsyncIOTest extends AbstractTest assertTrue(latch.await(5, TimeUnit.SECONDS)); // Make sure onDataAvailable() has been called twice assertEquals(2, count.get()); +*/ } @Test - public void testDirectAsyncWriteThenComplete() throws Exception + @Disabled("port to ee9") + public void testDirectAsyncWriteThenComplete() { + fail(); +/* // Use a small flow control window to stall the server writes. int clientWindow = 16; start(new EmptyHttpServlet() @@ -271,6 +236,7 @@ public class AsyncIOTest extends AbstractTest } }); assertTrue(latch.await(5, TimeUnit.SECONDS)); +*/ } private static void sleep(long ms) throws InterruptedIOException @@ -284,22 +250,4 @@ public class AsyncIOTest extends AbstractTest throw new InterruptedIOException(); } } - - private static class EmptyReadListener implements ReadListener - { - @Override - public void onDataAvailable() throws IOException - { - } - - @Override - public void onAllDataRead() throws IOException - { - } - - @Override - public void onError(Throwable t) - { - } - } } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncServletTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncServletTest.java index ce5fa5ce3cc..8103f27ef0d 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncServletTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/AsyncServletTest.java @@ -11,395 +11,364 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Random; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import jakarta.servlet.AsyncContext; -import jakarta.servlet.AsyncEvent; -import jakarta.servlet.AsyncListener; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.api.Session; -import org.eclipse.jetty.http2.api.Stream; -import org.eclipse.jetty.http2.api.server.ServerSessionListener; -import org.eclipse.jetty.http2.frames.DataFrame; -import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.frames.ResetFrame; -import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.FuturePromise; -import org.eclipse.jetty.util.Promise; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +@Disabled("port to ee9") public class AsyncServletTest extends AbstractTest { @Test - public void testStartAsyncThenDispatch() throws Exception + public void test() { - byte[] content = new byte[1024]; - new Random().nextBytes(content); - start(new HttpServlet() - { - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException - { - AsyncContext asyncContext = (AsyncContext)request.getAttribute(AsyncContext.class.getName()); - if (asyncContext == null) - { - AsyncContext context = request.startAsync(); - context.setTimeout(0); - request.setAttribute(AsyncContext.class.getName(), context); - context.start(() -> - { - sleep(1000); - context.dispatch(); - }); - } - else - { - response.getOutputStream().write(content); - } - } - }); - - Session session = newClient(new Session.Listener.Adapter()); - - MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); - HeadersFrame frame = new HeadersFrame(metaData, null, true); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - CountDownLatch latch = new CountDownLatch(1); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - try - { - BufferUtil.writeTo(frame.getData(), buffer); - callback.succeeded(); - if (frame.isEndStream()) - latch.countDown(); - } - catch (IOException x) - { - callback.failed(x); - } - } - }); - - assertTrue(latch.await(5, TimeUnit.SECONDS)); - assertArrayEquals(content, buffer.toByteArray()); + fail(); } - @Test - public void testStartAsyncThenClientSessionIdleTimeout() throws Exception - { - CountDownLatch serverLatch = new CountDownLatch(1); - start(new AsyncOnErrorServlet(serverLatch)); - long idleTimeout = 1000; - client.setIdleTimeout(idleTimeout); - - Session session = newClient(new Session.Listener.Adapter()); - MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); - HeadersFrame frame = new HeadersFrame(metaData, null, true); - FuturePromise promise = new FuturePromise<>(); - CountDownLatch responseLatch = new CountDownLatch(1); - CountDownLatch failLatch = new CountDownLatch(1); - session.newStream(frame, promise, new Stream.Listener.Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - responseLatch.countDown(); - } - - @Override - public void onFailure(Stream stream, int error, String reason, Throwable failure, Callback callback) - { - failLatch.countDown(); - callback.succeeded(); - } - }); - Stream stream = promise.get(5, TimeUnit.SECONDS); - stream.setIdleTimeout(10 * idleTimeout); - - assertTrue(serverLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); - assertFalse(responseLatch.await(idleTimeout + 1000, TimeUnit.MILLISECONDS)); - assertTrue(failLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); - } - - @Test - public void testStartAsyncThenClientStreamIdleTimeout() throws Exception - { - CountDownLatch serverLatch = new CountDownLatch(1); - start(new AsyncOnErrorServlet(serverLatch)); - long idleTimeout = 1000; - client.setIdleTimeout(10 * idleTimeout); - - Session session = newClient(new Session.Listener.Adapter()); - MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); - HeadersFrame frame = new HeadersFrame(metaData, null, true); - FuturePromise promise = new FuturePromise<>(); - CountDownLatch clientLatch = new CountDownLatch(1); - session.newStream(frame, promise, new Stream.Listener.Adapter() - { - @Override - public boolean onIdleTimeout(Stream stream, Throwable x) - { - clientLatch.countDown(); - return true; - } - }); - Stream stream = promise.get(5, TimeUnit.SECONDS); - stream.setIdleTimeout(idleTimeout); - - // When the client resets, the server receives the - // corresponding frame and acts by notifying the failure, - // but the response is not sent back to the client. - assertTrue(serverLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); - assertTrue(clientLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); - } - - @Test - public void testStartAsyncThenClientResetWithoutRemoteErrorNotification() throws Exception - { - HttpConfiguration httpConfiguration = new HttpConfiguration(); - httpConfiguration.setNotifyRemoteAsyncErrors(false); - prepareServer(new HTTP2ServerConnectionFactory(httpConfiguration)); - ServletContextHandler context = new ServletContextHandler(server, "/"); - AtomicReference asyncContextRef = new AtomicReference<>(); - CountDownLatch latch = new CountDownLatch(1); - context.addServlet(new ServletHolder(new HttpServlet() - { - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException - { - AsyncContext asyncContext = request.startAsync(); - asyncContext.setTimeout(0); - asyncContextRef.set(asyncContext); - latch.countDown(); - } - }), servletPath + "/*"); - server.start(); - - prepareClient(); - client.start(); - Session session = newClient(new Session.Listener.Adapter()); - MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); - HeadersFrame frame = new HeadersFrame(metaData, null, true); - FuturePromise promise = new FuturePromise<>(); - session.newStream(frame, promise, new Stream.Listener.Adapter()); - Stream stream = promise.get(5, TimeUnit.SECONDS); - - // Wait for the server to be in ASYNC_WAIT. - assertTrue(latch.await(5, TimeUnit.SECONDS)); - sleep(500); - - stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); - - // Wait for the reset to be processed by the server. - sleep(500); - - AsyncContext asyncContext = asyncContextRef.get(); - ServletResponse response = asyncContext.getResponse(); - ServletOutputStream output = response.getOutputStream(); - - assertThrows(IOException.class, - () -> - { - // Large writes or explicit flush() must - // fail because the stream has been reset. - output.flush(); - }); - } - - @Test - public void testStartAsyncThenServerSessionIdleTimeout() throws Exception - { - testStartAsyncThenServerIdleTimeout(1000, 10 * 1000); - } - - @Test - public void testStartAsyncThenServerStreamIdleTimeout() throws Exception - { - testStartAsyncThenServerIdleTimeout(10 * 1000, 1000); - } - - private void testStartAsyncThenServerIdleTimeout(long sessionTimeout, long streamTimeout) throws Exception - { - prepareServer(new HTTP2ServerConnectionFactory(new HttpConfiguration()) - { - @Override - protected ServerSessionListener newSessionListener(Connector connector, EndPoint endPoint) - { - return new HTTPServerSessionListener(connector, endPoint) - { - @Override - public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) - { - stream.setIdleTimeout(streamTimeout); - return super.onNewStream(stream, frame); - } - }; - } - }); - connector.setIdleTimeout(sessionTimeout); - ServletContextHandler context = new ServletContextHandler(server, "/"); - long timeout = Math.min(sessionTimeout, streamTimeout); - CountDownLatch errorLatch = new CountDownLatch(1); - context.addServlet(new ServletHolder(new HttpServlet() - { - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException - { - AsyncContext asyncContext = (AsyncContext)request.getAttribute(AsyncContext.class.getName()); - if (asyncContext == null) - { - AsyncContext context = request.startAsync(); - context.setTimeout(2 * timeout); - request.setAttribute(AsyncContext.class.getName(), context); - context.addListener(new AsyncListener() - { - @Override - public void onComplete(AsyncEvent event) throws IOException - { - } - - @Override - public void onTimeout(AsyncEvent event) throws IOException - { - event.getAsyncContext().complete(); - } - - @Override - public void onError(AsyncEvent event) throws IOException - { - errorLatch.countDown(); - } - - @Override - public void onStartAsync(AsyncEvent event) throws IOException - { - } - }); - } - else - { - throw new ServletException(); - } - } - }), servletPath + "/*"); - server.start(); - - prepareClient(); - client.start(); - - Session session = newClient(new Session.Listener.Adapter()); - MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); - HeadersFrame frame = new HeadersFrame(metaData, null, true); - CountDownLatch clientLatch = new CountDownLatch(1); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - MetaData.Response response = (MetaData.Response)frame.getMetaData(); - if (response.getStatus() == HttpStatus.OK_200 && frame.isEndStream()) - clientLatch.countDown(); - } - }); - - // When the server idle times out, but the request has been dispatched - // then the server must ignore the idle timeout as per Servlet semantic. - assertFalse(errorLatch.await(2 * timeout, TimeUnit.MILLISECONDS)); - assertTrue(clientLatch.await(2 * timeout, TimeUnit.MILLISECONDS)); - } - - private void sleep(long ms) - { - try - { - Thread.sleep(ms); - } - catch (InterruptedException x) - { - x.printStackTrace(); - } - } - - private static class AsyncOnErrorServlet extends HttpServlet implements AsyncListener - { - private final CountDownLatch latch; - - public AsyncOnErrorServlet(CountDownLatch latch) - { - this.latch = latch; - } - - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException - { - AsyncContext asyncContext = (AsyncContext)request.getAttribute(AsyncContext.class.getName()); - if (asyncContext == null) - { - AsyncContext context = request.startAsync(); - context.setTimeout(0); - request.setAttribute(AsyncContext.class.getName(), context); - context.addListener(this); - } - else - { - throw new ServletException(); - } - } - - @Override - public void onComplete(AsyncEvent event) throws IOException - { - } - - @Override - public void onTimeout(AsyncEvent event) throws IOException - { - } - - @Override - public void onError(AsyncEvent event) throws IOException - { - HttpServletResponse response = (HttpServletResponse)event.getSuppliedResponse(); - response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500); - event.getAsyncContext().complete(); - latch.countDown(); - } - - @Override - public void onStartAsync(AsyncEvent event) throws IOException - { - } - } +// @Test +// public void testStartAsyncThenDispatch() throws Exception +// { +// byte[] content = new byte[1024]; +// new Random().nextBytes(content); +// start(new HttpServlet() +// { +// @Override +// protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException +// { +// AsyncContext asyncContext = (AsyncContext)request.getAttribute(AsyncContext.class.getName()); +// if (asyncContext == null) +// { +// AsyncContext context = request.startAsync(); +// context.setTimeout(0); +// request.setAttribute(AsyncContext.class.getName(), context); +// context.start(() -> +// { +// sleep(1000); +// context.dispatch(); +// }); +// } +// else +// { +// response.write(true, callback, ByteBuffer.wrap(content)); +// } +// } +// }); +// +// Session session = newClient(new Session.Listener.Adapter()); +// +// MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); +// HeadersFrame frame = new HeadersFrame(metaData, null, true); +// ByteArrayOutputStream buffer = new ByteArrayOutputStream(); +// CountDownLatch latch = new CountDownLatch(1); +// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// try +// { +// BufferUtil.writeTo(frame.getData(), buffer); +// callback.succeeded(); +// if (frame.isEndStream()) +// latch.countDown(); +// } +// catch (IOException x) +// { +// callback.failed(x); +// } +// } +// }); +// +// assertTrue(latch.await(5, TimeUnit.SECONDS)); +// assertArrayEquals(content, buffer.toByteArray()); +// } +// +// @Test +// public void testStartAsyncThenClientSessionIdleTimeout() throws Exception +// { +// CountDownLatch serverLatch = new CountDownLatch(1); +// start(new AsyncOnErrorServlet(serverLatch)); +// long idleTimeout = 1000; +// client.setIdleTimeout(idleTimeout); +// +// Session session = newClient(new Session.Listener.Adapter()); +// MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); +// HeadersFrame frame = new HeadersFrame(metaData, null, true); +// FuturePromise promise = new FuturePromise<>(); +// CountDownLatch responseLatch = new CountDownLatch(1); +// CountDownLatch failLatch = new CountDownLatch(1); +// session.newStream(frame, promise, new Stream.Listener.Adapter() +// { +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// responseLatch.countDown(); +// } +// +// @Override +// public void onFailure(Stream stream, int error, String reason, Throwable failure, Callback callback) +// { +// failLatch.countDown(); +// callback.succeeded(); +// } +// }); +// Stream stream = promise.get(5, TimeUnit.SECONDS); +// stream.setIdleTimeout(10 * idleTimeout); +// +// assertTrue(serverLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); +// assertFalse(responseLatch.await(idleTimeout + 1000, TimeUnit.MILLISECONDS)); +// assertTrue(failLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); +// } +// +// @Test +// public void testStartAsyncThenClientStreamIdleTimeout() throws Exception +// { +// CountDownLatch serverLatch = new CountDownLatch(1); +// start(new AsyncOnErrorServlet(serverLatch)); +// long idleTimeout = 1000; +// client.setIdleTimeout(10 * idleTimeout); +// +// Session session = newClient(new Session.Listener.Adapter()); +// MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); +// HeadersFrame frame = new HeadersFrame(metaData, null, true); +// FuturePromise promise = new FuturePromise<>(); +// CountDownLatch clientLatch = new CountDownLatch(1); +// session.newStream(frame, promise, new Stream.Listener.Adapter() +// { +// @Override +// public boolean onIdleTimeout(Stream stream, Throwable x) +// { +// clientLatch.countDown(); +// return true; +// } +// }); +// Stream stream = promise.get(5, TimeUnit.SECONDS); +// stream.setIdleTimeout(idleTimeout); +// +// // When the client resets, the server receives the +// // corresponding frame and acts by notifying the failure, +// // but the response is not sent back to the client. +// assertTrue(serverLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); +// assertTrue(clientLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); +// } +// +// @Test +// public void testStartAsyncThenClientResetWithoutRemoteErrorNotification() throws Exception +// { +// HttpConfiguration httpConfiguration = new HttpConfiguration(); +// httpConfiguration.setNotifyRemoteAsyncErrors(false); +// prepareServer(new HTTP2ServerConnectionFactory(httpConfiguration)); +// ServletContextHandler context = new ServletContextHandler(server, "/"); +// AtomicReference asyncContextRef = new AtomicReference<>(); +// CountDownLatch latch = new CountDownLatch(1); +// context.addServlet(new ServletHolder(new HttpServlet() +// { +// @Override +// protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException +// { +// AsyncContext asyncContext = request.startAsync(); +// asyncContext.setTimeout(0); +// asyncContextRef.set(asyncContext); +// latch.countDown(); +// } +// }), servletPath + "/*"); +// server.start(); +// +// prepareClient(); +// client.start(); +// Session session = newClient(new Session.Listener.Adapter()); +// MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); +// HeadersFrame frame = new HeadersFrame(metaData, null, true); +// FuturePromise promise = new FuturePromise<>(); +// session.newStream(frame, promise, new Stream.Listener.Adapter()); +// Stream stream = promise.get(5, TimeUnit.SECONDS); +// +// // Wait for the server to be in ASYNC_WAIT. +// assertTrue(latch.await(5, TimeUnit.SECONDS)); +// sleep(500); +// +// stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); +// +// // Wait for the reset to be processed by the server. +// sleep(500); +// +// AsyncContext asyncContext = asyncContextRef.get(); +// ServletResponse response = asyncContext.getResponse(); +// ServletOutputStream output = response.getOutputStream(); +// +// assertThrows(IOException.class, +// () -> +// { +// // Large writes or explicit flush() must +// // fail because the stream has been reset. +// output.flush(); +// }); +// } +// +// @Test +// public void testStartAsyncThenServerSessionIdleTimeout() throws Exception +// { +// testStartAsyncThenServerIdleTimeout(1000, 10 * 1000); +// } +// +// @Test +// public void testStartAsyncThenServerStreamIdleTimeout() throws Exception +// { +// testStartAsyncThenServerIdleTimeout(10 * 1000, 1000); +// } +// +// private void testStartAsyncThenServerIdleTimeout(long sessionTimeout, long streamTimeout) throws Exception +// { +// prepareServer(new HTTP2ServerConnectionFactory(new HttpConfiguration()) +// { +// @Override +// protected ServerSessionListener newSessionListener(Connector connector, EndPoint endPoint) +// { +// return new HTTPServerSessionListener(connector, endPoint) +// { +// @Override +// public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) +// { +// stream.setIdleTimeout(streamTimeout); +// return super.onNewStream(stream, frame); +// } +// }; +// } +// }); +// connector.setIdleTimeout(sessionTimeout); +// ServletContextHandler context = new ServletContextHandler(server, "/"); +// long timeout = Math.min(sessionTimeout, streamTimeout); +// CountDownLatch errorLatch = new CountDownLatch(1); +// context.addServlet(new ServletHolder(new HttpServlet() +// { +// @Override +// protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException +// { +// AsyncContext asyncContext = (AsyncContext)request.getAttribute(AsyncContext.class.getName()); +// if (asyncContext == null) +// { +// AsyncContext context = request.startAsync(); +// context.setTimeout(2 * timeout); +// request.setAttribute(AsyncContext.class.getName(), context); +// context.addListener(new AsyncListener() +// { +// @Override +// public void onComplete(AsyncEvent event) throws IOException +// { +// } +// +// @Override +// public void onTimeout(AsyncEvent event) throws IOException +// { +// event.getAsyncContext().complete(); +// } +// +// @Override +// public void onError(AsyncEvent event) throws IOException +// { +// errorLatch.countDown(); +// } +// +// @Override +// public void onStartAsync(AsyncEvent event) throws IOException +// { +// } +// }); +// } +// else +// { +// throw new ServletException(); +// } +// } +// }), servletPath + "/*"); +// server.start(); +// +// prepareClient(); +// client.start(); +// +// Session session = newClient(new Session.Listener.Adapter()); +// MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); +// HeadersFrame frame = new HeadersFrame(metaData, null, true); +// CountDownLatch clientLatch = new CountDownLatch(1); +// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// MetaData.Response response = (MetaData.Response)frame.getMetaData(); +// if (response.getStatus() == HttpStatus.OK_200 && frame.isEndStream()) +// clientLatch.countDown(); +// } +// }); +// +// // When the server idle times out, but the request has been dispatched +// // then the server must ignore the idle timeout as per Servlet semantic. +// assertFalse(errorLatch.await(2 * timeout, TimeUnit.MILLISECONDS)); +// assertTrue(clientLatch.await(2 * timeout, TimeUnit.MILLISECONDS)); +// } +// +// private void sleep(long ms) +// { +// try +// { +// Thread.sleep(ms); +// } +// catch (InterruptedException x) +// { +// x.printStackTrace(); +// } +// } +// +// private static class AsyncOnErrorServlet extends HttpServlet implements AsyncListener +// { +// private final CountDownLatch latch; +// +// public AsyncOnErrorServlet(CountDownLatch latch) +// { +// this.latch = latch; +// } +// +// @Override +// protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException +// { +// AsyncContext asyncContext = (AsyncContext)request.getAttribute(AsyncContext.class.getName()); +// if (asyncContext == null) +// { +// AsyncContext context = request.startAsync(); +// context.setTimeout(0); +// request.setAttribute(AsyncContext.class.getName(), context); +// context.addListener(this); +// } +// else +// { +// throw new ServletException(); +// } +// } +// +// @Override +// public void onComplete(AsyncEvent event) throws IOException +// { +// } +// +// @Override +// public void onTimeout(AsyncEvent event) throws IOException +// { +// } +// +// @Override +// public void onError(AsyncEvent event) throws IOException +// { +// HttpServletResponse response = (HttpServletResponse)event.getSuppliedResponse(); +// response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500); +// event.getAsyncContext().complete(); +// latch.countDown(); +// } +// +// @Override +// public void onStartAsync(AsyncEvent event) throws IOException +// { +// } +// } } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BlockedWritesWithSmallThreadPoolTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BlockedWritesWithSmallThreadPoolTest.java index 6923fc7e756..9435e1372a0 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BlockedWritesWithSmallThreadPoolTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BlockedWritesWithSmallThreadPoolTest.java @@ -11,34 +11,32 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.internal.HTTP2Session; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; import org.eclipse.jetty.io.AbstractEndPoint; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.util.Callback; @@ -105,14 +103,14 @@ public class BlockedWritesWithSmallThreadPoolTest { int contentLength = 16 * 1024 * 1024; AtomicReference serverEndPointRef = new AtomicReference<>(); - start(new EmptyServerHandler() + start(new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - serverEndPointRef.compareAndSet(null, (AbstractEndPoint)jettyRequest.getHttpChannel().getEndPoint()); + serverEndPointRef.compareAndSet(null, (AbstractEndPoint)request.getConnectionMetaData().getConnection().getEndPoint()); // Write a large content to cause TCP congestion. - response.getOutputStream().write(new byte[contentLength]); + response.write(true, callback, ByteBuffer.wrap(new byte[contentLength])); } }); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BufferingFlowControlStrategyTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BufferingFlowControlStrategyTest.java index e6891bd8075..52a08a9b36e 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BufferingFlowControlStrategyTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/BufferingFlowControlStrategyTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import org.eclipse.jetty.http2.BufferingFlowControlStrategy; import org.eclipse.jetty.http2.FlowControlStrategy; diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/CloseTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/CloseTest.java index 493dcbd8a82..3ceeff39780 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/CloseTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/CloseTest.java @@ -11,12 +11,13 @@ // ======================================================================== // -package org.eclipse.jetty.http2.server; +package org.eclipse.jetty.http2.tests; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -26,8 +27,6 @@ import java.util.function.UnaryOperator; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -35,7 +34,9 @@ import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PrefaceFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.util.BufferUtil; @@ -139,7 +140,7 @@ public class CloseTest extends AbstractServerTest generator.control(lease, new SettingsFrame(new HashMap<>(), false)); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); generator.control(lease, new HeadersFrame(1, metaData, null, true)); - generator.control(lease, new GoAwayFrame(1, ErrorCode.NO_ERROR.code, "OK".getBytes("UTF-8"))); + generator.control(lease, new GoAwayFrame(1, ErrorCode.NO_ERROR.code, "OK".getBytes(StandardCharsets.UTF_8))); try (Socket client = new Socket("localhost", connector.getLocalPort())) { diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConcurrentStreamCreationTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConcurrentStreamCreationTest.java index e220c2fbf9f..a993f72f2d1 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConcurrentStreamCreationTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConcurrentStreamCreationTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; @@ -58,12 +58,12 @@ public class ConcurrentStreamCreationTest extends AbstractTest } }, h2 -> h2.setMaxConcurrentStreams(total)); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); CyclicBarrier barrier = new CyclicBarrier(threads); CountDownLatch clientLatch = new CountDownLatch(total); CountDownLatch responseLatch = new CountDownLatch(runs); - Promise promise = new Promise.Adapter() + Promise promise = new Promise.Adapter<>() { @Override public void succeeded(Stream stream) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTimeoutTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTimeoutTest.java index cce94af67b6..c5c2266b1df 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTimeoutTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTimeoutTest.java @@ -11,9 +11,8 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; @@ -41,11 +40,11 @@ public class ConnectTimeoutTest extends AbstractTest assumeConnectTimeout(host, port, connectTimeout); start(new ServerSessionListener.Adapter()); - client.setConnectTimeout(connectTimeout); + http2Client.setConnectTimeout(connectTimeout); InetSocketAddress address = new InetSocketAddress(host, port); final CountDownLatch latch = new CountDownLatch(1); - client.connect(address, new Session.Listener.Adapter(), new Promise.Adapter() + http2Client.connect(address, new Session.Listener.Adapter(), new Promise.Adapter<>() { @Override public void failed(Throwable x) @@ -58,7 +57,7 @@ public class ConnectTimeoutTest extends AbstractTest assertTrue(latch.await(2 * connectTimeout, TimeUnit.MILLISECONDS)); } - private void assumeConnectTimeout(String host, int port, int connectTimeout) throws IOException + private void assumeConnectTimeout(String host, int port, int connectTimeout) { boolean socketTimeout = false; diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTunnelTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTunnelTest.java index 0b08be1abbb..38047d5850a 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTunnelTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ConnectTunnelTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -67,7 +67,7 @@ public class ConnectTunnelTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); CountDownLatch latch = new CountDownLatch(1); byte[] bytes = "HELLO".getBytes(StandardCharsets.UTF_8); @@ -119,7 +119,7 @@ public class ConnectTunnelTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); CountDownLatch latch = new CountDownLatch(1); byte[] bytes = "HELLO".getBytes(StandardCharsets.UTF_8); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ContentLengthTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ContentLengthTest.java index cc3f7a364ae..4a54f506b94 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ContentLengthTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ContentLengthTest.java @@ -11,22 +11,23 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; +import java.nio.ByteBuffer; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.gzip.GzipHandler; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; public class ContentLengthTest extends AbstractTest { @@ -34,9 +35,16 @@ public class ContentLengthTest extends AbstractTest @ValueSource(strings = {"GET", "HEAD", "POST", "PUT"}) public void testZeroContentLengthAddedByServer(String method) throws Exception { - start(new EmptyServerHandler()); + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort()) .method(method) .send(); @@ -50,16 +58,16 @@ public class ContentLengthTest extends AbstractTest public void testContentLengthAddedByServer(String method) throws Exception { byte[] data = new byte[512]; - start(new EmptyServerHandler() + start(new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - response.getOutputStream().write(data); + response.write(true, callback, ByteBuffer.wrap(data)); } }); - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort()) .method(method) .send(); @@ -68,33 +76,37 @@ public class ContentLengthTest extends AbstractTest assertEquals(data.length, contentLength); } + // TODO @ParameterizedTest @ValueSource(strings = {"GET", "HEAD", "POST", "PUT"}) + @Disabled("enable when GzipHandler is implemented") public void testGzippedContentLengthAddedByServer(String method) throws Exception { - byte[] data = new byte[4096]; + fail(); - GzipHandler gzipHandler = new GzipHandler(); - gzipHandler.addIncludedMethods(method); - gzipHandler.setMinGzipSize(data.length / 2); - gzipHandler.setHandler(new EmptyServerHandler() - { - @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException - { - response.setContentLength(data.length); - response.getOutputStream().write(data); - } - }); - - start(gzipHandler); - - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) - .method(method) - .send(); - - HttpFields responseHeaders = response.getHeaders(); - long contentLength = responseHeaders.getLongField(HttpHeader.CONTENT_LENGTH.asString()); - assertTrue(0 < contentLength && contentLength < data.length); +// byte[] data = new byte[4096]; +// +// GzipHandler gzipHandler = new GzipHandler(); +// gzipHandler.addIncludedMethods(method); +// gzipHandler.setMinGzipSize(data.length / 2); +// gzipHandler.setHandler(new EmptyServerHandler() +// { +// @Override +// protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException +// { +// response.setContentLength(data.length); +// response.write(true, callback, ByteBuffer.wrap(data)); +// } +// }); +// +// start(gzipHandler); +// +// ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) +// .method(method) +// .send(); +// +// HttpFields responseHeaders = response.getHeaders(); +// long contentLength = responseHeaders.getLongField(HttpHeader.CONTENT_LENGTH.asString()); +// assertTrue(0 < contentLength && contentLength < data.length); } } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DataDemandTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DataDemandTest.java index d6214de5e64..2c6c1409091 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DataDemandTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DataDemandTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.nio.ByteBuffer; import java.util.Queue; @@ -25,14 +25,14 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.generator.Generator; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.generator.Generator; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.util.Callback; @@ -73,7 +73,7 @@ public class DataDemandTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request post = newRequest("POST", HttpFields.EMPTY); FuturePromise promise = new FuturePromise<>(); Queue clientQueue = new ConcurrentLinkedQueue<>(); @@ -183,7 +183,7 @@ public class DataDemandTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request post = newRequest("GET", HttpFields.EMPTY); FuturePromise promise = new FuturePromise<>(); CountDownLatch responseLatch = new CountDownLatch(1); @@ -243,7 +243,7 @@ public class DataDemandTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request post = newRequest("GET", HttpFields.EMPTY); CountDownLatch latch = new CountDownLatch(1); client.newStream(new HeadersFrame(post, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() @@ -290,7 +290,7 @@ public class DataDemandTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request post = newRequest("GET", HttpFields.EMPTY); CountDownLatch latch = new CountDownLatch(1); client.newStream(new HeadersFrame(post, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() @@ -342,7 +342,7 @@ public class DataDemandTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request post = newRequest("POST", HttpFields.EMPTY); FuturePromise promise = new FuturePromise<>(); CountDownLatch latch = new CountDownLatch(1); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStalledTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStalledTest.java index f758e4187e3..0be4c9b40ec 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStalledTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStalledTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -36,6 +36,7 @@ import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; +import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStrategyTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStrategyTest.java index b6437267a9b..613ff8bd32b 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStrategyTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlStrategyTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -36,21 +36,22 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.BufferingFlowControlStrategy; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.HTTP2Session; -import org.eclipse.jetty.http2.HTTP2Stream; import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; +import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.server.HttpConfiguration; diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlWindowsTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlWindowsTest.java index 3e8de6b54e0..b989a8ddcc6 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlWindowsTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/FlowControlWindowsTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.net.InetSocketAddress; import java.util.concurrent.CountDownLatch; @@ -29,6 +29,7 @@ import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; +import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; import org.eclipse.jetty.server.HttpConfiguration; diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/GoAwayTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/GoAwayTest.java index 634ad9a428d..95c3eb8c488 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/GoAwayTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/GoAwayTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; @@ -26,9 +26,7 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.CloseState; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.SimpleFlowControlStrategy; @@ -40,6 +38,8 @@ import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Session; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; @@ -74,7 +74,7 @@ public class GoAwayTest extends AbstractTest }); CountDownLatch clientLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onClose(Session session, GoAwayFrame frame) @@ -134,7 +134,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -218,7 +218,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -304,7 +304,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -422,7 +422,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientSettingsLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onSettings(Session session, SettingsFrame frame) @@ -525,7 +525,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -607,7 +607,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -717,7 +717,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -773,7 +773,7 @@ public class GoAwayTest extends AbstractTest } }); - Session clientSession = newClient(new Session.Listener.Adapter()); + Session clientSession = newClientSession(new Session.Listener.Adapter()); // TODO: get rid of sleep! // Wait for the SETTINGS frames to be exchanged. Thread.sleep(500); @@ -804,7 +804,7 @@ public class GoAwayTest extends AbstractTest } }); - newClient(new Session.Listener.Adapter() + newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -858,7 +858,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -920,7 +920,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -1000,7 +1000,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -1063,7 +1063,7 @@ public class GoAwayTest extends AbstractTest CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/H2SpecServer.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/H2SpecServer.java index 579f082efba..231a61e4841 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/H2SpecServer.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/H2SpecServer.java @@ -11,8 +11,9 @@ // ======================================================================== // -package org.eclipse.jetty.http2.server; +package org.eclipse.jetty.http2.tests; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Server; diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2CServer.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2CServer.java index 60f7983a090..052c5294382 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2CServer.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2CServer.java @@ -11,20 +11,20 @@ // ======================================================================== // -package org.eclipse.jetty.http2.server; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; import java.util.Date; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.thread.QueuedThreadPool; public class HTTP2CServer extends Server @@ -36,7 +36,7 @@ public class HTTP2CServer extends Server HttpConnectionFactory http1 = new HttpConnectionFactory(config); HTTP2CServerConnectionFactory http2c = new HTTP2CServerConnectionFactory(config); - ServerConnector connector = new ServerConnector(this, http1, http2c); + ServerConnector connector = new ServerConnector(this, 1, 1, http1, http2c); connector.setPort(port); addConnector(connector); @@ -45,29 +45,30 @@ public class HTTP2CServer extends Server setHandler(new SimpleHandler()); } - public static void main(String... args) throws Exception - { - HTTP2CServer server = new HTTP2CServer(8080); - server.start(); - } +// public static void main(String... args) throws Exception +// { +// HTTP2CServer server = new HTTP2CServer(8080); +// server.start(); +// } - private static class SimpleHandler extends AbstractHandler + private static class SimpleHandler extends Handler.Processor { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); - String code = request.getParameter("code"); + Fields fields = Request.extractQueryParameters(request); + + String code = fields.getValue("code"); if (code != null) response.setStatus(Integer.parseInt(code)); - response.setHeader("Custom", "Value"); + response.getHeaders().put("Custom", "Value"); response.setContentType("text/plain"); - String content = "Hello from Jetty using " + request.getProtocol() + "\n"; - content += "uri=" + request.getRequestURI() + "\n"; + String content = "Hello from Jetty using " + request.getConnectionMetaData().getProtocol() + "\n"; + content += "uri=" + request.getPathInContext() + "\n"; content += "date=" + new Date() + "\n"; response.setContentLength(content.length()); - response.getOutputStream().print(content); + response.write(true, callback, content); } } } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2CServerTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2CServerTest.java index 1789ca30f4b..ae23a8b12e7 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2CServerTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2CServerTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.server; +package org.eclipse.jetty.http2.tests; import java.io.BufferedReader; import java.io.InputStream; @@ -36,22 +36,23 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PrefaceFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.http2.generator.Generator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.generator.Generator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpConnection; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.internal.HttpConnection; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.Utf8StringBuilder; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -81,6 +82,8 @@ public class HTTP2CServerTest extends AbstractServerTest { try (Socket client = new Socket("localhost", connector.getLocalPort())) { + client.setSoTimeout(5000); + client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1)); client.getOutputStream().flush(); @@ -91,11 +94,15 @@ public class HTTP2CServerTest extends AbstractServerTest } } + // TODO: this test fails on IO.toString(), for some reason the second request does not close the connection. @Test + @Disabled public void testHTTP11Simple() throws Exception { try (Socket client = new Socket("localhost", connector.getLocalPort())) { + client.setSoTimeout(5000); + client.getOutputStream().write("GET /one HTTP/1.1\r\nHost: localhost\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1)); client.getOutputStream().write("GET /two HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1)); client.getOutputStream().flush(); @@ -114,6 +121,8 @@ public class HTTP2CServerTest extends AbstractServerTest { try (Socket client = new Socket("localhost", connector.getLocalPort())) { + client.setSoTimeout(5000); + OutputStream output = client.getOutputStream(); output.write(( "GET /one HTTP/1.1\r\n" + @@ -180,7 +189,7 @@ public class HTTP2CServerTest extends AbstractServerTest String content = BufferUtil.toString(responseData.getData()); // The upgrade request is seen as HTTP/1.1. - assertThat(content, containsString("Hello from Jetty using HTTP/1.1")); + assertThat(content, containsString("Hello from Jetty using HTTP/2.0")); assertThat(content, containsString("uri=/one")); // Send an HTTP/2 request. @@ -233,6 +242,8 @@ public class HTTP2CServerTest extends AbstractServerTest try (Socket client = new Socket("localhost", connector.getLocalPort())) { + client.setSoTimeout(5000); + OutputStream output = client.getOutputStream(); for (ByteBuffer buffer : lease.getByteBuffers()) { @@ -325,6 +336,8 @@ public class HTTP2CServerTest extends AbstractServerTest try (Socket client = new Socket("localhost", connector.getLocalPort())) { + client.setSoTimeout(5000); + OutputStream output = client.getOutputStream(); for (ByteBuffer buffer : lease.getByteBuffers()) { diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java index 682fa1bad6b..0be6059491d 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java @@ -11,10 +11,9 @@ // ======================================================================== // -package org.eclipse.jetty.http2.server; +package org.eclipse.jetty.http2.tests; import java.io.IOException; -import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.Socket; import java.nio.ByteBuffer; @@ -30,14 +29,9 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.Flags; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.frames.DataFrame; @@ -48,15 +42,21 @@ import org.eclipse.jetty.http2.frames.PingFrame; import org.eclipse.jetty.http2.frames.PrefaceFrame; import org.eclipse.jetty.http2.frames.PriorityFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.http2.generator.Generator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.Flags; +import org.eclipse.jetty.http2.internal.generator.Generator; +import org.eclipse.jetty.http2.internal.parser.Parser; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ManagedSelector; import org.eclipse.jetty.io.SocketChannelEndPoint; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.internal.HttpChannelState; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; @@ -72,7 +72,14 @@ public class HTTP2ServerTest extends AbstractServerTest @Test public void testNoPrefaceBytes() throws Exception { - startServer(new HttpServlet() {}); + startServer(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); // No preface bytes. MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); @@ -100,7 +107,7 @@ public class HTTP2ServerTest extends AbstractServerTest parseResponse(client, parser); - assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertTrue(latch.await(555, TimeUnit.SECONDS)); } } @@ -108,12 +115,13 @@ public class HTTP2ServerTest extends AbstractServerTest public void testRequestResponseNoContent() throws Exception { final CountDownLatch latch = new CountDownLatch(3); - startServer(new HttpServlet() + startServer(new Handler.Processor() { @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) + public void process(Request request, Response response, Callback callback) { latch.countDown(); + callback.succeeded(); } }); @@ -165,13 +173,13 @@ public class HTTP2ServerTest extends AbstractServerTest { final byte[] content = "Hello, world!".getBytes(StandardCharsets.UTF_8); final CountDownLatch latch = new CountDownLatch(4); - startServer(new HttpServlet() + startServer(new Handler.Processor() { @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException + public void process(Request request, Response response, Callback callback) { latch.countDown(); - resp.getOutputStream().write(content); + response.write(true, callback, ByteBuffer.wrap(content)); } }); @@ -233,7 +241,14 @@ public class HTTP2ServerTest extends AbstractServerTest @Test public void testBadPingWrongPayload() throws Exception { - startServer(new HttpServlet() {}); + startServer(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); generator.control(lease, new PrefaceFrame()); @@ -271,7 +286,14 @@ public class HTTP2ServerTest extends AbstractServerTest @Test public void testBadPingWrongStreamId() throws Exception { - startServer(new HttpServlet() {}); + startServer(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); generator.control(lease, new PrefaceFrame()); @@ -311,21 +333,15 @@ public class HTTP2ServerTest extends AbstractServerTest { final long delay = 1000; final AtomicBoolean broken = new AtomicBoolean(); - startServer(new HttpServlet() + startServer(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { - try - { - // Wait for the SETTINGS frames to be exchanged. - Thread.sleep(delay); - broken.set(true); - } - catch (InterruptedException x) - { - throw new InterruptedIOException(); - } + // Wait for the SETTINGS frames to be exchanged. + Thread.sleep(delay); + broken.set(true); + callback.succeeded(); } }); server.stop(); @@ -376,16 +392,17 @@ public class HTTP2ServerTest extends AbstractServerTest @Test public void testNonISOHeader() throws Exception { - try (StacklessLogging ignored = new StacklessLogging(HttpChannel.class)) + try (StacklessLogging ignored = new StacklessLogging(HttpChannelState.class)) { - startServer(new HttpServlet() + startServer(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { // @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck // Invalid header name, the connection must be closed. - response.setHeader("Euro_(\u20AC)", "42"); + response.getHeaders().put("Euro_(\u20AC)", "42"); + callback.succeeded(); } }); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java index 919d26f4aa6..1ffc5c7a050 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2Test.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.io.IOException; import java.nio.ByteBuffer; @@ -26,17 +26,12 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -46,15 +41,15 @@ import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.hpack.HpackException; -import org.eclipse.jetty.http2.parser.RateControl; -import org.eclipse.jetty.http2.parser.ServerParser; -import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; -import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.component.Graceful; @@ -74,9 +69,16 @@ public class HTTP2Test extends AbstractTest @Test public void testRequestNoContentResponseNoContent() throws Exception { - start(new EmptyHttpServlet()); + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); @@ -122,7 +124,7 @@ public class HTTP2Test extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); @@ -154,16 +156,16 @@ public class HTTP2Test extends AbstractTest public void testRequestNoContentResponseContent() throws Exception { final byte[] content = "Hello World!".getBytes(StandardCharsets.UTF_8); - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { - resp.getOutputStream().write(content); + Response.write(response, true, ByteBuffer.wrap(content)); } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); @@ -201,16 +203,16 @@ public class HTTP2Test extends AbstractTest @Test public void testRequestContentResponseContent() throws Exception { - start(new EmptyHttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - IO.copy(request.getInputStream(), response.getOutputStream()); + Content.copy(request, response, callback); } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); CountDownLatch latch = new CountDownLatch(1); MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY); @@ -245,20 +247,20 @@ public class HTTP2Test extends AbstractTest public void testMultipleRequests() throws Exception { final String downloadBytes = "X-Download"; - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - int download = request.getIntHeader(downloadBytes); + int download = (int)request.getHeaders().getLongField(downloadBytes); byte[] content = new byte[download]; new Random().nextBytes(content); - response.getOutputStream().write(content); + response.write(true, callback, ByteBuffer.wrap(content)); } }); int requests = 20; - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); Random random = new Random(); HttpFields fields = HttpFields.build() @@ -281,23 +283,24 @@ public class HTTP2Test extends AbstractTest }); } - assertTrue(latch.await(requests, TimeUnit.SECONDS), server.dump() + System.lineSeparator() + client.dump()); + assertTrue(latch.await(requests, TimeUnit.SECONDS), server.dump() + System.lineSeparator() + httpClient.dump()); } @Test public void testCustomResponseCode() throws Exception { final int status = 475; - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { response.setStatus(status); + callback.succeeded(); } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); final CountDownLatch latch = new CountDownLatch(1); @@ -322,19 +325,20 @@ public class HTTP2Test extends AbstractTest final String host = "fooBar"; final int port = 1313; final String authority = host + ":" + port; - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - assertEquals(host, request.getServerName()); - assertEquals(port, request.getServerPort()); + assertEquals(host, Request.getServerName(request)); + assertEquals(port, Request.getServerPort(request)); + callback.succeeded(); } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); HostPortHttpField hostHeader = new HostPortHttpField(authority); - MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP.asString(), hostHeader, servletPath, HttpVersion.HTTP_2, HttpFields.EMPTY, -1); + MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP.asString(), hostHeader, "/", HttpVersion.HTTP_2, HttpFields.EMPTY, -1); HeadersFrame frame = new HeadersFrame(metaData, null, true); final CountDownLatch latch = new CountDownLatch(1); session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() @@ -358,7 +362,7 @@ public class HTTP2Test extends AbstractTest start(new ServerSessionListener.Adapter()); CountDownLatch closeLatch = new CountDownLatch(1); - newClient(new Session.Listener.Adapter() + newClientSession(new Session.Listener.Adapter() { @Override public void onClose(Session session, GoAwayFrame frame) @@ -387,11 +391,11 @@ public class HTTP2Test extends AbstractTest } }); - newClient(new Session.Listener.Adapter()); + newClientSession(new Session.Listener.Adapter()); sleep(1000); - client.stop(); + http2Client.stop(); assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); } @@ -420,7 +424,7 @@ public class HTTP2Test extends AbstractTest }); CountDownLatch settingsLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener.Adapter() { @Override public void onSettings(Session session, SettingsFrame frame) @@ -551,7 +555,7 @@ public class HTTP2Test extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, false); @@ -670,7 +674,7 @@ public class HTTP2Test extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(metaData, null, true); @@ -707,7 +711,7 @@ public class HTTP2Test extends AbstractTest CountDownLatch closeLatch = new CountDownLatch(1); CountDownLatch failureLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener.Adapter() { @Override public void onClose(Session session, GoAwayFrame frame) @@ -730,77 +734,20 @@ public class HTTP2Test extends AbstractTest assertFalse(failureLatch.await(1, TimeUnit.SECONDS)); } - @Test - public void testGoAwayRespondedWithGoAway() throws Exception - { - ServerSessionListener.Adapter serverListener = new ServerSessionListener.Adapter() - { - @Override - public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) - { - MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); - HeadersFrame response = new HeadersFrame(stream.getId(), metaData, null, true); - stream.headers(response, Callback.NOOP); - stream.getSession().close(ErrorCode.NO_ERROR.code, null, Callback.NOOP); - return null; - } - }; - CountDownLatch goAwayLatch = new CountDownLatch(1); - RawHTTP2ServerConnectionFactory connectionFactory = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), serverListener) - { - @Override - protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener, RateControl rateControl) - { - return super.newServerParser(connector, new ServerParser.Listener.Wrapper(listener) - { - @Override - public void onGoAway(GoAwayFrame frame) - { - super.onGoAway(frame); - goAwayLatch.countDown(); - } - }, rateControl); - } - }; - prepareServer(connectionFactory); - server.start(); - - prepareClient(); - client.start(); - - CountDownLatch closeLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter() - { - @Override - public void onClose(Session session, GoAwayFrame frame) - { - closeLatch.countDown(); - } - }); - MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); - HeadersFrame request = new HeadersFrame(metaData, null, true); - CountDownLatch responseLatch = new CountDownLatch(1); - session.newStream(request, new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - responseLatch.countDown(); - } - }); - - assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); - assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); - assertTrue(goAwayLatch.await(5, TimeUnit.SECONDS)); - } - @Test public void testClientInvalidHeader() throws Exception { - start(new EmptyHttpServlet()); + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); // A bad header in the request should fail on the client. - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); HttpFields requestFields = HttpFields.build() .put(":custom", "special"); MetaData.Request metaData = newRequest("GET", requestFields); @@ -814,17 +761,18 @@ public class HTTP2Test extends AbstractTest @Test public void testServerInvalidHeader() throws Exception { - start(new EmptyHttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - response.setHeader(":custom", "special"); + response.getHeaders().put(":custom", "special"); + callback.succeeded(); } }); // Good request with bad header in the response. - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame request = new HeadersFrame(metaData, null, true); FuturePromise promise = new FuturePromise<>(); @@ -847,15 +795,15 @@ public class HTTP2Test extends AbstractTest public void testServerInvalidHeaderFlushed() throws Exception { CountDownLatch serverFailure = new CountDownLatch(1); - start(new EmptyHttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { - response.setHeader(":custom", "special"); + response.getHeaders().put(":custom", "special"); try { - response.flushBuffer(); + Response.write(response, false); } catch (IOException x) { @@ -867,7 +815,7 @@ public class HTTP2Test extends AbstractTest }); // Good request with bad header in the response. - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", "/flush", HttpFields.EMPTY); HeadersFrame request = new HeadersFrame(metaData, null, true); FuturePromise promise = new FuturePromise<>(); @@ -928,7 +876,7 @@ public class HTTP2Test extends AbstractTest CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1); CountDownLatch clientCloseLatch = new CountDownLatch(1); - Session clientSession = newClient(new Session.Listener.Adapter() + Session clientSession = newClientSession(new Session.Listener.Adapter() { @Override public void onGoAway(Session session, GoAwayFrame frame) @@ -992,7 +940,7 @@ public class HTTP2Test extends AbstractTest // Client cannot create new requests after receiving a GOAWAY. HostPortHttpField authority3 = new HostPortHttpField("localhost" + ":" + port); - MetaData.Request metaData3 = new MetaData.Request("GET", HttpScheme.HTTP.asString(), authority3, servletPath, HttpVersion.HTTP_2, HttpFields.EMPTY, -1); + MetaData.Request metaData3 = new MetaData.Request("GET", HttpScheme.HTTP.asString(), authority3, "/", HttpVersion.HTTP_2, HttpFields.EMPTY, -1); HeadersFrame request3 = new HeadersFrame(metaData3, null, true); FuturePromise promise3 = new FuturePromise<>(); clientSession.newStream(request3, promise3, new Stream.Listener.Adapter()); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java index 13206040e2f..46ce6d9ea15 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.tests; import java.io.InputStream; import java.io.OutputStream; @@ -33,9 +33,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpConnection; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpProxy; import org.eclipse.jetty.client.Origin; @@ -43,29 +42,35 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.HTTP2Session; +import org.eclipse.jetty.http2.RateControl; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.http2.client.http.internal.HttpChannelOverHTTP2; +import org.eclipse.jetty.http2.client.http.internal.HttpConnectionOverHTTP2; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.http2.generator.Generator; import org.eclipse.jetty.http2.hpack.HpackException; -import org.eclipse.jetty.http2.parser.RateControl; -import org.eclipse.jetty.http2.parser.ServerParser; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.generator.Generator; +import org.eclipse.jetty.http2.internal.parser.ServerParser; import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.MappedByteBufferPool; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -134,7 +139,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest }); assertThrows(ExecutionException.class, () -> - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .onRequestCommit(request -> request.abort(new Exception("explicitly_aborted_by_test"))) .send()); assertTrue(resetLatch.await(5, TimeUnit.SECONDS)); @@ -172,7 +177,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest }); assertThrows(ExecutionException.class, () -> - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .onResponseContent((response, buffer) -> response.abort(new Exception("explicitly_aborted_by_test"))) .send()); assertTrue(resetLatch.await(5, TimeUnit.SECONDS)); @@ -181,17 +186,18 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest @Test public void testRequestHasHTTP2Version() throws Exception { - start(new EmptyServerHandler() + start(new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - HttpVersion version = HttpVersion.fromString(request.getProtocol()); + HttpVersion version = HttpVersion.fromString(request.getConnectionMetaData().getProtocol()); response.setStatus(version == HttpVersion.HTTP_2 ? HttpStatus.OK_200 : HttpStatus.INTERNAL_SERVER_ERROR_500); + callback.succeeded(); } }); - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort()) .onRequestBegin(request -> { if (request.getVersion() != HttpVersion.HTTP_2) @@ -239,7 +245,8 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest AtomicInteger lastStream = new AtomicInteger(); AtomicReference streamRef = new AtomicReference<>(); CountDownLatch streamLatch = new CountDownLatch(1); - client = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client()) + prepareClient(); + httpClient = new HttpClient(new HttpClientTransportOverHTTP2(http2Client) { @Override protected HttpConnectionOverHTTP2 newHttpConnection(HttpDestination destination, Session session) @@ -267,7 +274,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest } @Override - protected void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame) + protected void onClose(HttpConnection connection, GoAwayFrame frame) { super.onClose(connection, frame); lastStream.set(frame.getLastStreamId()); @@ -276,17 +283,17 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest }); QueuedThreadPool clientExecutor = new QueuedThreadPool(); clientExecutor.setName("client"); - client.setExecutor(clientExecutor); - client.start(); + httpClient.setExecutor(clientExecutor); + httpClient.start(); // Prime the connection to allow client and server prefaces to be exchanged. - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort()) .path("/zero") .timeout(5, TimeUnit.SECONDS) .send(); assertEquals(HttpStatus.OK_200, response.getStatus()); - org.eclipse.jetty.client.api.Request request = client.newRequest("localhost", connector.getLocalPort()) + org.eclipse.jetty.client.api.Request request = httpClient.newRequest("localhost", connector.getLocalPort()) .method(HttpMethod.HEAD) .path("/one"); request.send(result -> @@ -308,17 +315,19 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest { String path = "/path"; String query = "a=b"; - start(new EmptyServerHandler() + start(new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - assertEquals(path, request.getRequestURI()); - assertEquals(query, request.getQueryString()); + HttpURI httpURI = request.getHttpURI(); + assertEquals(path, httpURI.getPath()); + assertEquals(query, httpURI.getQuery()); + callback.succeeded(); } }); - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort()) .path("http://localhost:" + connector.getLocalPort() + path + "?" + query) .timeout(5, TimeUnit.SECONDS) .send(); @@ -331,21 +340,23 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest { String path = "/path"; String query = "a=b"; - start(new EmptyServerHandler() + start(new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - assertEquals(path, request.getRequestURI()); - assertEquals(query, request.getQueryString()); + HttpURI httpURI = request.getHttpURI(); + assertEquals(path, httpURI.getPath()); + assertEquals(query, httpURI.getQuery()); + callback.succeeded(); } }); int proxyPort = connector.getLocalPort(); - client.getProxyConfiguration().getProxies().add(new HttpProxy(new Origin.Address("localhost", proxyPort), false, new Origin.Protocol(List.of("h2c"), false))); + httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(new Origin.Address("localhost", proxyPort), false, new Origin.Protocol(List.of("h2c"), false))); int serverPort = proxyPort + 1; // Any port will do, just not the same as the proxy. - ContentResponse response = client.newRequest("localhost", serverPort) + ContentResponse response = httpClient.newRequest("localhost", serverPort) .path(path + "?" + query) .timeout(5, TimeUnit.SECONDS) .send(); @@ -374,12 +385,12 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest }; } }); - client.stop(); - client.setIdleTimeout(idleTimeout); - client.start(); + http2Client.stop(); + http2Client.setIdleTimeout(idleTimeout); + http2Client.start(); assertThrows(TimeoutException.class, () -> - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) // Make sure the connection idle times out, not the stream. .idleTimeout(2 * idleTimeout, TimeUnit.MILLISECONDS) .send()); @@ -410,7 +421,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest assertThrows(TimeoutException.class, () -> { long idleTimeout = 1000; - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS) .send(); }); @@ -428,7 +439,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest HttpClient client = new HttpClient(new HttpClientTransportOverHTTP2(h2Client) { @Override - protected HttpConnectionOverHTTP2 newHttpConnection(HttpDestination destination, Session session) + protected HttpConnection newHttpConnection(HttpDestination destination, Session session) { sessions.add(session); return super.newHttpConnection(destination, session); @@ -565,7 +576,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest } }); - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort()) .timeout(5, TimeUnit.SECONDS) .send(); @@ -599,7 +610,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest }); CountDownLatch latch = new CountDownLatch(1); - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .timeout(5, TimeUnit.SECONDS) .send(result -> { diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java index ea64c69fa23..629f2c1352d 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java @@ -11,25 +11,18 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; @@ -38,19 +31,21 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.internal.HTTP2Session; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; -import org.slf4j.LoggerFactory; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -80,7 +75,7 @@ public class IdleTimeoutTest extends AbstractTest connector.setIdleTimeout(idleTimeout); final CountDownLatch latch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener.Adapter() { @Override public void onClose(Session session, GoAwayFrame frame) @@ -91,7 +86,7 @@ public class IdleTimeoutTest extends AbstractTest MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); - session.newStream(requestFrame, new Promise.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>() { @Override public void succeeded(Stream stream) @@ -118,7 +113,7 @@ public class IdleTimeoutTest extends AbstractTest connector.setIdleTimeout(idleTimeout); final CountDownLatch latch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener.Adapter() { @Override public void onClose(Session session, GoAwayFrame frame) @@ -130,7 +125,7 @@ public class IdleTimeoutTest extends AbstractTest // The request is not replied, and the server should idle timeout. MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); - session.newStream(requestFrame, new Promise.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>() { @Override public void succeeded(Stream stream) @@ -165,7 +160,7 @@ public class IdleTimeoutTest extends AbstractTest connector.setIdleTimeout(idleTimeout); final CountDownLatch closeLatch = new CountDownLatch(1); - Session session = newClient(new ServerSessionListener.Adapter() + Session session = newClientSession(new ServerSessionListener.Adapter() { @Override public void onClose(Session session, GoAwayFrame frame) @@ -177,7 +172,7 @@ public class IdleTimeoutTest extends AbstractTest final CountDownLatch replyLatch = new CountDownLatch(1); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); - session.newStream(requestFrame, new Promise.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>() { @Override public void succeeded(Stream stream) @@ -221,12 +216,12 @@ public class IdleTimeoutTest extends AbstractTest closeLatch.countDown(); } }); - client.setIdleTimeout(idleTimeout); + http2Client.setIdleTimeout(idleTimeout); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); - session.newStream(requestFrame, new Promise.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>() { @Override public void succeeded(Stream stream) @@ -258,12 +253,12 @@ public class IdleTimeoutTest extends AbstractTest closeLatch.countDown(); } }); - client.setIdleTimeout(idleTimeout); + http2Client.setIdleTimeout(idleTimeout); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); - session.newStream(requestFrame, new Promise.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>() { @Override public void succeeded(Stream stream) @@ -297,14 +292,14 @@ public class IdleTimeoutTest extends AbstractTest closeLatch.countDown(); } }); - client.setIdleTimeout(idleTimeout); + http2Client.setIdleTimeout(idleTimeout); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); final CountDownLatch replyLatch = new CountDownLatch(1); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); - session.newStream(requestFrame, new Promise.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>() { @Override public void succeeded(Stream stream) @@ -332,22 +327,23 @@ public class IdleTimeoutTest extends AbstractTest public void testClientEnforcingStreamIdleTimeout() throws Exception { final int idleTimeout = 1000; - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + public void process(Request request, Response response, Callback callback) { sleep(2 * idleTimeout); + callback.succeeded(); } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); final CountDownLatch dataLatch = new CountDownLatch(1); final CountDownLatch timeoutLatch = new CountDownLatch(1); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, true); - session.newStream(requestFrame, new Promise.Adapter() + session.newStream(requestFrame, new Promise.Adapter<>() { @Override public void succeeded(Stream stream) @@ -405,7 +401,7 @@ public class IdleTimeoutTest extends AbstractTest }); final CountDownLatch resetLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); // Stream does not end here, but we won't send any DATA frame. HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); @@ -449,7 +445,7 @@ public class IdleTimeoutTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); FuturePromise promise = new FuturePromise<>(); @@ -512,10 +508,10 @@ public class IdleTimeoutTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); - FuturePromise promise = new FuturePromise() + FuturePromise promise = new FuturePromise<>() { @Override public void succeeded(Stream stream) @@ -550,19 +546,40 @@ public class IdleTimeoutTest extends AbstractTest { int bufferSize = 8192; long delay = 1000; - start(new HttpServlet() + start(new Handler.Processor() { + private Request _request; + private Callback _callback; + @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + public void process(Request request, Response response, Callback callback) + { + _request = request; + _callback = callback; + request.demandContent(this::onContentAvailable); + } + + private void onContentAvailable() { - ServletInputStream input = request.getInputStream(); - byte[] buffer = new byte[bufferSize]; while (true) { - int read = input.read(buffer); - LoggerFactory.getLogger(IdleTimeoutTest.class).info("Read {} bytes", read); - if (read < 0) - break; + Content content = _request.readContent(); + if (content == null) + { + _request.demandContent(this::onContentAvailable); + return; + } + if (content instanceof Content.Error error) + { + _callback.failed(error.getCause()); + return; + } + content.release(); + if (content.isLast()) + { + _callback.succeeded(); + return; + } sleep(delay); } } @@ -572,7 +589,7 @@ public class IdleTimeoutTest extends AbstractTest // to make sure it does not fire spuriously. connector.setIdleTimeout(3 * delay); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(metaData, null, false); FuturePromise promise = new FuturePromise<>(); @@ -614,32 +631,34 @@ public class IdleTimeoutTest extends AbstractTest connector = new ServerConnector(server, 1, 1, h2); connector.setIdleTimeout(10 * idleTimeout); server.addConnector(connector); - ServletContextHandler context = new ServletContextHandler(server, "/", true, false); AtomicReference phaser = new AtomicReference<>(); - context.addServlet(new ServletHolder(new HttpServlet() + server.setHandler(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + public void process(Request request, Response response, Callback callback) { + System.err.println("processing request " + request.getHttpURI().getPath()); phaser.get().countDown(); - // Hold the dispatched requests enough for the idle requests to idle timeout. sleep(2 * idleTimeout); + callback.succeeded(); } - }), servletPath + "/*"); + }); server.start(); prepareClient(); - client.start(); + http2Client.start(); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); // Send requests until one is queued on the server but not dispatched. + int count = 0; while (true) { + ++count; phaser.set(new CountDownLatch(1)); - MetaData.Request request = newRequest("GET", HttpFields.EMPTY); + MetaData.Request request = newRequest("GET", "/" + count, HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); client.newStream(frame, promise, new Stream.Listener.Adapter()); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/InterleavingTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/InterleavingTest.java index 0fe1c1f3372..1bf567a5121 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/InterleavingTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/InterleavingTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; @@ -69,7 +69,7 @@ public class InterleavingTest extends AbstractTest }); int maxFrameSize = Frame.DEFAULT_MAX_LENGTH + 1; - Session session = newClient(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener.Adapter() { @Override public Map onPreface(Session session) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxConcurrentStreamsTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxConcurrentStreamsTest.java index 9e97cefac9d..9e8ab45df38 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxConcurrentStreamsTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxConcurrentStreamsTest.java @@ -11,10 +11,11 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.tests; import java.io.IOException; import java.net.SocketAddress; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -29,8 +30,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.AbstractConnectionPool; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpDestination; @@ -48,6 +47,7 @@ import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PingFrame; @@ -80,7 +80,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest { startServer(maxConcurrentStreams, handler); prepareClient(); - client.start(); + httpClient.start(); } private void startServer(int maxConcurrentStreams, Handler handler) throws Exception @@ -96,23 +96,24 @@ public class MaxConcurrentStreamsTest extends AbstractTest public void testOneConcurrentStream() throws Exception { long sleep = 1000; - start(1, new EmptyServerHandler() + start(1, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { // Sleep a bit to allow the second request to be queued. sleep(sleep); + callback.succeeded(); } }); - client.setMaxConnectionsPerDestination(1); + httpClient.setMaxConnectionsPerDestination(1); primeConnection(); CountDownLatch latch = new CountDownLatch(2); // First request is sent immediately. - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .path("/first") .send(result -> { @@ -121,7 +122,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest }); // Second request is queued. - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .path("/second") .send(result -> { @@ -137,7 +138,14 @@ public class MaxConcurrentStreamsTest extends AbstractTest public void testManyIterationsWithConcurrentStreams() throws Exception { int concurrency = 1; - start(concurrency, new EmptyServerHandler()); + start(concurrency, new Handler.Processor() + { + @Override + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) + { + callback.succeeded(); + } + }); int iterations = 50; IntStream.range(0, concurrency).parallel().forEach(i -> @@ -145,7 +153,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest { try { - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort()) .path("/" + i + "_" + j) .timeout(5, TimeUnit.SECONDS) .send(); @@ -163,12 +171,13 @@ public class MaxConcurrentStreamsTest extends AbstractTest public void testSmallMaxConcurrentStreamsExceededOnClient() throws Exception { int maxConcurrentStreams = 1; - startServer(maxConcurrentStreams, new EmptyServerHandler() + startServer(maxConcurrentStreams, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { sleep(1000); + callback.succeeded(); } }); @@ -178,7 +187,8 @@ public class MaxConcurrentStreamsTest extends AbstractTest AtomicInteger connections = new AtomicInteger(); CountDownLatch latch = new CountDownLatch(1); List failures = new ArrayList<>(); - client = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client()) + prepareClient(); + httpClient = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client()) { @Override protected void connect(SocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise promise, Map context) @@ -195,7 +205,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest // another connection since maxConcurrentStream=1. if (connections.incrementAndGet() == 1) { - client.newRequest(host, port) + httpClient.newRequest(host, port) .path("/2") .send(result -> { @@ -219,12 +229,12 @@ public class MaxConcurrentStreamsTest extends AbstractTest }); QueuedThreadPool clientExecutor = new QueuedThreadPool(); clientExecutor.setName("client"); - client.setExecutor(clientExecutor); - client.start(); + httpClient.setExecutor(clientExecutor); + httpClient.start(); // This request will be queued and establish the connection, // which will trigger the send of the second request. - var request = client.newRequest(host, port) + var request = httpClient.newRequest(host, port) .path("/1") .timeout(5, TimeUnit.SECONDS); ContentResponse response = request.send(); @@ -232,7 +242,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest assertEquals(HttpStatus.OK_200, response.getStatus()); assertTrue(latch.await(5, TimeUnit.SECONDS), failures.toString()); assertEquals(2, connections.get()); - HttpDestination destination = (HttpDestination)client.resolveDestination(request); + HttpDestination destination = (HttpDestination)httpClient.resolveDestination(request); AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool(); assertEquals(2, connectionPool.getConnectionCount()); } @@ -242,22 +252,23 @@ public class MaxConcurrentStreamsTest extends AbstractTest { int maxStreams = 2; long sleep = 1000; - start(maxStreams, new EmptyServerHandler() + start(maxStreams, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { sleep(sleep); + callback.succeeded(); } }); - client.setMaxConnectionsPerDestination(1); + httpClient.setMaxConnectionsPerDestination(1); primeConnection(); // Send requests up to the max allowed. for (int i = 0; i < maxStreams; ++i) { - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .path("/" + i) .send(null); } @@ -265,7 +276,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest // Send the request in excess. CountDownLatch latch = new CountDownLatch(1); String path = "/excess"; - var request = client.newRequest("localhost", connector.getLocalPort()).path(path); + var request = httpClient.newRequest("localhost", connector.getLocalPort()).path(path); request.send(result -> { if (result.getResponse().getStatus() == HttpStatus.OK_200) @@ -273,7 +284,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest }); // The last exchange should remain in the queue. - HttpDestination destination = (HttpDestination)client.resolveDestination(request); + HttpDestination destination = (HttpDestination)httpClient.resolveDestination(request); assertEquals(1, destination.getHttpExchanges().size()); assertEquals(path, destination.getHttpExchanges().peek().getRequest().getPath()); @@ -284,26 +295,27 @@ public class MaxConcurrentStreamsTest extends AbstractTest public void testAbortedWhileQueued() throws Exception { long sleep = 1000; - start(1, new EmptyServerHandler() + start(1, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { sleep(sleep); + callback.succeeded(); } }); - client.setMaxConnectionsPerDestination(1); + httpClient.setMaxConnectionsPerDestination(1); primeConnection(); // Send a request that is aborted while queued. - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .path("/aborted") .onRequestQueued(request -> request.abort(new Exception())) .send(null); // Must be able to send another request. - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()).path("/check").send(); + ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort()).path("/check").send(); assertEquals(HttpStatus.OK_200, response.getStatus()); } @@ -313,21 +325,22 @@ public class MaxConcurrentStreamsTest extends AbstractTest { int maxConcurrent = 10; long sleep = 500; - start(maxConcurrent, new EmptyServerHandler() + start(maxConcurrent, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { sleep(sleep); + callback.succeeded(); } }); - client.setMaxConnectionsPerDestination(1); + httpClient.setMaxConnectionsPerDestination(1); // The first request will open the connection, the others will be queued. CountDownLatch latch = new CountDownLatch(maxConcurrent); for (int i = 0; i < maxConcurrent; ++i) { - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .path("/" + i) .send(result -> latch.countDown()); } @@ -340,12 +353,12 @@ public class MaxConcurrentStreamsTest extends AbstractTest public void testManyConcurrentRequestsWithSmallConcurrentStreams() throws Exception { byte[] data = new byte[64 * 1024]; - start(1, new EmptyServerHandler() + start(1, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - response.getOutputStream().write(data); + response.write(true, callback, ByteBuffer.wrap(data)); } }); @@ -353,13 +366,13 @@ public class MaxConcurrentStreamsTest extends AbstractTest int runs = 1; int iterations = 32; - client.setMaxConnectionsPerDestination(32768); - client.setMaxRequestsQueuedPerDestination(1024 * 1024); - client.getTransport().setConnectionPoolFactory(destination -> + httpClient.setMaxConnectionsPerDestination(32768); + httpClient.setMaxRequestsQueuedPerDestination(1024 * 1024); + httpClient.getTransport().setConnectionPoolFactory(destination -> { try { - MultiplexConnectionPool pool = new MultiplexConnectionPool(destination, client.getMaxConnectionsPerDestination(), false, destination, 1); + MultiplexConnectionPool pool = new MultiplexConnectionPool(destination, httpClient.getMaxConnectionsPerDestination(), false, destination, 1); pool.preCreateConnections(parallelism * 2).get(); return pool; } @@ -369,7 +382,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest } }); // Prime the destination to pre-create connections. - client.GET("http://localhost:" + connector.getLocalPort()); + httpClient.GET("http://localhost:" + connector.getLocalPort()); int total = parallelism * runs * iterations; CountDownLatch latch = new CountDownLatch(total); @@ -380,7 +393,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest { for (int k = 0; k < iterations; ++k) { - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .path("/" + i + "_" + j + "_" + k) .send(result -> { @@ -399,19 +412,20 @@ public class MaxConcurrentStreamsTest extends AbstractTest public void testTwoStreamsFirstTimesOut() throws Exception { long timeout = 1000; - start(1, new EmptyServerHandler() + start(1, new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - if (target.endsWith("/1")) + if (request.getPathInContext().endsWith("/1")) sleep(2 * timeout); + callback.succeeded(); } }); - client.setMaxConnectionsPerDestination(1); + httpClient.setMaxConnectionsPerDestination(1); CountDownLatch latch = new CountDownLatch(1); - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .path("/1") .timeout(timeout, TimeUnit.MILLISECONDS) .send(result -> @@ -420,7 +434,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest latch.countDown(); }); - ContentResponse response2 = client.newRequest("localhost", connector.getLocalPort()) + ContentResponse response2 = httpClient.newRequest("localhost", connector.getLocalPort()) .path("/2") .send(); @@ -440,25 +454,22 @@ public class MaxConcurrentStreamsTest extends AbstractTest MetaData.Request request = (MetaData.Request)frame.getMetaData(); switch (request.getURI().getPath()) { - case "/1": + case "/1" -> { // Do not return to cause TCP congestion. assertTrue(awaitLatch(request1Latch, 15, TimeUnit.SECONDS)); MetaData.Response response1 = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response1, null, true), Callback.NOOP); - break; } - case "/3": + case "/3" -> { MetaData.Response response3 = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response3, null, true), Callback.NOOP); - break; } - default: + default -> { MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.INTERNAL_SERVER_ERROR_500, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); - break; } } // Return a Stream listener that consumes the content. @@ -476,7 +487,8 @@ public class MaxConcurrentStreamsTest extends AbstractTest prepareClient(); AtomicReference clientEndPointRef = new AtomicReference<>(); CountDownLatch clientEndPointLatch = new CountDownLatch(1); - client = new HttpClient(new HttpClientTransportOverHTTP2(http2Client) + prepareClient(); + httpClient = new HttpClient(new HttpClientTransportOverHTTP2(http2Client) { @Override public Connection newConnection(EndPoint endPoint, Map context) throws IOException @@ -486,12 +498,12 @@ public class MaxConcurrentStreamsTest extends AbstractTest return super.newConnection(endPoint, context); } }); - client.setMaxConnectionsPerDestination(1); - client.start(); + httpClient.setMaxConnectionsPerDestination(1); + httpClient.start(); // First request must cause TCP congestion. CountDownLatch response1Latch = new CountDownLatch(1); - client.newRequest("localhost", connector.getLocalPort()).path("/1") + httpClient.newRequest("localhost", connector.getLocalPort()).path("/1") .body(new BytesRequestContent(new byte[64 * 1024 * 1024])) .send(result -> { @@ -514,14 +526,14 @@ public class MaxConcurrentStreamsTest extends AbstractTest Thread.sleep(1000); // Second request cannot be sent due to TCP congestion and times out. - assertThrows(TimeoutException.class, () -> client.newRequest("localhost", connector.getLocalPort()) + assertThrows(TimeoutException.class, () -> httpClient.newRequest("localhost", connector.getLocalPort()) .path("/2") .timeout(1000, TimeUnit.MILLISECONDS) .send()); // Third request should succeed. CountDownLatch response3Latch = new CountDownLatch(1); - client.newRequest("localhost", connector.getLocalPort()) + httpClient.newRequest("localhost", connector.getLocalPort()) .path("/3") .send(result -> { @@ -555,25 +567,23 @@ public class MaxConcurrentStreamsTest extends AbstractTest MetaData.Request request = (MetaData.Request)frame.getMetaData(); switch (request.getURI().getPath()) { - case "/prime": + case "/prime" -> { session1 = stream.getSession(); // Send another request from here to force the opening of the 2nd connection. - client.newRequest("localhost", connector.getLocalPort()).path("/prime2").send(result -> + httpClient.newRequest("localhost", connector.getLocalPort()).path("/prime2").send(result -> { MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, result.getResponse().getStatus(), HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); }); - break; } - case "/prime2": + case "/prime2" -> { session2 = stream.getSession(); MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); - break; } - case "/update_max_streams": + case "/update_max_streams" -> { Session session = stream.getSession() == session1 ? session2 : session1; Map settings = new HashMap<>(); @@ -581,14 +591,12 @@ public class MaxConcurrentStreamsTest extends AbstractTest session.settings(new SettingsFrame(settings, false), Callback.NOOP); MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); - break; } - default: + default -> { sleep(processing); MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY); stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP); - break; } } return null; @@ -598,8 +606,8 @@ public class MaxConcurrentStreamsTest extends AbstractTest prepareServer(http2); server.start(); prepareClient(); - client.setMaxConnectionsPerDestination(2); - client.start(); + httpClient.setMaxConnectionsPerDestination(2); + httpClient.start(); // Prime the 2 connections. primeConnection(); @@ -607,13 +615,13 @@ public class MaxConcurrentStreamsTest extends AbstractTest String host = "localhost"; int port = connector.getLocalPort(); - assertEquals(1, client.getDestinations().size()); - HttpDestination destination = (HttpDestination)client.getDestinations().get(0); + assertEquals(1, httpClient.getDestinations().size()); + HttpDestination destination = (HttpDestination)httpClient.getDestinations().get(0); AbstractConnectionPool pool = (AbstractConnectionPool)destination.getConnectionPool(); assertEquals(2, pool.getConnectionCount()); // Send a request on one connection, which sends back a SETTINGS frame on the other connection. - ContentResponse response = client.newRequest(host, port) + ContentResponse response = httpClient.newRequest(host, port) .path("/update_max_streams") .send(); assertEquals(HttpStatus.OK_200, response.getStatus()); @@ -623,7 +631,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest CountDownLatch latch = new CountDownLatch(count); for (int i = 0; i < count; ++i) { - client.newRequest(host, port) + httpClient.newRequest(host, port) .path("/" + i) .send(result -> { @@ -648,7 +656,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest private void primeConnection() throws Exception { // Prime the connection so that the maxConcurrentStream setting arrives to the client. - client.newRequest("localhost", connector.getLocalPort()).path("/prime").send(); + httpClient.newRequest("localhost", connector.getLocalPort()).path("/prime").send(); // Wait for the server to clean up and remove the stream that primes the connection. sleep(1000); } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxPushedStreamsTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxPushedStreamsTest.java index 5bacd035932..25e3132da99 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxPushedStreamsTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MaxPushedStreamsTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.util.ArrayList; import java.util.List; @@ -26,8 +26,6 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -35,6 +33,8 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Session; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; @@ -114,9 +114,9 @@ public class MaxPushedStreamsTest extends AbstractTest return null; } }); - client.setMaxConcurrentPushedStreams(maxPushed); + http2Client.setMaxConcurrentPushedStreams(maxPushed); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); CountDownLatch responseLatch = new CountDownLatch(1); session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MultiplexedConnectionPoolTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MultiplexedConnectionPoolTest.java index 2803aaa54b3..a4c31de095c 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MultiplexedConnectionPoolTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MultiplexedConnectionPoolTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.tests; import java.util.ArrayList; import java.util.List; @@ -22,9 +22,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.ConnectionPool; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClientTransport; @@ -32,19 +29,20 @@ import org.eclipse.jetty.client.MultiplexConnectionPool; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Pool; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; @@ -54,7 +52,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; // Sibling of ConnectionPoolTest, but using H2 to multiplex connections. public class MultiplexedConnectionPoolTest { - private static final Logger LOG = LoggerFactory.getLogger(MultiplexedConnectionPoolTest.class); private static final int MAX_MULTIPLEX = 2; private Server server; @@ -132,30 +129,19 @@ public class MultiplexedConnectionPoolTest CountDownLatch[] reqExecutingLatches = new CountDownLatch[] {new CountDownLatch(1), new CountDownLatch(1), new CountDownLatch(1)}; CountDownLatch[] reqExecutedLatches = new CountDownLatch[] {new CountDownLatch(1), new CountDownLatch(1), new CountDownLatch(1)}; CountDownLatch[] reqFinishingLatches = new CountDownLatch[] {new CountDownLatch(1), new CountDownLatch(1), new CountDownLatch(1)}; - startServer(new EmptyServerHandler() + startServer(new Handler.Processor() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + public void process(Request request, Response response, Callback callback) throws Exception { - int req = Integer.parseInt(target.substring(1)); - try - { - LOG.debug("req {} is executing", req); - reqExecutingLatches[req].countDown(); - Thread.sleep(250); - reqExecutedLatches[req].countDown(); - LOG.debug("req {} executed", req); + int req = Integer.parseInt(request.getPathInContext().substring(1)); + reqExecutingLatches[req].countDown(); + Thread.sleep(250); + reqExecutedLatches[req].countDown(); - assertTrue(reqFinishingLatches[req].await(5, TimeUnit.SECONDS)); + assertTrue(reqFinishingLatches[req].await(5, TimeUnit.SECONDS)); - response.getWriter().println("req " + req + " executed"); - response.getWriter().flush(); - LOG.debug("req {} successful", req); - } - catch (Exception e) - { - throw new ServletException(e); - } + response.write(true, callback, "req " + req + " executed"); } }); @@ -170,19 +156,15 @@ public class MultiplexedConnectionPoolTest sendRequest(reqClientSuccessLatches, 0); // wait until handler is executing assertTrue(reqExecutingLatches[0].await(5, TimeUnit.SECONDS)); - LOG.debug("req 0 executing"); sendRequest(reqClientSuccessLatches, 1); // wait until handler executed sleep assertTrue(reqExecutedLatches[1].await(5, TimeUnit.SECONDS)); - LOG.debug("req 1 executed"); // Now the pool contains one connection that is expired but in use by 2 threads. sendRequest(reqClientSuccessLatches, 2); - LOG.debug("req2 sent"); assertTrue(reqExecutingLatches[2].await(5, TimeUnit.SECONDS)); - LOG.debug("req2 executing"); // The 3rd request has tried the expired request and marked it as closed as it has expired, then used a 2nd one. @@ -243,21 +225,13 @@ public class MultiplexedConnectionPoolTest return pool; }); - startServer(new EmptyServerHandler() + startServer(new Handler.Processor() { @Override - protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + public void process(Request request, Response response, Callback callback) { - int req = Integer.parseInt(target.substring(1)); - try - { - response.getWriter().println("req " + req + " executed"); - response.getWriter().flush(); - } - catch (Exception e) - { - throw new ServletException(e); - } + int req = Integer.parseInt(request.getPathInContext().substring(1)); + response.write(true, callback, "req " + req + " executed"); } }, 64, 1L); @@ -331,7 +305,14 @@ public class MultiplexedConnectionPoolTest return connectionPool; }); - startServer(new EmptyServerHandler()); + startServer(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); HttpClientTransport transport = new HttpClientTransportOverHTTP2(new HTTP2Client()); transport.setConnectionPoolFactory(factory.factory); @@ -393,24 +374,17 @@ public class MultiplexedConnectionPoolTest Semaphore handlerSignalingSemaphore = new Semaphore(0); Semaphore handlerWaitingSemaphore = new Semaphore(0); - startServer(new EmptyServerHandler() + startServer(new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + public void process(Request request, Response response, Callback callback) throws Exception { - if (!target.equals("/block")) - return; - - handlerSignalingSemaphore.release(); - - try + if (request.getPathInContext().equals("/block")) { + handlerSignalingSemaphore.release(); handlerWaitingSemaphore.acquire(); } - catch (Exception e) - { - throw new ServletException(e); - } + callback.succeeded(); } }); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PingTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PingTest.java index cc7bea7ff53..4dcc18b3f4d 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PingTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PingTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.util.Random; import java.util.concurrent.CountDownLatch; @@ -36,7 +36,7 @@ public class PingTest extends AbstractTest final byte[] payload = new byte[8]; new Random().nextBytes(payload); final CountDownLatch latch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener.Adapter() { @Override public void onPing(Session session, PingFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PrefaceTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PrefaceTest.java index c0936ec689d..e4a968458f9 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PrefaceTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PrefaceTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.io.InputStream; import java.io.OutputStream; @@ -36,17 +36,18 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.frames.FrameType; +import org.eclipse.jetty.http2.frames.GoAwayFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PingFrame; import org.eclipse.jetty.http2.frames.PrefaceFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.http2.generator.Generator; -import org.eclipse.jetty.http2.parser.Parser; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.generator.Generator; +import org.eclipse.jetty.http2.internal.parser.Parser; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; @@ -90,7 +91,7 @@ public class PrefaceTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener.Adapter() { @Override public Map onPreface(Session session) @@ -146,7 +147,7 @@ public class PrefaceTest extends AbstractTest } }); - ByteBufferPool byteBufferPool = client.getByteBufferPool(); + ByteBufferPool byteBufferPool = http2Client.getByteBufferPool(); try (SocketChannel socket = SocketChannel.open()) { socket.connect(new InetSocketAddress("localhost", connector.getLocalPort())); @@ -161,9 +162,10 @@ public class PrefaceTest extends AbstractTest generator.control(lease, new PingFrame(true)); List buffers = lease.getByteBuffers(); - socket.write(buffers.toArray(new ByteBuffer[buffers.size()])); + socket.write(buffers.toArray(new ByteBuffer[0])); Queue settings = new ArrayDeque<>(); + AtomicBoolean closed = new AtomicBoolean(); Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter() { @Override @@ -171,6 +173,12 @@ public class PrefaceTest extends AbstractTest { settings.offer(frame); } + + @Override + public void onGoAway(GoAwayFrame frame) + { + closed.set(true); + } }, 4096, 8192); parser.init(UnaryOperator.identity()); @@ -179,10 +187,12 @@ public class PrefaceTest extends AbstractTest { BufferUtil.clearToFill(buffer); int read = socket.read(buffer); - BufferUtil.flipToFlush(buffer, 0); if (read < 0) break; + BufferUtil.flipToFlush(buffer, 0); parser.parse(buffer); + if (closed.get()) + break; } assertEquals(2, settings.size()); @@ -288,7 +298,7 @@ public class PrefaceTest extends AbstractTest clientSettings.put(SettingsFrame.ENABLE_PUSH, 1); generator.control(lease, new SettingsFrame(clientSettings, false)); List buffers = lease.getByteBuffers(); - socket.write(buffers.toArray(new ByteBuffer[buffers.size()])); + socket.write(buffers.toArray(new ByteBuffer[0])); // However, we should not call onPreface() again. assertFalse(serverPrefaceLatch.get().await(1, TimeUnit.SECONDS)); @@ -340,12 +350,12 @@ public class PrefaceTest extends AbstractTest try (ServerSocket server = new ServerSocket(0)) { prepareClient(); - client.start(); + http2Client.start(); CountDownLatch failureLatch = new CountDownLatch(1); Promise.Completable promise = new Promise.Completable<>(); InetSocketAddress address = new InetSocketAddress("localhost", server.getLocalPort()); - client.connect(address, new Session.Listener.Adapter() + http2Client.connect(address, new Session.Listener.Adapter() { @Override public void onFailure(Session session, Throwable failure) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorKnowledgeHTTP2OverTLSTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorKnowledgeHTTP2OverTLSTest.java index 768cd1a8dcd..90e9c214fb3 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorKnowledgeHTTP2OverTLSTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorKnowledgeHTTP2OverTLSTest.java @@ -11,15 +11,16 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.net.InetSocketAddress; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; @@ -27,6 +28,8 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.HTTP2Cipher; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.io.ClientConnector; @@ -34,11 +37,12 @@ import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -52,7 +56,8 @@ public class PriorKnowledgeHTTP2OverTLSTest { private Server server; private ServerConnector connector; - private HTTP2Client client; + private HTTP2Client http2Client; + private HttpClient httpClient; private void start(Handler handler) throws Exception { @@ -82,15 +87,17 @@ public class PriorKnowledgeHTTP2OverTLSTest clientThreads.setName("client"); clientConnector.setExecutor(clientThreads); clientConnector.setSslContextFactory(newClientSslContextFactory()); - client = new HTTP2Client(clientConnector); - client.setUseALPN(false); - client.start(); + http2Client = new HTTP2Client(clientConnector); + HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(http2Client); + transport.setUseALPN(false); + httpClient = new HttpClient(transport); + httpClient.start(); } @AfterEach public void dispose() throws Exception { - LifeCycle.stop(client); + LifeCycle.stop(httpClient); LifeCycle.stop(server); } @@ -118,21 +125,21 @@ public class PriorKnowledgeHTTP2OverTLSTest } @Test - public void testDirectHTTP2OverTLS() throws Exception + public void testDirectHTTP2OverTLSWithHTTP2Client() throws Exception { // The client knows a priori that the server speaks h2 on a particular port. - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); + callback.succeeded(); } }); int port = connector.getLocalPort(); - MetaData.Response response = client.connect(client.getClientConnector().getSslContextFactory(), new InetSocketAddress("localhost", port), new Session.Listener.Adapter()) + MetaData.Response response = http2Client.connect(http2Client.getClientConnector().getSslContextFactory(), new InetSocketAddress("localhost", port), new Session.Listener.Adapter()) .thenCompose(session -> { CompletableFuture responsePromise = new CompletableFuture<>(); @@ -155,4 +162,26 @@ public class PriorKnowledgeHTTP2OverTLSTest assertEquals(HttpStatus.OK_200, response.getStatus()); } + + @Test + public void testDirectHTTP2OverTLSWithHttpClient() throws Exception + { + // The client knows a priori that the server speaks h2 on a particular port. + + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); + + ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort()) + .scheme(HttpScheme.HTTPS.asString()) + .timeout(5, TimeUnit.SECONDS) + .send(); + + assertEquals(HttpStatus.OK_200, response.getStatus()); + } } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorityTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorityTest.java index 6e54afd8958..3b044689c20 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorityTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PriorityTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -50,14 +50,14 @@ public class PriorityTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); int streamId = session.priority(new PriorityFrame(0, 13, false), Callback.NOOP); assertTrue(streamId > 0); CountDownLatch latch = new CountDownLatch(2); MetaData metaData = newRequest("GET", HttpFields.EMPTY); HeadersFrame headersFrame = new HeadersFrame(streamId, metaData, null, true); - session.newStream(headersFrame, new Promise.Adapter() + session.newStream(headersFrame, new Promise.Adapter<>() { @Override public void succeeded(Stream result) @@ -116,7 +116,7 @@ public class PriorityTest extends AbstractTest } }; - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData metaData1 = newRequest("GET", "/one", HttpFields.EMPTY); HeadersFrame headersFrame1 = new HeadersFrame(metaData1, null, true); FuturePromise promise1 = new FuturePromise<>(); @@ -164,7 +164,7 @@ public class PriorityTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData metaData = newRequest("GET", "/one", HttpFields.EMPTY); HeadersFrame headersFrame = new HeadersFrame(metaData, priorityFrame, true); session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter() diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyProtocolTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyProtocolTest.java index 5c70246d1e7..01d54776889 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyProtocolTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyProtocolTest.java @@ -11,9 +11,8 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; @@ -21,9 +20,6 @@ import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; @@ -31,6 +27,7 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.io.EndPoint; @@ -38,13 +35,13 @@ import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.ProxyConnectionFactory; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.TypeUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -84,24 +81,16 @@ public class ProxyProtocolTest @Test public void testProxyGetV1() throws Exception { - startServer(new AbstractHandler() + startServer(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) { - try - { - assertEquals("1.2.3.4", request.getRemoteAddr()); - assertEquals(1111, request.getRemotePort()); - assertEquals("5.6.7.8", request.getLocalAddr()); - assertEquals(2222, request.getLocalPort()); - } - catch (Throwable th) - { - th.printStackTrace(); - response.setStatus(500); - } - baseRequest.setHandled(true); + assertEquals("1.2.3.4", Request.getRemoteAddr(request)); + assertEquals(1111, Request.getRemotePort(request)); + assertEquals("5.6.7.8", Request.getLocalAddr(request)); + assertEquals(2222, Request.getLocalPort(request)); + callback.succeeded(); } }); @@ -135,28 +124,20 @@ public class ProxyProtocolTest @Test public void testProxyGetV2() throws Exception { - startServer(new AbstractHandler() + startServer(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) { - try - { - assertEquals("10.0.0.4", request.getRemoteAddr()); - assertEquals(33824, request.getRemotePort()); - assertEquals("10.0.0.5", request.getLocalAddr()); - assertEquals(8888, request.getLocalPort()); - EndPoint endPoint = baseRequest.getHttpChannel().getEndPoint(); - assertThat(endPoint, instanceOf(ProxyConnectionFactory.ProxyEndPoint.class)); - ProxyConnectionFactory.ProxyEndPoint proxyEndPoint = (ProxyConnectionFactory.ProxyEndPoint)endPoint; - assertNotNull(proxyEndPoint.getAttribute(ProxyConnectionFactory.TLS_VERSION)); - } - catch (Throwable th) - { - th.printStackTrace(); - response.setStatus(500); - } - baseRequest.setHandled(true); + assertEquals("10.0.0.4", Request.getRemoteAddr(request)); + assertEquals(33824, Request.getRemotePort(request)); + assertEquals("10.0.0.5", Request.getLocalAddr(request)); + assertEquals(8888, Request.getLocalPort(request)); + EndPoint endPoint = request.getConnectionMetaData().getConnection().getEndPoint(); + assertThat(endPoint, instanceOf(ProxyConnectionFactory.ProxyEndPoint.class)); + ProxyConnectionFactory.ProxyEndPoint proxyEndPoint = (ProxyConnectionFactory.ProxyEndPoint)endPoint; + assertNotNull(proxyEndPoint.getAttribute(ProxyConnectionFactory.TLS_VERSION)); + callback.succeeded(); } }); @@ -165,7 +146,7 @@ public class ProxyProtocolTest request1 = StringUtil.strip(request1, " "); SocketChannel channel = SocketChannel.open(); channel.connect(new InetSocketAddress("localhost", connector.getLocalPort())); - channel.write(ByteBuffer.wrap(TypeUtil.fromHexString(request1))); + channel.write(ByteBuffer.wrap(StringUtil.fromHexString(request1))); FuturePromise promise = new FuturePromise<>(); client.accept(null, channel, new Session.Listener.Adapter(), promise); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyTest.java index 8283d9473bc..06aa001f630 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ProxyTest.java @@ -11,204 +11,180 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HostPortHttpField; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpScheme; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.api.Session; -import org.eclipse.jetty.http2.api.Stream; -import org.eclipse.jetty.http2.frames.DataFrame; -import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; -import org.eclipse.jetty.proxy.AsyncProxyServlet; -import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.FuturePromise; -import org.eclipse.jetty.util.Promise; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +// TODO: move to ee9 with ProxyServlet support +@Disabled("move to ee9") public class ProxyTest { - private HTTP2Client client; - private Server proxy; - private ServerConnector proxyConnector; - private Server server; - private ServerConnector serverConnector; - - private void startServer(HttpServlet servlet) throws Exception - { - QueuedThreadPool serverPool = new QueuedThreadPool(); - serverPool.setName("server"); - server = new Server(serverPool); - serverConnector = new ServerConnector(server); - server.addConnector(serverConnector); - - ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false); - ServletHolder appServletHolder = new ServletHolder(servlet); - appCtx.addServlet(appServletHolder, "/*"); - - server.start(); - } - - private void startProxy(HttpServlet proxyServlet, Map initParams) throws Exception - { - QueuedThreadPool proxyPool = new QueuedThreadPool(); - proxyPool.setName("proxy"); - proxy = new Server(proxyPool); - - HttpConfiguration configuration = new HttpConfiguration(); - configuration.setSendDateHeader(false); - configuration.setSendServerVersion(false); - String value = initParams.get("outputBufferSize"); - if (value != null) - configuration.setOutputBufferSize(Integer.parseInt(value)); - proxyConnector = new ServerConnector(proxy, new HTTP2ServerConnectionFactory(configuration)); - proxy.addConnector(proxyConnector); - - ServletContextHandler proxyContext = new ServletContextHandler(proxy, "/", true, false); - ServletHolder proxyServletHolder = new ServletHolder(proxyServlet); - proxyServletHolder.setInitParameters(initParams); - proxyContext.addServlet(proxyServletHolder, "/*"); - - proxy.start(); - } - - private void startClient() throws Exception - { - QueuedThreadPool clientExecutor = new QueuedThreadPool(); - clientExecutor.setName("client"); - client = new HTTP2Client(); - client.setExecutor(clientExecutor); - client.start(); - } - - private Session newClient(Session.Listener listener) throws Exception - { - String host = "localhost"; - int port = proxyConnector.getLocalPort(); - InetSocketAddress address = new InetSocketAddress(host, port); - FuturePromise promise = new FuturePromise<>(); - client.connect(address, listener, promise); - return promise.get(5, TimeUnit.SECONDS); - } - - private MetaData.Request newRequest(String method, String path, HttpFields fields) - { - String host = "localhost"; - int port = proxyConnector.getLocalPort(); - String authority = host + ":" + port; - return new MetaData.Request(method, HttpScheme.HTTP.asString(), new HostPortHttpField(authority), path, HttpVersion.HTTP_2, fields, -1); - } - - @AfterEach - public void dispose() throws Exception - { - client.stop(); - proxy.stop(); - server.stop(); - } - @Test - public void testHTTPVersion() throws Exception + public void test() { - startServer(new HttpServlet() - { - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - { - assertEquals(HttpVersion.HTTP_1_1.asString(), request.getProtocol()); - } - }); - Map params = new HashMap<>(); - params.put("proxyTo", "http://localhost:" + serverConnector.getLocalPort()); - startProxy(new AsyncProxyServlet.Transparent(), params); - startClient(); - - CountDownLatch clientLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter()); - MetaData.Request metaData = newRequest("GET", "/", HttpFields.EMPTY); - HeadersFrame frame = new HeadersFrame(metaData, null, true); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - assertTrue(frame.isEndStream()); - MetaData.Response response = (MetaData.Response)frame.getMetaData(); - assertEquals(HttpStatus.OK_200, response.getStatus()); - clientLatch.countDown(); - } - }); - - assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); + fail(); } - @Test - public void testServerBigDownloadSlowClient() throws Exception - { - CountDownLatch serverLatch = new CountDownLatch(1); - byte[] content = new byte[1024 * 1024]; - startServer(new HttpServlet() - { - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException - { - response.getOutputStream().write(content); - serverLatch.countDown(); - } - }); - Map params = new HashMap<>(); - params.put("proxyTo", "http://localhost:" + serverConnector.getLocalPort()); - startProxy(new AsyncProxyServlet.Transparent(), params); - startClient(); - - CountDownLatch clientLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter()); - MetaData.Request metaData = newRequest("GET", "/", HttpFields.EMPTY); - HeadersFrame frame = new HeadersFrame(metaData, null, true); - session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - try - { - TimeUnit.MILLISECONDS.sleep(1); - callback.succeeded(); - if (frame.isEndStream()) - clientLatch.countDown(); - } - catch (InterruptedException x) - { - callback.failed(x); - } - } - }); - - assertTrue(serverLatch.await(15, TimeUnit.SECONDS)); - assertTrue(clientLatch.await(15, TimeUnit.SECONDS)); - } +// private HTTP2Client client; +// private Server proxy; +// private ServerConnector proxyConnector; +// private Server server; +// private ServerConnector serverConnector; +// +// private void startServer(HttpServlet servlet) throws Exception +// { +// QueuedThreadPool serverPool = new QueuedThreadPool(); +// serverPool.setName("server"); +// server = new Server(serverPool); +// serverConnector = new ServerConnector(server); +// server.addConnector(serverConnector); +// +// ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false); +// ServletHolder appServletHolder = new ServletHolder(servlet); +// appCtx.addServlet(appServletHolder, "/*"); +// +// server.start(); +// } +// +// private void startProxy(HttpServlet proxyServlet, Map initParams) throws Exception +// { +// QueuedThreadPool proxyPool = new QueuedThreadPool(); +// proxyPool.setName("proxy"); +// proxy = new Server(proxyPool); +// +// HttpConfiguration configuration = new HttpConfiguration(); +// configuration.setSendDateHeader(false); +// configuration.setSendServerVersion(false); +// String value = initParams.get("outputBufferSize"); +// if (value != null) +// configuration.setOutputBufferSize(Integer.parseInt(value)); +// proxyConnector = new ServerConnector(proxy, new HTTP2ServerConnectionFactory(configuration)); +// proxy.addConnector(proxyConnector); +// +// ServletContextHandler proxyContext = new ServletContextHandler(proxy, "/", true, false); +// ServletHolder proxyServletHolder = new ServletHolder(proxyServlet); +// proxyServletHolder.setInitParameters(initParams); +// proxyContext.addServlet(proxyServletHolder, "/*"); +// +// proxy.start(); +// } +// +// private void startClient() throws Exception +// { +// QueuedThreadPool clientExecutor = new QueuedThreadPool(); +// clientExecutor.setName("client"); +// client = new HTTP2Client(); +// client.setExecutor(clientExecutor); +// client.start(); +// } +// +// private Session newClient(Session.Listener listener) throws Exception +// { +// String host = "localhost"; +// int port = proxyConnector.getLocalPort(); +// InetSocketAddress address = new InetSocketAddress(host, port); +// FuturePromise promise = new FuturePromise<>(); +// client.connect(address, listener, promise); +// return promise.get(5, TimeUnit.SECONDS); +// } +// +// private MetaData.Request newRequest(String method, String path, HttpFields fields) +// { +// String host = "localhost"; +// int port = proxyConnector.getLocalPort(); +// String authority = host + ":" + port; +// return new MetaData.Request(method, HttpScheme.HTTP.asString(), new HostPortHttpField(authority), path, HttpVersion.HTTP_2, fields, -1); +// } +// +// @AfterEach +// public void dispose() throws Exception +// { +// client.stop(); +// proxy.stop(); +// server.stop(); +// } +// +// @Test +// public void testHTTPVersion() throws Exception +// { +// startServer(new HttpServlet() +// { +// @Override +// protected void service(HttpServletRequest request, HttpServletResponse response) +// { +// assertEquals(HttpVersion.HTTP_1_1.asString(), request.getProtocol()); +// } +// }); +// Map params = new HashMap<>(); +// params.put("proxyTo", "http://localhost:" + serverConnector.getLocalPort()); +// startProxy(new AsyncProxyServlet.Transparent(), params); +// startClient(); +// +// CountDownLatch clientLatch = new CountDownLatch(1); +// Session session = newClient(new Session.Listener.Adapter()); +// MetaData.Request metaData = newRequest("GET", "/", HttpFields.EMPTY); +// HeadersFrame frame = new HeadersFrame(metaData, null, true); +// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// assertTrue(frame.isEndStream()); +// MetaData.Response response = (MetaData.Response)frame.getMetaData(); +// assertEquals(HttpStatus.OK_200, response.getStatus()); +// clientLatch.countDown(); +// } +// }); +// +// assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); +// } +// +// @Test +// public void testServerBigDownloadSlowClient() throws Exception +// { +// CountDownLatch serverLatch = new CountDownLatch(1); +// byte[] content = new byte[1024 * 1024]; +// startServer(new HttpServlet() +// { +// @Override +// protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException +// { +// response.write(true, callback, ByteBuffer.wrap(content)); +// serverLatch.countDown(); +// } +// }); +// Map params = new HashMap<>(); +// params.put("proxyTo", "http://localhost:" + serverConnector.getLocalPort()); +// startProxy(new AsyncProxyServlet.Transparent(), params); +// startClient(); +// +// CountDownLatch clientLatch = new CountDownLatch(1); +// Session session = newClient(new Session.Listener.Adapter()); +// MetaData.Request metaData = newRequest("GET", "/", HttpFields.EMPTY); +// HeadersFrame frame = new HeadersFrame(metaData, null, true); +// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// try +// { +// TimeUnit.MILLISECONDS.sleep(1); +// callback.succeeded(); +// if (frame.isEndStream()) +// clientLatch.countDown(); +// } +// catch (InterruptedException x) +// { +// callback.failed(x); +// } +// } +// }); +// +// assertTrue(serverLatch.await(15, TimeUnit.SECONDS)); +// assertTrue(clientLatch.await(15, TimeUnit.SECONDS)); +// } } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushCacheFilterTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushCacheFilterTest.java index f895de76611..e97b7106ca0 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushCacheFilterTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushCacheFilterTest.java @@ -11,996 +11,971 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import jakarta.servlet.DispatcherType; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HostPortHttpField; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpScheme; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.api.Session; -import org.eclipse.jetty.http2.api.Stream; -import org.eclipse.jetty.http2.frames.DataFrame; -import org.eclipse.jetty.http2.frames.HeadersFrame; -import org.eclipse.jetty.http2.frames.PushPromiseFrame; -import org.eclipse.jetty.http2.frames.ResetFrame; -import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlets.PushCacheFilter; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.Promise; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +// TODO +@Disabled("move to ee9 or provide a PushCacheHandler") public class PushCacheFilterTest extends AbstractTest { - private String contextPath = "/push"; - - @Override - protected void customizeContext(ServletContextHandler context) - { - context.setContextPath(contextPath); - context.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); - } - - @Override - protected MetaData.Request newRequest(String method, String pathInfo, HttpFields fields) - { - return new MetaData.Request(method, HttpScheme.HTTP.asString(), new HostPortHttpField("localhost:" + connector.getLocalPort()), contextPath + servletPath + pathInfo, HttpVersion.HTTP_2, fields, -1); - } - - private String newURI(String pathInfo) - { - return "http://localhost:" + connector.getLocalPort() + contextPath + servletPath + pathInfo; - } - @Test - public void testPush() throws Exception + public void test() { - final String primaryResource = "/primary.html"; - final String secondaryResource = "/secondary.png"; - final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8); - start(new HttpServlet() - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String requestURI = req.getRequestURI(); - ServletOutputStream output = resp.getOutputStream(); - if (requestURI.endsWith(primaryResource)) - output.print("PRIMARY"); - else if (requestURI.endsWith(secondaryResource)) - output.write(secondaryData); - } - }); - - final Session session = newClient(new Session.Listener.Adapter()); - - // Request for the primary and secondary resource to build the cache. - final String referrerURI = newURI(primaryResource); - MetaData.Request primaryRequest = newRequest("GET", primaryResource, HttpFields.EMPTY); - final CountDownLatch warmupLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - { - // Request for the secondary resource. - HttpFields.Mutable secondaryFields = HttpFields.build() - .put(HttpHeader.REFERER, referrerURI); - MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); - session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - warmupLatch.countDown(); - } - }); - } - } - }); - assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); - - // Request again the primary resource, we should get the secondary resource pushed. - primaryRequest = newRequest("GET", primaryResource, HttpFields.EMPTY); - final CountDownLatch primaryResponseLatch = new CountDownLatch(2); - final CountDownLatch pushLatch = new CountDownLatch(2); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - MetaData.Response response = (MetaData.Response)frame.getMetaData(); - if (response.getStatus() == HttpStatus.OK_200) - primaryResponseLatch.countDown(); - } - - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - primaryResponseLatch.countDown(); - } - - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - return new Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - MetaData.Response response = (MetaData.Response)frame.getMetaData(); - if (response.getStatus() == HttpStatus.OK_200) - pushLatch.countDown(); - } - - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - pushLatch.countDown(); - } - }; - } - }); - assertTrue(pushLatch.await(5, TimeUnit.SECONDS)); - assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); + fail(); } - @Test - public void testPushReferrerNoPath() throws Exception - { - final String primaryResource = "/primary.html"; - final String secondaryResource = "/secondary.png"; - final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8); - start(new HttpServlet() - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String requestURI = req.getRequestURI(); - ServletOutputStream output = resp.getOutputStream(); - if (requestURI.endsWith(primaryResource)) - output.print("PRIMARY"); - else if (requestURI.endsWith(secondaryResource)) - output.write(secondaryData); - } - }); - - final Session session = newClient(new Session.Listener.Adapter()); - - // Request for the primary and secondary resource to build the cache. - // The referrerURI does not point to the primary resource, so there will be no - // resource association with the primary resource and therefore won't be pushed. - final String referrerURI = "http://localhost:" + connector.getLocalPort(); - HttpFields.Mutable primaryFields = HttpFields.build(); - MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch warmupLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - { - // Request for the secondary resource. - HttpFields.Mutable secondaryFields = HttpFields.build() - .put(HttpHeader.REFERER, referrerURI); - MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); - session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - warmupLatch.countDown(); - } - }); - } - } - }); - assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); - - // Request again the primary resource, we should not get the secondary resource pushed. - primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch primaryResponseLatch = new CountDownLatch(1); - final CountDownLatch pushLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - return new Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - pushLatch.countDown(); - } - }; - } - - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - primaryResponseLatch.countDown(); - } - }); - assertFalse(pushLatch.await(1, TimeUnit.SECONDS)); - assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); - } - - @Test - public void testPushIsReset() throws Exception - { - final String primaryResource = "/primary.html"; - final String secondaryResource = "/secondary.png"; - final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8); - start(new HttpServlet() - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String requestURI = req.getRequestURI(); - ServletOutputStream output = resp.getOutputStream(); - if (requestURI.endsWith(primaryResource)) - output.print("PRIMARY"); - else if (requestURI.endsWith(secondaryResource)) - output.write(secondaryData); - } - }); - - final Session session = newClient(new Session.Listener.Adapter()); - - // Request for the primary and secondary resource to build the cache. - final String primaryURI = newURI(primaryResource); - HttpFields.Mutable primaryFields = HttpFields.build(); - MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch warmupLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - { - // Request for the secondary resource. - HttpFields.Mutable secondaryFields = HttpFields.build() - .put(HttpHeader.REFERER, primaryURI); - MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); - session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - warmupLatch.countDown(); - } - }); - } - } - }); - assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); - - // Request again the primary resource, we should get the secondary resource pushed. - primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch primaryResponseLatch = new CountDownLatch(1); - final CountDownLatch pushLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - // Reset the stream as soon as we see the push. - ResetFrame resetFrame = new ResetFrame(stream.getId(), ErrorCode.REFUSED_STREAM_ERROR.code); - stream.reset(resetFrame, Callback.NOOP); - return new Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - pushLatch.countDown(); - } - }; - } - - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - primaryResponseLatch.countDown(); - } - }); - // We should not receive pushed data that we reset. - assertFalse(pushLatch.await(1, TimeUnit.SECONDS)); - assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); - - // Make sure the session is sane by requesting the secondary resource. - HttpFields.Mutable secondaryFields = HttpFields.build(); - secondaryFields.put(HttpHeader.REFERER, primaryURI); - MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); - final CountDownLatch secondaryResponseLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - secondaryResponseLatch.countDown(); - } - }); - assertTrue(secondaryResponseLatch.await(5, TimeUnit.SECONDS)); - } - - @Test - public void testPushWithoutPrimaryResponseContent() throws Exception - { - final String primaryResource = "/primary.html"; - final String secondaryResource = "/secondary.png"; - start(new HttpServlet() - { - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException - { - String requestURI = request.getRequestURI(); - final ServletOutputStream output = response.getOutputStream(); - if (requestURI.endsWith(secondaryResource)) - output.write("SECONDARY".getBytes(StandardCharsets.UTF_8)); - } - }); - - final Session session = newClient(new Session.Listener.Adapter()); - - // Request for the primary and secondary resource to build the cache. - final String primaryURI = newURI(primaryResource); - HttpFields.Mutable primaryFields = HttpFields.build(); - MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch warmupLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - if (frame.isEndStream()) - { - // Request for the secondary resource. - HttpFields.Mutable secondaryFields = HttpFields.build(); - secondaryFields.put(HttpHeader.REFERER, primaryURI); - MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); - session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - warmupLatch.countDown(); - } - }); - } - } - }); - assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); - - Thread.sleep(1000); - - // Request again the primary resource, we should get the secondary resource pushed. - primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch primaryResponseLatch = new CountDownLatch(1); - final CountDownLatch pushLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - if (frame.isEndStream()) - primaryResponseLatch.countDown(); - } - - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - return new Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - pushLatch.countDown(); - } - }; - } - }); - assertTrue(pushLatch.await(5, TimeUnit.SECONDS)); - assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); - } - - @Test - public void testRecursivePush() throws Exception - { - final String primaryResource = "/primary.html"; - final String secondaryResource1 = "/secondary1.css"; - final String secondaryResource2 = "/secondary2.js"; - final String tertiaryResource = "/tertiary.png"; - start(new HttpServlet() - { - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException - { - String requestURI = request.getRequestURI(); - final ServletOutputStream output = response.getOutputStream(); - if (requestURI.endsWith(primaryResource)) - output.print("PRIMARY"); - else if (requestURI.endsWith(secondaryResource1)) - output.print("body { background-image: url(\"" + tertiaryResource + "\"); }"); - else if (requestURI.endsWith(secondaryResource2)) - output.print("(function() { window.alert('HTTP/2'); })()"); - if (requestURI.endsWith(tertiaryResource)) - output.write("TERTIARY".getBytes(StandardCharsets.UTF_8)); - } - }); - - final Session session = newClient(new Session.Listener.Adapter()); - - // Request for the primary, secondary and tertiary resource to build the cache. - final String primaryURI = newURI(primaryResource); - HttpFields.Mutable primaryFields = HttpFields.build(); - MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch warmupLatch = new CountDownLatch(2); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - { - // Request for the secondary resources. - String secondaryURI1 = newURI(secondaryResource1); - HttpFields.Mutable secondaryFields1 = HttpFields.build() - .put(HttpHeader.REFERER, primaryURI); - MetaData.Request secondaryRequest1 = newRequest("GET", secondaryResource1, secondaryFields1); - session.newStream(new HeadersFrame(secondaryRequest1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - { - // Request for the tertiary resource. - HttpFields.Mutable tertiaryFields = HttpFields.build() - .put(HttpHeader.REFERER, secondaryURI1); - MetaData.Request tertiaryRequest = newRequest("GET", tertiaryResource, tertiaryFields); - session.newStream(new HeadersFrame(tertiaryRequest, null, true), new Promise.Adapter<>(), new Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - warmupLatch.countDown(); - } - }); - } - } - }); - - HttpFields.Mutable secondaryFields2 = HttpFields.build() - .put(HttpHeader.REFERER, primaryURI); - MetaData.Request secondaryRequest2 = newRequest("GET", secondaryResource2, secondaryFields2); - session.newStream(new HeadersFrame(secondaryRequest2, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - warmupLatch.countDown(); - } - }); - } - } - }); - assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); - - Thread.sleep(1000); - - // Request again the primary resource, we should get the secondary and tertiary resources pushed. - primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch primaryResponseLatch = new CountDownLatch(1); - final CountDownLatch primaryPushesLatch = new CountDownLatch(3); - final CountDownLatch recursiveLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - primaryResponseLatch.countDown(); - } - - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - // The stream id of the PUSH_PROMISE must - // always be a client stream and therefore odd. - assertEquals(1, frame.getStreamId() & 1); - return new Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - primaryPushesLatch.countDown(); - } - - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - return new Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - recursiveLatch.countDown(); - } - }; - } - }; - } - }); - - assertTrue(primaryPushesLatch.await(5, TimeUnit.SECONDS)); - assertFalse(recursiveLatch.await(1, TimeUnit.SECONDS)); - assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); - - // Make sure that explicitly requesting a secondary resource, we get the tertiary pushed. - CountDownLatch secondaryResponseLatch = new CountDownLatch(1); - CountDownLatch secondaryPushLatch = new CountDownLatch(1); - MetaData.Request secondaryRequest = newRequest("GET", secondaryResource1, HttpFields.EMPTY); - session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - secondaryResponseLatch.countDown(); - } - - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - return new Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - secondaryPushLatch.countDown(); - } - }; - } - }); - - assertTrue(secondaryPushLatch.await(5, TimeUnit.SECONDS)); - assertTrue(secondaryResponseLatch.await(5, TimeUnit.SECONDS)); - } - - @Test - public void testSelfPush() throws Exception - { - // The test case is that of a login page, for example. - // When the user sends the credentials to the login page, - // the login may fail and redirect to the same login page, - // perhaps with different query parameters. - // In this case a request for the login page will push - // the login page itself, which will generate the pushed - // request for the login page, which will push the login - // page itself, etc. which is not the desired behavior. - - final String primaryResource = "/login.html"; - start(new HttpServlet() - { - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException - { - ServletOutputStream output = response.getOutputStream(); - String credentials = request.getParameter("credentials"); - if (credentials == null) - { - output.print("LOGIN"); - } - else if ("secret".equals(credentials)) - { - output.print("OK"); - } - else - { - response.setStatus(HttpStatus.TEMPORARY_REDIRECT_307); - response.setHeader(HttpHeader.LOCATION.asString(), primaryResource); - } - } - }); - final String primaryURI = newURI(primaryResource); - - final Session session = newClient(new Session.Listener.Adapter()); - - // Login with the wrong credentials, causing a redirect to self. - HttpFields.Mutable primaryFields = HttpFields.build(); - MetaData.Request primaryRequest = newRequest("GET", primaryResource + "?credentials=wrong", primaryFields); - final CountDownLatch warmupLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - if (frame.isEndStream()) - { - MetaData.Response response = (MetaData.Response)frame.getMetaData(); - if (response.getStatus() == HttpStatus.TEMPORARY_REDIRECT_307) - { - // Follow the redirect. - String location = response.getFields().get(HttpHeader.LOCATION); - HttpFields.Mutable redirectFields = HttpFields.build(); - redirectFields.put(HttpHeader.REFERER, primaryURI); - MetaData.Request redirectRequest = newRequest("GET", location, redirectFields); - session.newStream(new HeadersFrame(redirectRequest, null, true), new Promise.Adapter<>(), new Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - warmupLatch.countDown(); - } - }); - } - } - } - }); - assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); - - Thread.sleep(1000); - - // Login with the right credentials, there must be no push. - primaryRequest = newRequest("GET", primaryResource + "?credentials=secret", primaryFields); - final CountDownLatch primaryResponseLatch = new CountDownLatch(1); - final CountDownLatch pushLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - primaryResponseLatch.countDown(); - } - - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - pushLatch.countDown(); - return null; - } - }); - assertFalse(pushLatch.await(1, TimeUnit.SECONDS)); - assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); - } - - @Test - public void testPushWithQueryParameters() throws Exception - { - String name = "foo"; - String value = "bar"; - final String primaryResource = "/primary.html?" + name + "=" + value; - final String secondaryResource = "/secondary.html?" + name + "=" + value; - start(new HttpServlet() - { - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - { - String requestURI = request.getRequestURI(); - if (requestURI.endsWith(primaryResource)) - { - response.setStatus(HttpStatus.OK_200); - } - else if (requestURI.endsWith(secondaryResource)) - { - String param = request.getParameter(name); - if (param == null) - response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500); - else - response.setStatus(HttpStatus.OK_200); - } - } - }); - - final Session session = newClient(new Session.Listener.Adapter()); - - // Request for the primary and secondary resource to build the cache. - final String primaryURI = newURI(primaryResource); - HttpFields.Mutable primaryFields = HttpFields.build(); - MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch warmupLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - if (frame.isEndStream()) - { - // Request for the secondary resource. - HttpFields.Mutable secondaryFields = HttpFields.build(); - secondaryFields.put(HttpHeader.REFERER, primaryURI); - MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); - session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - if (frame.isEndStream()) - warmupLatch.countDown(); - } - }); - } - } - }); - assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); - - Thread.sleep(1000); - - // Request again the primary resource, we should get the secondary resource pushed. - primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch primaryResponseLatch = new CountDownLatch(1); - final CountDownLatch pushLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - MetaData metaData = frame.getMetaData(); - assertTrue(metaData instanceof MetaData.Request); - MetaData.Request pushedRequest = (MetaData.Request)metaData; - assertEquals(contextPath + servletPath + secondaryResource, pushedRequest.getURI().getPathQuery()); - return new Adapter() - { - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - if (frame.isEndStream()) - { - MetaData.Response response = (MetaData.Response)frame.getMetaData(); - if (response.getStatus() == HttpStatus.OK_200) - pushLatch.countDown(); - } - } - }; - } - - @Override - public void onHeaders(Stream stream, HeadersFrame frame) - { - if (frame.isEndStream()) - primaryResponseLatch.countDown(); - } - }); - assertTrue(pushLatch.await(5, TimeUnit.SECONDS)); - assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); - } - - @Test - public void testPOSTRequestIsNotPushed() throws Exception - { - final String primaryResource = "/primary.html"; - final String secondaryResource = "/secondary.png"; - final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8); - start(new HttpServlet() - { - @Override - protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String requestURI = req.getRequestURI(); - ServletOutputStream output = resp.getOutputStream(); - if (requestURI.endsWith(primaryResource)) - output.print("PRIMARY"); - else if (requestURI.endsWith(secondaryResource)) - output.write(secondaryData); - } - }); - - final Session session = newClient(new Session.Listener.Adapter()); - - // Request for the primary and secondary resource to build the cache. - final String referrerURI = newURI(primaryResource); - HttpFields.Mutable primaryFields = HttpFields.build(); - MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch warmupLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - { - // Request for the secondary resource. - HttpFields.Mutable secondaryFields = HttpFields.build(); - secondaryFields.put(HttpHeader.REFERER, referrerURI); - MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); - session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - warmupLatch.countDown(); - } - }); - } - } - }); - assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); - - // Request again the primary resource with POST, we should not get the secondary resource pushed. - primaryRequest = newRequest("POST", primaryResource, primaryFields); - final CountDownLatch primaryResponseLatch = new CountDownLatch(1); - final CountDownLatch pushLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - return new Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - pushLatch.countDown(); - } - }; - } - - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - primaryResponseLatch.countDown(); - } - }); - assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); - assertFalse(pushLatch.await(1, TimeUnit.SECONDS)); - } - - @Test - public void testPushDisabled() throws Exception - { - final String primaryResource = "/primary.html"; - final String secondaryResource = "/secondary.png"; - final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8); - start(new HttpServlet() - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException - { - String requestURI = req.getRequestURI(); - ServletOutputStream output = resp.getOutputStream(); - if (requestURI.endsWith(primaryResource)) - output.print("PRIMARY"); - else if (requestURI.endsWith(secondaryResource)) - output.write(secondaryData); - } - }); - - final Session session = newClient(new Session.Listener.Adapter() - { - @Override - public Map onPreface(Session session) - { - Map settings = new HashMap<>(); - settings.put(SettingsFrame.ENABLE_PUSH, 0); - return settings; - } - }); - - // Request for the primary and secondary resource to build the cache. - final String referrerURI = newURI(primaryResource); - HttpFields.Mutable primaryFields = HttpFields.build(); - MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch warmupLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - { - // Request for the secondary resource. - HttpFields.Mutable secondaryFields = HttpFields.build(); - secondaryFields.put(HttpHeader.REFERER, referrerURI); - MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); - session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - warmupLatch.countDown(); - } - }); - } - } - }); - assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); - - // Request again the primary resource, we should not get the secondary resource pushed. - primaryRequest = newRequest("GET", primaryResource, primaryFields); - final CountDownLatch primaryResponseLatch = new CountDownLatch(1); - final CountDownLatch pushLatch = new CountDownLatch(1); - session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() - { - @Override - public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) - { - pushLatch.countDown(); - return null; - } - - @Override - public void onData(Stream stream, DataFrame frame, Callback callback) - { - callback.succeeded(); - if (frame.isEndStream()) - primaryResponseLatch.countDown(); - } - }); - assertFalse(pushLatch.await(1, TimeUnit.SECONDS)); - assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); - } +// private String contextPath = "/push"; +// +// @Override +// protected void customizeContext(ServletContextHandler context) +// { +// context.setContextPath(contextPath); +// context.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); +// } +// +// @Override +// protected MetaData.Request newRequest(String method, String path, HttpFields fields) +// { +// return new MetaData.Request(method, HttpScheme.HTTP.asString(), new HostPortHttpField("localhost:" + connector.getLocalPort()), contextPath + servletPath + path, HttpVersion.HTTP_2, fields, -1); +// } +// +// private String newURI(String pathInfo) +// { +// return "http://localhost:" + connector.getLocalPort() + contextPath + servletPath + pathInfo; +// } +// +// @Test +// public void testPush() throws Exception +// { +// final String primaryResource = "/primary.html"; +// final String secondaryResource = "/secondary.png"; +// final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8); +// start(new HttpServlet() +// { +// @Override +// protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException +// { +// String requestURI = req.getRequestURI(); +// ServletOutputStream output = resp.getOutputStream(); +// if (requestURI.endsWith(primaryResource)) +// output.print("PRIMARY"); +// else if (requestURI.endsWith(secondaryResource)) +// output.write(secondaryData); +// } +// }); +// +// final Session session = newClient(new Session.Listener.Adapter()); +// +// // Request for the primary and secondary resource to build the cache. +// final String referrerURI = newURI(primaryResource); +// MetaData.Request primaryRequest = newRequest("GET", primaryResource, HttpFields.EMPTY); +// final CountDownLatch warmupLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// { +// // Request for the secondary resource. +// HttpFields.Mutable secondaryFields = HttpFields.build() +// .put(HttpHeader.REFERER, referrerURI); +// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// warmupLatch.countDown(); +// } +// }); +// } +// } +// }); +// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); +// +// // Request again the primary resource, we should get the secondary resource pushed. +// primaryRequest = newRequest("GET", primaryResource, HttpFields.EMPTY); +// final CountDownLatch primaryResponseLatch = new CountDownLatch(2); +// final CountDownLatch pushLatch = new CountDownLatch(2); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// MetaData.Response response = (MetaData.Response)frame.getMetaData(); +// if (response.getStatus() == HttpStatus.OK_200) +// primaryResponseLatch.countDown(); +// } +// +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// primaryResponseLatch.countDown(); +// } +// +// @Override +// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) +// { +// return new Adapter() +// { +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// MetaData.Response response = (MetaData.Response)frame.getMetaData(); +// if (response.getStatus() == HttpStatus.OK_200) +// pushLatch.countDown(); +// } +// +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// pushLatch.countDown(); +// } +// }; +// } +// }); +// assertTrue(pushLatch.await(5, TimeUnit.SECONDS)); +// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); +// } +// +// @Test +// public void testPushReferrerNoPath() throws Exception +// { +// final String primaryResource = "/primary.html"; +// final String secondaryResource = "/secondary.png"; +// final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8); +// start(new HttpServlet() +// { +// @Override +// protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException +// { +// String requestURI = req.getRequestURI(); +// ServletOutputStream output = resp.getOutputStream(); +// if (requestURI.endsWith(primaryResource)) +// output.print("PRIMARY"); +// else if (requestURI.endsWith(secondaryResource)) +// output.write(secondaryData); +// } +// }); +// +// final Session session = newClient(new Session.Listener.Adapter()); +// +// // Request for the primary and secondary resource to build the cache. +// // The referrerURI does not point to the primary resource, so there will be no +// // resource association with the primary resource and therefore won't be pushed. +// final String referrerURI = "http://localhost:" + connector.getLocalPort(); +// HttpFields.Mutable primaryFields = HttpFields.build(); +// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch warmupLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// { +// // Request for the secondary resource. +// HttpFields.Mutable secondaryFields = HttpFields.build() +// .put(HttpHeader.REFERER, referrerURI); +// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// warmupLatch.countDown(); +// } +// }); +// } +// } +// }); +// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); +// +// // Request again the primary resource, we should not get the secondary resource pushed. +// primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch primaryResponseLatch = new CountDownLatch(1); +// final CountDownLatch pushLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) +// { +// return new Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// pushLatch.countDown(); +// } +// }; +// } +// +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// primaryResponseLatch.countDown(); +// } +// }); +// assertFalse(pushLatch.await(1, TimeUnit.SECONDS)); +// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); +// } +// +// @Test +// public void testPushIsReset() throws Exception +// { +// final String primaryResource = "/primary.html"; +// final String secondaryResource = "/secondary.png"; +// final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8); +// start(new HttpServlet() +// { +// @Override +// protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException +// { +// String requestURI = req.getRequestURI(); +// ServletOutputStream output = resp.getOutputStream(); +// if (requestURI.endsWith(primaryResource)) +// output.print("PRIMARY"); +// else if (requestURI.endsWith(secondaryResource)) +// output.write(secondaryData); +// } +// }); +// +// final Session session = newClient(new Session.Listener.Adapter()); +// +// // Request for the primary and secondary resource to build the cache. +// final String primaryURI = newURI(primaryResource); +// HttpFields.Mutable primaryFields = HttpFields.build(); +// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch warmupLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// { +// // Request for the secondary resource. +// HttpFields.Mutable secondaryFields = HttpFields.build() +// .put(HttpHeader.REFERER, primaryURI); +// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// warmupLatch.countDown(); +// } +// }); +// } +// } +// }); +// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); +// +// // Request again the primary resource, we should get the secondary resource pushed. +// primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch primaryResponseLatch = new CountDownLatch(1); +// final CountDownLatch pushLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) +// { +// // Reset the stream as soon as we see the push. +// ResetFrame resetFrame = new ResetFrame(stream.getId(), ErrorCode.REFUSED_STREAM_ERROR.code); +// stream.reset(resetFrame, Callback.NOOP); +// return new Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// pushLatch.countDown(); +// } +// }; +// } +// +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// primaryResponseLatch.countDown(); +// } +// }); +// // We should not receive pushed data that we reset. +// assertFalse(pushLatch.await(1, TimeUnit.SECONDS)); +// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); +// +// // Make sure the session is sane by requesting the secondary resource. +// HttpFields.Mutable secondaryFields = HttpFields.build(); +// secondaryFields.put(HttpHeader.REFERER, primaryURI); +// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); +// final CountDownLatch secondaryResponseLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// secondaryResponseLatch.countDown(); +// } +// }); +// assertTrue(secondaryResponseLatch.await(5, TimeUnit.SECONDS)); +// } +// +// @Test +// public void testPushWithoutPrimaryResponseContent() throws Exception +// { +// final String primaryResource = "/primary.html"; +// final String secondaryResource = "/secondary.png"; +// start(new HttpServlet() +// { +// @Override +// protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException +// { +// String requestURI = request.getRequestURI(); +// final ServletOutputStream output = response.getOutputStream(); +// if (requestURI.endsWith(secondaryResource)) +// output.write("SECONDARY".getBytes(StandardCharsets.UTF_8)); +// } +// }); +// +// final Session session = newClient(new Session.Listener.Adapter()); +// +// // Request for the primary and secondary resource to build the cache. +// final String primaryURI = newURI(primaryResource); +// HttpFields.Mutable primaryFields = HttpFields.build(); +// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch warmupLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// if (frame.isEndStream()) +// { +// // Request for the secondary resource. +// HttpFields.Mutable secondaryFields = HttpFields.build(); +// secondaryFields.put(HttpHeader.REFERER, primaryURI); +// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// warmupLatch.countDown(); +// } +// }); +// } +// } +// }); +// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); +// +// Thread.sleep(1000); +// +// // Request again the primary resource, we should get the secondary resource pushed. +// primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch primaryResponseLatch = new CountDownLatch(1); +// final CountDownLatch pushLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// if (frame.isEndStream()) +// primaryResponseLatch.countDown(); +// } +// +// @Override +// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) +// { +// return new Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// pushLatch.countDown(); +// } +// }; +// } +// }); +// assertTrue(pushLatch.await(5, TimeUnit.SECONDS)); +// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); +// } +// +// @Test +// public void testRecursivePush() throws Exception +// { +// final String primaryResource = "/primary.html"; +// final String secondaryResource1 = "/secondary1.css"; +// final String secondaryResource2 = "/secondary2.js"; +// final String tertiaryResource = "/tertiary.png"; +// start(new HttpServlet() +// { +// @Override +// protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException +// { +// String requestURI = request.getRequestURI(); +// final ServletOutputStream output = response.getOutputStream(); +// if (requestURI.endsWith(primaryResource)) +// output.print("PRIMARY"); +// else if (requestURI.endsWith(secondaryResource1)) +// output.print("body { background-image: url(\"" + tertiaryResource + "\"); }"); +// else if (requestURI.endsWith(secondaryResource2)) +// output.print("(function() { window.alert('HTTP/2'); })()"); +// if (requestURI.endsWith(tertiaryResource)) +// output.write("TERTIARY".getBytes(StandardCharsets.UTF_8)); +// } +// }); +// +// final Session session = newClient(new Session.Listener.Adapter()); +// +// // Request for the primary, secondary and tertiary resource to build the cache. +// final String primaryURI = newURI(primaryResource); +// HttpFields.Mutable primaryFields = HttpFields.build(); +// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch warmupLatch = new CountDownLatch(2); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// { +// // Request for the secondary resources. +// String secondaryURI1 = newURI(secondaryResource1); +// HttpFields.Mutable secondaryFields1 = HttpFields.build() +// .put(HttpHeader.REFERER, primaryURI); +// MetaData.Request secondaryRequest1 = newRequest("GET", secondaryResource1, secondaryFields1); +// session.newStream(new HeadersFrame(secondaryRequest1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// { +// // Request for the tertiary resource. +// HttpFields.Mutable tertiaryFields = HttpFields.build() +// .put(HttpHeader.REFERER, secondaryURI1); +// MetaData.Request tertiaryRequest = newRequest("GET", tertiaryResource, tertiaryFields); +// session.newStream(new HeadersFrame(tertiaryRequest, null, true), new Promise.Adapter<>(), new Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// warmupLatch.countDown(); +// } +// }); +// } +// } +// }); +// +// HttpFields.Mutable secondaryFields2 = HttpFields.build() +// .put(HttpHeader.REFERER, primaryURI); +// MetaData.Request secondaryRequest2 = newRequest("GET", secondaryResource2, secondaryFields2); +// session.newStream(new HeadersFrame(secondaryRequest2, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// warmupLatch.countDown(); +// } +// }); +// } +// } +// }); +// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); +// +// Thread.sleep(1000); +// +// // Request again the primary resource, we should get the secondary and tertiary resources pushed. +// primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch primaryResponseLatch = new CountDownLatch(1); +// final CountDownLatch primaryPushesLatch = new CountDownLatch(3); +// final CountDownLatch recursiveLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// primaryResponseLatch.countDown(); +// } +// +// @Override +// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) +// { +// // The stream id of the PUSH_PROMISE must +// // always be a client stream and therefore odd. +// assertEquals(1, frame.getStreamId() & 1); +// return new Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// primaryPushesLatch.countDown(); +// } +// +// @Override +// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) +// { +// return new Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// recursiveLatch.countDown(); +// } +// }; +// } +// }; +// } +// }); +// +// assertTrue(primaryPushesLatch.await(5, TimeUnit.SECONDS)); +// assertFalse(recursiveLatch.await(1, TimeUnit.SECONDS)); +// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); +// +// // Make sure that explicitly requesting a secondary resource, we get the tertiary pushed. +// CountDownLatch secondaryResponseLatch = new CountDownLatch(1); +// CountDownLatch secondaryPushLatch = new CountDownLatch(1); +// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource1, HttpFields.EMPTY); +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// secondaryResponseLatch.countDown(); +// } +// +// @Override +// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) +// { +// return new Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// secondaryPushLatch.countDown(); +// } +// }; +// } +// }); +// +// assertTrue(secondaryPushLatch.await(5, TimeUnit.SECONDS)); +// assertTrue(secondaryResponseLatch.await(5, TimeUnit.SECONDS)); +// } +// +// @Test +// public void testSelfPush() throws Exception +// { +// // The test case is that of a login page, for example. +// // When the user sends the credentials to the login page, +// // the login may fail and redirect to the same login page, +// // perhaps with different query parameters. +// // In this case a request for the login page will push +// // the login page itself, which will generate the pushed +// // request for the login page, which will push the login +// // page itself, etc. which is not the desired behavior. +// +// final String primaryResource = "/login.html"; +// start(new HttpServlet() +// { +// @Override +// protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException +// { +// ServletOutputStream output = response.getOutputStream(); +// String credentials = request.getParameter("credentials"); +// if (credentials == null) +// { +// output.print("LOGIN"); +// } +// else if ("secret".equals(credentials)) +// { +// output.print("OK"); +// } +// else +// { +// response.setStatus(HttpStatus.TEMPORARY_REDIRECT_307); +// response.getHeaders().put(HttpHeader.LOCATION, primaryResource); +// } +// } +// }); +// final String primaryURI = newURI(primaryResource); +// +// final Session session = newClient(new Session.Listener.Adapter()); +// +// // Login with the wrong credentials, causing a redirect to self. +// HttpFields.Mutable primaryFields = HttpFields.build(); +// MetaData.Request primaryRequest = newRequest("GET", primaryResource + "?credentials=wrong", primaryFields); +// final CountDownLatch warmupLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// if (frame.isEndStream()) +// { +// MetaData.Response response = (MetaData.Response)frame.getMetaData(); +// if (response.getStatus() == HttpStatus.TEMPORARY_REDIRECT_307) +// { +// // Follow the redirect. +// String location = response.getFields().get(HttpHeader.LOCATION); +// HttpFields.Mutable redirectFields = HttpFields.build(); +// redirectFields.put(HttpHeader.REFERER, primaryURI); +// MetaData.Request redirectRequest = newRequest("GET", location, redirectFields); +// session.newStream(new HeadersFrame(redirectRequest, null, true), new Promise.Adapter<>(), new Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// warmupLatch.countDown(); +// } +// }); +// } +// } +// } +// }); +// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); +// +// Thread.sleep(1000); +// +// // Login with the right credentials, there must be no push. +// primaryRequest = newRequest("GET", primaryResource + "?credentials=secret", primaryFields); +// final CountDownLatch primaryResponseLatch = new CountDownLatch(1); +// final CountDownLatch pushLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// primaryResponseLatch.countDown(); +// } +// +// @Override +// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) +// { +// pushLatch.countDown(); +// return null; +// } +// }); +// assertFalse(pushLatch.await(1, TimeUnit.SECONDS)); +// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); +// } +// +// @Test +// public void testPushWithQueryParameters() throws Exception +// { +// String name = "foo"; +// String value = "bar"; +// final String primaryResource = "/primary.html?" + name + "=" + value; +// final String secondaryResource = "/secondary.html?" + name + "=" + value; +// start(new HttpServlet() +// { +// @Override +// protected void doGet(HttpServletRequest request, HttpServletResponse response) +// { +// String requestURI = request.getRequestURI(); +// if (requestURI.endsWith(primaryResource)) +// { +// response.setStatus(HttpStatus.OK_200); +// } +// else if (requestURI.endsWith(secondaryResource)) +// { +// String param = request.getParameter(name); +// if (param == null) +// response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500); +// else +// response.setStatus(HttpStatus.OK_200); +// } +// } +// }); +// +// final Session session = newClient(new Session.Listener.Adapter()); +// +// // Request for the primary and secondary resource to build the cache. +// final String primaryURI = newURI(primaryResource); +// HttpFields.Mutable primaryFields = HttpFields.build(); +// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch warmupLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// if (frame.isEndStream()) +// { +// // Request for the secondary resource. +// HttpFields.Mutable secondaryFields = HttpFields.build(); +// secondaryFields.put(HttpHeader.REFERER, primaryURI); +// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// if (frame.isEndStream()) +// warmupLatch.countDown(); +// } +// }); +// } +// } +// }); +// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); +// +// Thread.sleep(1000); +// +// // Request again the primary resource, we should get the secondary resource pushed. +// primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch primaryResponseLatch = new CountDownLatch(1); +// final CountDownLatch pushLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) +// { +// MetaData metaData = frame.getMetaData(); +// assertTrue(metaData instanceof MetaData.Request); +// MetaData.Request pushedRequest = (MetaData.Request)metaData; +// assertEquals(contextPath + servletPath + secondaryResource, pushedRequest.getURI().getPathQuery()); +// return new Adapter() +// { +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// if (frame.isEndStream()) +// { +// MetaData.Response response = (MetaData.Response)frame.getMetaData(); +// if (response.getStatus() == HttpStatus.OK_200) +// pushLatch.countDown(); +// } +// } +// }; +// } +// +// @Override +// public void onHeaders(Stream stream, HeadersFrame frame) +// { +// if (frame.isEndStream()) +// primaryResponseLatch.countDown(); +// } +// }); +// assertTrue(pushLatch.await(5, TimeUnit.SECONDS)); +// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); +// } +// +// @Test +// public void testPOSTRequestIsNotPushed() throws Exception +// { +// final String primaryResource = "/primary.html"; +// final String secondaryResource = "/secondary.png"; +// final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8); +// start(new HttpServlet() +// { +// @Override +// protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException +// { +// String requestURI = req.getRequestURI(); +// ServletOutputStream output = resp.getOutputStream(); +// if (requestURI.endsWith(primaryResource)) +// output.print("PRIMARY"); +// else if (requestURI.endsWith(secondaryResource)) +// output.write(secondaryData); +// } +// }); +// +// final Session session = newClient(new Session.Listener.Adapter()); +// +// // Request for the primary and secondary resource to build the cache. +// final String referrerURI = newURI(primaryResource); +// HttpFields.Mutable primaryFields = HttpFields.build(); +// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch warmupLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// { +// // Request for the secondary resource. +// HttpFields.Mutable secondaryFields = HttpFields.build(); +// secondaryFields.put(HttpHeader.REFERER, referrerURI); +// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// warmupLatch.countDown(); +// } +// }); +// } +// } +// }); +// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); +// +// // Request again the primary resource with POST, we should not get the secondary resource pushed. +// primaryRequest = newRequest("POST", primaryResource, primaryFields); +// final CountDownLatch primaryResponseLatch = new CountDownLatch(1); +// final CountDownLatch pushLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) +// { +// return new Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// pushLatch.countDown(); +// } +// }; +// } +// +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// primaryResponseLatch.countDown(); +// } +// }); +// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); +// assertFalse(pushLatch.await(1, TimeUnit.SECONDS)); +// } +// +// @Test +// public void testPushDisabled() throws Exception +// { +// final String primaryResource = "/primary.html"; +// final String secondaryResource = "/secondary.png"; +// final byte[] secondaryData = "SECONDARY".getBytes(StandardCharsets.UTF_8); +// start(new HttpServlet() +// { +// @Override +// protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException +// { +// String requestURI = req.getRequestURI(); +// ServletOutputStream output = resp.getOutputStream(); +// if (requestURI.endsWith(primaryResource)) +// output.print("PRIMARY"); +// else if (requestURI.endsWith(secondaryResource)) +// output.write(secondaryData); +// } +// }); +// +// final Session session = newClient(new Session.Listener.Adapter() +// { +// @Override +// public Map onPreface(Session session) +// { +// Map settings = new HashMap<>(); +// settings.put(SettingsFrame.ENABLE_PUSH, 0); +// return settings; +// } +// }); +// +// // Request for the primary and secondary resource to build the cache. +// final String referrerURI = newURI(primaryResource); +// HttpFields.Mutable primaryFields = HttpFields.build(); +// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch warmupLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// { +// // Request for the secondary resource. +// HttpFields.Mutable secondaryFields = HttpFields.build(); +// secondaryFields.put(HttpHeader.REFERER, referrerURI); +// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields); +// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// warmupLatch.countDown(); +// } +// }); +// } +// } +// }); +// assertTrue(warmupLatch.await(5, TimeUnit.SECONDS)); +// +// // Request again the primary resource, we should not get the secondary resource pushed. +// primaryRequest = newRequest("GET", primaryResource, primaryFields); +// final CountDownLatch primaryResponseLatch = new CountDownLatch(1); +// final CountDownLatch pushLatch = new CountDownLatch(1); +// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter() +// { +// @Override +// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame) +// { +// pushLatch.countDown(); +// return null; +// } +// +// @Override +// public void onData(Stream stream, DataFrame frame, Callback callback) +// { +// callback.succeeded(); +// if (frame.isEndStream()) +// primaryResponseLatch.countDown(); +// } +// }); +// assertFalse(pushLatch.await(1, TimeUnit.SECONDS)); +// assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS)); +// } } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushedResourcesTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushedResourcesTest.java index 307ede0a2bf..8f9643d8344 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushedResourcesTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/PushedResourcesTest.java @@ -11,15 +11,13 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; +import java.nio.ByteBuffer; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Result; @@ -35,8 +33,9 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; import org.junit.jupiter.api.Test; @@ -80,7 +79,7 @@ public class PushedResourcesTest extends AbstractTest } }); - HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort()); + HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort()); ContentResponse response = request .pushListener((mainRequest, pushedRequest) -> null) .timeout(5, TimeUnit.SECONDS) @@ -103,36 +102,34 @@ public class PushedResourcesTest extends AbstractTest String path1 = "/secondary1"; String path2 = "/secondary2"; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); + String target = request.getPathInContext(); if (target.equals(path1)) { - response.getOutputStream().write(pushBytes1); + response.write(true, callback, ByteBuffer.wrap(pushBytes1)); } else if (target.equals(path2)) { - response.getOutputStream().write(pushBytes2); + response.write(true, callback, ByteBuffer.wrap(pushBytes2)); } else { - baseRequest.newPushBuilder() - .path(path1) - .push(); - baseRequest.newPushBuilder() - .path(path2) - .push(); - response.getOutputStream().write(bytes); + MetaData.Request push1 = new MetaData.Request(null, HttpURI.build(request.getHttpURI()).path(path1), HttpVersion.HTTP_2, HttpFields.EMPTY); + request.push(push1); + MetaData.Request push2 = new MetaData.Request(null, HttpURI.build(request.getHttpURI()).path(path2), HttpVersion.HTTP_2, HttpFields.EMPTY); + request.push(push2); + response.write(true, callback, ByteBuffer.wrap(bytes)); } } }); CountDownLatch latch1 = new CountDownLatch(1); CountDownLatch latch2 = new CountDownLatch(1); - HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort()); + HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort()); ContentResponse response = request .pushListener((mainRequest, pushedRequest) -> new BufferingResponseListener() { @@ -170,23 +167,30 @@ public class PushedResourcesTest extends AbstractTest String oldPath = "/old"; String newPath = "/new"; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); + String target = request.getPathInContext(); if (target.equals(oldPath)) - response.sendRedirect(newPath); + { + Response.sendRedirect(request, response, callback, newPath); + } else if (target.equals(newPath)) - response.getOutputStream().write(pushBytes); + { + response.write(true, callback, ByteBuffer.wrap(pushBytes)); + } else - baseRequest.newPushBuilder().path(oldPath).push(); + { + request.push(new MetaData.Request(null, HttpURI.build(request.getHttpURI()).path(oldPath), HttpVersion.HTTP_2, HttpFields.EMPTY)); + callback.succeeded(); + } } }); CountDownLatch latch = new CountDownLatch(1); - HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort()); + HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort()); ContentResponse response = request .pushListener((mainRequest, pushedRequest) -> new BufferingResponseListener() { diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java index 4633a864143..a12ac03bc7c 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.net.InetSocketAddress; import java.nio.ByteBuffer; @@ -34,6 +34,7 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; +import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.GoAwayFrame; @@ -335,7 +336,7 @@ public class RawHTTP2ProxyTest if (LOGGER.isDebugEnabled()) LOGGER.debug("CPS queueing {} for {} on {}", frame, stream, stream.getSession()); boolean connected; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { Deque deque = frames.computeIfAbsent(stream, s -> new ArrayDeque<>()); deque.offer(new FrameInfo(frame, callback)); @@ -352,14 +353,14 @@ public class RawHTTP2ProxyTest InetSocketAddress address = new InetSocketAddress(host, port); if (LOGGER.isDebugEnabled()) LOGGER.debug("CPS connecting to {}", address); - client.connect(address, new ServerToProxySessionListener(), new Promise() + client.connect(address, new ServerToProxySessionListener(), new Promise<>() { @Override public void succeeded(Session result) { if (LOGGER.isDebugEnabled()) LOGGER.debug("CPS connected to {} with {}", address, result); - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { proxyToServerSession = result; } @@ -381,7 +382,7 @@ public class RawHTTP2ProxyTest { Stream proxyToServerStream = null; Session proxyToServerSession = null; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { for (Map.Entry> entry : frames.entrySet()) { @@ -406,12 +407,12 @@ public class RawHTTP2ProxyTest { HeadersFrame clientToProxyFrame = (HeadersFrame)frameInfo.frame; HeadersFrame proxyToServerFrame = new HeadersFrame(clientToProxyFrame.getMetaData(), clientToProxyFrame.getPriority(), clientToProxyFrame.isEndStream()); - proxyToServerSession.newStream(proxyToServerFrame, new Promise() + proxyToServerSession.newStream(proxyToServerFrame, new Promise<>() { @Override public void succeeded(Stream result) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { if (LOGGER.isDebugEnabled()) LOGGER.debug("CPS created {}", result); @@ -436,27 +437,24 @@ public class RawHTTP2ProxyTest { if (LOGGER.isDebugEnabled()) LOGGER.debug("CPS forwarding {} from {} to {}", frameInfo, clientToProxyStream, proxyToServerStream); - switch (frameInfo.frame.getType()) + return switch (frameInfo.frame.getType()) { - case HEADERS: + case HEADERS -> { HeadersFrame clientToProxyFrame = (HeadersFrame)frameInfo.frame; HeadersFrame proxyToServerFrame = new HeadersFrame(proxyToServerStream.getId(), clientToProxyFrame.getMetaData(), clientToProxyFrame.getPriority(), clientToProxyFrame.isEndStream()); proxyToServerStream.headers(proxyToServerFrame, this); - return Action.SCHEDULED; + yield Action.SCHEDULED; } - case DATA: + case DATA -> { DataFrame clientToProxyFrame = (DataFrame)frameInfo.frame; DataFrame proxyToServerFrame = new DataFrame(proxyToServerStream.getId(), clientToProxyFrame.getData(), clientToProxyFrame.isEndStream()); proxyToServerStream.data(proxyToServerFrame, this); - return Action.SCHEDULED; + yield Action.SCHEDULED; } - default: - { - throw new IllegalStateException(); - } - } + default -> throw new IllegalStateException(); + }; } } @@ -555,7 +553,7 @@ public class RawHTTP2ProxyTest protected Action process() throws Throwable { Stream proxyToClientStream = null; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { for (Map.Entry> entry : frames.entrySet()) { @@ -580,32 +578,26 @@ public class RawHTTP2ProxyTest if (LOGGER.isDebugEnabled()) LOGGER.debug("SPC forwarding {} for {} to {}", frameInfo, serverToProxyStream, proxyToClientStream); - switch (frameInfo.frame.getType()) + return switch (frameInfo.frame.getType()) { - case HEADERS: + case HEADERS -> { HeadersFrame serverToProxyFrame = (HeadersFrame)frameInfo.frame; HeadersFrame proxyToClientFrame = new HeadersFrame(proxyToClientStream.getId(), serverToProxyFrame.getMetaData(), serverToProxyFrame.getPriority(), serverToProxyFrame.isEndStream()); proxyToClientStream.headers(proxyToClientFrame, this); - return Action.SCHEDULED; + yield Action.SCHEDULED; } - case DATA: + case DATA -> { DataFrame clientToProxyFrame = (DataFrame)frameInfo.frame; DataFrame proxyToServerFrame = new DataFrame(serverToProxyStream.getId(), clientToProxyFrame.getData(), clientToProxyFrame.isEndStream()); proxyToClientStream.data(proxyToServerFrame, this); - return Action.SCHEDULED; + yield Action.SCHEDULED; } - case PUSH_PROMISE: - { - // TODO - throw new UnsupportedOperationException(); - } - default: - { - throw new IllegalStateException(); - } - } + // TODO + case PUSH_PROMISE -> throw new UnsupportedOperationException(); + default -> throw new IllegalStateException(); + }; } @Override @@ -626,7 +618,7 @@ public class RawHTTP2ProxyTest { if (LOGGER.isDebugEnabled()) LOGGER.debug("SPC queueing {} for {} on {}", frame, stream, stream.getSession()); - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { Deque deque = frames.computeIfAbsent(stream, s -> new ArrayDeque<>()); deque.offer(new FrameInfo(frame, callback)); @@ -678,7 +670,7 @@ public class RawHTTP2ProxyTest private void link(Stream proxyToServerStream, Stream clientToProxyStream) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { streams.put(proxyToServerStream, clientToProxyStream); } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RequestTrailersTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RequestTrailersTest.java index d4cbbbd19d9..1d24beaa155 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RequestTrailersTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RequestTrailersTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.tests; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -73,7 +73,7 @@ public class RequestTrailersTest extends AbstractTest } }); - HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort()); + HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort()); HttpFields.Mutable trailers = HttpFields.build(); request.trailers(() -> trailers); if (content != null) @@ -113,7 +113,7 @@ public class RequestTrailersTest extends AbstractTest } }); - HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort()); + HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort()); HttpFields.Mutable trailers = HttpFields.build(); request.trailers(() -> trailers); AsyncRequestContent content = new AsyncRequestContent(); @@ -162,7 +162,7 @@ public class RequestTrailersTest extends AbstractTest } }); - HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort()); + HttpRequest request = (HttpRequest)httpClient.newRequest("localhost", connector.getLocalPort()); HttpFields.Mutable trailers = HttpFields.build(); request.trailers(() -> trailers); AsyncRequestContent content = new AsyncRequestContent(); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ResponseTrailerTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ResponseTrailerTest.java index 57ce3b89853..12efe2d4776 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ResponseTrailerTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/ResponseTrailerTest.java @@ -11,19 +11,16 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client.http; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; import java.net.InetSocketAddress; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.HashMap; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpURI; @@ -34,7 +31,9 @@ import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Promise; @@ -60,15 +59,17 @@ public class ResponseTrailerTest extends AbstractTest public void testEmptyTrailers(String data) throws Exception { - start(new EmptyServerHandler() + start(new Handler.Processor() { @Override - protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { // Send empty response trailers. - response.setTrailerFields(HashMap::new); + response.getTrailers(); if (data != null) - response.getOutputStream().write(data.getBytes(StandardCharsets.US_ASCII)); + response.write(true, callback, ByteBuffer.wrap(data.getBytes(StandardCharsets.US_ASCII))); + else + callback.succeeded(); } }); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SessionFailureTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SessionFailureTest.java index 445c5c42399..bb6fa5bfae5 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SessionFailureTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SessionFailureTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.io.InputStream; import java.io.OutputStream; @@ -20,11 +20,11 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.internal.HTTP2Session; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Promise; import org.junit.jupiter.api.Test; @@ -101,7 +101,7 @@ public class SessionFailureTest extends AbstractTest }); final CountDownLatch clientFailureLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener.Adapter() { @Override public void onFailure(Session session, Throwable failure) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SimpleFlowControlStrategyTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SimpleFlowControlStrategyTest.java index b15bb56d9c6..2428907725c 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SimpleFlowControlStrategyTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SimpleFlowControlStrategyTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http2.SimpleFlowControlStrategy; diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SmallThreadPoolLoadTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SmallThreadPoolLoadTest.java index 8b9b238389a..605cb916239 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SmallThreadPoolLoadTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/SmallThreadPoolLoadTest.java @@ -11,11 +11,9 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; -import java.io.IOException; import java.nio.ByteBuffer; -import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; @@ -23,10 +21,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.IntStream; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MetaData; @@ -36,11 +30,13 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.util.ByteArrayOutputStream2; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; -import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.Scheduler; import org.hamcrest.Matchers; @@ -57,13 +53,13 @@ public class SmallThreadPoolLoadTest extends AbstractTest private final AtomicLong requestIds = new AtomicLong(); @Override - protected void customizeContext(ServletContextHandler context) + protected void prepareServer(ConnectionFactory... connectionFactories) { - QueuedThreadPool serverThreads = (QueuedThreadPool)context.getServer().getThreadPool(); + super.prepareServer(connectionFactories); + QueuedThreadPool serverThreads = (QueuedThreadPool)server.getThreadPool(); serverThreads.setDetailedDump(true); serverThreads.setMaxThreads(5); serverThreads.setLowThreadsThreshold(1); - AbstractHTTP2ServerConnectionFactory h2 = connector.getBean(AbstractHTTP2ServerConnectionFactory.class); h2.setInitialSessionRecvWindow(Integer.MAX_VALUE); } @@ -71,10 +67,10 @@ public class SmallThreadPoolLoadTest extends AbstractTest @Test public void testConcurrentWithSmallServerThreadPool() throws Exception { - start(new LoadServlet()); + start(new LoadHandler()); // Only one connection to the server. - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); int runs = 10; int iterations = 512; @@ -96,10 +92,10 @@ public class SmallThreadPoolLoadTest extends AbstractTest // Dumps the state of the client if the test takes too long. Thread testThread = Thread.currentThread(); - Scheduler.Task task = client.getScheduler().schedule(() -> + Scheduler.Task task = http2Client.getScheduler().schedule(() -> { logger.warn("Interrupting test, it is taking too long - \nServer: \n" + - server.dump() + "\nClient: \n" + client.dump()); + server.dump() + "\nClient: \n" + http2Client.dump()); testThread.interrupt(); }, iterations * factor, TimeUnit.MILLISECONDS); @@ -184,32 +180,28 @@ public class SmallThreadPoolLoadTest extends AbstractTest latch.countDown(); else logger.warn("Request {} took too long - \nServer: \n" + - server.dump() + "\nClient: \n" + client.dump(), requestId); + server.dump() + "\nClient: \n" + http2Client.dump(), requestId); return !reset.get(); } - private static class LoadServlet extends HttpServlet + private static class LoadHandler extends Handler.Processor { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + public void process(Request request, Response response, Callback callback) throws Exception { - String method = request.getMethod().toUpperCase(Locale.ENGLISH); - switch (method) + switch (HttpMethod.fromString(request.getMethod())) { - case "GET": + case GET -> { - int contentLength = request.getIntHeader("X-Download"); + int contentLength = (int)request.getHeaders().getLongField("X-Download"); if (contentLength > 0) - response.getOutputStream().write(new byte[contentLength]); - break; + response.write(true, callback, ByteBuffer.wrap(new byte[contentLength])); + else + callback.succeeded(); } - case "POST": + case POST -> { - int contentLength = request.getContentLength(); - ByteArrayOutputStream2 bout = new ByteArrayOutputStream2(contentLength > 0 ? contentLength : 16 * 1024); - IO.copy(request.getInputStream(), bout); - response.getOutputStream().write(bout.getBuf(), 0, bout.getCount()); - break; + Content.copy(request, response, callback); } } } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCloseTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCloseTest.java index 5dc0beb00c9..78b0bc10bca 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCloseTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCloseTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -23,9 +23,6 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.ErrorCode; -import org.eclipse.jetty.http2.HTTP2Session; -import org.eclipse.jetty.http2.HTTP2Stream; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -33,6 +30,9 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PushPromiseFrame; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.HTTP2Stream; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.Promise; @@ -59,7 +59,7 @@ public class StreamCloseTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true); FuturePromise promise = new FuturePromise<>(); session.newStream(frame, promise, null); @@ -93,7 +93,7 @@ public class StreamCloseTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true); FuturePromise promise = new FuturePromise<>(); session.newStream(frame, promise, new Stream.Listener.Adapter() @@ -147,7 +147,7 @@ public class StreamCloseTest extends AbstractTest }); final CountDownLatch completeLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, false); FuturePromise promise = new FuturePromise<>(); session.newStream(frame, promise, new Stream.Listener.Adapter() @@ -192,7 +192,7 @@ public class StreamCloseTest extends AbstractTest public Stream.Listener onNewStream(Stream stream, HeadersFrame frame) { PushPromiseFrame pushFrame = new PushPromiseFrame(stream.getId(), newRequest("GET", HttpFields.EMPTY)); - stream.push(pushFrame, new Promise.Adapter() + stream.push(pushFrame, new Promise.Adapter<>() { @Override public void succeeded(final Stream pushedStream) @@ -217,7 +217,7 @@ public class StreamCloseTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true); final CountDownLatch clientLatch = new CountDownLatch(1); session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() @@ -269,7 +269,7 @@ public class StreamCloseTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true); final CountDownLatch clientLatch = new CountDownLatch(2); session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() @@ -333,7 +333,7 @@ public class StreamCloseTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); // First stream will be idle on server. HeadersFrame request1 = new HeadersFrame(newRequest("HEAD", HttpFields.EMPTY), null, true); diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCountTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCountTest.java index f94bc86accd..9f1ef4820cc 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCountTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamCountTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.nio.ByteBuffer; import java.util.HashMap; @@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -32,7 +31,8 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; -import org.eclipse.jetty.http2.generator.Generator; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.generator.Generator; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; @@ -81,7 +81,7 @@ public class StreamCountTest extends AbstractTest }); CountDownLatch settingsLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener.Adapter() { @Override public void onSettings(Session session, SettingsFrame frame) @@ -149,7 +149,7 @@ public class StreamCountTest extends AbstractTest }); CountDownLatch sessionResetLatch = new CountDownLatch(2); - Session session = newClient(new Session.Listener.Adapter() + Session session = newClientSession(new Session.Listener.Adapter() { @Override public void onReset(Session session, ResetFrame frame) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamResetTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamResetTest.java index 9750cff0d8a..72e8a4c4117 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamResetTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/StreamResetTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.io.IOException; import java.io.InterruptedIOException; @@ -31,16 +31,11 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -import jakarta.servlet.AsyncContext; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.WriteListener; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; @@ -48,10 +43,7 @@ import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http2.BufferingFlowControlStrategy; -import org.eclipse.jetty.http2.ErrorCode; import org.eclipse.jetty.http2.FlowControlStrategy; -import org.eclipse.jetty.http2.HTTP2Flusher; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.ISession; import org.eclipse.jetty.http2.IStream; import org.eclipse.jetty.http2.api.Session; @@ -64,30 +56,34 @@ import org.eclipse.jetty.http2.frames.PrefaceFrame; import org.eclipse.jetty.http2.frames.ResetFrame; import org.eclipse.jetty.http2.frames.SettingsFrame; import org.eclipse.jetty.http2.frames.WindowUpdateFrame; -import org.eclipse.jetty.http2.generator.Generator; +import org.eclipse.jetty.http2.internal.ErrorCode; +import org.eclipse.jetty.http2.internal.HTTP2Flusher; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.http2.internal.generator.Generator; import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.io.AbstractEndPoint; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.WriteFlusher; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.server.internal.HttpChannelState; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.FuturePromise; -import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -101,7 +97,7 @@ public class StreamResetTest extends AbstractTest { start(new ServerSessionListener.Adapter()); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); @@ -139,7 +135,7 @@ public class StreamResetTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); @@ -208,7 +204,7 @@ public class StreamResetTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request request1 = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame1 = new HeadersFrame(request1, null, false); FuturePromise promise1 = new FuturePromise<>(); @@ -266,18 +262,18 @@ public class StreamResetTest extends AbstractTest CountDownLatch commitLatch = new CountDownLatch(1); CountDownLatch resetLatch = new CountDownLatch(1); CountDownLatch dataLatch = new CountDownLatch(1); - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { Charset charset = StandardCharsets.UTF_8; byte[] data = "AFTER RESET".getBytes(charset); response.setStatus(200); response.setContentType("text/plain;charset=" + charset.name()); - response.setContentLength(data.length * 10); - response.flushBuffer(); + response.setContentLength(data.length * 10L); + Response.write(response, false); // Wait for the commit callback to complete. commitLatch.countDown(); @@ -300,8 +296,7 @@ public class StreamResetTest extends AbstractTest for (int i = 0; i < 100; i++) { Thread.sleep(100); - response.getOutputStream().write(data); - response.flushBuffer(); + Response.write(response, false, ByteBuffer.wrap(data)); } } catch (InterruptedException x) @@ -315,7 +310,7 @@ public class StreamResetTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, true); client.newStream(frame, new FuturePromise<>(), new Stream.Listener.Adapter() @@ -340,25 +335,31 @@ public class StreamResetTest extends AbstractTest assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); } + // TODO: This test writes after a failure and highlights some problem in the implementation + // of the handling of errors. For example, the request._error field is set by the failure, + // but checked during the succeed of the callback (so cannot turn a failure into a success) + // and also highlights that the implementation should be more precise at severing the link + // between channel and request, possibly where the request only has one field, the channel. @Test + @Disabled public void testAsyncWriteAfterStreamReceivingReset() throws Exception { CountDownLatch commitLatch = new CountDownLatch(1); CountDownLatch resetLatch = new CountDownLatch(1); CountDownLatch dataLatch = new CountDownLatch(1); - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void doGet(HttpServletRequest request, final HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { Charset charset = StandardCharsets.UTF_8; - final ByteBuffer data = ByteBuffer.wrap("AFTER RESET".getBytes(charset)); + ByteBuffer data = charset.encode("AFTER RESET"); response.setStatus(200); response.setContentType("text/plain;charset=" + charset.name()); response.setContentLength(data.remaining()); - response.flushBuffer(); - // Wait for the commit callback to complete. + Response.write(response, false); + commitLatch.countDown(); try @@ -374,7 +375,6 @@ public class StreamResetTest extends AbstractTest } // Write some content asynchronously after the stream has been reset. - final AsyncContext context = request.startAsync(); new Thread(() -> { try @@ -383,16 +383,11 @@ public class StreamResetTest extends AbstractTest // doGet() so this is really asynchronous. Thread.sleep(1000); - HttpOutput output = (HttpOutput)response.getOutputStream(); - output.sendContent(data, new Callback() + response.write(true, Callback.from(callback::succeeded, x -> { - @Override - public void failed(Throwable x) - { - context.complete(); - dataLatch.countDown(); - } - }); + callback.succeeded(); + dataLatch.countDown(); + }), data); } catch (Throwable x) { @@ -402,7 +397,7 @@ public class StreamResetTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, true); client.newStream(frame, new FuturePromise<>(), new Stream.Listener.Adapter() @@ -430,24 +425,26 @@ public class StreamResetTest extends AbstractTest @Test public void testClientResetConsumesQueuedData() throws Exception { - start(new EmptyHttpServlet()); + CountDownLatch dataLatch = new CountDownLatch(1); + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) throws Exception + { + // Wait for the data to be sent. + assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); + callback.succeeded(); + } + }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); client.newStream(frame, promise, new Stream.Listener.Adapter()); Stream stream = promise.get(5, TimeUnit.SECONDS); ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE); - CountDownLatch dataLatch = new CountDownLatch(1); - stream.data(new DataFrame(stream.getId(), data, false), new Callback() - { - @Override - public void succeeded() - { - dataLatch.countDown(); - } - }); + stream.data(new DataFrame(stream.getId(), data, false), Callback.from(dataLatch::countDown)); // The server does not read the data, so the flow control window should be zero. assertTrue(dataLatch.await(5, TimeUnit.SECONDS)); assertEquals(0, ((ISession)client).updateSendWindow(0)); @@ -475,32 +472,45 @@ public class StreamResetTest extends AbstractTest h2.setInitialStreamRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE); connector = new ServerConnector(server, 1, 1, h2); server.addConnector(connector); - ServletContextHandler context = new ServletContextHandler(server, "/"); - AtomicReference phaser = new AtomicReference<>(); - context.addServlet(new ServletHolder(new HttpServlet() + AtomicReference requestOnServer = new AtomicReference<>(); + AtomicBoolean blocker = new AtomicBoolean(true); + Object lock = new Object(); + server.setHandler(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { - phaser.get().countDown(); - IO.copy(request.getInputStream(), response.getOutputStream()); + requestOnServer.get().countDown(); + + // Block all threads until notified. + synchronized (lock) + { + while (blocker.get()) + { + lock.wait(); + } + } + + Content.copy(request, response, callback); } - }), servletPath + "/*"); + }); server.start(); prepareClient(); - client.start(); + httpClient.start(); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); // Send requests until one is queued on the server but not dispatched. AtomicReference latch = new AtomicReference<>(); List streams = new ArrayList<>(); + int count = 0; while (true) { - phaser.set(new CountDownLatch(1)); + ++count; + requestOnServer.set(new CountDownLatch(1)); - MetaData.Request request = newRequest("GET", HttpFields.EMPTY); + MetaData.Request request = newRequest("GET", "/" + count, HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); client.newStream(frame, promise, new Stream.Listener.Adapter() @@ -526,7 +536,8 @@ public class StreamResetTest extends AbstractTest ByteBuffer data = ByteBuffer.allocate(10); stream.data(new DataFrame(stream.getId(), data, false), Callback.NOOP); - if (!phaser.get().await(1, TimeUnit.SECONDS)) + // Exit the loop when a request is queued. + if (!requestOnServer.get().await(1, TimeUnit.SECONDS)) break; } @@ -548,11 +559,15 @@ public class StreamResetTest extends AbstractTest }); // Wait for WINDOW_UPDATEs to be processed by the client. - Thread.sleep(1000); - - assertThat(((ISession)client).updateSendWindow(0), Matchers.greaterThan(0)); + await().atMost(1000, TimeUnit.SECONDS).until(() -> ((ISession)client).updateSendWindow(0), Matchers.greaterThan(0)); latch.set(new CountDownLatch(2 * streams.size())); + // Notify all blocked threads to wakeup. + blocker.set(false); + synchronized (lock) + { + lock.notifyAll(); + } // Complete all streams. streams.forEach(s -> s.data(new DataFrame(s.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP)); @@ -562,27 +577,20 @@ public class StreamResetTest extends AbstractTest @Test public void testServerExceptionConsumesQueuedData() throws Exception { - try (StacklessLogging suppressor = new StacklessLogging(HttpChannel.class)) + try (StacklessLogging ignored = new StacklessLogging(HttpChannelState.class)) { - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { - try - { - // Wait to let the data sent by the client to be queued. - Thread.sleep(1000); - throw new IllegalStateException("explicitly_thrown_by_test"); - } - catch (InterruptedException e) - { - throw new InterruptedIOException(); - } + // Wait to let the data sent by the client to be queued. + Thread.sleep(1000); + throw new IllegalStateException("explicitly_thrown_by_test"); } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, false); @@ -616,20 +624,18 @@ public class StreamResetTest extends AbstractTest { int windowSize = FlowControlStrategy.DEFAULT_WINDOW_SIZE; CountDownLatch writeLatch = new CountDownLatch(1); - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - AsyncContext asyncContext = request.startAsync(); - asyncContext.start(() -> + new Thread(() -> { try { // Make sure we are in async wait before writing. Thread.sleep(1000); - response.getOutputStream().write(new byte[10 * windowSize]); - asyncContext.complete(); + Response.write(response, true, ByteBuffer.wrap(new byte[10 * windowSize])); } catch (IOException x) { @@ -639,14 +645,14 @@ public class StreamResetTest extends AbstractTest { x.printStackTrace(); } - }); + }).start(); } }); Deque dataQueue = new ArrayDeque<>(); AtomicLong received = new AtomicLong(); CountDownLatch latch = new CountDownLatch(1); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, true); FuturePromise promise = new FuturePromise<>(); @@ -676,17 +682,16 @@ public class StreamResetTest extends AbstractTest { int windowSize = FlowControlStrategy.DEFAULT_WINDOW_SIZE; CountDownLatch writeLatch = new CountDownLatch(1); - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { try { - ServletOutputStream output = response.getOutputStream(); - output.write(new byte[10 * windowSize]); + Response.write(response, true, ByteBuffer.wrap(new byte[10 * windowSize])); } - catch (IOException e) + catch (IOException x) { writeLatch.countDown(); } @@ -695,7 +700,7 @@ public class StreamResetTest extends AbstractTest AtomicLong received = new AtomicLong(); CountDownLatch latch = new CountDownLatch(1); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, true); FuturePromise promise = new FuturePromise<>(); @@ -731,48 +736,23 @@ public class StreamResetTest extends AbstractTest { int windowSize = FlowControlStrategy.DEFAULT_WINDOW_SIZE; CountDownLatch writeLatch = new CountDownLatch(1); - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - AsyncContext asyncContext = request.startAsync(); - ServletOutputStream output = response.getOutputStream(); - output.setWriteListener(new WriteListener() + response.write(true, Callback.from(callback::succeeded, x -> { - private boolean written; - - @Override - public void onWritePossible() throws IOException - { - while (output.isReady()) - { - if (written) - { - asyncContext.complete(); - break; - } - else - { - output.write(new byte[10 * windowSize]); - written = true; - } - } - } - - @Override - public void onError(Throwable t) - { - writeLatch.countDown(); - } - }); + writeLatch.countDown(); + callback.succeeded(); + }), ByteBuffer.wrap(new byte[10 * windowSize])); } }); Deque dataQueue = new ArrayDeque<>(); AtomicLong received = new AtomicLong(); CountDownLatch latch = new CountDownLatch(1); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, true); FuturePromise promise = new FuturePromise<>(); @@ -803,10 +783,10 @@ public class StreamResetTest extends AbstractTest CountDownLatch requestLatch = new CountDownLatch(1); CountDownLatch readLatch = new CountDownLatch(1); CountDownLatch failureLatch = new CountDownLatch(1); - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { try { @@ -814,11 +794,7 @@ public class StreamResetTest extends AbstractTest readLatch.await(); // Attempt to read after reset must throw. - request.getInputStream().read(); - } - catch (InterruptedException x) - { - throw new InterruptedIOException(); + Content.readAllBytes(request); } catch (IOException expected) { @@ -827,7 +803,7 @@ public class StreamResetTest extends AbstractTest } }); - Session client = newClient(new Session.Listener.Adapter()); + Session client = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame frame = new HeadersFrame(request, null, false); @@ -856,21 +832,19 @@ public class StreamResetTest extends AbstractTest CountDownLatch flusherLatch = new CountDownLatch(1); CountDownLatch writeLatch1 = new CountDownLatch(1); CountDownLatch writeLatch2 = new CountDownLatch(1); - start(new EmptyHttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - Request jettyRequest = (Request)request; - flusherRef.set(((AbstractEndPoint)jettyRequest.getHttpChannel().getEndPoint()).getWriteFlusher()); + flusherRef.set(((AbstractEndPoint)request.getConnectionMetaData().getConnection().getEndPoint()).getWriteFlusher()); flusherLatch.countDown(); - ServletOutputStream output = response.getOutputStream(); try { // Large write, it blocks due to TCP congestion. byte[] data = new byte[128 * 1024 * 1024]; - output.write(data); + Response.write(response, false, ByteBuffer.wrap(data)); } catch (IOException x) { @@ -878,7 +852,7 @@ public class StreamResetTest extends AbstractTest try { // Try to write again, must fail immediately. - output.write(0xFF); + Response.write(response, true, ByteBuffer.wrap(new byte[]{1})); } catch (IOException e) { @@ -888,7 +862,7 @@ public class StreamResetTest extends AbstractTest } }); - ByteBufferPool byteBufferPool = client.getByteBufferPool(); + ByteBufferPool byteBufferPool = http2Client.getByteBufferPool(); try (SocketChannel socket = SocketChannel.open()) { String host = "localhost"; @@ -905,7 +879,7 @@ public class StreamResetTest extends AbstractTest // Max session HTTP/2 flow control window. generator.control(lease, new WindowUpdateFrame(0, Integer.MAX_VALUE - FlowControlStrategy.DEFAULT_WINDOW_SIZE)); - HttpURI uri = HttpURI.from("http", host, port, servletPath); + HttpURI uri = HttpURI.from("http", host, port, "/"); MetaData.Request request = new MetaData.Request(HttpMethod.GET.asString(), uri, HttpVersion.HTTP_2, HttpFields.EMPTY); int streamId = 3; HeadersFrame headersFrame = new HeadersFrame(streamId, request, null, true); @@ -936,62 +910,49 @@ public class StreamResetTest extends AbstractTest CountDownLatch requestLatch1 = new CountDownLatch(1); CountDownLatch requestLatch2 = new CountDownLatch(1); CountDownLatch writeLatch1 = new CountDownLatch(1); - start(new EmptyHttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { - if (request.getPathInfo().equals("/1")) - service1(request, response); - else if (request.getPathInfo().equals("/2")) - service2(request, response); + String target = request.getPathInContext(); + if (target.equals("/1")) + service1(request, response, callback); + else if (target.equals("/2")) + service2(response, callback); else throw new IllegalArgumentException(); } - private void service1(HttpServletRequest request, HttpServletResponse response) throws IOException + private void service1(Request request, Response response, Callback callback) throws Exception { - try - { - Request jettyRequest = (Request)request; - exchanger.exchange(((AbstractEndPoint)jettyRequest.getHttpChannel().getEndPoint()).getWriteFlusher()); - - ServletOutputStream output = response.getOutputStream(); - // Large write, it blocks due to TCP congestion. - output.write(new byte[128 * 1024 * 1024]); - } - catch (InterruptedException x) - { - throw new InterruptedIOException(); - } + exchanger.exchange(((AbstractEndPoint)request.getConnectionMetaData().getConnection().getEndPoint()).getWriteFlusher()); + // Large write, it blocks due to TCP congestion. + response.write(true, callback, ByteBuffer.wrap(new byte[128 * 1024 * 1024])); } - private void service2(HttpServletRequest request, HttpServletResponse response) throws IOException + private void service2(Response response, Callback callback) throws Exception { try { requestLatch1.countDown(); requestLatch2.await(); - ServletOutputStream output = response.getOutputStream(); int length = 512 * 1024; AbstractHTTP2ServerConnectionFactory h2 = connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class); if (h2 != null) length = h2.getHttpConfiguration().getOutputAggregationSize(); - // Medium write so we don't aggregate it, must not block. - output.write(new byte[length * 2]); + // Medium write, so we don't aggregate it, must not block. + Response.write(response, true, ByteBuffer.wrap(new byte[length * 2])); } catch (IOException x) { writeLatch1.countDown(); - } - catch (InterruptedException x) - { - throw new InterruptedIOException(); + callback.succeeded(); } } }); - ByteBufferPool byteBufferPool = client.getByteBufferPool(); + ByteBufferPool byteBufferPool = http2Client.getByteBufferPool(); try (SocketChannel socket = SocketChannel.open()) { String host = "localhost"; @@ -1008,7 +969,7 @@ public class StreamResetTest extends AbstractTest // Max session HTTP/2 flow control window. generator.control(lease, new WindowUpdateFrame(0, Integer.MAX_VALUE - FlowControlStrategy.DEFAULT_WINDOW_SIZE)); - HttpURI uri = HttpURI.from("http", host, port, servletPath + "/1"); + HttpURI uri = HttpURI.from("http", host, port, "/1"); MetaData.Request request = new MetaData.Request(HttpMethod.GET.asString(), uri, HttpVersion.HTTP_2, HttpFields.EMPTY); HeadersFrame headersFrame = new HeadersFrame(3, request, null, true); generator.control(lease, headersFrame); @@ -1019,7 +980,7 @@ public class StreamResetTest extends AbstractTest waitUntilTCPCongested(exchanger.exchange(null)); // Send a second request. - uri = HttpURI.from("http", host, port, servletPath + "/2"); + uri = HttpURI.from("http", host, port, "/2"); request = new MetaData.Request(HttpMethod.GET.asString(), uri, HttpVersion.HTTP_2, HttpFields.EMPTY); int streamId = 5; headersFrame = new HeadersFrame(streamId, request, null, true); @@ -1078,7 +1039,7 @@ public class StreamResetTest extends AbstractTest }, http2Factory); CountDownLatch failureLatch = new CountDownLatch(1); - Session client = newClient(new Session.Listener.Adapter() + Session client = newClientSession(new Session.Listener.Adapter() { @Override public void onFailure(Session session, Throwable failure) diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java index 646197f4ab3..f12aed18ce7 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/TrailersTest.java @@ -11,28 +11,20 @@ // ======================================================================== // -package org.eclipse.jetty.http2.client; +package org.eclipse.jetty.http2.tests; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http2.HTTP2Session; import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.api.Stream; import org.eclipse.jetty.http2.api.server.ServerSessionListener; @@ -40,6 +32,9 @@ import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.http2.internal.HTTP2Session; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; @@ -48,10 +43,13 @@ import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.StringUtil; import org.junit.jupiter.api.Test; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; public class TrailersTest extends AbstractTest @@ -82,7 +80,7 @@ public class TrailersTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); HttpFields.Mutable requestFields = HttpFields.build(); requestFields.put("X-Request", "true"); @@ -103,37 +101,55 @@ public class TrailersTest extends AbstractTest } @Test - public void testServletRequestTrailers() throws Exception + public void testHandlerRequestTrailers() throws Exception { CountDownLatch trailerLatch = new CountDownLatch(1); - start(new HttpServlet() + start(new Handler.Processor() { + private Request _request; + private Callback _callback; + @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - Request jettyRequest = (Request)request; + _request = request; + _callback = callback; + request.demandContent(this::firstRead); + } + + private void firstRead() + { + Content content = _request.readContent(); + // No trailers yet. - assertNull(jettyRequest.getTrailerHttpFields()); + assertThat(content, not(instanceOf(Content.Trailers.class))); trailerLatch.countDown(); - // Read the content. - ServletInputStream input = jettyRequest.getInputStream(); + _request.demandContent(this::otherReads); + } + + private void otherReads() + { while (true) { - int read = input.read(); - if (read < 0) - break; + Content content = _request.readContent(); + if (content == null) + { + _request.demandContent(this::otherReads); + return; + } + if (content instanceof Content.Trailers contentTrailers) + { + HttpFields trailers = contentTrailers.getTrailers(); + assertNotNull(trailers.get("X-Trailer")); + _callback.succeeded(); + } } - - // Now we have the trailers. - HttpFields trailers = jettyRequest.getTrailerHttpFields(); - assertNotNull(trailers); - assertNotNull(trailers.get("X-Trailer")); } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); HttpFields.Mutable requestFields = HttpFields.build(); requestFields.put("X-Request", "true"); @@ -158,7 +174,7 @@ public class TrailersTest extends AbstractTest Callback.Completable callback = new Callback.Completable(); stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(16), false), callback); - assertTrue(trailerLatch.await(5, TimeUnit.SECONDS)); + assertTrue(trailerLatch.await(555, TimeUnit.SECONDS)); // Send the trailers. callback.thenRun(() -> @@ -201,7 +217,7 @@ public class TrailersTest extends AbstractTest } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, true); CountDownLatch latch = new CountDownLatch(1); @@ -238,25 +254,20 @@ public class TrailersTest extends AbstractTest { String trailerName = "X-Trailer"; String trailerValue = "Zot!"; - start(new EmptyHttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { - Request jettyRequest = (Request)request; - Response jettyResponse = jettyRequest.getResponse(); - HttpFields.Mutable trailers = HttpFields.build(); - jettyResponse.setTrailerFields(() -> - trailers.stream().collect(Collectors.toMap(HttpField::getName, HttpField::getValue))); - - jettyResponse.getOutputStream().write("hello_trailers".getBytes(StandardCharsets.UTF_8)); - jettyResponse.flushBuffer(); + HttpFields.Mutable trailers = response.getTrailers(); + Response.write(response, false, UTF_8.encode("hello_trailers")); // Force the content to be sent above, and then only send the trailers below. trailers.put(trailerName, trailerValue); + callback.succeeded(); } }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("GET", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, true); CountDownLatch latch = new CountDownLatch(1); @@ -296,9 +307,16 @@ public class TrailersTest extends AbstractTest @Test public void testRequestTrailerInvalidHpackSent() throws Exception { - start(new EmptyHttpServlet()); + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("POST", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); @@ -324,21 +342,14 @@ public class TrailersTest extends AbstractTest public void testRequestTrailerInvalidHpackReceived() throws Exception { CountDownLatch serverLatch = new CountDownLatch(1); - start(new HttpServlet() + start(new Handler.Processor() { @Override - protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) throws Exception { try { - // Read the content to read the trailers - ServletInputStream input = request.getInputStream(); - while (true) - { - int read = input.read(); - if (read < 0) - break; - } + Content.consumeAll(request); } catch (IOException x) { @@ -349,7 +360,7 @@ public class TrailersTest extends AbstractTest }); CountDownLatch clientLatch = new CountDownLatch(1); - Session session = newClient(new Session.Listener.Adapter()); + Session session = newClientSession(new Session.Listener.Adapter()); MetaData.Request request = newRequest("POST", HttpFields.EMPTY); HeadersFrame requestFrame = new HeadersFrame(request, null, false); FuturePromise promise = new FuturePromise<>(); diff --git a/jetty-http2/http2-server/src/test/resources/jetty-logging.properties b/jetty-core/jetty-http2/jetty-http2-tests/src/test/resources/jetty-logging.properties similarity index 73% rename from jetty-http2/http2-server/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-http2/jetty-http2-tests/src/test/resources/jetty-logging.properties index d71badfeed6..d74b419ac50 100644 --- a/jetty-http2/http2-server/src/test/resources/jetty-logging.properties +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/resources/jetty-logging.properties @@ -1,4 +1,3 @@ -# Jetty Logging using jetty-slf4j-impl #org.eclipse.jetty.LEVEL=DEBUG #org.eclipse.jetty.http2.LEVEL=DEBUG org.eclipse.jetty.http2.hpack.LEVEL=INFO diff --git a/jetty-core/jetty-http2/pom.xml b/jetty-core/jetty-http2/pom.xml index beff5fc97b3..a4930dac5c6 100644 --- a/jetty-core/jetty-http2/pom.xml +++ b/jetty-core/jetty-http2/pom.xml @@ -1,16 +1,16 @@ - jetty-project org.eclipse.jetty - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 org.eclipse.jetty.http2 - http2-parent + http2 pom - Jetty :: HTTP2 + Jetty Core :: HTTP2 http2-client @@ -18,6 +18,7 @@ http2-hpack http2-http-client-transport http2-server + jetty-http2-tests diff --git a/jetty-core/jetty-http3/http3-client/pom.xml b/jetty-core/jetty-http3/http3-client/pom.xml index 739143764b1..0888f2154e5 100644 --- a/jetty-core/jetty-http3/http3-client/pom.xml +++ b/jetty-core/jetty-http3/http3-client/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.http3 - http3-parent - 11.0.10-SNAPSHOT + http3 + 12.0.0-SNAPSHOT 4.0.0 http3-client - Jetty :: HTTP3 :: Client + Jetty Core :: HTTP3 :: Client ${project.groupId}.client diff --git a/jetty-http3/http3-client/src/main/java/module-info.java b/jetty-core/jetty-http3/http3-client/src/main/java/module-info.java similarity index 100% rename from jetty-http3/http3-client/src/main/java/module-info.java rename to jetty-core/jetty-http3/http3-client/src/main/java/module-info.java diff --git a/jetty-core/jetty-http3/http3-common/pom.xml b/jetty-core/jetty-http3/http3-common/pom.xml index dbb6ae75cb5..9d09c2feef8 100644 --- a/jetty-core/jetty-http3/http3-common/pom.xml +++ b/jetty-core/jetty-http3/http3-common/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.http3 - http3-parent - 11.0.10-SNAPSHOT + http3 + 12.0.0-SNAPSHOT 4.0.0 http3-common - Jetty :: HTTP3 :: Common + Jetty Core :: HTTP3 :: Common ${project.groupId}.common diff --git a/jetty-http3/http3-common/src/main/java/module-info.java b/jetty-core/jetty-http3/http3-common/src/main/java/module-info.java similarity index 100% rename from jetty-http3/http3-common/src/main/java/module-info.java rename to jetty-core/jetty-http3/http3-common/src/main/java/module-info.java diff --git a/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3StreamConnection.java b/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3StreamConnection.java index 83e2aefad73..5e2bd754452 100644 --- a/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3StreamConnection.java +++ b/jetty-core/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3StreamConnection.java @@ -90,13 +90,6 @@ public abstract class HTTP3StreamConnection extends AbstractConnection fillInterested(); } - @Override - public void onClose(Throwable cause) - { - tryReleaseBuffer(true); - super.onClose(cause); - } - @Override protected boolean onReadTimeout(Throwable timeout) { @@ -120,7 +113,7 @@ public abstract class HTTP3StreamConnection extends AbstractConnection processDataDemand(); if (!parserDataMode) { - if (hasBuffer() && buffer.hasRemaining()) + if (buffer != null && buffer.hasRemaining()) processNonDataFrames(); else fillInterested(); @@ -173,8 +166,8 @@ public abstract class HTTP3StreamConnection extends AbstractConnection LOG.debug("setting fill interest on {}", this); fillInterested(); } - tryReleaseBuffer(false); } + tryReleaseBuffer(false); return; } } @@ -353,7 +346,7 @@ public abstract class HTTP3StreamConnection extends AbstractConnection private void tryAcquireBuffer() { - if (!hasBuffer()) + if (buffer == null) { buffer = buffers.acquire(getInputBufferSize(), isUseInputDirectByteBuffers()); if (LOG.isDebugEnabled()) @@ -363,7 +356,7 @@ public abstract class HTTP3StreamConnection extends AbstractConnection private void tryReleaseBuffer(boolean force) { - if (hasBuffer()) + if (buffer != null) { if (buffer.hasRemaining() && force) buffer.clear(); @@ -377,11 +370,6 @@ public abstract class HTTP3StreamConnection extends AbstractConnection } } - public boolean hasBuffer() - { - return buffer != null; - } - private MessageParser.Result parseAndFill(boolean setFillInterest) { try diff --git a/jetty-core/jetty-http3/http3-http-client-transport/pom.xml b/jetty-core/jetty-http3/http3-http-client-transport/pom.xml index 5364f97bd14..84098a9f96c 100644 --- a/jetty-core/jetty-http3/http3-http-client-transport/pom.xml +++ b/jetty-core/jetty-http3/http3-http-client-transport/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.http3 - http3-parent - 11.0.10-SNAPSHOT + http3 + 12.0.0-SNAPSHOT 4.0.0 http3-http-client-transport - Jetty :: HTTP3 :: HTTP Client Transport + Jetty Core :: HTTP3 :: HTTP Client Transport ${project.groupId}.client.http diff --git a/jetty-http3/http3-http-client-transport/src/main/java/module-info.java b/jetty-core/jetty-http3/http3-http-client-transport/src/main/java/module-info.java similarity index 100% rename from jetty-http3/http3-http-client-transport/src/main/java/module-info.java rename to jetty-core/jetty-http3/http3-http-client-transport/src/main/java/module-info.java diff --git a/jetty-core/jetty-http3/http3-http-client-transport/src/main/java/org/eclipse/jetty/http3/client/http/internal/HttpChannelOverHTTP3.java b/jetty-core/jetty-http3/http3-http-client-transport/src/main/java/org/eclipse/jetty/http3/client/http/internal/HttpChannelOverHTTP3.java index f8759b7fd45..7ec91645258 100644 --- a/jetty-core/jetty-http3/http3-http-client-transport/src/main/java/org/eclipse/jetty/http3/client/http/internal/HttpChannelOverHTTP3.java +++ b/jetty-core/jetty-http3/http3-http-client-transport/src/main/java/org/eclipse/jetty/http3/client/http/internal/HttpChannelOverHTTP3.java @@ -11,489 +11,98 @@ // ======================================================================== // -package org.eclipse.jetty.http3.server.internal; +package org.eclipse.jetty.http3.client.http.internal; -import java.io.IOException; -import java.util.function.Consumer; - -import org.eclipse.jetty.http.BadMessageException; -import org.eclipse.jetty.http.HttpField; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpGenerator; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpHeaderValue; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http.PreEncodedHttpField; +import org.eclipse.jetty.client.HttpChannel; +import org.eclipse.jetty.client.HttpDestination; +import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.HttpReceiver; +import org.eclipse.jetty.client.HttpSender; +import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http3.api.Stream; -import org.eclipse.jetty.http3.frames.HeadersFrame; -import org.eclipse.jetty.http3.internal.HTTP3Stream; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpInput; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.thread.AutoLock; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.eclipse.jetty.http3.client.internal.HTTP3SessionClient; +import org.eclipse.jetty.http3.internal.HTTP3ErrorCode; public class HttpChannelOverHTTP3 extends HttpChannel { - private static final Logger LOG = LoggerFactory.getLogger(HttpChannelOverHTTP3.class); - private static final HttpField SERVER_VERSION = new PreEncodedHttpField(HttpHeader.SERVER, HttpConfiguration.SERVER_VERSION); - private static final HttpField POWERED_BY = new PreEncodedHttpField(HttpHeader.X_POWERED_BY, HttpConfiguration.SERVER_VERSION); + private final HttpConnectionOverHTTP3 connection; + private final HTTP3SessionClient session; + private final HttpSenderOverHTTP3 sender; + private final HttpReceiverOverHTTP3 receiver; + private Stream stream; - private final AutoLock lock = new AutoLock(); - private final HTTP3Stream stream; - private final ServerHTTP3StreamConnection connection; - private HttpInput.Content content; - private boolean expect100Continue; - private boolean delayedUntilContent; - - public HttpChannelOverHTTP3(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP3 transport, HTTP3Stream stream, ServerHTTP3StreamConnection connection) + public HttpChannelOverHTTP3(HttpDestination destination, HttpConnectionOverHTTP3 connection, HTTP3SessionClient session) { - super(connector, configuration, endPoint, transport); - this.stream = stream; + super(destination); this.connection = connection; + this.session = session; + sender = new HttpSenderOverHTTP3(this); + receiver = new HttpReceiverOverHTTP3(this); + } + + public HTTP3SessionClient getSession() + { + return session; + } + + public Stream.Client.Listener getStreamListener() + { + return receiver; } @Override - public HttpTransportOverHTTP3 getHttpTransport() + protected HttpSender getHttpSender() { - return (HttpTransportOverHTTP3)super.getHttpTransport(); + return sender; } @Override - public void setIdleTimeout(long timeoutMs) + protected HttpReceiver getHttpReceiver() { - stream.setIdleTimeout(timeoutMs); + return receiver; } - void consumeInput() + public Stream getStream() { - getRequest().getHttpInput().consumeAll(); + return stream; + } + + public void setStream(Stream stream) + { + this.stream = stream; } @Override - public boolean isExpecting100Continue() + public void send(HttpExchange exchange) { - return expect100Continue; + sender.send(exchange); } @Override - public void continue100(int available) throws IOException + public void exchangeTerminated(HttpExchange exchange, Result result) { - if (isExpecting100Continue()) - { - expect100Continue = false; + super.exchangeTerminated(exchange, result); - // is content missing? - if (available == 0) - { - if (getResponse().isCommitted()) - throw new IOException("Committed before 100 Continues"); - - boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false); - if (!committed) - throw new IOException("Concurrent commit while trying to send 100-Continue"); - } - } - } - - public Runnable onRequest(HeadersFrame frame) - { - try - { - MetaData.Request request = (MetaData.Request)frame.getMetaData(); - HttpFields fields = request.getFields(); - - expect100Continue = fields.contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()); - - HttpFields.Mutable response = getResponse().getHttpFields(); - if (getHttpConfiguration().getSendServerVersion()) - response.add(SERVER_VERSION); - if (getHttpConfiguration().getSendXPoweredBy()) - response.add(POWERED_BY); - - onRequest(request); - - boolean endStream = frame.isLast(); - if (endStream) - { - onContentComplete(); - onRequestComplete(); - } - - boolean connect = request instanceof MetaData.ConnectRequest; - delayedUntilContent = getHttpConfiguration().isDelayDispatchUntilContent() && - !endStream && !expect100Continue && !connect; - - if (connect) - { - // Delay the demand of DATA frames for CONNECT with :protocol, - // since we want the other protocol to trigger content demand. - if (request.getProtocol() == null) - stream.demand(); - } - else - { - // When the dispatch to the application is delayed, then - // demand for content, so when it arrives we can dispatch. - if (delayedUntilContent) - stream.demand(); - else - connection.setApplicationMode(true); - } - - if (LOG.isDebugEnabled()) - { - LOG.debug("HTTP3 request #{}/{}, delayed={}:{}{} {} {}{}{}", - stream.getId(), Integer.toHexString(stream.getSession().hashCode()), - delayedUntilContent, System.lineSeparator(), - request.getMethod(), request.getURI(), request.getHttpVersion(), - System.lineSeparator(), fields); - } - - return delayedUntilContent ? null : this; - } - catch (BadMessageException x) - { - if (LOG.isDebugEnabled()) - LOG.debug("onRequest() failure", x); - onBadMessage(x); - return null; - } - catch (Throwable x) - { - onBadMessage(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, null, x)); - return null; - } - } - - @Override - protected void commit(MetaData.Response info) - { - super.commit(info); - if (LOG.isDebugEnabled()) - { - LOG.debug("HTTP3 commit response #{}/{}:{}{} {} {}{}{}", - stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(), info.getHttpVersion(), info.getStatus(), info.getReason(), - System.lineSeparator(), info.getFields()); - } - } - - public Runnable onDataAvailable() - { - boolean woken = getRequest().getHttpInput().onContentProducible(); - if (LOG.isDebugEnabled()) - { - LOG.debug("HTTP3 request data available #{}/{} woken: {}", - stream.getId(), - Integer.toHexString(stream.getSession().hashCode()), - woken); - } - - boolean wasDelayed = delayedUntilContent; - delayedUntilContent = false; - - if (wasDelayed) - connection.setApplicationMode(true); - - return wasDelayed || woken ? this : null; - } - - public Runnable onTrailer(HeadersFrame frame) - { - HttpFields trailers = frame.getMetaData().getFields(); - if (trailers.size() > 0) - onTrailers(trailers); - - if (LOG.isDebugEnabled()) - { - LOG.debug("HTTP3 Request #{}/{}, trailers:{}{}", - stream.getId(), Integer.toHexString(stream.getSession().hashCode()), - System.lineSeparator(), trailers); - } - - // This will generate EOF -> need to call onContentProducible. - boolean handle = onRequestComplete(); - boolean woken = getRequest().getHttpInput().onContentProducible(); - handle |= woken; - - boolean wasDelayed = delayedUntilContent; - delayedUntilContent = false; - - if (wasDelayed) - connection.setApplicationMode(true); - - return wasDelayed || handle ? this : null; - } - - public boolean onIdleTimeout(Throwable failure, Consumer consumer) - { - boolean wasDelayed = delayedUntilContent; - delayedUntilContent = false; - - if (wasDelayed) - connection.setApplicationMode(true); - - getHttpTransport().onIdleTimeout(failure); - - boolean neverDispatched = getState().isIdle(); - boolean hasDemand = stream.hasDemand(); - - if (LOG.isDebugEnabled()) - { - LOG.debug("HTTP3 request idle timeout #{}/{}, dispatched={} demand={}", - stream.getId(), - Integer.toHexString(stream.getSession().hashCode()), - !neverDispatched, - hasDemand); - } - - if (neverDispatched) - { - try (AutoLock l = lock.lock()) - { - content = new HttpInput.ErrorContent(failure); - } - consumer.accept(this::handleWithContext); - } - else if (hasDemand) - { - try (AutoLock l = lock.lock()) - { - content = new HttpInput.ErrorContent(failure); - } - if (getRequest().getHttpInput().onContentProducible()) - consumer.accept(this::handleWithContext); - } - return false; - } - - private void handleWithContext() - { - ContextHandler context = getState().getContextHandler(); - if (context != null) - context.handle(getRequest(), this); + Stream stream = getStream(); + if (stream != null && result.isFailed()) + stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), result.getFailure()); else - handle(); - } - - public Runnable onFailure(Throwable failure) - { - consumeInput(); - - getHttpTransport().onFailure(failure); - - boolean handle = failed(failure); - - return () -> - { - if (handle) - handleWithContext(); - else if (getHttpConfiguration().isNotifyRemoteAsyncErrors()) - getState().asyncError(failure); - }; + release(); } @Override - public boolean needContent() + public void release() { - try (AutoLock l = lock.lock()) - { - if (content != null) - { - if (LOG.isDebugEnabled()) - LOG.debug("need content has immediate content {} on {}", content, this); - return true; - } - } - - HttpInput.Content result = readContent(); - - if (result != null) - { - if (LOG.isDebugEnabled()) - LOG.debug("need content read content {} on {}", this.content, this); - return true; - } - - if (LOG.isDebugEnabled()) - LOG.debug("need content demanding content on {}", this); - stream.demand(); - return false; + setStream(null); + connection.release(this); } @Override - public HttpInput.Content produceContent() + public String toString() { - HttpInput.Content result; - try (AutoLock l = lock.lock()) - { - result = content; - } - - if (result == null) - result = readContent(); - - if (result == null) - return null; - - if (!result.isSpecial()) - { - HttpInput.Content newContent = result.isEof() ? new HttpInput.EofContent() : null; - try (AutoLock l = lock.lock()) - { - content = newContent; - } - } - if (LOG.isDebugEnabled()) - LOG.debug("produced content {} on {}", result, this); - return result; - } - - private HttpInput.Content readContent() - { - Stream.Data data = stream.readData(); - if (LOG.isDebugEnabled()) - LOG.debug("read data {} on {}", data, this); - if (data != null) - { - HttpInput.Content result = newContent(data); - - boolean handle = onContent(result); - - try (AutoLock l = lock.lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("read content {} on {}", result, this); - content = result; - } - - if (data.isLast()) - { - boolean handleContent = onContentComplete(); - // This will generate EOF -> must happen before onContentProducible(). - boolean handleRequest = onRequestComplete(); - handle |= handleContent | handleRequest; - } - - return result; - } - else - { - // The call to readData() may have parsed the trailer frame which - // triggers the content complete event which sets the content to EOF. - try (AutoLock l = lock.lock()) - { - return content; - } - } - } - - private HttpInput.Content newContent(Stream.Data data) - { - return new DataContent(data); - } - - @Override - public boolean failAllContent(Throwable failure) - { - if (LOG.isDebugEnabled()) - LOG.debug("failing all content with {} {}", failure, this); - // TODO: must read as much as possible to seek EOF. - HttpInput.Content result; - try (AutoLock l = lock.lock()) - { - result = content; - if (result == null) - return false; - if (result.isSpecial()) - return result.isEof(); - content = null; - } - result.failed(failure); - return false; - } - - @Override - public boolean failed(Throwable failure) - { - HttpInput.Content contentToFail = null; - try (AutoLock l = lock.lock()) - { - if (content == null) - { - content = new HttpInput.ErrorContent(failure); - } - else - { - if (content.isSpecial()) - { - // Either EOF or error already, no nothing. - } - else - { - contentToFail = content; - content = new HttpInput.ErrorContent(failure); - } - } - } - - if (contentToFail != null) - contentToFail.failed(failure); - - return getRequest().getHttpInput().onContentProducible(); - } - - @Override - protected boolean eof() - { - try (AutoLock l = lock.lock()) - { - if (content == null) - { - content = new HttpInput.EofContent(); - } - else if (!content.isEof()) - { - if (content.remaining() == 0) - content = new HttpInput.EofContent(); - else - throw new IllegalStateException(); - } - return false; - } - } - - private static class DataContent extends HttpInput.Content - { - private final Stream.Data data; - - public DataContent(Stream.Data data) - { - super(data.getByteBuffer()); - this.data = data; - } - - @Override - public boolean isEof() - { - return data.isLast(); - } - - @Override - public void succeeded() - { - data.complete(); - } - - @Override - public void failed(Throwable x) - { - data.complete(); - } + return String.format("%s[send=%s,recv=%s]", + super.toString(), + sender, + receiver); } } diff --git a/jetty-core/jetty-http3/http3-qpack/pom.xml b/jetty-core/jetty-http3/http3-qpack/pom.xml index b4dc33ab22a..b0fec3ef58f 100644 --- a/jetty-core/jetty-http3/http3-qpack/pom.xml +++ b/jetty-core/jetty-http3/http3-qpack/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.http3 - http3-parent - 11.0.10-SNAPSHOT + http3 + 12.0.0-SNAPSHOT 4.0.0 http3-qpack - Jetty :: HTTP3 :: QPACK + Jetty Core :: HTTP3 :: QPACK ${project.groupId}.qpack @@ -51,7 +51,7 @@
- org.eclipse.jetty.tests + org.eclipse.jetty jetty-http-tools test diff --git a/jetty-http3/http3-qpack/src/main/java/module-info.java b/jetty-core/jetty-http3/http3-qpack/src/main/java/module-info.java similarity index 100% rename from jetty-http3/http3-qpack/src/main/java/module-info.java rename to jetty-core/jetty-http3/http3-qpack/src/main/java/module-info.java diff --git a/jetty-core/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/metadata/Http3Fields.java b/jetty-core/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/metadata/Http3Fields.java index f34ec85710e..b4c8c5b9b58 100644 --- a/jetty-core/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/metadata/Http3Fields.java +++ b/jetty-core/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/metadata/Http3Fields.java @@ -115,9 +115,15 @@ public class Http3Fields implements HttpFields } @Override - public Immutable asImmutable() + public HttpFields asImmutable() { - return new Immutable(stream().toArray(HttpField[]::new)); + return HttpFields.from(stream().toArray(HttpField[]::new)); + } + + @Override + public HttpFields takeAsImmutable() + { + return asImmutable(); } @Override diff --git a/jetty-http3/http3-qpack/src/test/resources/jetty-logging.properties b/jetty-core/jetty-http3/http3-qpack/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-http3/http3-qpack/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-http3/http3-qpack/src/test/resources/jetty-logging.properties diff --git a/jetty-core/jetty-http3/http3-server/pom.xml b/jetty-core/jetty-http3/http3-server/pom.xml index 14f97b9e8a8..2b33c2189e4 100644 --- a/jetty-core/jetty-http3/http3-server/pom.xml +++ b/jetty-core/jetty-http3/http3-server/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.http3 - http3-parent - 11.0.10-SNAPSHOT + http3 + 12.0.0-SNAPSHOT 4.0.0 http3-server - Jetty :: HTTP3 :: Server + Jetty Core :: HTTP3 :: Server ${project.groupId}.server diff --git a/jetty-http3/http3-server/src/main/java/module-info.java b/jetty-core/jetty-http3/http3-server/src/main/java/module-info.java similarity index 100% rename from jetty-http3/http3-server/src/main/java/module-info.java rename to jetty-core/jetty-http3/http3-server/src/main/java/module-info.java diff --git a/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java b/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java index 3371f3337df..e0373781f35 100644 --- a/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java +++ b/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java @@ -22,10 +22,11 @@ import org.eclipse.jetty.http3.api.Stream; import org.eclipse.jetty.http3.frames.HeadersFrame; import org.eclipse.jetty.http3.internal.HTTP3Stream; import org.eclipse.jetty.http3.server.internal.HTTP3StreamServer; -import org.eclipse.jetty.http3.server.internal.HttpChannelOverHTTP3; +import org.eclipse.jetty.http3.server.internal.HttpStreamOverHTTP3; import org.eclipse.jetty.http3.server.internal.ServerHTTP3Session; import org.eclipse.jetty.http3.server.internal.ServerHTTP3StreamConnection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.ConnectionMetaData; import org.eclipse.jetty.server.HttpConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,15 +41,17 @@ public class HTTP3ServerConnectionFactory extends AbstractHTTP3ServerConnectionF public HTTP3ServerConnectionFactory(HttpConfiguration configuration) { super(configuration, new HTTP3SessionListener()); - configuration.addCustomizer((connector, httpConfig, request) -> + configuration.addCustomizer((request, responseHeaders) -> { - HTTP3ServerConnector http3Connector = connector.getServer().getBean(HTTP3ServerConnector.class); - if (http3Connector != null && HttpVersion.HTTP_2.is(request.getHttpVersion().asString())) + ConnectionMetaData connectionMetaData = request.getConnectionMetaData(); + HTTP3ServerConnector http3Connector = connectionMetaData.getConnector().getServer().getBean(HTTP3ServerConnector.class); + if (http3Connector != null && HttpVersion.HTTP_2 == connectionMetaData.getHttpVersion()) { HttpField altSvc = http3Connector.getAltSvcHttpField(); if (altSvc != null) - request.getResponse().getHttpFields().add(altSvc); + responseHeaders.add(altSvc); } + return request; }); } @@ -70,9 +73,9 @@ public class HTTP3ServerConnectionFactory extends AbstractHTTP3ServerConnectionF { boolean result = session.getStreams().stream() .map(stream -> (HTTP3Stream)stream) - .map(stream -> (HttpChannelOverHTTP3)stream.getAttachment()) + .map(stream -> (HttpStreamOverHTTP3)stream.getAttachment()) .filter(Objects::nonNull) - .map(channel -> channel.getState().isIdle()) + .map(HttpStreamOverHTTP3::isIdle) .reduce(true, Boolean::logicalAnd); if (LOG.isDebugEnabled()) LOG.debug("{} idle timeout on {}", result ? "confirmed" : "ignored", session); diff --git a/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java b/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java new file mode 100644 index 00000000000..cfa8b8a04ba --- /dev/null +++ b/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java @@ -0,0 +1,489 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.http3.server.internal; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http3.api.Stream; +import org.eclipse.jetty.http3.frames.DataFrame; +import org.eclipse.jetty.http3.frames.HeadersFrame; +import org.eclipse.jetty.http3.internal.HTTP3ErrorCode; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpStream; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HttpStreamOverHTTP3 implements HttpStream +{ + private static final Logger LOG = LoggerFactory.getLogger(HttpStreamOverHTTP3.class); + + private final long nanoTime = System.nanoTime(); + private final ServerHTTP3StreamConnection connection; + private final HttpChannel httpChannel; + private final HTTP3StreamServer stream; + private Content content; + private MetaData.Response metaData; + private boolean committed; + + public HttpStreamOverHTTP3(ServerHTTP3StreamConnection connection, HttpChannel httpChannel, HTTP3StreamServer stream) + { + this.connection = connection; + this.httpChannel = httpChannel; + this.stream = stream; + } + + @Override + public String getId() + { + return String.valueOf(stream.getId()); + } + + @Override + public long getNanoTimeStamp() + { + return nanoTime; + } + + public Runnable onRequest(HeadersFrame frame) + { + try + { + MetaData.Request request = (MetaData.Request)frame.getMetaData(); + + Runnable handler = httpChannel.onRequest(request); + + if (frame.isLast()) + content = Content.EOF; + + HttpFields fields = request.getFields(); + + // TODO: handle 100 continue. +// expect100Continue = fields.contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()); + + boolean connect = request instanceof MetaData.ConnectRequest; + + if (!connect) + connection.setApplicationMode(true); + + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP3 request #{}/{}, {} {} {}{}{}", + stream.getId(), Integer.toHexString(stream.getSession().hashCode()), + request.getMethod(), request.getURI(), request.getHttpVersion(), + System.lineSeparator(), fields); + } + + return handler; + } + catch (BadMessageException x) + { + if (LOG.isDebugEnabled()) + LOG.debug("onRequest() failure", x); + onBadMessage(x); + return null; + } + catch (Throwable x) + { + onBadMessage(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, null, x)); + return null; + } + } + + private void onBadMessage(BadMessageException x) + { + // TODO + } + + @Override + public Content readContent() + { + while (true) + { + Content content = this.content; + this.content = Content.next(content); + if (content != null) + return content; + + Stream.Data data = stream.readData(); + if (data == null) + return null; + + this.content = newContent(data); + } + } + + @Override + public void demandContent() + { + stream.demand(); + } + + public Runnable onDataAvailable() + { + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP3 request data available #{}/{}", + stream.getId(), Integer.toHexString(stream.getSession().hashCode())); + } + + Stream.Data data = stream.readData(); + if (data == null) + { + stream.demand(); + return null; + } + + content = newContent(data); + return httpChannel.onContentAvailable(); + } + + private Content.Abstract newContent(Stream.Data data) + { + return new Content.Abstract(false, data.isLast()) + { + @Override + public ByteBuffer getByteBuffer() + { + return data.getByteBuffer(); + } + + @Override + public void release() + { + data.complete(); + } + }; + } + + public Runnable onTrailer(HeadersFrame frame) + { + HttpFields trailers = frame.getMetaData().getFields().asImmutable(); + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP3 Request #{}/{}, trailer:{}{}", + stream.getId(), Integer.toHexString(stream.getSession().hashCode()), + System.lineSeparator(), trailers); + } + content = new Content.Trailers(trailers); + return httpChannel.onContentAvailable(); + } + + @Override + public void prepareResponse(HttpFields.Mutable headers) + { + // Nothing to do here. + } + + @Override + public void send(MetaData.Request request, MetaData.Response response, boolean last, Callback callback, ByteBuffer... buffers) + { + if (buffers.length > 1) + throw new IllegalStateException(); + + ByteBuffer content = buffers.length == 0 ? BufferUtil.EMPTY_BUFFER : buffers[0]; + if (response != null) + sendHeaders(request, response, content, last, callback); + else + sendContent(request, content, last, callback); + } + + private void sendHeaders(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback) + { + metaData = response; + + HeadersFrame headersFrame; + DataFrame dataFrame = null; + HeadersFrame trailersFrame = null; + + boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod()); + boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest; + int status = response.getStatus(); + boolean interimResponse = status == HttpStatus.CONTINUE_100 || status == HttpStatus.PROCESSING_102; + if (interimResponse) + { + // Must not commit interim responses. + if (hasContent) + { + callback.failed(new IllegalStateException("Interim response cannot have content")); + return; + } + headersFrame = new HeadersFrame(metaData, false); + } + else + { + committed = true; + if (lastContent) + { + long realContentLength = BufferUtil.length(content); + long contentLength = response.getContentLength(); + if (contentLength < 0) + { + metaData = new MetaData.Response( + response.getHttpVersion(), + response.getStatus(), + response.getReason(), + response.getFields(), + realContentLength, + response.getTrailerSupplier() + ); + } + else if (hasContent && contentLength != realContentLength) + { + callback.failed(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, String.format("Incorrect Content-Length %d!=%d", contentLength, realContentLength))); + return; + } + } + + if (hasContent) + { + headersFrame = new HeadersFrame(metaData, false); + if (lastContent) + { + HttpFields trailers = retrieveTrailers(); + if (trailers == null) + { + dataFrame = new DataFrame(content, true); + } + else + { + dataFrame = new DataFrame(content, false); + trailersFrame = new HeadersFrame(new MetaData(HttpVersion.HTTP_3, trailers), true); + } + } + else + { + dataFrame = new DataFrame(content, false); + } + } + else + { + if (lastContent) + { + if (isTunnel(request, metaData)) + { + headersFrame = new HeadersFrame(metaData, false); + } + else + { + HttpFields trailers = retrieveTrailers(); + if (trailers == null) + { + headersFrame = new HeadersFrame(metaData, true); + } + else + { + headersFrame = new HeadersFrame(metaData, false); + trailersFrame = new HeadersFrame(new MetaData(HttpVersion.HTTP_3, trailers), true); + } + } + } + else + { + headersFrame = new HeadersFrame(metaData, false); + } + } + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP3 Response #{}/{}:{}{} {}{}{}", + stream.getId(), Integer.toHexString(stream.getSession().hashCode()), + System.lineSeparator(), HttpVersion.HTTP_3, metaData.getStatus(), + System.lineSeparator(), metaData.getFields()); + } + + CompletableFuture cf = stream.respond(headersFrame); + + DataFrame df = dataFrame; + if (df != null) + cf = cf.thenCompose(s -> s.data(df)); + + HeadersFrame tf = trailersFrame; + if (tf != null) + cf = cf.thenCompose(s -> s.trailer(tf)); + + callback.completeWith(cf); + } + + private void sendContent(MetaData.Request request, ByteBuffer content, boolean lastContent, Callback callback) + { + boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod()); + boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest; + if (hasContent || (lastContent && !isTunnel(request, metaData))) + { + if (lastContent) + { + HttpFields trailers = retrieveTrailers(); + if (trailers == null) + { + callback.completeWith(sendDataFrame(content, true, true)); + } + else + { + if (hasContent) + { + callback.completeWith(sendDataFrame(content, lastContent, false) + .thenCompose(s -> sendTrailerFrame(trailers))); + } + else + { + callback.completeWith(sendTrailerFrame(trailers)); + } + } + } + else + { + callback.completeWith(sendDataFrame(content, false, false)); + } + } + else + { + callback.succeeded(); + } + } + + private HttpFields retrieveTrailers() + { + Supplier supplier = metaData.getTrailerSupplier(); + if (supplier == null) + return null; + HttpFields trailers = supplier.get(); + if (trailers == null) + return null; + return trailers.size() == 0 ? null : trailers; + } + + private boolean isTunnel(MetaData.Request request, MetaData.Response response) + { + return MetaData.isTunnel(request.getMethod(), response.getStatus()); + } + + private CompletableFuture sendDataFrame(ByteBuffer content, boolean lastContent, boolean endStream) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP3 Response #{}/{}: {} content bytes{}", + stream.getId(), Integer.toHexString(stream.getSession().hashCode()), + content.remaining(), lastContent ? " (last chunk)" : ""); + } + DataFrame frame = new DataFrame(content, endStream); + return stream.data(frame); + } + + private CompletableFuture sendTrailerFrame(HttpFields trailers) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("HTTP3 Response #{}/{}: trailer{}{}", + stream.getId(), Integer.toHexString(stream.getSession().hashCode()), + System.lineSeparator(), trailers); + } + + HeadersFrame frame = new HeadersFrame(new MetaData(HttpVersion.HTTP_3, trailers), true); + return stream.trailer(frame); + } + + @Override + public boolean isPushSupported() + { + return false; + } + + @Override + public void push(MetaData.Request request) + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isCommitted() + { + return committed; + } + + @Override + public boolean isComplete() + { + // TODO + return false; + } + + public boolean isIdle() + { + // TODO: is this necessary? + return true; + } + + @Override + public void setUpgradeConnection(Connection connection) + { + throw new UnsupportedOperationException(); + } + + @Override + public Connection upgrade() + { + return null; + } + + @Override + public void succeeded() + { + httpChannel.recycle(); + + // If the stream is not closed, it is still reading the request content. + // Send a reset to the other end so that it stops sending data. + if (!stream.isClosed()) + { + if (LOG.isDebugEnabled()) + LOG.debug("HTTP3 Response #{}/{}: unconsumed request content, resetting stream", stream.getId(), Integer.toHexString(stream.getSession().hashCode())); + stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), new IOException("unconsumed content")); + } + } + + @Override + public void failed(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("HTTP3 Response #{}/{} aborted", stream.getId(), Integer.toHexString(stream.getSession().hashCode())); + stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), x); + } + + public boolean onIdleTimeout(Throwable failure, Consumer consumer) + { + Runnable runnable = httpChannel.onFailure(failure); + if (runnable != null) + consumer.accept(runnable); + return !httpChannel.isRequestHandled(); + } + + public Runnable onFailure(Throwable failure) + { + return httpChannel.onFailure(failure); + } +} diff --git a/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3StreamConnection.java b/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3StreamConnection.java index 51f1403a2aa..1bac9916b2f 100644 --- a/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3StreamConnection.java +++ b/jetty-core/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3StreamConnection.java @@ -13,18 +13,29 @@ package org.eclipse.jetty.http3.server.internal; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Set; import java.util.function.Consumer; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http3.frames.HeadersFrame; import org.eclipse.jetty.http3.internal.HTTP3Stream; import org.eclipse.jetty.http3.internal.HTTP3StreamConnection; import org.eclipse.jetty.http3.internal.parser.MessageParser; +import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.quic.common.QuicStreamEndPoint; +import org.eclipse.jetty.server.ConnectionMetaData; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.HostPort; -public class ServerHTTP3StreamConnection extends HTTP3StreamConnection +public class ServerHTTP3StreamConnection extends HTTP3StreamConnection implements ConnectionMetaData { + private final HttpChannel.Factory httpChannelFactory = new HttpChannel.DefaultFactory(); + private final Attributes attributes = new Attributes.Lazy(); private final Connector connector; private final HttpConfiguration httpConfiguration; private final ServerHTTP3Session session; @@ -45,33 +56,138 @@ public class ServerHTTP3StreamConnection extends HTTP3StreamConnection public Runnable onRequest(HTTP3StreamServer stream, HeadersFrame frame) { - HttpTransportOverHTTP3 transport = new HttpTransportOverHTTP3(stream); - HttpChannelOverHTTP3 channel = new HttpChannelOverHTTP3(connector, httpConfiguration, getEndPoint(), transport, stream, this); - stream.setAttachment(channel); - return channel.onRequest(frame); + HttpChannel httpChannel = httpChannelFactory.newHttpChannel(this); + HttpStreamOverHTTP3 httpStream = new HttpStreamOverHTTP3(this, httpChannel, stream); + httpChannel.setHttpStream(httpStream); + stream.setAttachment(httpStream); + return httpStream.onRequest(frame); } public Runnable onDataAvailable(HTTP3Stream stream) { - HttpChannelOverHTTP3 channel = (HttpChannelOverHTTP3)stream.getAttachment(); - return channel.onDataAvailable(); + HttpStreamOverHTTP3 httpStream = (HttpStreamOverHTTP3)stream.getAttachment(); + return httpStream.onDataAvailable(); } public Runnable onTrailer(HTTP3Stream stream, HeadersFrame frame) { - HttpChannelOverHTTP3 channel = (HttpChannelOverHTTP3)stream.getAttachment(); - return channel.onTrailer(frame); + HttpStreamOverHTTP3 httpStream = (HttpStreamOverHTTP3)stream.getAttachment(); + return httpStream.onTrailer(frame); } public boolean onIdleTimeout(HTTP3Stream stream, Throwable failure, Consumer consumer) { - HttpChannelOverHTTP3 channel = (HttpChannelOverHTTP3)stream.getAttachment(); - return channel.onIdleTimeout(failure, consumer); + HttpStreamOverHTTP3 httpStream = (HttpStreamOverHTTP3)stream.getAttachment(); + return httpStream.onIdleTimeout(failure, consumer); } public Runnable onFailure(HTTP3Stream stream, Throwable failure) { - HttpChannelOverHTTP3 channel = (HttpChannelOverHTTP3)stream.getAttachment(); - return channel.onFailure(failure); + HttpStreamOverHTTP3 httpStream = (HttpStreamOverHTTP3)stream.getAttachment(); + return httpStream.onFailure(failure); + } + + @Override + public String getId() + { + return session.getQuicSession().getConnectionId().toString(); + } + + @Override + public HttpConfiguration getHttpConfiguration() + { + return httpConfiguration; + } + + @Override + public HttpVersion getHttpVersion() + { + return HttpVersion.HTTP_3; + } + + @Override + public String getProtocol() + { + return getHttpVersion().asString(); + } + + @Override + public Connection getConnection() + { + return getEndPoint().getConnection(); + } + + @Override + public Connector getConnector() + { + return connector; + } + + @Override + public boolean isPersistent() + { + return true; + } + + @Override + public boolean isSecure() + { + return true; + } + + @Override + public SocketAddress getRemoteSocketAddress() + { + return getEndPoint().getRemoteSocketAddress(); + } + + @Override + public SocketAddress getLocalSocketAddress() + { + return getEndPoint().getLocalSocketAddress(); + } + + @Override + public HostPort getServerAuthority() + { + HostPort override = httpConfiguration.getServerAuthority(); + if (override != null) + return override; + + // TODO cache the HostPort? + SocketAddress addr = getLocalSocketAddress(); + if (addr instanceof InetSocketAddress inet) + return new HostPort(inet.getHostString(), inet.getPort()); + return new HostPort(addr.toString(), -1); + } + + @Override + public Object getAttribute(String name) + { + return attributes.getAttribute(name); + } + + @Override + public Object setAttribute(String name, Object attribute) + { + return attributes.setAttribute(name, attribute); + } + + @Override + public Object removeAttribute(String name) + { + return attributes.removeAttribute(name); + } + + @Override + public Set getAttributeNameSet() + { + return attributes.getAttributeNameSet(); + } + + @Override + public void clearAttributes() + { + attributes.clearAttributes(); } } diff --git a/jetty-core/jetty-http3/http3-tests/pom.xml b/jetty-core/jetty-http3/http3-tests/pom.xml index 7acdd1b359d..4e03c332b54 100644 --- a/jetty-core/jetty-http3/http3-tests/pom.xml +++ b/jetty-core/jetty-http3/http3-tests/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.http3 - http3-parent - 11.0.10-SNAPSHOT + http3 + 12.0.0-SNAPSHOT 4.0.0 http3-tests - Jetty :: HTTP3 :: Tests + Jetty Core :: HTTP3 :: Tests diff --git a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java index e6a9b7bf687..b94f10ec627 100644 --- a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java +++ b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java @@ -42,11 +42,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +// TODO check that the Awaitility usage is the right thing to do. public class ClientServerTest extends AbstractClientServerTest { @Test @@ -184,8 +186,8 @@ public class ClientServerTest extends AbstractClientServerTest assertTrue(clientResponseLatch.await(5, TimeUnit.SECONDS)); HTTP3Session serverSession = serverSessionRef.get(); - assertTrue(serverSession.getStreams().isEmpty()); - assertTrue(clientSession.getStreams().isEmpty()); + await().atMost(5, TimeUnit.SECONDS).until(() -> serverSession.getStreams().isEmpty()); // onRequest is called *before* the serverSession's streams collection is cleaned up -> racy + await().atMost(5, TimeUnit.SECONDS).until(() -> clientSession.getStreams().isEmpty()); // onResponse is called *before* the clientSession's streams collection is cleaned up -> racy QuicSession serverQuicSession = serverSession.getProtocolSession().getQuicSession(); assertTrue(serverQuicSession.getQuicStreamEndPoints().stream() @@ -458,7 +460,7 @@ public class ClientServerTest extends AbstractClientServerTest assertTrue(responseFailureLatch.await(5, TimeUnit.SECONDS)); assertTrue(streamFailureLatch.await(5, TimeUnit.SECONDS)); assertTrue(serverSessionRef.get().getStreams().isEmpty()); - assertTrue(clientSession.getStreams().isEmpty()); + await().atMost(5, TimeUnit.SECONDS).until(() -> clientSession.getStreams().isEmpty()); // onFailure is called *before* the clientSession's streams collection is cleaned up -> racy // Verify that the connection is still good. CountDownLatch responseLatch = new CountDownLatch(1); diff --git a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/DataDemandTest.java b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/DataDemandTest.java index afc2de9b067..749a7bd4fa1 100644 --- a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/DataDemandTest.java +++ b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/DataDemandTest.java @@ -32,8 +32,6 @@ import org.eclipse.jetty.http3.api.Stream; import org.eclipse.jetty.http3.frames.DataFrame; import org.eclipse.jetty.http3.frames.HeadersFrame; import org.eclipse.jetty.http3.internal.HTTP3Stream; -import org.eclipse.jetty.http3.internal.HTTP3StreamConnection; -import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.util.BufferUtil; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; @@ -42,7 +40,6 @@ import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -587,58 +584,4 @@ public class DataDemandTest extends AbstractClientServerTest assertTrue(lastDataLatch.await(5, TimeUnit.SECONDS)); } - - @Test - public void testOnDataAvailableThenNoReadThenIdleTimeoutReleasesNetworkBuffer() throws Exception - { - long idleTimeout = 1000; - CountDownLatch onDataLatch = new CountDownLatch(1); - CountDownLatch idleLatch = new CountDownLatch(1); - CountDownLatch closeLatch = new CountDownLatch(1); - start(new Session.Server.Listener() - { - @Override - public Stream.Server.Listener onRequest(Stream.Server stream, HeadersFrame frame) - { - HTTP3Stream http3Stream = (HTTP3Stream)stream; - http3Stream.setIdleTimeout(idleTimeout); - http3Stream.getEndPoint().getConnection().addEventListener(new Connection.Listener.Adapter() - { - @Override - public void onClosed(Connection connection) - { - assertFalse(((HTTP3StreamConnection)connection).hasBuffer()); - closeLatch.countDown(); - } - }); - stream.demand(); - return new Stream.Server.Listener() - { - @Override - public void onDataAvailable(Stream.Server stream) - { - // Do not read. - onDataLatch.countDown(); - } - - @Override - public boolean onIdleTimeout(Stream.Server stream, Throwable failure) - { - idleLatch.countDown(); - return true; - } - }; - } - }); - - Session.Client session = newSession(new Session.Client.Listener() {}); - - HeadersFrame request = new HeadersFrame(newRequest("/"), false); - Stream stream = session.newRequest(request, new Stream.Client.Listener() {}).get(5, TimeUnit.SECONDS); - stream.data(new DataFrame(ByteBuffer.allocate(16 * 1024), true)); - - assertTrue(onDataLatch.await(5, TimeUnit.SECONDS)); - assertTrue(idleLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS)); - assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); - } } diff --git a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java index c024ce23386..96f686f4771 100644 --- a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java +++ b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HandlerClientServerTest.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.http3.tests; -import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -22,8 +21,6 @@ import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MetaData; @@ -31,9 +28,11 @@ import org.eclipse.jetty.http3.api.Session; import org.eclipse.jetty.http3.api.Stream; import org.eclipse.jetty.http3.frames.DataFrame; import org.eclipse.jetty.http3.frames.HeadersFrame; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -48,13 +47,13 @@ public class HandlerClientServerTest extends AbstractClientServerTest public void testGet() throws Exception { CountDownLatch serverLatch = new CountDownLatch(1); - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - jettyRequest.setHandled(true); serverLatch.countDown(); + callback.succeeded(); } }); @@ -83,13 +82,12 @@ public class HandlerClientServerTest extends AbstractClientServerTest public void testPost() throws Exception { CountDownLatch serverLatch = new CountDownLatch(1); - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - jettyRequest.setHandled(true); - IO.copy(request.getInputStream(), response.getOutputStream()); + Content.copy(request, response, callback); serverLatch.countDown(); } }); diff --git a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java index f81b9090d1b..8ae1382c744 100644 --- a/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java +++ b/jetty-core/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.http3.tests; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -21,14 +20,12 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.LongConsumer; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; @@ -41,14 +38,14 @@ public class HttpClientTransportOverHTTP3Test extends AbstractClientServerTest @Test public void testRequestHasHTTP3Version() throws Exception { - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - jettyRequest.setHandled(true); - HttpVersion version = HttpVersion.fromString(request.getProtocol()); + HttpVersion version = HttpVersion.fromString(request.getConnectionMetaData().getProtocol()); response.setStatus(version == HttpVersion.HTTP_3 ? HttpStatus.OK_200 : HttpStatus.INTERNAL_SERVER_ERROR_500); + callback.succeeded(); } }); @@ -68,13 +65,12 @@ public class HttpClientTransportOverHTTP3Test extends AbstractClientServerTest public void testRequestResponseWithSmallContent() throws Exception { String content = "Hello, World!"; - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - jettyRequest.setHandled(true); - response.getOutputStream().print(content); + response.write(true, callback, content); } }); @@ -87,13 +83,12 @@ public class HttpClientTransportOverHTTP3Test extends AbstractClientServerTest @Test public void testDelayedClientRead() throws Exception { - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - jettyRequest.setHandled(true); - response.getOutputStream().write(new byte[10 * 1024]); + response.write(true, callback, ByteBuffer.wrap(new byte[10 * 1024])); } }); @@ -143,12 +138,12 @@ public class HttpClientTransportOverHTTP3Test extends AbstractClientServerTest @Test public void testDelayDemandAfterHeaders() throws Exception { - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - jettyRequest.setHandled(true); + callback.succeeded(); } }); @@ -196,13 +191,12 @@ public class HttpClientTransportOverHTTP3Test extends AbstractClientServerTest @Test public void testDelayDemandAfterLastContentChunk() throws Exception { - start(new AbstractHandler() + start(new Handler.Processor() { @Override - public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback) { - jettyRequest.setHandled(true); - response.getOutputStream().print("0"); + response.write(true, callback, "0"); } }); diff --git a/jetty-http3/http3-tests/src/test/resources/jetty-logging.properties b/jetty-core/jetty-http3/http3-tests/src/test/resources/jetty-logging.properties similarity index 81% rename from jetty-http3/http3-tests/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-http3/http3-tests/src/test/resources/jetty-logging.properties index 468634e1068..552e858ed9b 100644 --- a/jetty-http3/http3-tests/src/test/resources/jetty-logging.properties +++ b/jetty-core/jetty-http3/http3-tests/src/test/resources/jetty-logging.properties @@ -1,4 +1,5 @@ #org.eclipse.jetty.LEVEL=DEBUG +org.eclipse.jetty.jmx.LEVEL=INFO #org.eclipse.jetty.http3.LEVEL=DEBUG #org.eclipse.jetty.quic.LEVEL=DEBUG org.eclipse.jetty.quic.quiche.LEVEL=INFO diff --git a/jetty-core/jetty-http3/pom.xml b/jetty-core/jetty-http3/pom.xml index 3810a8fe33b..d597603973d 100644 --- a/jetty-core/jetty-http3/pom.xml +++ b/jetty-core/jetty-http3/pom.xml @@ -1,16 +1,16 @@ - jetty-project org.eclipse.jetty - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 org.eclipse.jetty.http3 - http3-parent + http3 pom - Jetty :: HTTP3 + Jetty Core :: HTTP3 http3-qpack diff --git a/jetty-core/jetty-io/pom.xml b/jetty-core/jetty-io/pom.xml index efc9c928d40..9b5b2c88f9f 100644 --- a/jetty-core/jetty-io/pom.xml +++ b/jetty-core/jetty-io/pom.xml @@ -1,13 +1,13 @@ - jetty-project org.eclipse.jetty - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 jetty-io - Jetty :: IO Utility + Jetty Core :: IO Utility ${project.groupId}.io org.eclipse.jetty.io.* diff --git a/jetty-io/src/main/java/module-info.java b/jetty-core/jetty-io/src/main/java/module-info.java similarity index 100% rename from jetty-io/src/main/java/module-info.java rename to jetty-core/jetty-io/src/main/java/module-info.java diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java index f7a48e35971..35d4b16077c 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java @@ -32,7 +32,7 @@ import org.slf4j.LoggerFactory; * will schedule a callback to {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)} * as appropriate.

*/ -public abstract class AbstractConnection implements Connection +public abstract class AbstractConnection implements Connection, Invocable { private static final Logger LOG = LoggerFactory.getLogger(AbstractConnection.class); @@ -52,6 +52,15 @@ public abstract class AbstractConnection implements Connection _readCallback = new ReadCallback(); } + @Deprecated + @Override + public InvocationType getInvocationType() + { + // TODO consider removing the #fillInterested method from the connection and only use #fillInterestedCallback + // so a connection need not be Invocable + return Invocable.super.getInvocationType(); + } + @Override public void addEventListener(EventListener listener) { @@ -60,9 +69,10 @@ public abstract class AbstractConnection implements Connection } @Override - public void removeEventListener(EventListener listener) + public void removeEventListener(EventListener eventListener) { - _listeners.remove(listener); + if (eventListener instanceof Listener listener) + _listeners.remove(listener); } public int getInputBufferSize() @@ -301,17 +311,15 @@ public abstract class AbstractConnection implements Connection @Override public final String toString() { - return String.format("%s@%h::%s", getClass().getSimpleName(), this, getEndPoint()); + return String.format("%s@%x::%s", getClass().getSimpleName(), hashCode(), getEndPoint()); } public String toConnectionString() { - return String.format("%s@%h", - getClass().getSimpleName(), - this); + return String.format("%s@%x", getClass().getSimpleName(), hashCode()); } - private class ReadCallback implements Callback + private class ReadCallback implements Callback, Invocable { @Override public void succeeded() @@ -330,5 +338,11 @@ public abstract class AbstractConnection implements Connection { return String.format("AC.ReadCB@%h{%s}", AbstractConnection.this, AbstractConnection.this); } + + @Override + public InvocationType getInvocationType() + { + return AbstractConnection.this.getInvocationType(); + } } } diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java index 0323973b5b4..e9d60b85bd6 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java @@ -419,10 +419,15 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint // for a dispatched servlet or suspended request to extend beyond the connections idle // time. So if this test would always close an idle endpoint that is not handled, then // we would need a mode to ignore timeouts for some HTTP states - if (isOpen() && (outputShutdown || inputShutdown) && !(fillFailed || writeFailed)) + if (isOpen() && (inputShutdown || outputShutdown) && !(fillFailed || writeFailed)) close(); else - LOG.debug("Ignored idle endpoint {}", this); + LOG.debug("handled idle inputShutdown={} outputShutdown={} fillFailed={} writeFailed={} for {}", + inputShutdown, + outputShutdown, + fillFailed, + writeFailed, + this); } @Override diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayRetainableByteBufferPool.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayRetainableByteBufferPool.java index 3fe42c147ee..e1c6051c0e1 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayRetainableByteBufferPool.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ArrayRetainableByteBufferPool.java @@ -386,4 +386,34 @@ public class ArrayRetainableByteBufferPool implements RetainableByteBufferPool, entries > 0 ? (inUse * 100) / entries : 0); } } + + /** + * A variant of the {@link ArrayRetainableByteBufferPool} that + * uses buckets of buffers that increase in size by a power of + * 2 (eg 1k, 2k, 4k, 8k, etc.). + */ + public static class ExponentialPool extends ArrayRetainableByteBufferPool + { + public ExponentialPool() + { + this(0, -1, Integer.MAX_VALUE); + } + + public ExponentialPool(int minCapacity, int maxCapacity, int maxBucketSize) + { + this(minCapacity, maxCapacity, maxBucketSize, -1L, -1L); + } + + public ExponentialPool(int minCapacity, int maxCapacity, int maxBucketSize, long maxHeapMemory, long maxDirectMemory) + { + super(minCapacity, + -1, + maxCapacity, + maxBucketSize, + maxHeapMemory, + maxDirectMemory, + c -> 32 - Integer.numberOfLeadingZeros(c - 1), + i -> 1 << i); + } + } } diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java index e5ec873b162..d40f8692b53 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java @@ -34,20 +34,20 @@ public interface Connection extends Closeable * * @param listener the listener to add */ - public void addEventListener(EventListener listener); + void addEventListener(EventListener listener); /** *

Removes a listener of connection events.

* * @param listener the listener to remove */ - public void removeEventListener(EventListener listener); + void removeEventListener(EventListener listener); /** *

Callback method invoked when this connection is opened.

*

Creators of the connection implementation are responsible for calling this method.

*/ - public void onOpen(); + void onOpen(); /** *

Callback method invoked when this connection is closed.

@@ -55,12 +55,12 @@ public interface Connection extends Closeable * * @param cause The cause of the close or null for a normal close */ - public void onClose(Throwable cause); + void onClose(Throwable cause); /** * @return the {@link EndPoint} associated with this Connection. */ - public EndPoint getEndPoint(); + EndPoint getEndPoint(); /** *

Performs a logical close of this connection.

@@ -69,7 +69,7 @@ public interface Connection extends Closeable * before closing the associated {@link EndPoint}.

*/ @Override - public void close(); + void close(); /** *

Callback method invoked upon an idle timeout event.

@@ -82,17 +82,17 @@ public interface Connection extends Closeable * @return true to let the EndPoint handle the idle timeout, * false to tell the EndPoint to halt the handling of the idle timeout. */ - public boolean onIdleExpired(); + boolean onIdleExpired(); - public long getMessagesIn(); + long getMessagesIn(); - public long getMessagesOut(); + long getMessagesOut(); - public long getBytesIn(); + long getBytesIn(); - public long getBytesOut(); + long getBytesOut(); - public long getCreatedTimeStamp(); + long getCreatedTimeStamp(); /** *

{@link Connection} implementations implement this interface when they @@ -102,7 +102,7 @@ public interface Connection extends Closeable * @see EndPoint#upgrade(Connection) * @see UpgradeTo */ - public interface UpgradeFrom + interface UpgradeFrom { /** *

Invoked during an {@link EndPoint#upgrade(Connection) upgrade} @@ -115,7 +115,7 @@ public interface Connection extends Closeable * having consumed its bytes. * The returned buffer may be null if there are no unconsumed bytes. */ - public ByteBuffer onUpgradeFrom(); + ByteBuffer onUpgradeFrom(); } /** @@ -123,7 +123,7 @@ public interface Connection extends Closeable * can be upgraded to the protocol they speak (e.g. HTTP/2) * from a different protocol (e.g. HTTP/1.1).

*/ - public interface UpgradeTo + interface UpgradeTo { /** *

Invoked during an {@link EndPoint#upgrade(Connection) upgrade} @@ -136,7 +136,7 @@ public interface Connection extends Closeable * The buffer does not belong to any pool and should be discarded after * having consumed its bytes. */ - public void onUpgradeTo(ByteBuffer buffer); + void onUpgradeTo(ByteBuffer buffer); } /** @@ -147,23 +147,14 @@ public interface Connection extends Closeable * the Connector or ConnectionFactory are added as listeners to all new connections *

*/ - public interface Listener extends EventListener + interface Listener extends EventListener { - public void onOpened(Connection connection); - - public void onClosed(Connection connection); - - public static class Adapter implements Listener + default void onOpened(Connection connection) { - @Override - public void onOpened(Connection connection) - { - } + } - @Override - public void onClosed(Connection connection) - { - } + default void onClosed(Connection connection) + { } } } diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java index 028e95241f9..3617060dc3d 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java @@ -89,10 +89,10 @@ import org.eclipse.jetty.util.thread.Invocable; */ public interface EndPoint extends Closeable { - /** + /** * Marks an {@code EndPoint} that wraps another {@code EndPoint}. */ - public interface Wrapper + public interface Wrapper { /** * @return The wrapped {@code EndPoint} diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java index f1ac8e77886..ea86c19a2cd 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java @@ -159,11 +159,12 @@ public abstract class IdleTimeout { if (idleLeft <= 0) { + TimeoutException timeout = new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms"); if (LOG.isDebugEnabled()) - LOG.debug("{} idle timeout expired", this); + LOG.debug("{} idle timeout expired", this, timeout); try { - onIdleExpired(new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms")); + onIdleExpired(timeout); } finally { diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/LogarithmicArrayByteBufferPool.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/LogarithmicArrayByteBufferPool.java index d01e10cc809..9842e91fd9e 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/LogarithmicArrayByteBufferPool.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/LogarithmicArrayByteBufferPool.java @@ -21,6 +21,8 @@ package org.eclipse.jetty.io; */ public class LogarithmicArrayByteBufferPool extends ArrayByteBufferPool { + // TODO test this class and use it! + /** * Creates a new ByteBufferPool with a default configuration. */ diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java index 9d3bebfa117..223093acec6 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java @@ -40,6 +40,8 @@ import org.slf4j.LoggerFactory; @ManagedObject public class MappedByteBufferPool extends AbstractByteBufferPool implements Dumpable { + // TODO Logarithmic buffer sizes + private static final Logger LOG = LoggerFactory.getLogger(MappedByteBufferPool.class); private final ConcurrentMap _directBuffers = new ConcurrentHashMap<>(); diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/QuietException.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/QuietException.java index aa8a1adf884..9e496c5c607 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/QuietException.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/QuietException.java @@ -20,4 +20,26 @@ package org.eclipse.jetty.io; */ public interface QuietException { + class Exception extends java.lang.Exception implements QuietException + { + public Exception() + { + super(); + } + + public Exception(String message) + { + super(message); + } + + public Exception(String message, Throwable cause) + { + super(message, cause); + } + + public Exception(Throwable cause) + { + super(cause); + } + } } diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java index eb982bd34d5..ffc7f7bec17 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java @@ -51,7 +51,8 @@ public class SocketChannelEndPoint extends SelectableChannelEndPoint } catch (Throwable x) { - LOG.trace("Could not retrieve remote socket address", x); + if (LOG.isTraceEnabled()) + LOG.trace("Could not retrieve remote socket address", x); return null; } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/package-info.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/package-info.java similarity index 100% rename from jetty-io/src/main/java/org/eclipse/jetty/io/package-info.java rename to jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/package-info.java diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index aeb34eab9c2..923bee960cf 100644 --- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.io.RetainableByteBufferPool; import org.eclipse.jetty.io.WriteFlusher; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.Invocable; @@ -634,7 +635,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr { Throwable failure = _failure; if (failure != null) - rethrow(failure); + throw IO.rethrow(failure); if (_sslEngine.isInboundDone()) return filled = -1; continue; @@ -716,7 +717,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr case CLOSED: Throwable failure = _failure; if (failure != null) - rethrow(failure); + throw IO.rethrow(failure); return filled = -1; case BUFFER_UNDERFLOW: @@ -818,9 +819,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr catch (Throwable x) { close(x); - rethrow(x); - // Never reached. - throw new AssertionError(); + throw IO.rethrow(x); } } @@ -1173,9 +1172,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr catch (Throwable x) { close(x); - rethrow(x); - // Never reached. - throw new AssertionError(); + throw IO.rethrow(x); } } @@ -1509,17 +1506,6 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr } } - private void rethrow(Throwable x) throws IOException - { - if (x instanceof RuntimeException) - throw (RuntimeException)x; - if (x instanceof Error) - throw (Error)x; - if (x instanceof IOException) - throw (IOException)x; - throw new IOException(x); - } - @Override public String toString() { diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/package-info.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/package-info.java similarity index 100% rename from jetty-io/src/main/java/org/eclipse/jetty/io/ssl/package-info.java rename to jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/package-info.java diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayRetainableByteBufferPoolTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayRetainableByteBufferPoolTest.java index 816352a60d3..18470a6faee 100644 --- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayRetainableByteBufferPoolTest.java +++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ArrayRetainableByteBufferPoolTest.java @@ -321,7 +321,7 @@ public class ArrayRetainableByteBufferPoolTest @Test public void testExponentialPool() throws IOException { - ArrayRetainableByteBufferPool pool = new ExponentialPool(); + ArrayRetainableByteBufferPool pool = new ArrayRetainableByteBufferPool.ExponentialPool(); assertThat(pool.acquire(1, false).capacity(), is(1)); assertThat(pool.acquire(2, false).capacity(), is(2)); RetainableByteBuffer b3 = pool.acquire(3, false); diff --git a/jetty-core/jetty-jmx/pom.xml b/jetty-core/jetty-jmx/pom.xml index ed023dfe038..2d12fb02714 100644 --- a/jetty-core/jetty-jmx/pom.xml +++ b/jetty-core/jetty-jmx/pom.xml @@ -1,12 +1,12 @@ org.eclipse.jetty - jetty-project - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 jetty-jmx - Jetty :: JMX Management + Jetty Core :: JMX Management JMX management artifact for jetty. ${project.groupId}.jmx diff --git a/jetty-jmx/src/main/java/module-info.java b/jetty-core/jetty-jmx/src/main/java/module-info.java similarity index 100% rename from jetty-jmx/src/main/java/module-info.java rename to jetty-core/jetty-jmx/src/main/java/module-info.java diff --git a/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/package-info.java b/jetty-core/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/package-info.java similarity index 100% rename from jetty-jmx/src/main/java/org/eclipse/jetty/jmx/package-info.java rename to jetty-core/jetty-jmx/src/main/java/org/eclipse/jetty/jmx/package-info.java diff --git a/jetty-core/jetty-jndi/pom.xml b/jetty-core/jetty-jndi/pom.xml index 65500dba2af..4c30a3fb1f2 100644 --- a/jetty-core/jetty-jndi/pom.xml +++ b/jetty-core/jetty-jndi/pom.xml @@ -1,13 +1,13 @@ org.eclipse.jetty - jetty-project - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 jetty-jndi - Jetty :: JNDI Naming + Jetty Core :: JNDI Naming JNDI spi impl for java namespace. @@ -54,20 +54,23 @@ jetty-server provided
- - org.eclipse.jetty - jetty-security + + + +
diff --git a/jetty-jndi/src/main/java/module-info.java b/jetty-core/jetty-jndi/src/main/java/module-info.java similarity index 92% rename from jetty-jndi/src/main/java/module-info.java rename to jetty-core/jetty-jndi/src/main/java/module-info.java index 00ef86a14c8..10fc8742b90 100644 --- a/jetty-jndi/src/main/java/module-info.java +++ b/jetty-core/jetty-jndi/src/main/java/module-info.java @@ -19,10 +19,10 @@ module org.eclipse.jetty.jndi requires transitive org.eclipse.jetty.server; // Only required if using MailSessionReference. - requires static jakarta.mail; + // requires static jakarta.mail; // Only required if using DataSourceCloser. requires static java.sql; - requires static org.eclipse.jetty.security; + // requires static org.eclipse.jetty.security; exports org.eclipse.jetty.jndi; exports org.eclipse.jetty.jndi.factories; diff --git a/jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java b/jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java index 295f3ff17dc..5d5ce4690d2 100644 --- a/jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java +++ b/jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/MailSessionReference.java @@ -13,23 +13,13 @@ package org.eclipse.jetty.jndi.factories; -import java.util.Enumeration; import java.util.Hashtable; -import java.util.Iterator; -import java.util.Map; import java.util.Properties; import javax.naming.Context; import javax.naming.Name; -import javax.naming.RefAddr; import javax.naming.Reference; -import javax.naming.StringRefAddr; import javax.naming.spi.ObjectFactory; -import jakarta.mail.Authenticator; -import jakarta.mail.PasswordAuthentication; -import jakarta.mail.Session; -import org.eclipse.jetty.util.security.Password; - /** * MailSessionReference * @@ -43,8 +33,8 @@ import org.eclipse.jetty.util.security.Password; */ public class MailSessionReference extends Reference implements ObjectFactory { - - public static class PasswordAuthenticator extends Authenticator + // FIXME: Move to appropriate EE9 tree + /*public static class PasswordAuthenticator extends Authenticator { PasswordAuthentication passwordAuthentication; private String user; @@ -87,13 +77,14 @@ public class MailSessionReference extends Reference implements ObjectFactory } } - ; + */ public MailSessionReference() { super("javax.jakarta.Session", MailSessionReference.class.getName(), null); } + /** * Create a javax.mail.Session instance based on the information passed in the Reference * @@ -108,7 +99,8 @@ public class MailSessionReference extends Reference implements ObjectFactory @Override public Object getObjectInstance(Object ref, Name arg1, Context arg2, Hashtable arg3) throws Exception { - if (ref == null) + return null; + /*if (ref == null) return null; Reference reference = (Reference)ref; @@ -134,29 +126,34 @@ public class MailSessionReference extends Reference implements ObjectFactory if (password == null) return Session.getInstance(props); else - return Session.getInstance(props, new PasswordAuthenticator(user, password)); + return Session.getInstance(props, new PasswordAuthenticator(user, password));*/ } public void setUser(String user) { + /* StringRefAddr addr = (StringRefAddr)get("user"); if (addr != null) { throw new RuntimeException("user already set on SessionReference, can't be changed"); } add(new StringRefAddr("user", user)); + */ } public void setPassword(String password) { + /* StringRefAddr addr = (StringRefAddr)get("pwd"); if (addr != null) throw new RuntimeException("password already set on SessionReference, can't be changed"); add(new StringRefAddr("pwd", password)); + */ } public void setProperties(Properties properties) { + /* Iterator entries = properties.entrySet().iterator(); while (entries.hasNext()) { @@ -166,5 +163,6 @@ public class MailSessionReference extends Reference implements ObjectFactory throw new RuntimeException("property " + e.getKey() + " already set on Session reference, can't be changed"); add(new StringRefAddr((String)e.getKey(), (String)e.getValue())); } + */ } } diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/package-info.java b/jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/package-info.java similarity index 100% rename from jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/package-info.java rename to jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/factories/package-info.java diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/package-info.java b/jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/package-info.java similarity index 100% rename from jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/package-info.java rename to jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/java/package-info.java diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/package-info.java b/jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/package-info.java similarity index 100% rename from jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/package-info.java rename to jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/local/package-info.java diff --git a/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/package-info.java b/jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/package-info.java similarity index 100% rename from jetty-jndi/src/main/java/org/eclipse/jetty/jndi/package-info.java rename to jetty-core/jetty-jndi/src/main/java/org/eclipse/jetty/jndi/package-info.java diff --git a/jetty-core/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java b/jetty-core/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java index e2361ec7751..25a81510dd4 100644 --- a/jetty-core/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java +++ b/jetty-core/jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java @@ -30,13 +30,9 @@ import javax.naming.Reference; import javax.naming.StringRefAddr; import javax.naming.spi.ObjectFactory; -import jakarta.servlet.ServletContextEvent; -import jakarta.servlet.ServletContextListener; import org.eclipse.jetty.jndi.NamingContext; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.HandlerList; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,130 +67,132 @@ public class TestJNDI } } + @Disabled("port to ee9") @Test public void testThreadContextClassloaderAndCurrentContext() throws Exception { - - //create a jetty context, and start it so that its classloader it created - //and it is the current context - ClassLoader currentLoader = Thread.currentThread().getContextClassLoader(); - ContextHandler ch = new ContextHandler(); - URLClassLoader chLoader = new URLClassLoader(new URL[0], currentLoader); - ch.setClassLoader(chLoader); - Server server = new Server(); - HandlerList hl = new HandlerList(); - server.setHandler(hl); - hl.addHandler(ch); - - //Create another one - ContextHandler ch2 = new ContextHandler(); - URLClassLoader ch2Loader = new URLClassLoader(new URL[0], currentLoader); - ch2.setClassLoader(ch2Loader); - hl.addHandler(ch2); - - try - { - ch.setContextPath("/ch"); - ch.addEventListener(new ServletContextListener() - { - private Context comp; - private Object testObj = new Object(); - - @Override - public void contextInitialized(ServletContextEvent sce) - { - try - { - InitialContext initCtx = new InitialContext(); - Context java = (Context)initCtx.lookup("java:"); - assertNotNull(java); - comp = (Context)initCtx.lookup("java:comp"); - assertNotNull(comp); - Context env = ((Context)comp).createSubcontext("env"); - assertNotNull(env); - env.bind("ch", testObj); - } - catch (Exception e) - { - throw new IllegalStateException(e); - } - } - - @Override - public void contextDestroyed(ServletContextEvent sce) - { - try - { - assertNotNull(comp); - assertEquals(testObj, comp.lookup("env/ch")); - comp.destroySubcontext("env"); - } - catch (Exception e) - { - throw new IllegalStateException(e); - } - } - }); - //Starting the context makes it current and creates a classloader for it - ch.start(); - - ch2.setContextPath("/ch2"); - ch2.addEventListener(new ServletContextListener() - { - private Context comp; - private Object testObj = new Object(); - - @Override - public void contextInitialized(ServletContextEvent sce) - { - try - { - InitialContext initCtx = new InitialContext(); - comp = (Context)initCtx.lookup("java:comp"); - assertNotNull(comp); - - //another context's bindings should not be visible - Context env = ((Context)comp).createSubcontext("env"); - try - { - env.lookup("ch"); - fail("java:comp/env visible from another context!"); - } - catch (NameNotFoundException e) - { - //expected - } - } - catch (Exception e) - { - throw new IllegalStateException(e); - } - } - - @Override - public void contextDestroyed(ServletContextEvent sce) - { - try - { - assertNotNull(comp); - comp.destroySubcontext("env"); - } - catch (Exception e) - { - throw new IllegalStateException(e); - } - } - }); - //make the new context the current one - ch2.start(); - } - finally - { - ch.stop(); - ch2.stop(); - Thread.currentThread().setContextClassLoader(currentLoader); - } + fail(); +// +// //create a jetty context, and start it so that its classloader it created +// //and it is the current context +// ClassLoader currentLoader = Thread.currentThread().getContextClassLoader(); +// ContextHandler ch = new ContextHandler(); +// URLClassLoader chLoader = new URLClassLoader(new URL[0], currentLoader); +// ch.setClassLoader(chLoader); +// Server server = new Server(); +// HandlerList hl = new HandlerList(); +// server.setHandler(hl); +// hl.addHandler(ch); +// +// //Create another one +// ContextHandler ch2 = new ContextHandler(); +// URLClassLoader ch2Loader = new URLClassLoader(new URL[0], currentLoader); +// ch2.setClassLoader(ch2Loader); +// hl.addHandler(ch2); +// +// try +// { +// ch.setContextPath("/ch"); +// ch.addEventListener(new ServletContextListener() +// { +// private Context comp; +// private Object testObj = new Object(); +// +// @Override +// public void contextInitialized(ServletContextEvent sce) +// { +// try +// { +// InitialContext initCtx = new InitialContext(); +// Context java = (Context)initCtx.lookup("java:"); +// assertNotNull(java); +// comp = (Context)initCtx.lookup("java:comp"); +// assertNotNull(comp); +// Context env = ((Context)comp).createSubcontext("env"); +// assertNotNull(env); +// env.bind("ch", testObj); +// } +// catch (Exception e) +// { +// throw new IllegalStateException(e); +// } +// } +// +// @Override +// public void contextDestroyed(ServletContextEvent sce) +// { +// try +// { +// assertNotNull(comp); +// assertEquals(testObj, comp.lookup("env/ch")); +// comp.destroySubcontext("env"); +// } +// catch (Exception e) +// { +// throw new IllegalStateException(e); +// } +// } +// }); +// //Starting the context makes it current and creates a classloader for it +// ch.start(); +// +// ch2.setContextPath("/ch2"); +// ch2.addEventListener(new ServletContextListener() +// { +// private Context comp; +// private Object testObj = new Object(); +// +// @Override +// public void contextInitialized(ServletContextEvent sce) +// { +// try +// { +// InitialContext initCtx = new InitialContext(); +// comp = (Context)initCtx.lookup("java:comp"); +// assertNotNull(comp); +// +// //another context's bindings should not be visible +// Context env = ((Context)comp).createSubcontext("env"); +// try +// { +// env.lookup("ch"); +// fail("java:comp/env visible from another context!"); +// } +// catch (NameNotFoundException e) +// { +// //expected +// } +// } +// catch (Exception e) +// { +// throw new IllegalStateException(e); +// } +// } +// +// @Override +// public void contextDestroyed(ServletContextEvent sce) +// { +// try +// { +// assertNotNull(comp); +// comp.destroySubcontext("env"); +// } +// catch (Exception e) +// { +// throw new IllegalStateException(e); +// } +// } +// }); +// //make the new context the current one +// ch2.start(); +// } +// finally +// { +// ch.stop(); +// ch2.stop(); +// Thread.currentThread().setContextClassLoader(currentLoader); +// } } @Test diff --git a/jetty-core/jetty-keystore/pom.xml b/jetty-core/jetty-keystore/pom.xml index a9861920478..e79ea40ab93 100644 --- a/jetty-core/jetty-keystore/pom.xml +++ b/jetty-core/jetty-keystore/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty - jetty-project - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 jetty-keystore jar - Jetty :: Test Keystore + Jetty Core :: Test Keystore Test keystore with self-signed SSL Certificate. diff --git a/jetty-core/jetty-quic/pom.xml b/jetty-core/jetty-quic/pom.xml index b82fec0e834..3420abe7083 100644 --- a/jetty-core/jetty-quic/pom.xml +++ b/jetty-core/jetty-quic/pom.xml @@ -1,16 +1,16 @@ - jetty-project org.eclipse.jetty - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 org.eclipse.jetty.quic - quic-parent + quic pom - Jetty :: QUIC + Jetty Core :: QUIC quic-quiche diff --git a/jetty-core/jetty-quic/quic-client/pom.xml b/jetty-core/jetty-quic/quic-client/pom.xml index 9bab3761ee6..bfb114e8633 100644 --- a/jetty-core/jetty-quic/quic-client/pom.xml +++ b/jetty-core/jetty-quic/quic-client/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.quic - quic-parent - 11.0.10-SNAPSHOT + quic + 12.0.0-SNAPSHOT 4.0.0 quic-client - Jetty :: QUIC :: Client + Jetty Core :: QUIC :: Client ${project.groupId}.client diff --git a/jetty-quic/quic-client/src/main/java/module-info.java b/jetty-core/jetty-quic/quic-client/src/main/java/module-info.java similarity index 100% rename from jetty-quic/quic-client/src/main/java/module-info.java rename to jetty-core/jetty-quic/quic-client/src/main/java/module-info.java diff --git a/jetty-core/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java b/jetty-core/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java index cd6d1fb0866..52c8ad2eff9 100644 --- a/jetty-core/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java +++ b/jetty-core/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java @@ -13,13 +13,9 @@ package org.eclipse.jetty.quic.client; -import java.io.IOException; -import java.io.PrintWriter; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic; @@ -31,11 +27,13 @@ import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.quic.server.QuicServerConnector; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.AfterEach; @@ -50,12 +48,13 @@ public class End2EndClientTest private Server server; private QuicServerConnector connector; private HttpClient client; - private final String responseContent = "" + - "\n" + - "\t\n" + - "\t\tRequest served\n" + - "\t\n" + - ""; + private final String responseContent = """ + + + Request served + + + """; @BeforeEach public void setUp() throws Exception @@ -72,14 +71,12 @@ public class End2EndClientTest connector = new QuicServerConnector(server, sslContextFactory, http1, http2); server.addConnector(connector); - server.setHandler(new AbstractHandler() + server.setHandler(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); - PrintWriter writer = response.getWriter(); - writer.print(responseContent); + response.write(true, callback, responseContent); } }); diff --git a/jetty-quic/quic-client/src/test/resources/jetty-logging.properties b/jetty-core/jetty-quic/quic-client/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-quic/quic-client/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-quic/quic-client/src/test/resources/jetty-logging.properties diff --git a/jetty-quic/quic-client/src/test/resources/keystore.p12 b/jetty-core/jetty-quic/quic-client/src/test/resources/keystore.p12 similarity index 100% rename from jetty-quic/quic-client/src/test/resources/keystore.p12 rename to jetty-core/jetty-quic/quic-client/src/test/resources/keystore.p12 diff --git a/jetty-core/jetty-quic/quic-common/pom.xml b/jetty-core/jetty-quic/quic-common/pom.xml index a46fd39de65..8cbebc4aaa1 100644 --- a/jetty-core/jetty-quic/quic-common/pom.xml +++ b/jetty-core/jetty-quic/quic-common/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.quic - quic-parent - 11.0.10-SNAPSHOT + quic + 12.0.0-SNAPSHOT 4.0.0 quic-common - Jetty :: QUIC :: Common + Jetty Core :: QUIC :: Common ${project.groupId}.common diff --git a/jetty-quic/quic-common/src/main/java/module-info.java b/jetty-core/jetty-quic/quic-common/src/main/java/module-info.java similarity index 100% rename from jetty-quic/quic-common/src/main/java/module-info.java rename to jetty-core/jetty-quic/quic-common/src/main/java/module-info.java diff --git a/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/package-info.java b/jetty-core/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/package-info.java similarity index 100% rename from jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/package-info.java rename to jetty-core/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/package-info.java diff --git a/jetty-core/jetty-quic/quic-quiche/pom.xml b/jetty-core/jetty-quic/quic-quiche/pom.xml index 903e12135d3..e38c5b93ac2 100644 --- a/jetty-core/jetty-quic/quic-quiche/pom.xml +++ b/jetty-core/jetty-quic/quic-quiche/pom.xml @@ -2,14 +2,14 @@ org.eclipse.jetty.quic - quic-parent - 11.0.10-SNAPSHOT + quic + 12.0.0-SNAPSHOT 4.0.0 quic-quiche pom - Jetty :: QUIC :: Quiche + Jetty Core :: QUIC :: Quiche quic-quiche-common diff --git a/jetty-quic/quic-quiche/quic-quiche-common/pom.xml b/jetty-core/jetty-quic/quic-quiche/quic-quiche-common/pom.xml similarity index 92% rename from jetty-quic/quic-quiche/quic-quiche-common/pom.xml rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-common/pom.xml index 42e23c7eb12..460ad552c49 100644 --- a/jetty-quic/quic-quiche/quic-quiche-common/pom.xml +++ b/jetty-core/jetty-quic/quic-quiche/quic-quiche-common/pom.xml @@ -3,12 +3,12 @@ org.eclipse.jetty.quic quic-quiche - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT 4.0.0 quic-quiche-common - Jetty :: QUIC :: Quiche :: Common + Jetty Core :: QUIC :: Quiche :: Common ${project.groupId}.quic-quiche-common diff --git a/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/module-info.java b/jetty-core/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/module-info.java similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-common/src/main/java/module-info.java rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-common/src/main/java/module-info.java diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/pom.xml b/jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/pom.xml similarity index 95% rename from jetty-quic/quic-quiche/quic-quiche-foreign-incubator/pom.xml rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/pom.xml index 7e837607465..3a25efdda98 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/pom.xml +++ b/jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/pom.xml @@ -3,12 +3,12 @@ org.eclipse.jetty.quic quic-quiche - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT 4.0.0 quic-quiche-foreign-incubator - Jetty :: QUIC :: Quiche :: Foreign Binding (incubator) + Jetty Core :: QUIC :: Quiche :: Foreign Binding (incubator) ${project.groupId}.quic-quiche-foreign-incubator diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/module-info.java b/jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/module-info.java similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/module-info.java rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/module-info.java diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/sockaddr.java b/jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/sockaddr.java similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/sockaddr.java rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/sockaddr.java diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/resources/META-INF/services/org.eclipse.jetty.quic.quiche.QuicheBinding b/jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/resources/META-INF/services/org.eclipse.jetty.quic.quiche.QuicheBinding similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/resources/META-INF/services/org.eclipse.jetty.quic.quiche.QuicheBinding rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/main/resources/META-INF/services/org.eclipse.jetty.quic.quiche.QuicheBinding diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java b/jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java similarity index 98% rename from jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java index f578bf0fe01..3bf7ee9fcd2 100644 --- a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java +++ b/jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/java/org/eclipse/jetty/quic/quiche/foreign/incubator/LowLevelQuicheTest.java @@ -32,6 +32,8 @@ import org.eclipse.jetty.quic.quiche.SSLKeyPair; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; import static org.eclipse.jetty.quic.quiche.Quiche.QUICHE_MIN_CLIENT_INITIAL_LEN; import static org.hamcrest.MatcherAssert.assertThat; @@ -39,6 +41,8 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.Is.is; +// TODO: make this test work in Java 18 too. +@EnabledOnJre(value = JRE.JAVA_17, disabledReason = "Java 18's Foreign APIs are incompatible with Java 17's Foreign APIs") public class LowLevelQuicheTest { private final Collection connectionsToDisposeOf = new ArrayList<>(); diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/jetty-logging.properties b/jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/jetty-logging.properties diff --git a/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/keystore.p12 b/jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/keystore.p12 similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/keystore.p12 rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-foreign-incubator/src/test/resources/keystore.p12 diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/pom.xml b/jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/pom.xml similarity index 94% rename from jetty-quic/quic-quiche/quic-quiche-jna/pom.xml rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/pom.xml index 47a2f88c0ac..f8b10857a8c 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/pom.xml +++ b/jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/pom.xml @@ -3,12 +3,12 @@ org.eclipse.jetty.quic quic-quiche - 11.0.10-SNAPSHOT + 12.0.0-SNAPSHOT 4.0.0 quic-quiche-jna - Jetty :: QUIC :: Quiche :: JNA Binding + Jetty Core :: QUIC :: Quiche :: JNA Binding ${project.groupId}.quic-quiche-jna diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/module-info.java b/jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/module-info.java similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/module-info.java rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/module-info.java diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/sockaddr.java b/jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/sockaddr.java similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/sockaddr.java rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/sockaddr.java diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/resources/META-INF/services/org.eclipse.jetty.quic.quiche.QuicheBinding b/jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/main/resources/META-INF/services/org.eclipse.jetty.quic.quiche.QuicheBinding similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-jna/src/main/resources/META-INF/services/org.eclipse.jetty.quic.quiche.QuicheBinding rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/main/resources/META-INF/services/org.eclipse.jetty.quic.quiche.QuicheBinding diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java b/jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/test/java/org/eclipse/jetty/quic/quiche/jna/LowLevelQuicheTest.java diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/resources/jetty-logging.properties b/jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-jna/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/test/resources/jetty-logging.properties diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/test/resources/keystore.p12 b/jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/test/resources/keystore.p12 similarity index 100% rename from jetty-quic/quic-quiche/quic-quiche-jna/src/test/resources/keystore.p12 rename to jetty-core/jetty-quic/quic-quiche/quic-quiche-jna/src/test/resources/keystore.p12 diff --git a/jetty-core/jetty-quic/quic-server/pom.xml b/jetty-core/jetty-quic/quic-server/pom.xml index f57d0246a0d..14f2ef5110a 100644 --- a/jetty-core/jetty-quic/quic-server/pom.xml +++ b/jetty-core/jetty-quic/quic-server/pom.xml @@ -2,13 +2,13 @@ org.eclipse.jetty.quic - quic-parent - 11.0.10-SNAPSHOT + quic + 12.0.0-SNAPSHOT 4.0.0 quic-server - Jetty :: QUIC :: Server + Jetty Core :: QUIC :: Server ${project.groupId}.server diff --git a/jetty-quic/quic-server/src/main/java/module-info.java b/jetty-core/jetty-quic/quic-server/src/main/java/module-info.java similarity index 100% rename from jetty-quic/quic-server/src/main/java/module-info.java rename to jetty-core/jetty-quic/quic-server/src/main/java/module-info.java diff --git a/jetty-core/jetty-quic/quic-server/src/test/java/org/eclipse/jetty/quic/server/ServerQuicConnectorTest.java b/jetty-core/jetty-quic/quic-server/src/test/java/org/eclipse/jetty/quic/server/ServerQuicConnectorTest.java index 519099674be..2c861fe2fbf 100644 --- a/jetty-core/jetty-quic/quic-server/src/test/java/org/eclipse/jetty/quic/server/ServerQuicConnectorTest.java +++ b/jetty-core/jetty-quic/quic-server/src/test/java/org/eclipse/jetty/quic/server/ServerQuicConnectorTest.java @@ -13,18 +13,14 @@ package org.eclipse.jetty.quic.server; -import java.io.IOException; -import java.io.PrintWriter; - -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpCompliance; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -49,18 +45,18 @@ public class ServerQuicConnectorTest connector.setPort(8443); server.addConnector(connector); - server.setHandler(new AbstractHandler() + server.setHandler(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); - PrintWriter writer = response.getWriter(); - writer.println("\n" + - "\t\n" + - "\t\tRequest served\n" + - "\t\n" + - ""); + response.write(true, callback, """ + + + Request served + + + """); } }); @@ -90,17 +86,15 @@ public class ServerQuicConnectorTest connector.setPort(8443); server.addConnector(connector); - server.setHandler(new AbstractHandler() + server.setHandler(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); int contentLength = 16 * 1024 * 1024; response.setContentLength(contentLength); response.setContentType("text/plain"); - ServletOutputStream outputStream = response.getOutputStream(); - outputStream.println("0".repeat(contentLength)); + response.write(true, callback, "0".repeat(contentLength)); } }); diff --git a/jetty-quic/quic-server/src/test/resources/jetty-logging.properties b/jetty-core/jetty-quic/quic-server/src/test/resources/jetty-logging.properties similarity index 100% rename from jetty-quic/quic-server/src/test/resources/jetty-logging.properties rename to jetty-core/jetty-quic/quic-server/src/test/resources/jetty-logging.properties diff --git a/jetty-quic/quic-server/src/test/resources/keystore.p12 b/jetty-core/jetty-quic/quic-server/src/test/resources/keystore.p12 similarity index 100% rename from jetty-quic/quic-server/src/test/resources/keystore.p12 rename to jetty-core/jetty-quic/quic-server/src/test/resources/keystore.p12 diff --git a/jetty-core/jetty-rewrite/pom.xml b/jetty-core/jetty-rewrite/pom.xml index 2d902ddc17a..63c0a9c8318 100644 --- a/jetty-core/jetty-rewrite/pom.xml +++ b/jetty-core/jetty-rewrite/pom.xml @@ -1,13 +1,13 @@ org.eclipse.jetty - jetty-project - 11.0.10-SNAPSHOT + jetty-core + 12.0.0-SNAPSHOT 4.0.0 jetty-rewrite - Jetty :: Rewrite Handler + Jetty Core :: Rewrite Handler Jetty Rewrite Handler @@ -20,29 +20,21 @@ org.eclipse.jetty jetty-server
- - org.eclipse.jetty.toolchain - jetty-jakarta-servlet-api - org.slf4j slf4j-api + org.eclipse.jetty jetty-slf4j-impl test - org.eclipse.jetty.tests + org.eclipse.jetty jetty-http-tools test - - org.eclipse.jetty.toolchain - jetty-test-helper - test -
diff --git a/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml b/jetty-core/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml similarity index 100% rename from jetty-rewrite/src/main/config/etc/jetty-rewrite.xml rename to jetty-core/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml diff --git a/jetty-rewrite/src/main/java/module-info.java b/jetty-core/jetty-rewrite/src/main/java/module-info.java similarity index 95% rename from jetty-rewrite/src/main/java/module-info.java rename to jetty-core/jetty-rewrite/src/main/java/module-info.java index 4c8f38c7545..6c5ce923415 100644 --- a/jetty-rewrite/src/main/java/module-info.java +++ b/jetty-core/jetty-rewrite/src/main/java/module-info.java @@ -13,10 +13,8 @@ module org.eclipse.jetty.rewrite { - requires jetty.servlet.api; - requires org.slf4j; - requires transitive org.eclipse.jetty.server; + requires org.slf4j; exports org.eclipse.jetty.rewrite; exports org.eclipse.jetty.rewrite.handler; diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/RewriteCustomizer.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/RewriteCustomizer.java index fe717c12d09..534b6ead1f7 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/RewriteCustomizer.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/RewriteCustomizer.java @@ -15,21 +15,22 @@ package org.eclipse.jetty.rewrite; import java.io.IOException; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.rewrite.handler.RuleContainer; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConfiguration.Customizer; import org.eclipse.jetty.server.Request; public class RewriteCustomizer extends RuleContainer implements Customizer { @Override - public void customize(Connector connector, HttpConfiguration channelConfig, Request request) + public Request customize(Request request, HttpFields.Mutable responseHeaders) { try { - matchAndApply(request.getPathInfo(), request, request.getResponse()); + // TODO: rule are able to complete the request/response, but customizers cannot. + Request.WrapperProcessor input = new Request.WrapperProcessor(request); + return matchAndApply(input); } catch (IOException e) { diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CompactPathRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CompactPathRule.java index 6f1558bcecb..f0223cc5967 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CompactPathRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CompactPathRule.java @@ -15,37 +15,31 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.URIUtil; /** - * Rewrite the URI by compacting to remove // + *

Rewrites the URI by compacting to remove occurrences of {@code //}.

+ *

For example, {@code //foo/bar//baz} is compacted to {@code /foo/bar/baz}.

*/ -public class CompactPathRule extends Rule implements Rule.ApplyURI +public class CompactPathRule extends Rule { - public CompactPathRule() - { - _handling = false; - _terminating = false; - } - @Override - public void applyURI(Request request, String oldURI, String newURI) throws IOException + public Request.WrapperProcessor matchAndApply(Request.WrapperProcessor input) throws IOException { - String uri = request.getRequestURI(); - if (uri.startsWith("/")) - uri = URIUtil.compactPath(uri); - request.setHttpURI(HttpURI.build(request.getHttpURI(), uri)); - } + String path = input.getPathInContext(); + String compacted = URIUtil.compactPath(path); - @Override - public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException - { - if (target.startsWith("/")) - return URIUtil.compactPath(target); - return target; + if (path.equals(compacted)) + return null; + + return new Request.WrapperProcessor(input) + { + @Override + public String getPathInContext() + { + return compacted; + } + }; } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java index 63782528372..63ed30c7917 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/CookiePatternRule.java @@ -14,16 +14,18 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; +import java.util.List; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpCookie; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.annotation.Name; /** - * Sets the cookie in the response whenever the rule finds a match. + *

Sets a response cookie whenever the rule matches.

* - * @see Cookie + * @see HttpCookie */ public class CookiePatternRule extends PatternRule { @@ -38,16 +40,20 @@ public class CookiePatternRule extends PatternRule public CookiePatternRule(@Name("pattern") String pattern, @Name("name") String name, @Name("value") String value) { super(pattern); - _handling = false; - _terminating = false; setName(name); setValue(value); } /** - * Assigns the cookie name. - * - * @param name a String specifying the name of the cookie. + * @return the response cookie name + */ + public String getName() + { + return _name; + } + + /** + * @param name the response cookie name */ public void setName(String name) { @@ -55,10 +61,15 @@ public class CookiePatternRule extends PatternRule } /** - * Assigns the cookie value. - * - * @param value a String specifying the value of the cookie - * @see Cookie#setValue(String) + * @return the response cookie value + */ + public String getValue() + { + return _value; + } + + /** + * @param value the response cookie value */ public void setValue(String value) { @@ -66,30 +77,34 @@ public class CookiePatternRule extends PatternRule } @Override - public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + public Request.WrapperProcessor apply(Request.WrapperProcessor input) throws IOException { + // TODO: fix once Request.getCookies() is implemented (currently always returns null) // Check that cookie is not already set - Cookie[] cookies = request.getCookies(); + List cookies = Request.getCookies(input); if (cookies != null) { - for (Cookie cookie : cookies) + for (HttpCookie cookie : cookies) { if (_name.equals(cookie.getName()) && _value.equals(cookie.getValue())) - return target; + return null; } } - // set it - response.addCookie(new Cookie(_name, _value)); - return target; + return new Request.WrapperProcessor(input) + { + @Override + public void process(Request ignored, Response response, Callback callback) throws Exception + { + Response.addCookie(response, new HttpCookie(_name, _value)); + super.process(this, response, callback); + } + }; } - /** - * Returns the cookie contents. - */ @Override public String toString() { - return super.toString() + "[" + _name + "," + _value + "]"; + return "%s@%x[set-cookie:%s=%s]".formatted(super.toString(), hashCode(), getName(), getValue()); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRule.java index 9f44ff38df9..441875faab4 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRule.java @@ -15,15 +15,13 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.server.Request; public class ForceRequestHeaderValueRule extends Rule { private String headerName; - private String forcedValue; + private String headerValue; public String getHeaderName() { @@ -35,40 +33,40 @@ public class ForceRequestHeaderValueRule extends Rule this.headerName = headerName; } - public String getForcedValue() + public String getHeaderValue() { - return forcedValue; + return headerValue; } - public void setForcedValue(String forcedValue) + public void setHeaderValue(String headerValue) { - this.forcedValue = forcedValue; + this.headerValue = headerValue; } @Override - public String matchAndApply(String target, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException + public Request.WrapperProcessor matchAndApply(Request.WrapperProcessor input) throws IOException { - String existingValue = httpServletRequest.getHeader(headerName); + HttpFields headers = input.getHeaders(); + String existingValue = headers.get(headerName); + + // No hit, skip this rule. if (existingValue == null) + return null; + + // Already what we expect, skip this rule. + if (existingValue.equals(headerValue)) + return null; + + HttpFields.Mutable newHeaders = HttpFields.build(headers); + newHeaders.remove(headerName); + newHeaders.add(headerName, headerValue); + return new Request.WrapperProcessor(input) { - // no hit, skip this rule. - return null; - } - - if (existingValue.equals(forcedValue)) - { - // already what we expect, skip this rule. - return null; - } - - Request baseRequest = Request.getBaseRequest(httpServletRequest); - if (baseRequest == null) - return null; - - HttpFields.Mutable replacement = HttpFields.build(baseRequest.getHttpFields()) - .remove(headerName) - .add(headerName, forcedValue); - baseRequest.setHttpFields(replacement); - return target; + @Override + public HttpFields getHeaders() + { + return newHeaders; + } + }; } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java index e4c9790faef..abbef9b784e 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRule.java @@ -13,13 +13,11 @@ package org.eclipse.jetty.rewrite.handler; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.server.Request; /** - * Set the scheme for the request + *

Sets the request URI scheme, by default {@code https}.

*/ public class ForwardedSchemeHeaderRule extends HeaderRule { @@ -30,19 +28,22 @@ public class ForwardedSchemeHeaderRule extends HeaderRule return _scheme; } - /** - * @param scheme the scheme to set on the request. Defaults to "https" - */ public void setScheme(String scheme) { _scheme = scheme; } @Override - protected String apply(String target, String value, HttpServletRequest request, HttpServletResponse response) + protected Request.WrapperProcessor apply(Request.WrapperProcessor input, String value) { - Request baseRequest = Request.getBaseRequest(request); - baseRequest.setHttpURI(HttpURI.build(baseRequest.getHttpURI()).scheme(_scheme)); - return target; + HttpURI newURI = HttpURI.build(input.getHttpURI()).scheme(getScheme()); + return new Request.WrapperProcessor(input) + { + @Override + public HttpURI getHttpURI() + { + return newURI; + } + }; } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java index 24cbf6aaab9..327fb37b7cb 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRule.java @@ -15,118 +15,80 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.annotation.Name; /** - * Sets the header in the response whenever the rule finds a match. + *

Puts or adds a response header whenever the rule matches a path Servlet pattern.

*/ public class HeaderPatternRule extends PatternRule { - private String _name; - private String _value; + private String _headerName; + private String _headerValue; private boolean _add; - public HeaderPatternRule() - { - this(null, null, null); - } - public HeaderPatternRule(@Name("pattern") String pattern, @Name("name") String name, @Name("value") String value) { super(pattern); - _handling = false; - _terminating = false; - _add = false; - setName(name); - setValue(value); + _headerName = name; + _headerValue = value; } - /** - * Sets the header name. - * - * @param name name of the header field - */ - public void setName(String name) + public String getHeaderName() { - _name = name; + return _headerName; } - /** - * Sets the header value. The value can be either a String or int value. - * - * @param value of the header field - */ - public void setValue(String value) + public void setHeaderName(String name) { - _value = value; + _headerName = name; } - /** - * Sets the Add flag. - * - * @param add If true, the header is added to the response, otherwise the header it is set on the response. - */ - public void setAdd(boolean add) + public String getHeaderValue() { - _add = add; + return _headerValue; } - /** - * Invokes this method when a match found. If the header had already been set, - * the new value overwrites the previous one. Otherwise, it adds the new - * header name and value. - * - * @see org.eclipse.jetty.rewrite.handler.Rule#matchAndApply(String, jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse) - */ - @Override - public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + public void setHeaderValue(String value) { - // process header - if (_add) - response.addHeader(_name, _value); - else - response.setHeader(_name, _value); - return target; + _headerValue = value; } - /** - * Returns the header name. - * - * @return the header name. - */ - public String getName() - { - return _name; - } - - /** - * Returns the header value. - * - * @return the header value. - */ - public String getValue() - { - return _value; - } - - /** - * Returns the add flag value. - * - * @return true if add flag set - */ public boolean isAdd() { return _add; } /** - * Returns the header contents. + * @param add true to add the response header, false to put the response header. */ + public void setAdd(boolean add) + { + _add = add; + } + + @Override + public Request.WrapperProcessor apply(Request.WrapperProcessor input) throws IOException + { + return new Request.WrapperProcessor(input) + { + @Override + public void process(Request ignored, Response response, Callback callback) throws Exception + { + if (isAdd()) + response.addHeader(getHeaderName(), getHeaderValue()); + else + response.setHeader(getHeaderName(), getHeaderValue()); + super.process(ignored, response, callback); + } + }; + } + @Override public String toString() { - return super.toString() + "[" + _name + "," + _value + "]"; + return "%s[header:%s=%s]".formatted(super.toString(), getHeaderName(), getHeaderValue()); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRule.java index c3249891b2a..8dbad4c5431 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRule.java @@ -16,57 +16,54 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; import java.util.regex.Matcher; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.annotation.Name; /** - * Rule to add a header based on a Regex match + *

Puts or adds a response header whenever the rule matches a path regex pattern.

*/ public class HeaderRegexRule extends RegexRule { - private String _name; - private String _value; - private boolean _add = false; - - public HeaderRegexRule() - { - this(null, null, null); - } + private String _headerName; + private String _headerValue; + private boolean _add; public HeaderRegexRule(@Name("regex") String regex, @Name("name") String name, @Name("value") String value) { super(regex); - setHandling(false); - setTerminating(false); - setName(name); - setValue(value); + _headerName = name; + _headerValue = value; } - /** - * Sets the header name. - * - * @param name name of the header field - */ - public void setName(String name) + public String getHeaderName() { - _name = name; + return _headerName; } - /** - * Sets the header value. The value can be either a String or int value. - * - * @param value of the header field - */ - public void setValue(String value) + public void setHeaderName(String name) { - _value = value; + _headerName = name; + } + + public String getHeaderValue() + { + return _headerValue; + } + + public void setHeaderValue(String value) + { + _headerValue = value; + } + + public boolean isAdd() + { + return _add; } /** - * Sets the Add flag. - * - * @param add If true, the header is added to the response, otherwise the header it is set on the response. + * @param add true to add the response header, false to put the response header. */ public void setAdd(boolean add) { @@ -74,51 +71,25 @@ public class HeaderRegexRule extends RegexRule } @Override - protected String apply(String target, HttpServletRequest request, HttpServletResponse response, Matcher matcher) - throws IOException + protected Request.WrapperProcessor apply(Request.WrapperProcessor input, Matcher matcher) throws IOException { - // process header - if (_add) - response.addHeader(_name, _value); - else - response.setHeader(_name, _value); - return target; + return new Request.WrapperProcessor(input) + { + @Override + public void process(Request ignored, Response response, Callback callback) throws Exception + { + if (isAdd()) + response.addHeader(getHeaderName(), matcher.replaceAll(getHeaderValue())); + else + response.setHeader(getHeaderName(), matcher.replaceAll(getHeaderValue())); + super.process(ignored, response, callback); + } + }; } - /** - * Returns the header name. - * - * @return the header name. - */ - public String getName() - { - return _name; - } - - /** - * Returns the header value. - * - * @return the header value. - */ - public String getValue() - { - return _value; - } - - /** - * @return the add flag value. - */ - public boolean isAdd() - { - return _add; - } - - /** - * @return the header contents. - */ @Override public String toString() { - return super.toString() + "[" + _name + "," + _value + "]"; + return "%s[header:%s=%s]".formatted(super.toString(), getHeaderName(), getHeaderValue()); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java index ce4653ec3a0..df54bf95e3e 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/HeaderRule.java @@ -15,29 +15,24 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; /** - * Abstract rule that matches against request headers. + *

Abstract rule that matches against request headers.

*/ - public abstract class HeaderRule extends Rule { - private String _header; + private String _headerName; private String _headerValue; - public String getHeader() + public String getHeaderName() { - return _header; + return _headerName; } - /** - * @param header the header name to check for - */ - public void setHeader(String header) + public void setHeaderName(String header) { - _header = header; + _headerName = header; } public String getHeaderValue() @@ -46,8 +41,8 @@ public abstract class HeaderRule extends Rule } /** - * @param headerValue the header value to match against. If null, then the - * presence of the header is enough to match + * @param headerValue the header value to match against. + * If {@code null}, then the presence of the header is enough to match */ public void setHeaderValue(String headerValue) { @@ -55,34 +50,30 @@ public abstract class HeaderRule extends Rule } @Override - public String matchAndApply(String target, HttpServletRequest request, - HttpServletResponse response) throws IOException + public Request.WrapperProcessor matchAndApply(Request.WrapperProcessor input) throws IOException { - String requestHeaderValue = request.getHeader(_header); - - if (requestHeaderValue != null) - if (_headerValue == null || _headerValue.equals(requestHeaderValue)) - apply(target, requestHeaderValue, request, response); - + String value = input.getHeaders().get(getHeaderName()); + if (value == null) + return null; + String headerValue = getHeaderValue(); + if (headerValue == null || headerValue.equals(value)) + return apply(input, value); return null; } /** - * Apply the rule to the request + *

Invoked after the header matched the {@code Request} headers to apply the rule's logic.

* - * @param target field to attempt match - * @param value header value found - * @param request request object - * @param response response object - * @return The target (possible updated) - * @throws IOException exceptions dealing with operating on request or response - * objects + * @param input the input {@code Request} and {@code Processor} + * @param value the header value + * @return the possibly wrapped {@code Request} and {@code Processor} + * @throws IOException if applying the rule failed */ - protected abstract String apply(String target, String value, HttpServletRequest request, HttpServletResponse response) throws IOException; + protected abstract Request.WrapperProcessor apply(Request.WrapperProcessor input, String value) throws IOException; @Override public String toString() { - return super.toString() + "[" + _header + ":" + _headerValue + "]"; + return "%s[header:%s=%s]".formatted(super.toString(), getHeaderName(), getHeaderValue()); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/InvalidURIRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/InvalidURIRule.java new file mode 100644 index 00000000000..bd62a2d0b21 --- /dev/null +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/InvalidURIRule.java @@ -0,0 +1,130 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.rewrite.handler; + +import java.io.IOException; + +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.StringUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

Rule that protects against invalid unicode characters in URLs, + * returning a configurable status code with body message.

+ *

The logic is as follows:

+ *
    + *
  • if a decoded URI character is an iso control character, apply the rule
  • + *
  • if no {@link Character.UnicodeBlock} is found for a decoded URI character, apply the rule
  • + *
  • if a decoded URI character is in UnicodeBlock.SPECIALS, apply the rule
  • + *
+ */ +public class InvalidURIRule extends Rule +{ + private static final Logger LOG = LoggerFactory.getLogger(InvalidURIRule.class); + + private int _code = HttpStatus.BAD_REQUEST_400; + private String _message = "Illegal URI"; + + @Override + public boolean isTerminating() + { + return true; + } + + public int getCode() + { + return _code; + } + + /** + * @param code the response code + */ + public void setCode(int code) + { + _code = code; + } + + public String getMessage() + { + return _message; + } + + /** + *

Sets the message for the response body (if the response code may have a body).

+ * + * @param message the response message + */ + public void setMessage(String message) + { + _message = message; + } + + @Override + public Request.WrapperProcessor matchAndApply(Request.WrapperProcessor input) throws IOException + { + String path = input.getHttpURI().getDecodedPath(); + + int i = 0; + while (i < path.length()) + { + int codepoint = path.codePointAt(i); + if (!isValidChar(codepoint)) + return apply(input); + i += Character.charCount(codepoint); + } + + return null; + } + + private Request.WrapperProcessor apply(Request.WrapperProcessor input) + { + return new Request.WrapperProcessor(input) + { + @Override + public void process(Request ignored, Response response, Callback callback) + { + String message = getMessage(); + if (StringUtil.isBlank(message)) + { + response.setStatus(getCode()); + callback.succeeded(); + } + else + { + Response.writeError(this, response, callback, getCode(), message); + } + } + }; + } + + protected boolean isValidChar(int codepoint) + { + Character.UnicodeBlock block = Character.UnicodeBlock.of(codepoint); + + if (LOG.isDebugEnabled()) + LOG.debug("{} {} {} {}", Character.charCount(codepoint), codepoint, block, Character.isISOControl(codepoint)); + + return (!Character.isISOControl(codepoint)) && block != null && !Character.UnicodeBlock.SPECIALS.equals(block); + } + + @Override + public String toString() + { + return super.toString() + "[" + _code + ":" + _message + "]"; + } +} diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java index 3a77506e89c..6779bf3faa5 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/PatternRule.java @@ -15,26 +15,21 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.pathmap.ServletPathSpec; +import org.eclipse.jetty.server.Request; /** - * Abstract rule that use a {@link ServletPathSpec} for pattern matching. It uses the - * servlet pattern syntax. + *

Abstract rule that uses the Servlet pattern syntax via + * {@link ServletPathSpec} for path pattern matching.

*/ +// TODO: add boolean useCanonical and use canonicalPath?query instead of pathQuery() public abstract class PatternRule extends Rule { - protected String _pattern; - - protected PatternRule() - { - } + private String _pattern; protected PatternRule(String pattern) { - this(); - setPattern(pattern); + _pattern = pattern; } public String getPattern() @@ -42,43 +37,31 @@ public abstract class PatternRule extends Rule return _pattern; } - /** - * Sets the rule pattern. - * - * @param pattern the pattern - */ public void setPattern(String pattern) { _pattern = pattern; } @Override - public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + public Request.WrapperProcessor matchAndApply(Request.WrapperProcessor input) throws IOException { - if (ServletPathSpec.match(_pattern, target)) - { - return apply(target, request, response); - } + if (ServletPathSpec.match(_pattern, input.getHttpURI().getPath())) + return apply(input); return null; } /** - * Apply the rule to the request + *

Invoked after the Servlet pattern matched the URI path to apply the rule's logic.

* - * @param target field to attempt match - * @param request request object - * @param response response object - * @return The target (possible updated) - * @throws IOException exceptions dealing with operating on request or response objects + * @param input the input {@code Request} and {@code Processor} + * @return the possibly wrapped {@code Request} and {@code Processor} + * @throws IOException if applying the rule failed */ - protected abstract String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException; + protected abstract Request.WrapperProcessor apply(Request.WrapperProcessor input) throws IOException; - /** - * Returns the rule pattern. - */ @Override public String toString() { - return super.toString() + "[" + _pattern + "]"; + return "%s[pattern=%s]".formatted(super.toString(), getPattern()); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java index 76558e13ba0..4571d933c60 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRule.java @@ -15,39 +15,41 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.annotation.Name; /** - * Issues a (3xx) Redirect response whenever the rule finds a match. - *

- * All redirects are part of the 3xx Redirection status code set. - *

- * Defaults to 302 Found + *

Issues a (3xx) redirect response whenever the rule finds a request path Servlet pattern match.

+ *

All redirects are part of the {@code 3xx Redirection} status code set.

+ *

Defaults to {@code 302 Found}.

*/ public class RedirectPatternRule extends PatternRule { private String _location; private int _statusCode = HttpStatus.FOUND_302; - public RedirectPatternRule() - { - this(null, null); - } - public RedirectPatternRule(@Name("pattern") String pattern, @Name("location") String location) { super(pattern); - _handling = true; - _terminating = true; _location = location; } + @Override + public boolean isTerminating() + { + return true; + } + + public String getLocation() + { + return _location; + } + /** - * Sets the redirect location. - * * @param value the location to redirect. */ public void setLocation(String value) @@ -55,45 +57,40 @@ public class RedirectPatternRule extends PatternRule _location = value; } + public int getStatusCode() + { + return _statusCode; + } + /** - * Sets the redirect status code. - * * @param statusCode the 3xx redirect status code */ public void setStatusCode(int statusCode) { - if ((300 <= statusCode) || (statusCode >= 399)) - { - _statusCode = statusCode; - } - else - { + if (!HttpStatus.isRedirection(statusCode)) throw new IllegalArgumentException("Invalid redirect status code " + statusCode + " (must be a value between 300 and 399)"); - } + _statusCode = statusCode; } @Override - public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + public Request.WrapperProcessor apply(Request.WrapperProcessor input) throws IOException { - String location = response.encodeRedirectURL(_location); - response.setHeader("Location", RedirectUtil.toRedirectURL(request, location)); - response.setStatus(_statusCode); - response.getOutputStream().flush(); // no output / content - response.getOutputStream().close(); - return target; + return new Request.WrapperProcessor(input) + { + @Override + public void process(Request ignored, Response response, Callback callback) + { + String location = getLocation(); + response.setStatus(getStatusCode()); + response.setHeader(HttpHeader.LOCATION, Request.toRedirectURI(this, location)); + callback.succeeded(); + } + }; } - /** - * Returns the redirect status code and location. - */ @Override public String toString() { - StringBuilder str = new StringBuilder(); - str.append(super.toString()); - str.append('[').append(_statusCode); - str.append('>').append(_location); - str.append(']'); - return str.toString(); + return "%s[redirect:%d>%s]".formatted(super.toString(), getStatusCode(), getLocation()); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRule.java index b6ad8d306cd..ff556325927 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRule.java @@ -16,78 +16,75 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; import java.util.regex.Matcher; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.annotation.Name; /** - * Issues a (3xx) Redirect response whenever the rule finds a match via regular expression. - *

- * The replacement string may use $n" to replace the nth capture group. - *

- * All redirects are part of the {@code 3xx Redirection} status code set. - *

- * Defaults to {@code 302 Found} + *

Issues a (3xx) redirect response whenever the rule finds a request path regular expression match

+ *

The replacement string may use {@code $n} to replace the nth capture group.

+ *

All redirects are part of the {@code 3xx Redirection} status code set.

+ *

Defaults to {@code 302 Found}.

*/ public class RedirectRegexRule extends RegexRule { protected String _location; private int _statusCode = HttpStatus.FOUND_302; - public RedirectRegexRule() - { - this(null, null); - } - public RedirectRegexRule(@Name("regex") String regex, @Name("location") String location) { super(regex); - setHandling(true); - setTerminating(true); - setLocation(location); + _location = location; + } + + @Override + public boolean isTerminating() + { + return true; + } + + public String getLocation() + { + return _location; } /** - * Sets the redirect location. - * - * @param location the URI to redirect to + * @param location the location to redirect. */ public void setLocation(String location) { _location = location; } - /** - * Sets the redirect status code. - * - * @param statusCode the 3xx redirect status code - */ + public int getStatusCode() + { + return _statusCode; + } + public void setStatusCode(int statusCode) { - if (statusCode >= 300 && statusCode <= 399) - _statusCode = statusCode; - else + if (!HttpStatus.isRedirection(statusCode)) throw new IllegalArgumentException("Invalid redirect status code " + statusCode + " (must be a value between 300 and 399)"); + _statusCode = statusCode; } @Override - protected String apply(String target, HttpServletRequest request, HttpServletResponse response, Matcher matcher) throws IOException + protected Request.WrapperProcessor apply(Request.WrapperProcessor input, Matcher matcher) throws IOException { - target = _location; - for (int g = 1; g <= matcher.groupCount(); g++) + return new Request.WrapperProcessor(input) { - String group = matcher.group(g); - target = StringUtil.replace(target, "$" + g, group); - } - - target = response.encodeRedirectURL(target); - response.setHeader("Location", RedirectUtil.toRedirectURL(request, target)); - response.setStatus(_statusCode); - response.getOutputStream().flush(); // no output / content - response.getOutputStream().close(); - return target; + @Override + public void process(Request ignored, Response response, Callback callback) + { + String target = matcher.replaceAll(getLocation()); + response.setStatus(_statusCode); + response.setHeader(HttpHeader.LOCATION, Request.toRedirectURI(this, target)); + callback.succeeded(); + } + }; } /** @@ -96,11 +93,6 @@ public class RedirectRegexRule extends RegexRule @Override public String toString() { - StringBuilder str = new StringBuilder(); - str.append(super.toString()); - str.append('[').append(_statusCode); - str.append('>').append(_location); - str.append(']'); - return str.toString(); + return "%s[redirect:%d>%s]".formatted(super.toString(), getStatusCode(), getLocation()); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java index d286b4806bf..74dc7e7a795 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RegexRule.java @@ -17,72 +17,62 @@ import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; /** - * Abstract rule to use as a base class for rules that match with a regular expression. + *

Abstract rule that uses the regular expression syntax for path pattern matching.

*/ +// TODO: add boolean useCanonical and use canonicalPath?query instead of pathQuery() public abstract class RegexRule extends Rule { - protected Pattern _regex; + private Pattern _regex; - protected RegexRule() - { - } - - protected RegexRule(String pattern) + public RegexRule(String pattern) { setRegex(pattern); } /** - * Sets the regular expression string used to match with string URI. - * - * @param regex the regular expression. - */ - public void setRegex(String regex) - { - _regex = regex == null ? null : Pattern.compile(regex); - } - - /** - * @return get the regular expression + * @return the regular expression */ public String getRegex() { return _regex == null ? null : _regex.pattern(); } - @Override - public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + /** + *

Sets the regular expression to match with the request path.

+ * + * @param regex the regular expression + */ + public void setRegex(String regex) { + _regex = regex == null ? null : Pattern.compile(regex); + } + + @Override + public Request.WrapperProcessor matchAndApply(Request.WrapperProcessor input) throws IOException + { + String target = input.getHttpURI().getPathQuery(); Matcher matcher = _regex.matcher(target); - boolean matches = matcher.matches(); - if (matches) - return apply(target, request, response, matcher); + if (matcher.matches()) + return apply(input, matcher); return null; } /** - * Apply this rule to the request/response pair. - * Called by {@link #matchAndApply(String, HttpServletRequest, HttpServletResponse)} if the regex matches. + *

Invoked after the regular expression matched the URI path to apply the rule's logic.

* - * @param target field to attempt match - * @param request request object - * @param response response object - * @param matcher The Regex matcher that matched the request (with capture groups available for replacement). - * @return The target (possible updated). - * @throws IOException exceptions dealing with operating on request or response objects + * @param input the input {@code Request} and {@code Processor} + * @param matcher the {@code Matcher} that matched the request path, with capture groups available for replacement. + * @return the possibly wrapped {@code Request} and {@code Processor} + * @throws IOException if applying the rule failed */ - protected abstract String apply(String target, HttpServletRequest request, HttpServletResponse response, Matcher matcher) throws IOException; + protected abstract Request.WrapperProcessor apply(Request.WrapperProcessor input, Matcher matcher) throws IOException; - /** - * Returns the regular expression string. - */ @Override public String toString() { - return super.toString() + "[" + _regex + "]"; + return "%s[regex:%s]".formatted(super.toString(), getRegex()); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java index 3f9464a43b0..941eace7b27 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRule.java @@ -15,8 +15,11 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.annotation.Name; /** @@ -27,36 +30,41 @@ public class ResponsePatternRule extends PatternRule private int _code; private String _message; - public ResponsePatternRule() - { - this(null, null, null); - } - - public ResponsePatternRule(@Name("pattern") String pattern, @Name("code") String code, @Name("message") String message) + public ResponsePatternRule(@Name("pattern") String pattern, @Name("code") int code, @Name("message") String message) { super(pattern); - _handling = true; - _terminating = true; - setCode(code); - setMessage(message); + _code = code; + _message = message; } - /** - * Sets the response status code. - * - * @param code response code - */ - public void setCode(String code) + @Override + public boolean isTerminating() { - _code = code == null ? 0 : Integer.parseInt(code); + return true; + } + + public int getCode() + { + return _code; } /** - * Sets the message for the {@link org.eclipse.jetty.server.Response#sendError(int, String)} method. - * Reasons will only reflect - * if the code value is greater or equal to 400. + * @param code the response code + */ + public void setCode(int code) + { + _code = code; + } + + public String getMessage() + { + return _message; + } + + /** + *

Sets the message for the response body (if the response code may have a body).

* - * @param message the reason + * @param message the response message */ public void setMessage(String message) { @@ -64,25 +72,33 @@ public class ResponsePatternRule extends PatternRule } @Override - public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + public Request.WrapperProcessor apply(Request.WrapperProcessor input) throws IOException { - // status code 400 and up are error codes - if (_code > 0) + if (getCode() < HttpStatus.CONTINUE_100) + return null; + + return new Request.WrapperProcessor(input) { - if (_message != null && !_message.isEmpty()) - response.sendError(_code, _message); - else - response.setStatus(_code); - } - return target; + @Override + public void process(Request ignored, Response response, Callback callback) + { + String message = getMessage(); + if (StringUtil.isBlank(message)) + { + response.setStatus(getCode()); + callback.succeeded(); + } + else + { + Response.writeError(this, response, callback, getCode(), message); + } + } + }; } - /** - * Returns the code and reason string. - */ @Override public String toString() { - return super.toString() + "[" + _code + "," + _message + "]"; + return "%s[response:%d>%s]".formatted(super.toString(), getCode(), getMessage()); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java index 4904f48772a..0787021cbdc 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java @@ -13,16 +13,10 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; -import java.util.Arrays; -import java.util.EnumSet; +import java.util.List; -import jakarta.servlet.DispatcherType; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.HandlerWrapper; /** *

Rewrite handler is responsible for managing the rules. Its capabilities @@ -31,8 +25,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; * whenever the rule finds a match. * *

The rules can be matched by the either: pattern matching of @{@link org.eclipse.jetty.http.pathmap.ServletPathSpec} - * (eg {@link PatternRule}), regular expressions (eg {@link RegexRule}) or certain conditions set - * (eg {@link MsieSslRule} - the requests must be in SSL mode). + * (eg {@link PatternRule}), regular expressions (eg {@link RegexRule}) or custom logic. * *

The rules can be grouped into rule containers (class {@link RuleContainer}), and will only * be applied if the request matches the conditions for their container @@ -46,7 +39,6 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; *

  • {@link ResponsePatternRule} - sets the status/error codes.
  • *
  • {@link RewritePatternRule} - rewrites the requested URI.
  • *
  • {@link RewriteRegexRule} - rewrites the requested URI using regular expression for pattern matching.
  • - *
  • {@link MsieSslRule} - disables the keep alive on SSL for IE5 and IE6.
  • *
  • {@link ForwardedSchemeHeaderRule} - set the scheme according to the headers present.
  • *
  • {@link VirtualHostRuleContainer} - checks whether the request matches one of a set of virtual host names.
  • * @@ -160,14 +152,18 @@ import org.eclipse.jetty.server.handler.HandlerWrapper; * </Set> * */ -public class RewriteHandler extends HandlerWrapper +public class RewriteHandler extends Handler.Wrapper { - private RuleContainer _rules; - private EnumSet _dispatchTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC); + private final RuleContainer _rules; public RewriteHandler() { - _rules = new RuleContainer(); + this(new RuleContainer()); + } + + public RewriteHandler(RuleContainer rules) + { + _rules = rules; addBean(_rules); } @@ -176,7 +172,7 @@ public class RewriteHandler extends HandlerWrapper * * @return an array of {@link Rule}. */ - public Rule[] getRules() + public List getRules() { return _rules.getRules(); } @@ -186,27 +182,11 @@ public class RewriteHandler extends HandlerWrapper * * @param rules an array of {@link Rule}. */ - public void setRules(Rule[] rules) + public void setRules(List rules) { _rules.setRules(rules); } - /** - * Assigns the rules to process. - * - * @param rules a {@link RuleContainer} containing other rules to process - */ - public void setRuleContainer(RuleContainer rules) - { - updateBean(_rules, rules); - _rules = rules; - } - - public RuleContainer getRuleContainer() - { - return _rules; - } - /** * Add a Rule * @@ -218,43 +198,7 @@ public class RewriteHandler extends HandlerWrapper } /** - * @return the rewriteRequestURI If true, this handler will rewrite the value - * returned by {@link HttpServletRequest#getRequestURI()}. - */ - public boolean isRewriteRequestURI() - { - return _rules.isRewriteRequestURI(); - } - - /** - * @param rewriteRequestURI true if this handler will rewrite the value - * returned by {@link HttpServletRequest#getRequestURI()}. - */ - public void setRewriteRequestURI(boolean rewriteRequestURI) - { - _rules.setRewriteRequestURI(rewriteRequestURI); - } - - /** - * @return true if this handler will rewrite the value - * returned by {@link HttpServletRequest#getPathInfo()}. - */ - public boolean isRewritePathInfo() - { - return _rules.isRewritePathInfo(); - } - - /** - * @param rewritePathInfo true if this handler will rewrite the value - * returned by {@link HttpServletRequest#getPathInfo()}. - */ - public void setRewritePathInfo(boolean rewritePathInfo) - { - _rules.setRewritePathInfo(rewritePathInfo); - } - - /** - * @return the originalPathAttribte. If non null, this string will be used + * @return the originalPathAttribute. If non null, this string will be used * as the attribute name to store the original request path. */ public String getOriginalPathAttribute() @@ -271,34 +215,20 @@ public class RewriteHandler extends HandlerWrapper _rules.setOriginalPathAttribute(originalPathAttribute); } - public EnumSet getDispatcherTypes() - { - return _dispatchTypes; - } - - public void setDispatcherTypes(EnumSet types) - { - _dispatchTypes = EnumSet.copyOf(types); - } - - public void setDispatcherTypes(DispatcherType... types) - { - _dispatchTypes = EnumSet.copyOf(Arrays.asList(types)); - } - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public Request.Processor handle(Request request) throws Exception { - if (isStarted()) - { - if (_dispatchTypes.contains(baseRequest.getDispatcherType())) - { - String returned = _rules.matchAndApply(target, request, response); - target = (returned == null) ? target : returned; - } + if (!isStarted()) + return null; - if (!baseRequest.isHandled()) - super.handle(target, baseRequest, request, response); - } + Request.WrapperProcessor input = new Request.WrapperProcessor(request); + Request.WrapperProcessor output = _rules.matchAndApply(input); + + // No rule matched, call super with the original request. + if (output == null) + return super.handle(request); + + // At least one rule matched, call super with the result of the rule applications. + return output.wrapProcessor(super.handle(output)); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java index fedd8c5e0d3..78d724d0a2a 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewritePatternRule.java @@ -15,88 +15,72 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.pathmap.ServletPathSpec; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.annotation.Name; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Rewrite the URI by replacing the matched {@link ServletPathSpec} path with a fixed string. + *

    A rule to rewrite the path and query that match a Servlet pattern with a fixed string.

    */ -public class RewritePatternRule extends PatternRule implements Rule.ApplyURI +public class RewritePatternRule extends PatternRule { - private String _replacement; - private String _query; + private static final Logger LOG = LoggerFactory.getLogger(RewritePatternRule.class); - public RewritePatternRule() - { - this(null, null); - } + private String _path; + private String _query; public RewritePatternRule(@Name("pattern") String pattern, @Name("replacement") String replacement) { super(pattern); - _handling = false; - _terminating = false; setReplacement(replacement); } /** - * Whenever a match is found, it replaces with this value. + *

    The replacement for the path and query matched by this rule.

    * - * @param replacement the replacement string. + * @param replacement the replacement path */ public void setReplacement(String replacement) { if (replacement == null) { - _replacement = null; + _path = null; _query = null; } else { String[] split = replacement.split("\\?", 2); - _replacement = split[0]; + _path = split[0]; _query = split.length == 2 ? split[1] : null; } } @Override - public String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + public Request.WrapperProcessor apply(Request.WrapperProcessor input) throws IOException { - target = URIUtil.addPaths(_replacement, ServletPathSpec.pathInfo(_pattern, target)); - return target; + HttpURI httpURI = input.getHttpURI(); + String newQuery = URIUtil.addQueries(httpURI.getQuery(), _query); + String newPath = URIUtil.addPaths(_path, ServletPathSpec.pathInfo(getPattern(), httpURI.getPath())); + HttpURI newURI = HttpURI.build(httpURI, newPath, httpURI.getParam(), newQuery); + if (LOG.isDebugEnabled()) + LOG.debug("rewriting {} to {}", httpURI, newURI); + return new Request.WrapperProcessor(input) + { + @Override + public HttpURI getHttpURI() + { + return newURI; + } + }; } - /** - * This method will add _query to the requests's queryString and also combine it with existing queryStrings in - * the request. However it won't take care for duplicate. E.g. if request.getQueryString contains a parameter - * param1 = true and _query will contain param1=false the result will be param1=true&param1=false. - * To cover this use case some more complex pattern matching is necessary. We can implement this if there's use - * cases. - * - * @param request the request - * @param oldURI the old URI - * @param newURI the new URI - * @throws IOException if unable to apply the URI - */ - @Override - public void applyURI(Request request, String oldURI, String newURI) throws IOException - { - HttpURI baseURI = request.getHttpURI(); - String query = URIUtil.addQueries(baseURI.getQuery(), _query); - request.setHttpURI(HttpURI.build(baseURI, newURI, baseURI.getParam(), query)); - } - - /** - * Returns the replacement string. - */ @Override public String toString() { - return super.toString() + "[" + _replacement + "]"; + return "%s[rewrite:%s%s]".formatted(super.toString(), _path, _query == null ? "" : "?" + _query); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java index 9b4a6f6878b..e84db590a97 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java @@ -16,37 +16,22 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; import java.util.regex.Matcher; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.annotation.Name; /** - * Rewrite the URI by matching with a regular expression. - * The replacement string may use $n" to replace the nth capture group. - * If the replacement string contains ? character, then it is split into a path - * and query string component. The replacement query string may also contain $Q, which - * is replaced with the original query string. - * The returned target contains only the path. + *

    A rule to rewrite the path and query that match a regular expression pattern with a fixed string.

    + *

    The replacement String follows standard {@link Matcher#replaceAll(String)} behavior, including named groups

    + *

    */ -public class RewriteRegexRule extends RegexRule implements Rule.ApplyURI +public class RewriteRegexRule extends RegexRule { - private String _replacement; - private String _query; - private boolean _queryGroup; - - public RewriteRegexRule() - { - this(null, null); - } + private String replacement; public RewriteRegexRule(@Name("regex") String regex, @Name("replacement") String replacement) { super(regex); - setHandling(false); - setTerminating(false); setReplacement(replacement); } @@ -57,73 +42,29 @@ public class RewriteRegexRule extends RegexRule implements Rule.ApplyURI */ public void setReplacement(String replacement) { - if (replacement == null) - { - _replacement = null; - _query = null; - _queryGroup = false; - } - else - { - String[] split = replacement.split("\\?", 2); - _replacement = split[0]; - _query = split.length == 2 ? split[1] : null; - _queryGroup = _query != null && _query.contains("$Q"); - } + this.replacement = replacement; } @Override - public String apply(String target, HttpServletRequest request, HttpServletResponse response, Matcher matcher) throws IOException + public Request.WrapperProcessor apply(Request.WrapperProcessor input, Matcher matcher) throws IOException { - target = _replacement; - String query = _query; - for (int g = 1; g <= matcher.groupCount(); g++) - { - String group = matcher.group(g); - if (group == null) - group = ""; - else - group = Matcher.quoteReplacement(group); - target = target.replaceAll("\\$" + g, group); - if (query != null) - query = query.replaceAll("\\$" + g, group); - } + HttpURI httpURI = input.getHttpURI(); + String replacedPath = matcher.replaceAll(replacement); - if (query != null) + HttpURI newURI = HttpURI.build(httpURI, replacedPath); + return new Request.WrapperProcessor(input) { - if (_queryGroup) - query = query.replace("$Q", request.getQueryString() == null ? "" : request.getQueryString()); - request.setAttribute("org.eclipse.jetty.rewrite.handler.RewriteRegexRule.Q", query); - } - - return target; + @Override + public HttpURI getHttpURI() + { + return newURI; + } + }; } - @Override - public void applyURI(Request request, String oldURI, String newURI) throws IOException - { - HttpURI baseURI = request.getHttpURI(); - if (_query == null) - { - request.setHttpURI(HttpURI.build(baseURI, newURI, baseURI.getParam(), baseURI.getQuery())); - } - else - { - // TODO why isn't _query used? - String query = (String)request.getAttribute("org.eclipse.jetty.rewrite.handler.RewriteRegexRule.Q"); - if (!_queryGroup) - query = URIUtil.addQueries(baseURI.getQuery(), query); - - request.setHttpURI(HttpURI.build(baseURI, newURI, baseURI.getParam(), query)); - } - } - - /** - * Returns the replacement string. - */ @Override public String toString() { - return super.toString() + "[" + _replacement + "]"; + return "%s[rewrite:%s]".formatted(super.toString(), replacement); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java index 3d69b721924..d2eeefdb10e 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/Rule.java @@ -15,76 +15,36 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; /** - * An abstract rule for creating rewrite rules. + *

    An abstract rule that, upon matching a certain condition, may wrap + * the {@code Request} or the {@code Processor} to execute custom logic.

    */ public abstract class Rule { - /** - * Interface used to apply a changed target if {@link RuleContainer#setRewriteRequestURI(boolean)} is true. - */ - public interface ApplyURI - { - void applyURI(Request request, String oldURI, String newURI) throws IOException; - } - - protected boolean _terminating; - protected boolean _handling; + private boolean _terminating; /** - * This method calls tests the rule against the request/response pair and if the Rule - * applies, then the rule's action is triggered. + *

    Tests whether the given {@code Request} should apply, and if so the rule logic is triggered.

    * - * @param target The target of the request - * @param request the request - * @param response the response - * @return The new target if the rule has matched, else null - * @throws IOException if unable to match the rule + * @param input the input {@code Request} and {@code Processor} + * @return the possibly wrapped {@code Request} and {@code Processor}, or {@code null} if the rule did not match + * @throws IOException if applying the rule failed */ - public abstract String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException; + public abstract Request.WrapperProcessor matchAndApply(Request.WrapperProcessor input) throws IOException; /** - * Sets terminating to true or false. - * - * @param terminating If true, this rule will terminate the loop if this rule has been applied. - */ - public void setTerminating(boolean terminating) - { - _terminating = terminating; - } - - /** - * Returns the terminating flag value. - * - * @return true if the rule needs to terminate; false otherwise. + * @return when {@code true}, rules after this one are not processed */ public boolean isTerminating() { return _terminating; } - /** - * Returns the handling flag value. - * - * @return true if the rule handles the request and nested handlers should not be called. - */ - public boolean isHandling() + public void setTerminating(boolean value) { - return _handling; - } - - /** - * Set the handling flag value. - * - * @param handling true if the rule handles the request and nested handlers should not be called. - */ - public void setHandling(boolean handling) - { - _handling = handling; + _terminating = value; } /** @@ -93,6 +53,6 @@ public abstract class Rule @Override public String toString() { - return this.getClass().getName() + (_handling ? "[H" : "[h") + (_terminating ? "T]" : "t]"); + return "%s@%x[terminating=%b]".formatted(getClass().getSimpleName(), hashCode(), isTerminating()); } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java index abcd2c550f5..3e4f4c96adb 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java @@ -14,13 +14,12 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.ArrayUtil; -import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.component.Dumpable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,36 +28,37 @@ import org.slf4j.LoggerFactory; * Base container to group rules. Can be extended so that the contained rules * will only be applied under certain conditions */ -public class RuleContainer extends Rule implements Dumpable +public class RuleContainer extends Rule implements Iterable, Dumpable { public static final String ORIGINAL_QUERYSTRING_ATTRIBUTE_SUFFIX = ".QUERYSTRING"; private static final Logger LOG = LoggerFactory.getLogger(RuleContainer.class); - protected Rule[] _rules; + private final List _rules = new ArrayList<>(); - protected String _originalPathAttribute; - protected String _originalQueryStringAttribute; - protected boolean _rewriteRequestURI = true; - protected boolean _rewritePathInfo = true; + private String _originalPathAttribute; + private String _originalQueryStringAttribute; /** - * Returns the list of rules. - * - * @return an array of {@link Rule}. + * @return the list of {@code Rule}s */ - public Rule[] getRules() + public List getRules() { return _rules; } /** - * Assigns the rules to process. - * - * @param rules an array of {@link Rule}. + * @param rules the list of {@link Rule}. */ - public void setRules(Rule[] rules) + public void setRules(List rules) { - _rules = rules; + _rules.clear(); + _rules.addAll(rules); + } + + @Override + public Iterator iterator() + { + return _rules.iterator(); } /** @@ -68,43 +68,7 @@ public class RuleContainer extends Rule implements Dumpable */ public void addRule(Rule rule) { - _rules = ArrayUtil.addToArray(_rules, rule, Rule.class); - } - - /** - * @return the rewriteRequestURI If true, this handler will rewrite the value - * returned by {@link HttpServletRequest#getRequestURI()}. - */ - public boolean isRewriteRequestURI() - { - return _rewriteRequestURI; - } - - /** - * @param rewriteRequestURI true if this handler will rewrite the value - * returned by {@link HttpServletRequest#getRequestURI()}. - */ - public void setRewriteRequestURI(boolean rewriteRequestURI) - { - _rewriteRequestURI = rewriteRequestURI; - } - - /** - * @return true if this handler will rewrite the value - * returned by {@link HttpServletRequest#getPathInfo()}. - */ - public boolean isRewritePathInfo() - { - return _rewritePathInfo; - } - - /** - * @param rewritePathInfo true if this handler will rewrite the value - * returned by {@link HttpServletRequest#getPathInfo()}. - */ - public void setRewritePathInfo(boolean rewritePathInfo) - { - _rewritePathInfo = rewritePathInfo; + _rules.add(rule); } /** @@ -117,98 +81,64 @@ public class RuleContainer extends Rule implements Dumpable } /** - * @param originalPathAttribte If non null, this string will be used + * @param originalPathAttribute If non null, this string will be used * as the attribute name to store the original request path. */ - public void setOriginalPathAttribute(String originalPathAttribte) + public void setOriginalPathAttribute(String originalPathAttribute) { - _originalPathAttribute = originalPathAttribte; - _originalQueryStringAttribute = originalPathAttribte + ORIGINAL_QUERYSTRING_ATTRIBUTE_SUFFIX; + _originalPathAttribute = originalPathAttribute; + _originalQueryStringAttribute = originalPathAttribute + ORIGINAL_QUERYSTRING_ATTRIBUTE_SUFFIX; } /** - * Process the contained rules + *

    Processes the rules.

    * - * @param target target field to pass on to the contained rules - * @param request request object to pass on to the contained rules - * @param response response object to pass on to the contained rules + * @param input the input {@code Request} and {@code Processor} + * @return a {@code Request} and {@code Processor}, possibly wrapped by rules to implement the rule's logic, + * or {@code null} if no rule matched */ @Override - public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + public Request.WrapperProcessor matchAndApply(Request.WrapperProcessor input) throws IOException { - return apply(target, request, response); - } - - /** - * Process the contained rules (called by matchAndApply) - * - * @param target target field to pass on to the contained rules - * @param request request object to pass on to the contained rules - * @param response response object to pass on to the contained rules - * @return the target - * @throws IOException if unable to apply the rule - */ - protected String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException - { - boolean originalSet = _originalPathAttribute == null; - - if (_rules == null) - return target; + if (_originalPathAttribute != null) + { + HttpURI httpURI = input.getHttpURI(); + input.setAttribute(_originalPathAttribute, httpURI.getPath()); + if (_originalQueryStringAttribute != null) + input.setAttribute(_originalQueryStringAttribute, httpURI.getQuery()); + } + boolean match = false; for (Rule rule : _rules) { - String applied = rule.matchAndApply(target, request, response); - if (applied != null) + if (LOG.isDebugEnabled()) + LOG.debug("applying {}", rule); + Request.WrapperProcessor output = rule.matchAndApply(input); + if (output == null) { - LOG.debug("applied {}", rule); - LOG.debug("rewrote {} to {}", target, applied); - if (!originalSet) - { - originalSet = true; - request.setAttribute(_originalPathAttribute, target); + if (LOG.isDebugEnabled()) + LOG.debug("no match {}", rule); + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("match {}", rule); - String query = request.getQueryString(); - if (query != null) - request.setAttribute(_originalQueryStringAttribute, query); - } + match = true; - // Ugly hack, we should just pass baseRequest into the API from RewriteHandler itself. - Request baseRequest = Request.getBaseRequest(request); - - if (_rewriteRequestURI) - { - String encoded = URIUtil.encodePath(applied); - if (rule instanceof Rule.ApplyURI) - ((Rule.ApplyURI)rule).applyURI(baseRequest, baseRequest.getRequestURI(), encoded); - else - { - HttpURI baseUri = baseRequest.getHttpURI(); - baseRequest.setHttpURI(HttpURI.build(baseUri, encoded) - .param(baseUri.getParam()) - .query(baseUri.getQuery())); - } - } - - if (_rewritePathInfo) - baseRequest.setContext(baseRequest.getContext(), applied); - - target = applied; - - if (rule.isHandling()) - { - LOG.debug("handling {}", rule); - baseRequest.setHandled(true); - } + // Chain the rules. + input = output; if (rule.isTerminating()) { - LOG.debug("terminating {}", rule); + if (LOG.isDebugEnabled()) + LOG.debug("terminating {}", rule); break; } } } - return target; + return match ? input : null; } @Override diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/TerminatingPatternRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/TerminatingPatternRule.java index 86f3a8df19f..63b6aff20bf 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/TerminatingPatternRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/TerminatingPatternRule.java @@ -15,38 +15,28 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; /** - * If this rule matches, terminate the processing of other rules. - * Allowing the request to be processed by the handlers after the rewrite rules. + *

    If this rule matches, terminates the processing of other rules, allowing + * the request to be processed by the handlers after the {@link RewriteHandler}.

    */ public class TerminatingPatternRule extends PatternRule { - public TerminatingPatternRule() - { - this(null); - } - public TerminatingPatternRule(String pattern) { super(pattern); - super.setTerminating(true); } @Override - public void setTerminating(boolean terminating) + public boolean isTerminating() { - if (!terminating) - { - throw new RuntimeException("Not allowed to disable terminating on a " + TerminatingPatternRule.class.getName()); - } + return true; } @Override - protected String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + protected Request.WrapperProcessor apply(Request.WrapperProcessor input) throws IOException { - return target; + return input; } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/TerminatingRegexRule.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/TerminatingRegexRule.java index 044709cb140..9cbbc091bd8 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/TerminatingRegexRule.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/TerminatingRegexRule.java @@ -16,8 +16,7 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; import java.util.regex.Matcher; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.annotation.Name; /** @@ -26,29 +25,20 @@ import org.eclipse.jetty.util.annotation.Name; */ public class TerminatingRegexRule extends RegexRule { - public TerminatingRegexRule() - { - this(null); - } - public TerminatingRegexRule(@Name("regex") String regex) { super(regex); - super.setTerminating(true); } @Override - public void setTerminating(boolean terminating) + public boolean isTerminating() { - if (!terminating) - { - throw new RuntimeException("Not allowed to disable terminating on a " + TerminatingRegexRule.class.getName()); - } + return true; } @Override - public String apply(String target, HttpServletRequest request, HttpServletResponse response, Matcher matcher) throws IOException + public Request.WrapperProcessor apply(Request.WrapperProcessor input, Matcher matcher) throws IOException { - return target; + return input; } } diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java index 46222fe14b5..51b9f9afa1e 100644 --- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java +++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainer.java @@ -14,94 +14,69 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.util.ArrayUtil; +import org.eclipse.jetty.server.Request; /** - * Groups rules that apply only to a specific virtual host - * or sets of virtual hosts + *

    Groups rules that apply only to one or more specific virtual hosts.

    */ - public class VirtualHostRuleContainer extends RuleContainer { - private String[] _virtualHosts; + private final List _virtualHosts = new ArrayList<>(); /** - * Set the virtual hosts that the rules within this container will apply to - * - * @param virtualHosts Array of virtual hosts that the rules within this container are applied to. - * A null hostname or null/empty array means any hostname is acceptable. + * @return the virtual hosts to match */ - public void setVirtualHosts(String[] virtualHosts) - { - if (virtualHosts == null) - { - _virtualHosts = virtualHosts; - } - else - { - _virtualHosts = new String[virtualHosts.length]; - for (int i = 0; i < virtualHosts.length; i++) - { - _virtualHosts[i] = normalizeHostname(virtualHosts[i]); - } - } - } - - /** - * Get the virtual hosts that the rules within this container will apply to - * - * @return Array of virtual hosts that the rules within this container are applied to. - * A null hostname or null/empty array means any hostname is acceptable. - */ - public String[] getVirtualHosts() + public List getVirtualHosts() { return _virtualHosts; } /** - * @param virtualHost add a virtual host to the existing list of virtual hosts - * A null hostname means any hostname is acceptable + *

    Sets the virtual hosts to match for the rules within this container to be applied.

    + * + * @param virtualHosts the virtual hosts to match */ - public void addVirtualHost(String virtualHost) + public void setVirtualHosts(List virtualHosts) { - _virtualHosts = ArrayUtil.addToArray(_virtualHosts, virtualHost, String.class); + _virtualHosts.clear(); + if (virtualHosts != null) + virtualHosts.forEach(this::addVirtualHost); } /** - * Process the contained rules if the request is applicable to the virtual hosts of this rule - * - * @param target target field to pass on to the contained rules - * @param request request object to pass on to the contained rules - * @param response response object to pass on to the contained rules + * @param virtualHost the virtual host to add to the existing list of virtual hosts */ - @Override - public String matchAndApply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException + public void addVirtualHost(String virtualHost) { - if (_virtualHosts != null && _virtualHosts.length > 0) + _virtualHosts.add(normalizeHostName(virtualHost)); + } + + @Override + public Request.WrapperProcessor matchAndApply(Request.WrapperProcessor input) throws IOException + { + if (_virtualHosts.isEmpty()) + return super.matchAndApply(input); + + String serverName = Request.getServerName(input); + for (String virtualHost : getVirtualHosts()) { - String requestHost = normalizeHostname(request.getServerName()); - for (String ruleHost : _virtualHosts) - { - if (ruleHost == null || - ruleHost.equalsIgnoreCase(requestHost) || - (ruleHost.startsWith("*.") && ruleHost.regionMatches(true, 2, requestHost, requestHost.indexOf(".") + 1, ruleHost.length() - 2)) - ) - { - return apply(target, request, response); - } - } - } - else - { - return apply(target, request, response); + if (virtualHost == null || virtualHost.equalsIgnoreCase(serverName)) + return super.matchAndApply(input); + + // Handle case-insensitive wildcard host names. + if (virtualHost.startsWith("*.") && + virtualHost.regionMatches(true, 2, serverName, serverName.indexOf(".") + 1, virtualHost.length() - 2)) + return super.matchAndApply(input); } + + // No virtual host match, skip the other rules. return null; } - private String normalizeHostname(String host) + private String normalizeHostName(String host) { if (host == null) return null; diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/package-info.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/package-info.java similarity index 100% rename from jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/package-info.java rename to jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/package-info.java diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTest.java new file mode 100644 index 00000000000..1c2c23872cc --- /dev/null +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/AbstractRuleTest.java @@ -0,0 +1,41 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.rewrite.handler; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.LifeCycle; +import org.junit.jupiter.api.AfterEach; + +public abstract class AbstractRuleTest +{ + protected Server _server = new Server(); + protected LocalConnector _connector = new LocalConnector(_server); + protected RewriteHandler _rewriteHandler = new RewriteHandler(); + + @AfterEach + public void stop() throws Exception + { + LifeCycle.stop(_server); + } + + protected void start(Handler handler) throws Exception + { + _server.addConnector(_connector); + _server.setHandler(_rewriteHandler); + _rewriteHandler.setHandler(handler); + _server.start(); + } +} diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java index fed5da7ce91..9d471ebd441 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/CookiePatternRuleTest.java @@ -13,25 +13,16 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringReader; +import java.util.Arrays; +import java.util.List; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.server.handler.HandlerList; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -40,71 +31,48 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.fail; -public class CookiePatternRuleTest +public class CookiePatternRuleTest extends AbstractRuleTest { - private Server server; - private LocalConnector localConnector; - - public void startServer(CookiePatternRule rule) throws Exception + private void start(CookiePatternRule rule) throws Exception { - server = new Server(); - localConnector = new LocalConnector(server); - server.addConnector(localConnector); - - RewriteHandler rewriteHandler = new RewriteHandler(); -// rewriteHandler.setRewriteRequestURI(false); - rewriteHandler.addRule(rule); - - Handler dummyHandler = new AbstractHandler() + _rewriteHandler.addRule(rule); + start(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) { - response.setContentType("text/plain"); - response.setCharacterEncoding("utf-8"); - PrintWriter out = response.getWriter(); - out.printf("target=%s%n", target); - out.printf("baseRequest.requestUri=%s%n", baseRequest.getRequestURI()); - out.printf("baseRequest.originalUri=%s%n", baseRequest.getOriginalURI()); - out.printf("request.requestUri=%s%n", request.getRequestURI()); - out.printf("request.queryString=%s%n", request.getQueryString()); - baseRequest.setHandled(true); + response.setContentType("text/plain;charset=utf-8"); + response.write(false, Callback.NOOP, "pathInContext=%s%n".formatted(request.getPathInContext())); + response.write(false, Callback.NOOP, "path=%s%n".formatted(request.getHttpURI().getPath())); + response.write(false, Callback.NOOP, "query=%s%n".formatted(request.getHttpURI().getQuery())); + Request original = Request.unWrap(request); + response.write(false, Callback.NOOP, "originalPath=%s%n".formatted(original.getHttpURI().getPath())); + response.write(false, Callback.NOOP, "originalQuery=%s%n".formatted(original.getHttpURI().getQuery())); + callback.succeeded(); } - }; - - server.setHandler(new HandlerList(rewriteHandler, dummyHandler)); - server.start(); + }); } - @AfterEach - public void stopServer() throws Exception - { - if (server != null) - { - server.stop(); - } - } - - @BeforeEach - public void init() throws Exception + @Test + public void testRule() throws Exception { CookiePatternRule rule = new CookiePatternRule(); rule.setPattern("*"); rule.setName("cookie"); rule.setValue("value"); - startServer(rule); + start(rule); - StringBuilder rawRequest = new StringBuilder(); - rawRequest.append("GET / HTTP/1.1\r\n"); - rawRequest.append("Host: local\r\n"); - rawRequest.append("Connection: close\r\n"); - rawRequest.append("\r\n"); + String rawRequest = """ + GET / HTTP/1.1 + Host: local + Connection: close + + """; - String rawResponse = localConnector.getResponse(rawRequest.toString()); + String rawResponse = _connector.getResponse(rawRequest); HttpTester.Response response = HttpTester.parseResponse(rawResponse); - // verify HttpField setCookieField = response.getField(HttpHeader.SET_COOKIE); assertThat("response should have Set-Cookie", setCookieField, notNullValue()); for (String value : setCookieField.getValues()) @@ -116,53 +84,54 @@ public class CookiePatternRuleTest } @Test - public void testSetAlready() throws Exception + public void testCookieAlreadySet() throws Exception { CookiePatternRule rule = new CookiePatternRule(); rule.setPattern("*"); rule.setName("set"); rule.setValue("already"); - startServer(rule); + start(rule); - StringBuilder rawRequest = new StringBuilder(); - rawRequest.append("GET / HTTP/1.1\r\n"); - rawRequest.append("Host: local\r\n"); - rawRequest.append("Connection: close\r\n"); - rawRequest.append("Cookie: set=already\r\n"); // already present on request - rawRequest.append("\r\n"); + // Cookie already present on the request. + String rawRequest = """ + GET / HTTP/1.1 + Host: local + Connection: close + Cookie: set=already + + """; - String rawResponse = localConnector.getResponse(rawRequest.toString()); + String rawResponse = _connector.getResponse(rawRequest); HttpTester.Response response = HttpTester.parseResponse(rawResponse); - // verify assertThat("response should not have Set-Cookie", response.getField(HttpHeader.SET_COOKIE), nullValue()); } @Test - public void testUrlQuery() throws Exception + public void testPathQuery() throws Exception { CookiePatternRule rule = new CookiePatternRule(); rule.setPattern("*"); rule.setName("fruit"); rule.setValue("banana"); - startServer(rule); + start(rule); - StringBuilder rawRequest = new StringBuilder(); - rawRequest.append("GET /other?fruit=apple HTTP/1.1\r\n"); - rawRequest.append("Host: local\r\n"); - rawRequest.append("Connection: close\r\n"); - rawRequest.append("\r\n"); + String rawRequest = """ + GET /other?fruit=apple HTTP/1.1 + Host: local + Connection: close + + """; - String rawResponse = localConnector.getResponse(rawRequest.toString()); + String rawResponse = _connector.getResponse(rawRequest); HttpTester.Response response = HttpTester.parseResponse(rawResponse); String responseContent = response.getContent(); - assertResponseContentLine(responseContent, "baseRequest.requestUri=", "/other"); - assertResponseContentLine(responseContent, "request.queryString=", "fruit=apple"); + assertResponseContentLine(responseContent, "path=", "/other"); + assertResponseContentLine(responseContent, "query=", "fruit=apple"); - // verify HttpField setCookieField = response.getField(HttpHeader.SET_COOKIE); assertThat("response should have Set-Cookie", setCookieField, notNullValue()); for (String value : setCookieField.getValues()) @@ -174,28 +143,28 @@ public class CookiePatternRuleTest } @Test - public void testUrlParameter() throws Exception + public void testPathParameter() throws Exception { CookiePatternRule rule = new CookiePatternRule(); rule.setPattern("*"); rule.setName("fruit"); rule.setValue("banana"); - startServer(rule); + start(rule); - StringBuilder rawRequest = new StringBuilder(); - rawRequest.append("GET /other;fruit=apple HTTP/1.1\r\n"); - rawRequest.append("Host: local\r\n"); - rawRequest.append("Connection: close\r\n"); - rawRequest.append("\r\n"); + String rawRequest = """ + GET /other;fruit=apple HTTP/1.1 + Host: local + Connection: close + + """; - String rawResponse = localConnector.getResponse(rawRequest.toString()); + String rawResponse = _connector.getResponse(rawRequest); HttpTester.Response response = HttpTester.parseResponse(rawResponse); String responseContent = response.getContent(); - assertResponseContentLine(responseContent, "baseRequest.requestUri=", "/other;fruit=apple"); + assertResponseContentLine(responseContent, "path=", "/other;fruit=apple"); - // verify HttpField setCookieField = response.getField(HttpHeader.SET_COOKIE); assertThat("response should have Set-Cookie", setCookieField, notNullValue()); for (String value : setCookieField.getValues()) @@ -206,33 +175,17 @@ public class CookiePatternRuleTest } } - private void assertResponseContentLine(String responseContent, String linePrefix, String expectedEquals) throws IOException + private void assertResponseContentLine(String responseContent, String linePrefix, String expectedEquals) { - String line; - try (StringReader stringReader = new StringReader(responseContent); - BufferedReader bufferedReader = new BufferedReader(stringReader)) - { - boolean foundIt = false; - while ((line = bufferedReader.readLine()) != null) - { - if (line.startsWith(linePrefix)) - { - if (foundIt) - { - // duplicate lines - fail("Found multiple lines prefixed with: " + linePrefix); - } - // found it - String actualValue = line.substring(linePrefix.length()); - assertThat("Line:" + linePrefix, actualValue, is(expectedEquals)); - foundIt = true; - } - } + List matches = Arrays.stream(responseContent.split("\n")) + .filter(line -> line.startsWith(linePrefix)) + .map(line -> line.substring(linePrefix.length())) + .filter(line -> line.equals(expectedEquals)) + .toList(); - if (!foundIt) - { - fail("Unable to find line prefixed with: " + linePrefix); - } - } + if (matches.size() == 0) + fail("Unable to find line prefixed with: " + linePrefix); + if (matches.size() > 1) + fail("Found multiple lines prefixed with: " + linePrefix); } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRuleTest.java index 5aff2292246..9aee152d520 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForceRequestHeaderValueRuleTest.java @@ -13,24 +13,13 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.util.Collections; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.server.handler.HandlerList; -import org.eclipse.jetty.util.component.LifeCycle; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -38,91 +27,65 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertEquals; -public class ForceRequestHeaderValueRuleTest +public class ForceRequestHeaderValueRuleTest extends AbstractRuleTest { - private Server server; - private LocalConnector connector; - private ForceRequestHeaderValueRule rule; - - @BeforeEach - public void setup() throws Exception + public void start(ForceRequestHeaderValueRule rule) throws Exception { - server = new Server(); - connector = new LocalConnector(server); - server.addConnector(connector); - - HandlerList handlers = new HandlerList(); - - RewriteHandler rewriteHandler = new RewriteHandler(); - rule = new ForceRequestHeaderValueRule(); - rewriteHandler.addRule(rule); - - Handler handler = new AbstractHandler() + _rewriteHandler.addRule(rule); + start(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) { - response.setContentType("text/plain"); - response.setCharacterEncoding("utf-8"); - OutputStream stream = response.getOutputStream(); - OutputStreamWriter out = new OutputStreamWriter(stream); - out.append("Echo\n"); - for (String headerName : Collections.list(request.getHeaderNames())) + response.setContentType("text/plain;charset=utf-8"); + for (HttpField httpField : request.getHeaders()) { - // Combine all values for header into single output on response body - out.append("Request Header[").append(headerName).append("]: [") - .append(request.getHeader(headerName)).append("]\n"); + response.write(false, Callback.NOOP, "Request Header[%s]: [%s]%n".formatted(httpField.getName(), httpField.getValue())); } - out.flush(); - baseRequest.setHandled(true); + response.write(true, callback, BufferUtil.EMPTY_BUFFER); } - }; - - handlers.addHandler(rewriteHandler); - handlers.addHandler(handler); - server.setHandler(handlers); - server.start(); - } - - @AfterEach - public void teardown() - { - LifeCycle.stop(server); + }); } @Test public void testNormalRequest() throws Exception { + ForceRequestHeaderValueRule rule = new ForceRequestHeaderValueRule(); rule.setHeaderName("Accept"); - rule.setForcedValue("*/*"); + rule.setHeaderValue("*/*"); + start(rule); - StringBuilder request = new StringBuilder(); - request.append("GET /echo/foo HTTP/1.1\r\n"); - request.append("Host: local\r\n"); - request.append("Connection: closed\r\n"); - request.append("\r\n"); + String request = """ + GET /echo/foo HTTP/1.1 + Host: local + Connection: close + + """; - HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(request.toString())); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); assertEquals(200, response.getStatus()); assertThat(response.getContent(), not(containsString("[Accept]"))); assertThat(response.getContent(), containsString("[Host]: [local]")); - assertThat(response.getContent(), containsString("[Connection]: [closed]")); + assertThat(response.getContent(), containsString("[Connection]: [close]")); } @Test public void testOneAcceptHeaderRequest() throws Exception { + ForceRequestHeaderValueRule rule = new ForceRequestHeaderValueRule(); rule.setHeaderName("Accept"); - rule.setForcedValue("*/*"); + rule.setHeaderValue("*/*"); + start(rule); - StringBuilder request = new StringBuilder(); - request.append("GET /echo/foo HTTP/1.1\r\n"); - request.append("Host: local\r\n"); - request.append("Accept: */*\r\n"); - request.append("Connection: closed\r\n"); - request.append("\r\n"); + String request = """ + GET /echo/foo HTTP/1.1 + Host: local + Accept: */* + Connection: closed + + """; - String rawResponse = connector.getResponse(request.toString()); + String rawResponse = _connector.getResponse(request); HttpTester.Response response = HttpTester.parseResponse(rawResponse); assertEquals(200, response.getStatus()); assertThat(response.getContent(), containsString("[Accept]: [*/*]")); @@ -133,19 +96,22 @@ public class ForceRequestHeaderValueRuleTest @Test public void testThreeAcceptHeadersRequest() throws Exception { + ForceRequestHeaderValueRule rule = new ForceRequestHeaderValueRule(); rule.setHeaderName("Accept"); - rule.setForcedValue("text/*"); + rule.setHeaderValue("text/*"); + start(rule); - StringBuilder request = new StringBuilder(); - request.append("GET /echo/foo HTTP/1.1\r\n"); - request.append("Host: local\r\n"); - request.append("Accept: images/jpeg\r\n"); - request.append("Accept: text/plain\r\n"); - request.append("Accept: */*\r\n"); - request.append("Connection: closed\r\n"); - request.append("\r\n"); + String request = """ + GET /echo/foo HTTP/1.1 + Host: local + Accept: images/jpeg + Accept: text/plain + Accept: */* + Connection: closed + + """; - String rawResponse = connector.getResponse(request.toString()); + String rawResponse = _connector.getResponse(request); HttpTester.Response response = HttpTester.parseResponse(rawResponse); assertEquals(200, response.getStatus()); assertThat(response.getContent(), containsString("[Accept]: [text/*]")); @@ -156,21 +122,24 @@ public class ForceRequestHeaderValueRuleTest @Test public void testInterleavedAcceptHeadersRequest() throws Exception { + ForceRequestHeaderValueRule rule = new ForceRequestHeaderValueRule(); rule.setHeaderName("Accept"); - rule.setForcedValue("application/*"); + rule.setHeaderValue("application/*"); + start(rule); - StringBuilder request = new StringBuilder(); - request.append("GET /echo/foo HTTP/1.1\r\n"); - request.append("Host: local\r\n"); - request.append("Accept: images/jpeg\r\n"); // not value intended to be forced - request.append("Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0\r\n"); - request.append("accept: text/plain\r\n"); // interleaved with other headers shouldn't matter - request.append("Accept-Charset: iso-8859-5, unicode-1-1;q=0.8\r\n"); - request.append("ACCEPT: */*\r\n"); // case shouldn't matter - request.append("Connection: closed\r\n"); - request.append("\r\n"); + String request = """ + GET /echo/foo HTTP/1.1 + Host: local + Accept: images/jpeg + Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0 + accept: text/plain + Accept-Charset: iso-8859-5, unicode-1-1;q=0.8 + ACCEPT: */* + Connection: closed + + """; - String rawResponse = connector.getResponse(request.toString()); + String rawResponse = _connector.getResponse(request); HttpTester.Response response = HttpTester.parseResponse(rawResponse); assertEquals(200, response.getStatus()); assertThat(response.getContent(), containsString("[Accept]: [application/*]")); diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java index 12c3ee0ea22..c4fb6bc2f3d 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ForwardedSchemeHeaderRuleTest.java @@ -13,76 +13,117 @@ package org.eclipse.jetty.rewrite.handler; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpURI; -import org.junit.jupiter.api.BeforeEach; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -public class ForwardedSchemeHeaderRuleTest extends AbstractRuleTestCase +public class ForwardedSchemeHeaderRuleTest extends AbstractRuleTest { - private ForwardedSchemeHeaderRule _rule; - - @BeforeEach - public void init() throws Exception + public void start(ForwardedSchemeHeaderRule rule) throws Exception { - start(false); - _rule = new ForwardedSchemeHeaderRule(); - _request.setHttpURI(HttpURI.build(_request.getRequestURI()).scheme((String)null)); + _rewriteHandler.addRule(rule); + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + response.setHeader("request-scheme", request.getHttpURI().getScheme()); + callback.succeeded(); + } + }); } @Test public void testDefaultScheme() throws Exception { - setRequestHeader("X-Forwarded-Scheme", "https"); - _rule.setHeader("X-Forwarded-Scheme"); - _rule.setHeaderValue("https"); - _rule.matchAndApply("/", _request, _response); - assertEquals("https", _request.getScheme()); + ForwardedSchemeHeaderRule rule = new ForwardedSchemeHeaderRule(); + rule.setHeaderName("X-Forwarded-Scheme"); + rule.setHeaderValue("https"); + start(rule); + + String request = """ + GET / HTTP/1.1 + Host: local + X-Forwarded-Scheme: https + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(200, response.getStatus()); + assertEquals("https", response.get("request-scheme")); } @Test public void testScheme() throws Exception { - setRequestHeader("X-Forwarded-Scheme", "https"); - _rule.setHeader("X-Forwarded-Scheme"); - _rule.setHeaderValue("https"); - _rule.setScheme("https"); - _rule.matchAndApply("/", _request, _response); - assertEquals("https", _request.getScheme()); + ForwardedSchemeHeaderRule rule = new ForwardedSchemeHeaderRule(); + rule.setHeaderName("X-Forwarded-Scheme"); + rule.setHeaderValue("https"); + rule.setScheme("wss"); + start(rule); - _rule.setScheme("http"); - _rule.matchAndApply("/", _request, _response); - assertEquals("http", _request.getScheme()); + String request = """ + GET / HTTP/1.1 + Host: local + X-Forwarded-Scheme: https + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(200, response.getStatus()); + assertEquals("wss", response.get("request-scheme")); } @Test public void testHeaderValue() throws Exception { - setRequestHeader("Front-End-Https", "on"); - _rule.setHeader("Front-End-Https"); - _rule.setHeaderValue("on"); - _rule.setScheme("https"); - _rule.matchAndApply("/", _request, _response); - assertEquals("https", _request.getScheme()); + ForwardedSchemeHeaderRule rule = new ForwardedSchemeHeaderRule(); + rule.setHeaderName("Front-End-Https"); + rule.setHeaderValue("on"); + rule.setScheme("http"); + start(rule); - _request.setHttpURI(HttpURI.build(_request.getRequestURI()).scheme("other")); - // header value doesn't match rule's value - setRequestHeader("Front-End-Https", "off"); - _rule.matchAndApply("/", _request, _response); - assertEquals("other", _request.getScheme()); + String request = """ + GET / HTTP/1.1 + Host: local + Front-End-Https: on + + """; - _request.setHttpURI(HttpURI.build(_request.getRequestURI()).scheme((String)null)); - // header value can be any value - setRequestHeader("Front-End-Https", "any"); - _rule.setHeaderValue(null); - _rule.matchAndApply("/", _request, _response); - assertEquals("https", _request.getScheme()); - } + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(200, response.getStatus()); + assertEquals("http", response.get("request-scheme")); - private void setRequestHeader(String header, String headerValue) - { - _request.setHttpFields(HttpFields.build(_request.getHttpFields()).put(header, headerValue)); + // Value does not match, scheme is retained. + rule.setScheme("other"); + request = """ + GET other://local/ HTTP/1.1 + Host: local + Front-End-Https: off + + """; + + response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(200, response.getStatus()); + assertEquals("other", response.get("request-scheme")); + + rule.setScheme("ws"); + // Null value should match. + rule.setHeaderValue(null); + request = """ + GET / HTTP/1.1 + Host: local + Front-End-Https: any + + """; + + response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(200, response.getStatus()); + assertEquals("ws", response.get("request-scheme")); } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java index a107ec70a90..7539d183337 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderPatternRuleTest.java @@ -13,98 +13,73 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; -import java.util.Iterator; +import java.util.List; -import org.junit.jupiter.api.BeforeEach; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -public class HeaderPatternRuleTest extends AbstractRuleTestCase +public class HeaderPatternRuleTest extends AbstractRuleTest { - private HeaderPatternRule _rule; - - @BeforeEach - public void init() throws Exception + private void start(HeaderPatternRule rule) throws Exception { - start(false); - _rule = new HeaderPatternRule(); - _rule.setPattern("*"); - } - - @Test - public void testHeaderWithTextValues() throws IOException - { - // different keys - String[][] headers = { - {"hnum#1", "test1"}, - {"hnum#2", "2test2"}, - {"hnum#3", "test3"} - }; - assertHeaders(headers); - } - - @Test - public void testHeaderWithNumberValues() throws IOException - { - String[][] headers = { - {"hello", "1"}, - {"hello", "-1"}, - {"hello", "100"}, - {"hello", "100"}, - {"hello", "100"}, - {"hello", "100"}, - {"hello", "100"}, - - {"hello1", "200"} - }; - assertHeaders(headers); - } - - @Test - public void testHeaderOverwriteValues() throws IOException - { - String[][] headers = { - {"size", "100"}, - {"size", "200"}, - {"size", "300"}, - {"size", "400"}, - {"size", "500"}, - {"title", "abc"}, - {"title", "bac"}, - {"title", "cba"}, - {"title1", "abba"}, - {"title1", "abba1"}, - {"title1", "abba"}, - {"title1", "abba1"} - }; - assertHeaders(headers); - - Iterator e = _response.getHeaders("size").iterator(); - int count = 0; - while (e.hasNext()) + _rewriteHandler.addRule(rule); + start(new Handler.Processor() { - e.next(); - count++; - } - - assertEquals(1, count); - assertEquals("500", _response.getHeader("size")); - assertEquals("cba", _response.getHeader("title")); - assertEquals("abba1", _response.getHeader("title1")); + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); } - private void assertHeaders(String[][] headers) throws IOException + @Test + public void testHeaderWithTextValue() throws Exception { - for (String[] header : headers) + String name = "X-Response"; + String value = "TEXT"; + HeaderPatternRule rule = new HeaderPatternRule("/", name, value); + start(rule); + + String request = """ + GET / HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(200, response.getStatus()); + assertEquals(value, response.get(name)); + } + + @Test + public void testHeaderWithNumberValues() throws Exception + { + String name = "X-Response"; + List values = List.of("1", "-1", "100"); + + for (String value : values) { - _rule.setName(header[0]); - _rule.setValue(header[1]); + HeaderPatternRule rule = new HeaderPatternRule("/", name, value); + start(rule); - _rule.apply(null, _request, _response); + String request = """ + GET / HTTP/1.1 + Host: localhost + + """; - assertEquals(header[1], _response.getHeader(header[0])); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(200, response.getStatus()); + assertEquals(value, response.get(name)); + + stop(); } } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRuleTest.java index cf0178a0c32..6c7a1f8d807 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/HeaderRegexRuleTest.java @@ -13,118 +13,112 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; -import java.util.Iterator; +import java.util.List; -import org.junit.jupiter.api.BeforeEach; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; -public class HeaderRegexRuleTest extends AbstractRuleTestCase +public class HeaderRegexRuleTest extends AbstractRuleTest { - - private HeaderRegexRule _rule; - - @BeforeEach - public void init() throws Exception + private void start(HeaderRegexRule rule) throws Exception { - start(false); - _rule = new HeaderRegexRule(); - _rule.setRegex("\\*"); - } - - @Test - public void testHeaderWithTextValues() throws IOException - { - // different keys - String[][] headers = - { - {"hnum#1", "test1"}, - {"hnum#2", "2test2"}, - {"hnum#3", "test3"} - }; - assertHeaders(headers); - } - - @Test - public void testHeaderWithNumberValues() throws IOException - { - String[][] headers = - { - {"hello", "1"}, - {"hello", "-1"}, - {"hello", "100"}, - {"hello", "100"}, - {"hello", "100"}, - {"hello", "100"}, - {"hello", "100"}, - {"hello1", "200"} - }; - assertHeaders(headers); - } - - @Test - public void testHeaderOverwriteValues() throws IOException - { - String[][] headers = - { - {"size", "100"}, - {"size", "200"}, - {"size", "300"}, - {"size", "400"}, - {"size", "500"}, - {"title", "abc"}, - {"title", "bac"}, - {"title", "cba"}, - {"title1", "abba"}, - {"title1", "abba1"}, - {"title1", "abba"}, - {"title1", "abba1"} - }; - assertHeaders(headers); - Iterator e = _response.getHeaders("size").iterator(); - int count = 0; - while (e.hasNext()) + _rewriteHandler.addRule(rule); + start(new Handler.Processor() { - e.next(); - count++; - } - assertEquals(1, count); - assertEquals("500", _response.getHeader("size")); - assertEquals("cba", _response.getHeader("title")); - assertEquals("abba1", _response.getHeader("title1")); + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); } @Test - public void testMatch() throws Exception + public void testHeaderWithTextValue() throws Exception { - _rule.setRegex("/my/dir/file/(.*)$"); - _rule.setName("cache-control"); - _rule.setValue("no-store"); - _rule.matchAndApply("/my/dir/file/", _request, _response); - assertEquals("no-store", _response.getHeader("cache-control")); + String name = "X-Response"; + String value = "TEXT"; + HeaderRegexRule rule = new HeaderRegexRule("/", name, value); + start(rule); + + String request = """ + GET / HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(200, response.getStatus()); + assertEquals(value, response.get(name)); } @Test - public void testNotMatch() throws Exception + public void testHeaderWithNumberValues() throws Exception { - reset(); - _rule.setRegex("/my/dir/file/(.*)$"); - _rule.setName("cache-control"); - _rule.setValue("no-store"); - _rule.matchAndApply("/my/dir/file_not_match/", _request, _response); - assertEquals(null, _response.getHeader("cache-control")); - } + String name = "X-Response"; + List values = List.of("1", "-1", "100"); - private void assertHeaders(String[][] headers) throws IOException - { - for (String[] header : headers) + for (String value : values) { - _rule.setName(header[0]); - _rule.setValue(header[1]); - _rule.apply(null, _request, _response, null); - assertEquals(header[1], _response.getHeader(header[0])); + HeaderRegexRule rule = new HeaderRegexRule("/", name, value); + start(rule); + + String request = """ + GET / HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(200, response.getStatus()); + assertEquals(value, response.get(name)); + + stop(); } } + + @Test + public void testMatchWithGroup() throws Exception + { + String name = "X-Response"; + String value = "TEXT"; + HeaderRegexRule rule = new HeaderRegexRule("/my/dir/([^/]+)", name, "$1"); + start(rule); + + String request = """ + GET /my/dir/$V HTTP/1.1 + Host: localhost + + """.replace("$V", value); + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(200, response.getStatus()); + assertEquals(value, response.get(name)); + } + + @Test + public void testNoMatch() throws Exception + { + String name = "X-Response"; + String value = "TEXT"; + HeaderRegexRule rule = new HeaderRegexRule("/my/dir/([^/]+)", name, "$1"); + start(rule); + + String request = """ + GET /my/no/match HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(200, response.getStatus()); + assertNull(response.get(name)); + } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/InvalidURIRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/InvalidURIRuleTest.java new file mode 100644 index 00000000000..0445c963d73 --- /dev/null +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/InvalidURIRuleTest.java @@ -0,0 +1,254 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.rewrite.handler; + +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class InvalidURIRuleTest extends AbstractRuleTest +{ + private void start(InvalidURIRule rule) throws Exception + { + _rewriteHandler.addRule(rule); + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); + } + + @Test + public void testValidUrl() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + start(rule); + + String request = """ + GET /valid/uri.html HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + } + + @Test + public void testInvalidUrl() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + start(rule); + + String request = """ + GET /invalid%0c/uri.html HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.NOT_ACCEPTABLE_406, response.getStatus()); + } + + @Test + public void testInvalidUrlWithMessage() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + rule.setMessage("foo"); + start(rule); + + String request = """ + GET /%01/ HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.NOT_ACCEPTABLE_406, response.getStatus()); + assertThat(response.getContent(), containsString(rule.getMessage())); + } + + @Test + public void testInvalidJspWithNullByteEncoded() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + start(rule); + + String request = """ + GET /jsp/bean1.jsp%00 HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + // The rule is not invoked because byte NULL is rejected at parsing level. + assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus()); + } + + @Test + public void testInvalidJspWithControlByteEncoded() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + start(rule); + + String request = """ + GET /jsp/bean1.jsp%01 HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.NOT_ACCEPTABLE_406, response.getStatus()); + } + + @Test + public void testInvalidJspWithNullByte() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + start(rule); + + String request = """ + GET /jsp/bean1.jsp\000 HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + // The rule is not invoked because byte NULL is rejected at parsing level. + assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus()); + } + + @Test + public void testInvalidJspWithControlByte() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + start(rule); + + String request = """ + GET /jsp/bean1.jsp\001 HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + // The rule is not invoked because byte CNTL bytes are rejected at parsing level. + assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus()); + } + + @Test + public void testInvalidShamrockWithNullByte() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + start(rule); + + String request = """ + GET /jsp/shamrock-%00%E2%98%98.jsp HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + // The rule is not invoked because byte NULL is rejected at parsing level. + assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus()); + } + + @Test + public void testInvalidShamrockWithControlByteEncoded() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + start(rule); + + String request = """ + GET /jsp/shamrock-%0F%E2%98%98.jsp HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.NOT_ACCEPTABLE_406, response.getStatus()); + } + + @Test + public void testValidShamrock() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + start(rule); + + String request = """ + GET /jsp/shamrock-%E2%98%98.jsp HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + } + + @Test + public void testInvalidUTF8() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + start(rule); + + String request = """ + GET /jsp/shamrock-%A0%A1.jsp HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + // The rule is not invoked because the UTF-8 sequence is invalid. + assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus()); + } + + @Test + public void testIncompleteUTF8() throws Exception + { + InvalidURIRule rule = new InvalidURIRule(); + rule.setCode(HttpStatus.NOT_ACCEPTABLE_406); + start(rule); + + String request = """ + GET /foo%CE%BA%E1 HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + // The rule is not invoked because the UTF-8 sequence is incomplete. + assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus()); + } +} diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java index 7b225df60f0..f44b680da8f 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/PatternRuleTest.java @@ -13,144 +13,154 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; +import java.util.stream.Stream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpURI; -import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; -public class PatternRuleTest +public class PatternRuleTest extends AbstractRuleTest { - private PatternRule _rule; - - @BeforeEach - public void init() + public static Stream matches() { - _rule = new TestPatternRule(); - } + return Stream.of( + Arguments.of("/abc", "/abc"), + Arguments.of("/abc/", "/abc/"), - @AfterEach - public void destroy() - { - _rule = null; - } + Arguments.of("/abc/path/longer", "/abc/path/longer"), + Arguments.of("/abc/path/longer/", "/abc/path/longer/"), - @Test - public void testTrueMatch() throws IOException - { - String[][] matchCases = { - // index 0 - pattern - // index 1 - URI to match + Arguments.of("/abc/*", "/abc/hello.jsp"), + Arguments.of("/abc/*", "/abc/a"), + Arguments.of("/abc/*", "/abc/a/hello.jsp"), + Arguments.of("/abc/*", "/abc/a/b"), + Arguments.of("/abc/*", "/abc/a/b/hello.jsp"), + Arguments.of("/abc/*", "/abc/a/b/c"), + Arguments.of("/abc/*", "/abc/a/b/c/hello.jsp"), - {"/abc", "/abc"}, - {"/abc/", "/abc/"}, + Arguments.of("/abc/def/*", "/abc/def/gf"), + Arguments.of("/abc/def/*", "/abc/def/gf.html"), + Arguments.of("/abc/def/*", "/abc/def/ghi"), + Arguments.of("/abc/def/*", "/abc/def/ghi/"), + Arguments.of("/abc/def/*", "/abc/def/ghi/hello.html"), - {"/abc/path/longer", "/abc/path/longer"}, - {"/abc/path/longer/", "/abc/path/longer/"}, + Arguments.of("*.do", "/abc.do"), + Arguments.of("*.do", "/abc/hello.do"), + Arguments.of("*.do", "/abc/def/hello.do"), + Arguments.of("*.do", "/abc/def/ghi/hello.do"), - {"/abc/*", "/abc/hello.jsp"}, - {"/abc/*", "/abc/a"}, - {"/abc/*", "/abc/a/hello.jsp"}, - {"/abc/*", "/abc/a/b"}, - {"/abc/*", "/abc/a/b/hello.jsp"}, - {"/abc/*", "/abc/a/b/c"}, - {"/abc/*", "/abc/a/b/c/hello.jsp"}, + Arguments.of("*.jsp", "/abc.jsp"), + Arguments.of("*.jsp", "/abc/hello.jsp"), + Arguments.of("*.jsp", "/abc/def/hello.jsp"), + Arguments.of("*.jsp", "/abc/def/ghi/hello.jsp"), - {"/abc/def/*", "/abc/def/gf"}, - {"/abc/def/*", "/abc/def/gf.html"}, - {"/abc/def/*", "/abc/def/ghi"}, - {"/abc/def/*", "/abc/def/ghi/"}, - {"/abc/def/*", "/abc/def/ghi/hello.html"}, + Arguments.of("/", "/Other"), + Arguments.of("/", "/Other/hello.do"), + Arguments.of("/", "/Other/path"), + Arguments.of("/", "/Other/path/hello.do"), + Arguments.of("/", "/abc/def"), - {"*.do", "/abc.do"}, - {"*.do", "/abc/hello.do"}, - {"*.do", "/abc/def/hello.do"}, - {"*.do", "/abc/def/ghi/hello.do"}, - - {"*.jsp", "/abc.jsp"}, - {"*.jsp", "/abc/hello.jsp"}, - {"*.jsp", "/abc/def/hello.jsp"}, - {"*.jsp", "/abc/def/ghi/hello.jsp"}, - - {"/", "/Other"}, - {"/", "/Other/hello.do"}, - {"/", "/Other/path"}, - {"/", "/Other/path/hello.do"}, - {"/", "/abc/def"}, - - {"/abc:/def", "/abc:/def"} - }; - - for (String[] matchCase : matchCases) - { - assertMatch(true, matchCase); - } - } - - @Test - public void testFalseMatch() throws IOException - { - String[][] matchCases = { - - {"/abc", "/abcd"}, - {"/abc/", "/abcd/"}, - - {"/abc/path/longer", "/abc/path/longer/"}, - {"/abc/path/longer", "/abc/path/longer1"}, - {"/abc/path/longer/", "/abc/path/longer"}, - {"/abc/path/longer/", "/abc/path/longer1/"}, - - {"/*.jsp", "/hello.jsp"}, - {"/abc/*.jsp", "/abc/hello.jsp"}, - - {"*.jsp", "/hello.1jsp"}, - {"*.jsp", "/hello.jsp1"}, - {"*.jsp", "/hello.do"}, - - {"*.jsp", "/abc/hello.do"}, - {"*.jsp", "/abc/def/hello.do"}, - {"*.jsp", "/abc.do"} - }; - - for (String[] matchCase : matchCases) - { - assertMatch(false, matchCase); - } - } - - private void assertMatch(boolean flag, String[] matchCase) throws IOException - { - _rule.setPattern(matchCase[0]); - final String uri = matchCase[1]; - - String result = _rule.matchAndApply(uri, - new Request(null, null) - { - { - setMetaData(new MetaData.Request("GET", HttpURI.from(uri), HttpVersion.HTTP_1_0, HttpFields.EMPTY)); - } - }, null + Arguments.of("/abc:/def", "/abc:/def") ); - - assertEquals(flag, result != null, "pattern: " + matchCase[0] + " uri: " + matchCase[1]); } - private class TestPatternRule extends PatternRule + public static Stream noMatches() { - @Override - public String apply(String target, - HttpServletRequest request, HttpServletResponse response) throws IOException + return Stream.of( + Arguments.of("/abc", "/abcd"), + Arguments.of("/abc/", "/abcd/"), + + Arguments.of("/abc/path/longer", "/abc/path/longer/"), + Arguments.of("/abc/path/longer", "/abc/path/longer1"), + Arguments.of("/abc/path/longer/", "/abc/path/longer"), + Arguments.of("/abc/path/longer/", "/abc/path/longer1/"), + + Arguments.of("/*.jsp", "/hello.jsp"), + Arguments.of("/abc/*.jsp", "/abc/hello.jsp"), + + Arguments.of("*.jsp", "/hello.1jsp"), + Arguments.of("*.jsp", "/hello.jsp1"), + Arguments.of("*.jsp", "/hello.do"), + + Arguments.of("*.jsp", "/abc/hello.do"), + Arguments.of("*.jsp", "/abc/def/hello.do"), + Arguments.of("*.jsp", "/abc.do") + ); + } + + private void start(PatternRule rule) throws Exception + { + _rewriteHandler.addRule(rule); + start(new Handler.Processor() { - return target; + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); + } + + @ParameterizedTest + @MethodSource("matches") + public void testTrueMatch(String pattern, String uri) throws Exception + { + TestPatternRule rule = new TestPatternRule(pattern); + start(rule); + + String request = """ + GET $U HTTP/1.1 + Host: localhost + + """.replace("$U", uri); + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertTrue(rule._applied); + } + + @ParameterizedTest + @MethodSource("noMatches") + public void testFalseMatch(String pattern, String uri) throws Exception + { + TestPatternRule rule = new TestPatternRule(pattern); + start(rule); + + String request = """ + GET $U HTTP/1.1 + Host: localhost + + """.replace("$U", uri); + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertFalse(rule._applied); + } + + private static class TestPatternRule extends PatternRule + { + private boolean _applied; + + private TestPatternRule(String pattern) + { + super(pattern); + } + + @Override + public Request.WrapperProcessor apply(Request.WrapperProcessor input) + { + _applied = true; + return input; } } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java index 163ba2aa332..2019cbd3a51 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectPatternRuleTest.java @@ -13,54 +13,66 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; - import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; -import org.junit.jupiter.api.BeforeEach; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; -public class RedirectPatternRuleTest extends AbstractRuleTestCase +public class RedirectPatternRuleTest extends AbstractRuleTest { - @BeforeEach - public void init() throws Exception + private void start(RedirectPatternRule rule) throws Exception { - start(false); - } - - private void assertRedirectResponse(int expectedStatusCode, String expectedLocation) throws IOException - { - assertThat("Response status code", _response.getStatus(), is(expectedStatusCode)); - assertThat("Response location", _response.getHeader(HttpHeader.LOCATION.asString()), is(expectedLocation)); + _rewriteHandler.addRule(rule); + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); } @Test - public void testGlobPattern() throws IOException + public void testGlobPattern() throws Exception { String location = "http://eclipse.com"; + RedirectPatternRule rule = new RedirectPatternRule("*", location); + start(rule); - RedirectPatternRule rule = new RedirectPatternRule(); - rule.setPattern("*"); - rule.setLocation(location); + String request = """ + GET / HTTP/1.1 + Host: localhost + + """; - rule.apply("/", _request, _response); - assertRedirectResponse(HttpStatus.FOUND_302, location); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.FOUND_302, response.getStatus()); + assertEquals(location, response.get(HttpHeader.LOCATION)); } @Test - public void testPrefixPattern() throws IOException + public void testPrefixPattern() throws Exception { String location = "http://api.company.com/"; - - RedirectPatternRule rule = new RedirectPatternRule(); - rule.setPattern("/api/*"); - rule.setLocation(location); + RedirectPatternRule rule = new RedirectPatternRule("/api/*", location); rule.setStatusCode(HttpStatus.MOVED_PERMANENTLY_301); + start(rule); - rule.apply("/api/rest?foo=1", _request, _response); - assertRedirectResponse(HttpStatus.MOVED_PERMANENTLY_301, location); + String request = """ + GET /api/rest?foo=1 HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.MOVED_PERMANENTLY_301, response.getStatus()); + assertEquals(location, response.get(HttpHeader.LOCATION)); } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRuleTest.java index 72866d201f0..10e3b438a89 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRuleTest.java @@ -13,80 +13,98 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; - import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; -import org.junit.jupiter.api.BeforeEach; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; -public class RedirectRegexRuleTest extends AbstractRuleTestCase +public class RedirectRegexRuleTest extends AbstractRuleTest { - @BeforeEach - public void init() throws Exception + private void start(RedirectRegexRule rule) throws Exception { - start(false); - } - - private void assertRedirectResponse(int expectedStatusCode, String expectedLocation) throws IOException - { - assertThat("Response status code", _response.getStatus(), is(expectedStatusCode)); - assertThat("Response location", _response.getHeader(HttpHeader.LOCATION.asString()), is(expectedLocation)); + _rewriteHandler.addRule(rule); + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); } @Test - public void testLocationWithReplacementGroupEmpty() throws IOException + public void testLocationWithReplacementGroupEmpty() throws Exception { RedirectRegexRule rule = new RedirectRegexRule("/my/dir/file/(.*)$", "http://www.mortbay.org/$1"); + start(rule); - // Resource is dir - rule.matchAndApply("/my/dir/file/", _request, _response); - assertRedirectResponse(HttpStatus.FOUND_302, "http://www.mortbay.org/"); + String request = """ + GET /my/dir/file/ HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.FOUND_302, response.getStatus()); + assertEquals("http://www.mortbay.org/", response.get(HttpHeader.LOCATION)); } @Test - public void testLocationWithPathReplacement() throws IOException + public void testLocationWithPathReplacement() throws Exception { RedirectRegexRule rule = new RedirectRegexRule("/documentation/(.*)$", "/docs/$1"); + start(rule); - // Resource is dir - rule.matchAndApply("/documentation/top.html", _request, _response); - assertRedirectResponse(HttpStatus.FOUND_302, "http://0.0.0.0/docs/top.html"); + String request = """ + GET /documentation/top.html HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.FOUND_302, response.getStatus()); + assertEquals("http://localhost/docs/top.html", response.get(HttpHeader.LOCATION)); } @Test - public void testLocationWithReplacmentGroupSimple() throws IOException + public void testLocationWithReplacementGroupSimple() throws Exception { RedirectRegexRule rule = new RedirectRegexRule("/my/dir/file/(.*)$", "http://www.mortbay.org/$1"); + start(rule); - // Resource is an image - rule.matchAndApply("/my/dir/file/image.png", _request, _response); - assertRedirectResponse(HttpStatus.FOUND_302, "http://www.mortbay.org/image.png"); + String request = """ + GET /my/dir/file/image.png HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.FOUND_302, response.getStatus()); + assertEquals("http://www.mortbay.org/image.png", response.get(HttpHeader.LOCATION)); } @Test - public void testLocationWithReplacementGroupDeepWithParams() throws IOException + public void testLocationWithReplacementGroupWithQuery() throws Exception { RedirectRegexRule rule = new RedirectRegexRule("/my/dir/file/(.*)$", "http://www.mortbay.org/$1"); - - // Resource is api with parameters - rule.matchAndApply("/my/dir/file/api/rest/foo?id=100&sort=date", _request, _response); - assertRedirectResponse(HttpStatus.FOUND_302, "http://www.mortbay.org/api/rest/foo?id=100&sort=date"); - } - - @Test - public void testMovedPermanently() throws IOException - { - RedirectRegexRule rule = new RedirectRegexRule(); - rule.setRegex("/api/(.*)$"); - rule.setLocation("http://api.company.com/$1"); rule.setStatusCode(HttpStatus.MOVED_PERMANENTLY_301); + start(rule); - // Resource is api with parameters - rule.matchAndApply("/api/rest/foo?id=100&sort=date", _request, _response); - assertRedirectResponse(HttpStatus.MOVED_PERMANENTLY_301, "http://api.company.com/rest/foo?id=100&sort=date"); + String request = """ + GET /my/dir/file/api/rest/foo?id=100&sort=date HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.MOVED_PERMANENTLY_301, response.getStatus()); + assertEquals("http://www.mortbay.org/api/rest/foo?id=100&sort=date", response.get(HttpHeader.LOCATION)); } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java index 0664bf1afd0..e0397d8560b 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RegexRuleTest.java @@ -13,103 +13,121 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; import java.util.regex.Matcher; +import java.util.stream.Stream; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; -public class RegexRuleTest +public class RegexRuleTest extends AbstractRuleTest { - private RegexRule _rule; - - @BeforeEach - public void init() + public static Stream matches() { - _rule = new TestRegexRule(); - } - - @AfterEach - public void destroy() - { - _rule = null; - } - - @Test - public void testTrueMatch() throws IOException - { - String[][] matchCases = { + return Stream.of( // regex: *.jsp - {"/.*.jsp", "/hello.jsp"}, - {"/.*.jsp", "/abc/hello.jsp"}, + Arguments.of("/.*.jsp", "/hello.jsp"), + Arguments.of("/.*.jsp", "/abc/hello.jsp"), // regex: /abc or /def - {"/abc|/def", "/abc"}, - {"/abc|/def", "/def"}, + Arguments.of("/abc|/def", "/abc"), + Arguments.of("/abc|/def", "/def"), // regex: *.do or *.jsp - {".*\\.do|.*\\.jsp", "/hello.do"}, - {".*\\.do|.*\\.jsp", "/hello.jsp"}, - {".*\\.do|.*\\.jsp", "/abc/hello.do"}, - {".*\\.do|.*\\.jsp", "/abc/hello.jsp"}, + Arguments.of(".*\\.do|.*\\.jsp", "/hello.do"), + Arguments.of(".*\\.do|.*\\.jsp", "/hello.jsp"), + Arguments.of(".*\\.do|.*\\.jsp", "/abc/hello.do"), + Arguments.of(".*\\.do|.*\\.jsp", "/abc/hello.jsp"), - {"/abc/.*.htm|/def/.*.htm", "/abc/hello.htm"}, - {"/abc/.*.htm|/def/.*.htm", "/abc/def/hello.htm"}, + Arguments.of("/abc/.*.htm|/def/.*.htm", "/abc/hello.htm"), + Arguments.of("/abc/.*.htm|/def/.*.htm", "/abc/def/hello.htm"), // regex: /abc/*.jsp - {"/abc/.*.jsp", "/abc/hello.jsp"}, - {"/abc/.*.jsp", "/abc/def/hello.jsp"} - }; - - for (String[] matchCase : matchCases) - { - assertMatch(true, matchCase); - } - } - - @Test - public void testFalseMatch() throws IOException - { - String[][] matchCases = { - {"/abc/.*.jsp", "/hello.jsp"} - }; - - for (String[] matchCase : matchCases) - { - assertMatch(false, matchCase); - } - } - - private void assertMatch(boolean flag, String[] matchCase) throws IOException - { - _rule.setRegex(matchCase[0]); - final String uri = matchCase[1]; - String result = _rule.matchAndApply(uri, - new Request(null, null) - { - @Override - public String getRequestURI() - { - return uri; - } - }, null + Arguments.of("/abc/.*.jsp", "/abc/hello.jsp"), + Arguments.of("/abc/.*.jsp", "/abc/def/hello.jsp") ); - - assertEquals(flag, result != null, "regex: " + matchCase[0] + " uri: " + matchCase[1]); } - private class TestRegexRule extends RegexRule + public static Stream noMatches() { - @Override - public String apply(String target, HttpServletRequest request, HttpServletResponse response, Matcher matcher) throws IOException + return Stream.of( + Arguments.of("/abc/.*.jsp", "/hello.jsp") + ); + } + + private void start(RegexRule rule) throws Exception + { + _rewriteHandler.addRule(rule); + start(new Handler.Processor() { - return target; + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); + } + + @ParameterizedTest + @MethodSource("matches") + public void testTrueMatch(String pattern, String uri) throws Exception + { + TestRegexRule rule = new TestRegexRule(pattern); + start(rule); + + String request = """ + GET $U HTTP/1.1 + Host: localhost + + """.replace("$U", uri); + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertTrue(rule._applied); + } + + @ParameterizedTest + @MethodSource("noMatches") + public void testFalseMatch(String pattern, String uri) throws Exception + { + TestRegexRule rule = new TestRegexRule(pattern); + start(rule); + + String request = """ + GET $U HTTP/1.1 + Host: localhost + + """.replace("$U", uri); + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertFalse(rule._applied); + } + + private static class TestRegexRule extends RegexRule + { + private boolean _applied; + + public TestRegexRule(String pattern) + { + super(pattern); + } + + @Override + public Request.WrapperProcessor apply(Request.WrapperProcessor input, Matcher matcher) + { + _applied = true; + return input; } } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java index 63e8b71efed..e16c8716f67 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/ResponsePatternRuleTest.java @@ -13,49 +13,64 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; - -import org.eclipse.jetty.server.Dispatcher; -import org.junit.jupiter.api.BeforeEach; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -public class ResponsePatternRuleTest extends AbstractRuleTestCase +public class ResponsePatternRuleTest extends AbstractRuleTest { - private ResponsePatternRule _rule; - - @BeforeEach - public void init() throws Exception + private void start(ResponsePatternRule rule) throws Exception { - start(false); - _rule = new ResponsePatternRule(); - _rule.setPattern("/test"); - } - - @Test - public void testStatusCodeNoMessage() throws IOException - { - for (int i = 1; i < 600; i++) + _rewriteHandler.addRule(rule); + start(new Handler.Processor() { - _rule.setCode("" + i); - _rule.setMessage(null); - _rule.apply(null, _request, _response); - - assertEquals(i, _response.getStatus()); - assertNull(_request.getAttribute(Dispatcher.ERROR_MESSAGE)); - } + @Override + public void process(Request request, Response response, Callback callback) + { + callback.succeeded(); + } + }); } @Test - public void testStatusCodeMessage() throws IOException + public void testStatusCodeNoMessage() throws Exception { - _rule.setCode("499"); - _rule.setMessage("Message 499"); - _rule.apply(null, _request, _response); + ResponsePatternRule rule = new ResponsePatternRule("/test", HttpStatus.NO_CONTENT_204, null); + start(rule); - assertEquals(499, _response.getStatus()); - assertEquals("Message 499", _request.getAttribute(Dispatcher.ERROR_MESSAGE)); + String request = """ + GET /test HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(rule.getCode(), response.getStatus()); + } + + @Test + public void testStatusCodeMessage() throws Exception + { + ResponsePatternRule rule = new ResponsePatternRule("/test", HttpStatus.BAD_REQUEST_400, "MESSAGE"); + + start(rule); + + String request = """ + GET /test HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(rule.getCode(), response.getStatus()); + assertThat(response.getContent(), containsString(rule.getMessage())); } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java index 88df5b75ae1..0e724a97b03 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteHandlerTest.java @@ -13,183 +13,113 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; +import java.util.List; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -public class RewriteHandlerTest extends AbstractRuleTestCase +public class RewriteHandlerTest extends AbstractRuleTest { - private RewriteHandler _handler; - private RewritePatternRule _rule1; - private RewritePatternRule _rule2; - private RewritePatternRule _rule3; - private RewriteRegexRule _rule4; - @BeforeEach public void init() throws Exception { - _handler = new RewriteHandler(); - _handler.setServer(_server); - _handler.setHandler(new AbstractHandler() + RewritePatternRule rule1 = new RewritePatternRule("/aaa/*", "/bbb"); + RewritePatternRule rule2 = new RewritePatternRule("/bbb/*", "/ccc"); + RewritePatternRule rule3 = new RewritePatternRule("/ccc/*", "/ddd"); + RewriteRegexRule rule4 = new RewriteRegexRule("/xxx/(.*)", "/$1/zzz"); + _rewriteHandler.setRules(List.of(rule1, rule2, rule3, rule4)); + _rewriteHandler.setOriginalPathAttribute("originalPath"); + start(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) { - response.setStatus(201); - request.setAttribute("target", target); - request.setAttribute("URI", request.getRequestURI()); - request.setAttribute("info", request.getPathInfo()); + response.setStatus(HttpStatus.OK_200); + response.setHeader("X-Path", request.getHttpURI().getPath()); + response.setHeader("X-Original-Path", (String)request.getAttribute(_rewriteHandler.getOriginalPathAttribute())); + callback.succeeded(); } }); - _handler.start(); - - _rule1 = new RewritePatternRule(); - _rule1.setPattern("/aaa/*"); - _rule1.setReplacement("/bbb"); - _rule2 = new RewritePatternRule(); - _rule2.setPattern("/bbb/*"); - _rule2.setReplacement("/ccc"); - _rule3 = new RewritePatternRule(); - _rule3.setPattern("/ccc/*"); - _rule3.setReplacement("/ddd"); - _rule4 = new RewriteRegexRule(); - _rule4.setRegex("/xxx/(.*)"); - _rule4.setReplacement("/$1/zzz"); - - _handler.setRules(new Rule[]{_rule1, _rule2, _rule3, _rule4}); - - start(false); } @Test - public void test() throws Exception + public void testXXXtoBar() throws Exception { - _response.setStatus(200); - _request.setHandled(false); - _handler.setOriginalPathAttribute("/before"); - _handler.setRewriteRequestURI(true); - _handler.setRewritePathInfo(true); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/xxx/bar")); - _request.setContext(_request.getContext(), "/xxx/bar"); - _handler.handle("/xxx/bar", _request, _request, _response); - assertEquals(201, _response.getStatus()); - assertEquals("/bar/zzz", _request.getAttribute("target")); - assertEquals("/bar/zzz", _request.getAttribute("URI")); - assertEquals("/bar/zzz", _request.getAttribute("info")); - assertEquals(null, _request.getAttribute("before")); + String request = """ + GET /xxx/bar HTTP/1.1 + Host: localhost + + """; - _response.setStatus(200); - _request.setHandled(false); - _handler.setOriginalPathAttribute("/before"); - _handler.setRewriteRequestURI(false); - _handler.setRewritePathInfo(false); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/foo/bar")); - _request.setContext(_request.getContext(), "/foo/bar"); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/bar/zzz", response.get("X-Path"), "X-Path response value"); + } - _handler.handle("/foo/bar", _request, _request, _response); - assertEquals(201, _response.getStatus()); - assertEquals("/foo/bar", _request.getAttribute("target")); - assertEquals("/foo/bar", _request.getAttribute("URI")); - assertEquals("/foo/bar", _request.getAttribute("info")); - assertEquals(null, _request.getAttribute("before")); + @Test + public void testFooNoChange() throws Exception + { + String request = """ + GET /foo/bar HTTP/1.1 + Host: localhost + + """; - _response.setStatus(200); - _request.setHandled(false); - _handler.setOriginalPathAttribute(null); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/aaa/bar")); - _request.setContext(_request.getContext(), "/aaa/bar"); - _handler.handle("/aaa/bar", _request, _request, _response); - assertEquals(201, _response.getStatus()); - assertEquals("/ddd/bar", _request.getAttribute("target")); - assertEquals("/aaa/bar", _request.getAttribute("URI")); - assertEquals("/aaa/bar", _request.getAttribute("info")); - assertEquals(null, _request.getAttribute("before")); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/foo/bar", response.get("X-Path"), "X-Path response value"); + } - _response.setStatus(200); - _request.setHandled(false); - _handler.setOriginalPathAttribute("before"); - _handler.setRewriteRequestURI(true); - _handler.setRewritePathInfo(true); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/aaa/bar")); - _request.setContext(_request.getContext(), "/aaa/bar"); - _handler.handle("/aaa/bar", _request, _request, _response); - assertEquals(201, _response.getStatus()); - assertEquals("/ddd/bar", _request.getAttribute("target")); - assertEquals("/ddd/bar", _request.getAttribute("URI")); - assertEquals("/ddd/bar", _request.getAttribute("info")); - assertEquals("/aaa/bar", _request.getAttribute("before")); + @Test + public void testAAAtoDDD() throws Exception + { + String request = """ + GET /aaa/bar HTTP/1.1 + Host: localhost + + """; - _response.setStatus(200); - _request.setHandled(false); - _rule2.setTerminating(true); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/aaa/bar")); - _request.setContext(_request.getContext(), "/aaa/bar"); - _handler.handle("/aaa/bar", _request, _request, _response); - assertEquals(201, _response.getStatus()); - assertEquals("/ccc/bar", _request.getAttribute("target")); - assertEquals("/ccc/bar", _request.getAttribute("URI")); - assertEquals("/ccc/bar", _request.getAttribute("info")); - assertEquals("/aaa/bar", _request.getAttribute("before")); - - _response.setStatus(200); - _request.setHandled(false); - _rule2.setHandling(true); - _request.setAttribute("before", null); - _request.setAttribute("target", null); - _request.setAttribute("URI", null); - _request.setAttribute("info", null); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/aaa/bar")); - _request.setContext(_request.getContext(), "/aaa/bar"); - _handler.handle("/aaa/bar", _request, _request, _response); - assertEquals(200, _response.getStatus()); - assertEquals(null, _request.getAttribute("target")); - assertEquals(null, _request.getAttribute("URI")); - assertEquals(null, _request.getAttribute("info")); - assertEquals("/aaa/bar", _request.getAttribute("before")); - assertTrue(_request.isHandled()); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/ddd/bar", response.get("X-Path"), "X-Path response value"); + assertEquals("/aaa/bar", response.get("X-Original-Path"), "X-Original-Path response value"); } @Test public void testEncodedPattern() throws Exception { - _response.setStatus(200); - _request.setHandled(false); - _handler.setOriginalPathAttribute("/before"); - _handler.setRewriteRequestURI(true); - _handler.setRewritePathInfo(false); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/ccc/x%20y")); - _request.setContext(_request.getContext(), "/ccc/x y"); - _handler.handle("/ccc/x y", _request, _request, _response); - assertEquals(201, _response.getStatus()); - assertEquals("/ddd/x y", _request.getAttribute("target")); - assertEquals("/ddd/x%20y", _request.getAttribute("URI")); - assertEquals("/ccc/x y", _request.getAttribute("info")); + String request = """ + GET /ccc/x%20y HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/ddd/x%20y", response.get("X-Path")); + assertEquals("/ccc/x%20y", response.get("X-Original-Path")); } @Test public void testEncodedRegex() throws Exception { - _response.setStatus(200); - _request.setHandled(false); - _handler.setOriginalPathAttribute("/before"); - _handler.setRewriteRequestURI(true); - _handler.setRewritePathInfo(false); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/xxx/x%20y")); - _request.setContext(_request.getContext(), "/xxx/x y"); - _handler.handle("/xxx/x y", _request, _request, _response); - assertEquals(201, _response.getStatus()); - assertEquals("/x y/zzz", _request.getAttribute("target")); - assertEquals("/x%20y/zzz", _request.getAttribute("URI")); - assertEquals("/xxx/x y", _request.getAttribute("info")); + String request = """ + GET /xxx/x%20y HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/x%20y/zzz", response.get("X-Path")); + assertEquals("/xxx/x%20y", response.get("X-Original-Path")); } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java index 23747faace7..2ffc25ffaa0 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewritePatternRuleTest.java @@ -13,110 +13,105 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; +import java.util.stream.Stream; -import org.eclipse.jetty.http.HttpURI; -import org.junit.jupiter.api.BeforeEach; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; -public class RewritePatternRuleTest extends AbstractRuleTestCase +public class RewritePatternRuleTest extends AbstractRuleTest { - // TODO: Parameterize - private final String[][] _tests = + public static Stream data() + { + return Stream.of( + Arguments.of("/", "/replace", "/foo/bar", "/replace"), + Arguments.of("/*", "/replace/foo/bar", "/foo/bar", "/replace/foo/bar/foo/bar"), + Arguments.of("/foo/*", "/replace/bar", "/foo/bar", "/replace/bar/bar"), + Arguments.of("/foo/bar", "/replace", "/foo/bar", "/replace"), + Arguments.of("*.txt", "/replace", "/foo/bar.txt", "/replace"), + Arguments.of("/foo/*", "/replace", "/foo/bar/%20x", "/replace/bar/%20x"), + Arguments.of("/old/context", "/replace?given=param", "/old/context", "/replace?given=param") + ); + } + + private void start(RewritePatternRule rule) throws Exception + { + _rewriteHandler.addRule(rule); + start(new Handler.Processor() { - {"/foo/bar", "/", "/replace"}, - {"/foo/bar", "/*", "/replace/foo/bar"}, - {"/foo/bar", "/foo/*", "/replace/bar"}, - {"/foo/bar", "/foo/bar", "/replace"}, - {"/foo/bar.txt", "*.txt", "/replace"}, - {"/foo/bar/%20x", "/foo/*", "/replace/bar/%20x"}, - }; - private RewritePatternRule _rule; + @Override + public void process(Request request, Response response, Callback callback) + { + response.setHeader("X-URI", request.getHttpURI().getPathQuery()); + callback.succeeded(); + } + }); + } - @BeforeEach - public void init() throws Exception + @ParameterizedTest + @MethodSource("data") + public void testRewritePatternRule(String pattern, String replacement, String inputURI, String expectURI) throws Exception { - start(false); - _rule = new RewritePatternRule(); - _rule.setReplacement("/replace"); + RewritePatternRule rule = new RewritePatternRule(pattern, replacement); + start(rule); + + String request = """ + GET $U HTTP/1.1 + Host: localhost + Connection: close + + """.replace("$U", inputURI); + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals(expectURI, response.get("X-URI"), "X-URI response header value"); } @Test - public void testMatchAndApplyAndApplyURI() throws IOException - { - for (String[] test : _tests) - { - _rule.setPattern(test[1]); - String result = _rule.matchAndApply(test[0], _request, _response); - assertThat(test[1], test[2], is(result)); - - _rule.applyURI(_request, null, result); - assertThat(_request.getRequestURI(), is(test[2])); - } - } - - @Test - public void testReplacementWithQueryString() throws IOException - { - String replacement = "/replace?given=param"; - String[] split = replacement.split("\\?", 2); - String path = split[0]; - String queryString = split[1]; - - RewritePatternRule rewritePatternRule = new RewritePatternRule(); - rewritePatternRule.setPattern("/old/context"); - rewritePatternRule.setReplacement(replacement); - - String result = rewritePatternRule.matchAndApply("/old/context", _request, _response); - assertThat(result, is(path)); - - rewritePatternRule.applyURI(_request, null, result); - assertThat("queryString matches expected", _request.getQueryString(), is(queryString)); - assertThat("request URI matches expected", _request.getRequestURI(), is(path)); - } - - @Test - public void testRequestWithQueryString() throws IOException + public void testRequestWithQueryString() throws Exception { String replacement = "/replace"; - String queryString = "request=parameter"; - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/old/context", null, queryString).asImmutable()); + RewritePatternRule rule = new RewritePatternRule("/context", replacement); + start(rule); - RewritePatternRule rewritePatternRule = new RewritePatternRule(); - rewritePatternRule.setPattern("/old/context"); - rewritePatternRule.setReplacement(replacement); + String query = "a=b"; + String request = """ + GET /context?$Q HTTP/1.1 + Host: localhost + + """.replace("$Q", query); - String result = rewritePatternRule.matchAndApply("/old/context", _request, _response); - assertThat("result matches expected", result, is(replacement)); - - rewritePatternRule.applyURI(_request, null, result); - assertThat("request URI matches expected", _request.getRequestURI(), is(replacement)); - assertThat("queryString matches expected", _request.getQueryString(), is(queryString)); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals(replacement + "?" + query, response.get("X-URI")); } @Test - public void testRequestAndReplacementWithQueryString() throws IOException + public void testRequestAndReplacementWithQueryString() throws Exception { - String requestQueryString = "request=parameter"; - String replacement = "/replace?given=param"; - String[] split = replacement.split("\\?", 2); - String path = split[0]; - String queryString = split[1]; - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/old/context", null, requestQueryString).asImmutable()); + String replacementPath = "/replace"; + String replacementQuery = "c=d"; + RewritePatternRule rule = new RewritePatternRule("/context", replacementPath + "?" + replacementQuery); + start(rule); - RewritePatternRule rewritePatternRule = new RewritePatternRule(); - rewritePatternRule.setPattern("/old/context"); - rewritePatternRule.setReplacement(replacement); + String query = "a=b"; + String request = """ + GET /context?$Q HTTP/1.1 + Host: localhost + + """.replace("$Q", query); - String result = rewritePatternRule.matchAndApply("/old/context", _request, _response); - assertThat(result, is(path)); - - rewritePatternRule.applyURI(_request, null, result); - assertThat("queryString matches expected", _request.getQueryString(), - is(requestQueryString + "&" + queryString)); - assertThat("request URI matches expected", _request.getRequestURI(), is(path)); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals(replacementPath + "?" + query + "&" + replacementQuery, response.get("X-URI")); } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java index f0a6ed9a596..68574bad6f3 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRuleTest.java @@ -13,127 +13,135 @@ package org.eclipse.jetty.rewrite.handler; -import java.nio.charset.StandardCharsets; import java.util.stream.Stream; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.http.HttpURI; -import org.eclipse.jetty.util.MultiMap; -import org.eclipse.jetty.util.URIUtil; -import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.StringUtil; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; -public class RewriteRegexRuleTest extends AbstractRuleTestCase +public class RewriteRegexRuleTest extends AbstractRuleTest { public static Stream scenarios() { return Stream.of( - new Scenario("/foo0/bar", null, ".*", "/replace", "/replace", null), - new Scenario("/foo1/bar", "n=v", ".*", "/replace", "/replace", "n=v"), - new Scenario("/foo2/bar", null, "/xxx.*", "/replace", null, null), - new Scenario("/foo3/bar", null, "/(.*)/(.*)", "/$2/$1/xxx", "/bar/foo3/xxx", null), - new Scenario("/f%20o3/bar", null, "/(.*)/(.*)", "/$2/$1/xxx", "/bar/f%20o3/xxx", null), - new Scenario("/foo4/bar", null, "/(.*)/(.*)", "/test?p2=$2&p1=$1", "/test", "p2=bar&p1=foo4"), - new Scenario("/foo5/bar", "n=v", "/(.*)/(.*)", "/test?p2=$2&p1=$1", "/test", "n=v&p2=bar&p1=foo5"), - new Scenario("/foo6/bar", null, "/(.*)/(.*)", "/foo6/bar?p2=$2&p1=$1", "/foo6/bar", "p2=bar&p1=foo6"), - new Scenario("/foo7/bar", "n=v", "/(.*)/(.*)", "/foo7/bar?p2=$2&p1=$1", "/foo7/bar", "n=v&p2=bar&p1=foo7"), - new Scenario("/foo8/bar", null, "/(foo8)/(.*)(bar)", "/$3/$1/xxx$2", "/bar/foo8/xxx", null), - new Scenario("/foo9/$bar", null, ".*", "/$replace", "/$replace", null), - new Scenario("/fooA/$bar", null, "/fooA/(.*)", "/$1/replace", "/$bar/replace", null), - new Scenario("/fooB/bar/info", null, "/fooB/(NotHere)?([^/]*)/(.*)", "/$3/other?p1=$2", "/info/other", "p1=bar"), - new Scenario("/fooC/bar/info", null, "/fooC/(NotHere)?([^/]*)/(.*)", "/$3/other?p1=$2&$Q", "/info/other", "p1=bar&"), - new Scenario("/fooD/bar/info", "n=v", "/fooD/(NotHere)?([^/]*)/(.*)", "/$3/other?p1=$2&$Q", "/info/other", "p1=bar&n=v"), - new Scenario("/fooE/bar/info", "n=v", "/fooE/(NotHere)?([^/]*)/(.*)", "/$3/other?p1=$2", "/info/other", "n=v&p1=bar") + // Simple replacement + new Scenario("/foo0/bar", "/.*", "/replace", "/replace", null), + // Non-matching rule (no replacement done) + new Scenario("/foo2/bar", "/xxx.*", "/replace", "/foo2/bar", null), + // Replacement with named references + new Scenario("/foo1/bar?n=v", "^/.*\\?(?.*)$", "/replace?${query}", "/replace", "n=v"), + // group of everything after last slash + new Scenario("/foo3/bar", "/(.*)/(.*)", "/$2/$1/xxx", "/bar/foo3/xxx", null), + // TODO: path is not encoded when it reaches the X-Path handler + new Scenario("/f%20o3/bar", "/(.*)/(.*)", "/$2/$1/xxx", "/bar/f%20o3/xxx", null), + new Scenario("/foo4/bar", "/(.*)/(.*)", "/test?p2=$2&p1=$1", "/test", "p2=bar&p1=foo4"), + new Scenario("/foo4.2/bar/zed", "/(.*)/(.*)", "/test?p2=$2&p1=$1", "/test", "p2=zed&p1=foo4.2/bar"), + new Scenario("/foo4.3/bar/zed", "/([^/]*)/([^/]*)/.*", "/test?p2=$2&p1=$1", "/test", "p2=bar&p1=foo4.3"), + // Example of bad regex group (accidentally covered query) + new Scenario("/foo4.4/bar?x=y", "/(.*)/(.*)", "/test?p2=$2&p1=$1", "/test", "p2=bar?x=y&p1=foo4.4"), + // Fixed Example of above bad regex group (covered query properly) + new Scenario("/foo4.5/bar?x=y", "/([^/]*)/([^/?]*).*", "/test?p2=$2&p1=$1", "/test", "p2=bar&p1=foo4.5"), + // specific regex groups + new Scenario("/foo5/bar", "^/(foo5)/(.*)(bar)", "/$3/$1/xxx$2", "/bar/foo5/xxx", null), + // target input with raw "$" + new Scenario("/foo6/$bar", "/.*", "/replace", "/replace", null), + // target input with raw "$", and replacement with "$" character + new Scenario("/foo6/$bar", "/.*", "/\\$replace", "/$replace", null), + new Scenario("/fooA/$bar", "/fooA/(.*)", "/$1/replace", "/$bar/replace", null), + new Scenario("/fooB/bar/info", "/fooB/(NotHere)?([^/]*)/(.*)", "/$3/other?p1=$2", "/info/other", "p1=bar"), + new Scenario("/fooC/bar/info", "/fooC/(NotHere)?([^/]*)/([^?]*)(?:\\?|/\\?|/|)(?.*)", "/$3/other?p1=$2&${query}", "/info/other", "p1=bar&"), + new Scenario("/fooD/bar/info?n=v", "/fooD/(NotHere)?([^/]*)/([^?]*)(?:\\?|/\\?|/|)(?.*)", "/$3/other?p1=$2&${query}", "/info/other", "p1=bar&n=v"), + new Scenario("/fooE/bar/info?n=v", "/fooE/(NotHere)?([^/]*)/([^?]*)(?:\\?|/\\?|/|)(?.*)", "/$3/other?p1=$2", "/info/other", "p1=bar") ).map(Arguments::of); } + private void start(RewriteRegexRule rule) throws Exception + { + _rewriteHandler.addRule(rule); + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + HttpURI httpURI = request.getHttpURI(); + response.setHeader("X-Path", httpURI.getPath()); + if (httpURI.getQuery() != null) + response.setHeader("X-Query", httpURI.getQuery()); + callback.succeeded(); + } + }); + } + @ParameterizedTest @MethodSource("scenarios") public void testRequestUriEnabled(Scenario scenario) throws Exception { - start(false); - RewriteRegexRule rule = new RewriteRegexRule(); + RewriteRegexRule rule = new RewriteRegexRule(scenario.regex, scenario.replacement); + start(rule); - reset(); - _request.setHttpURI(HttpURI.build(_request.getHttpURI())); + String request = """ + GET $T HTTP/1.1 + Host: localhost + + """.replace("$T", scenario.pathQuery); - rule.setRegex(scenario.regex); - rule.setReplacement(scenario.replacement); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus(), "Response status code"); + assertEquals(scenario.expectedPath, response.get("X-Path"), "Response X-Path header value"); + if (scenario.expectedQuery != null) + assertEquals(scenario.expectedQuery, response.get("X-Query"), "Response X-Query header value"); + } - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), scenario.uriPathQuery, null, scenario.queryString)); - - String result = rule.matchAndApply(scenario.uriPathQuery, _request, _response); - assertEquals(scenario.expectedRequestURI, result); - rule.applyURI(_request, scenario.uriPathQuery, result); - - if (result != null) - { - assertEquals(scenario.expectedRequestURI, _request.getRequestURI()); - assertEquals(scenario.expectedQueryString, _request.getQueryString()); - } - - if (scenario.expectedQueryString != null) - { - MultiMap params = new MultiMap(); - UrlEncoded.decodeTo(scenario.expectedQueryString, params, StandardCharsets.UTF_8); - - for (String n : params.keySet()) - { - assertEquals(params.getString(n), _request.getParameter(n)); - } - } + public static Stream inputPathQueries() + { + return Stream.of( + Arguments.of("/foo/bar", "/test?&p2=bar&p1=foo"), + Arguments.of("/foo/bar/", "/test?&p2=bar&p1=foo"), + Arguments.of("/foo/bar?", "/test?&p2=bar&p1=foo"), + Arguments.of("/foo/bar/?", "/test?&p2=bar&p1=foo"), + Arguments.of("/foo/bar?a=b", "/test?a=b&p2=bar&p1=foo"), + Arguments.of("/foo/bar/?a=b", "/test?a=b&p2=bar&p1=foo") + ); } @ParameterizedTest - @MethodSource("scenarios") - public void testContainedRequestUriEnabled(Scenario scenario) throws Exception + @MethodSource("inputPathQueries") + public void testRegexOptionalTargetQuery(String target, String expectedResult) throws Exception { - start(false); - RewriteRegexRule rule = new RewriteRegexRule(); + String regex = "^/([^/]*)/([^/\\?]*)(?:\\?|/\\?|/|)(?.*)$"; + String replacement = "/test?${query}&p2=$2&p1=$1"; + RewriteRegexRule rule = new RewriteRegexRule(regex, replacement); + start(rule); - RuleContainer container = new RuleContainer(); - container.setRewriteRequestURI(true); - container.addRule(rule); + String request = """ + GET $T HTTP/1.1 + Host: localhost + + """.replace("$T", target); - reset(); - rule.setRegex(scenario.regex); - rule.setReplacement(scenario.replacement); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus(), "Response status code"); + String result = response.get("X-Path"); + String query = response.get("X-Query"); + if (StringUtil.isNotBlank(query)) + result = result + '?' + query; - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), scenario.uriPathQuery, null, scenario.queryString)); - _request.getAttributes().clearAttributes(); - - String result = container.apply(URIUtil.decodePath(scenario.uriPathQuery), _request, _response); - assertEquals(URIUtil.decodePath(scenario.expectedRequestURI == null ? scenario.uriPathQuery : scenario.expectedRequestURI), result); - assertEquals(scenario.expectedRequestURI == null ? scenario.uriPathQuery : scenario.expectedRequestURI, _request.getRequestURI()); - assertEquals(scenario.expectedQueryString, _request.getQueryString()); + assertThat(result, is(expectedResult)); } - private static class Scenario + private record Scenario(String pathQuery, String regex, String replacement, String expectedPath, String expectedQuery) { - String uriPathQuery; - String queryString; - String regex; - String replacement; - String expectedRequestURI; - String expectedQueryString; - - public Scenario(String uriPathQuery, String queryString, String regex, String replacement, String expectedRequestURI, String expectedQueryString) - { - this.uriPathQuery = uriPathQuery; - this.queryString = queryString; - this.regex = regex; - this.replacement = replacement; - this.expectedRequestURI = expectedRequestURI; - this.expectedQueryString = expectedQueryString; - } - - @Override - public String toString() - { - return String.format("%s?%s>%s|%s", uriPathQuery, queryString, regex, replacement); - } } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/TerminatingPatternRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/TerminatingPatternRuleTest.java index 1dcd04fd434..1de18bef32c 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/TerminatingPatternRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/TerminatingPatternRuleTest.java @@ -13,83 +13,68 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; -public class TerminatingPatternRuleTest extends AbstractRuleTestCase +public class TerminatingPatternRuleTest extends AbstractRuleTest { - private RewriteHandler rewriteHandler; - @BeforeEach public void init() throws Exception { - rewriteHandler = new RewriteHandler(); - rewriteHandler.setServer(_server); - rewriteHandler.setHandler(new AbstractHandler() + TerminatingPatternRule rule1 = new TerminatingPatternRule("/login.jsp"); + _rewriteHandler.addRule(rule1); + RedirectRegexRule rule2 = new RedirectRegexRule("^/login.*$", "http://login.company.com/"); + rule2.setStatusCode(HttpStatus.SEE_OTHER_303); + _rewriteHandler.addRule(rule2); + start(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, - ServletException + public void process(Request request, Response response, Callback callback) { response.setStatus(HttpStatus.CREATED_201); - request.setAttribute("target", target); - request.setAttribute("URI", request.getRequestURI()); - request.setAttribute("info", request.getPathInfo()); + callback.succeeded(); } }); - rewriteHandler.start(); - - TerminatingPatternRule rule1 = new TerminatingPatternRule(); - rule1.setPattern("/login.jsp"); - rewriteHandler.addRule(rule1); - RedirectRegexRule rule2 = new RedirectRegexRule("^/login.*$", "http://login.company.com/"); - rewriteHandler.addRule(rule2); - - start(false); - } - - private void assertIsRedirect(int expectedStatus, String expectedLocation) - { - assertThat("Response Status", _response.getStatus(), is(expectedStatus)); - assertThat("Response Location Header", _response.getHeader(HttpHeader.LOCATION.asString()), is(expectedLocation)); - } - - private void assertIsRequest(String expectedRequestPath) - { - assertThat("Response Status", _response.getStatus(), is(HttpStatus.CREATED_201)); - assertThat("Request Target", _request.getAttribute("target"), is(expectedRequestPath)); } @Test - public void testTerminatingEarly() throws IOException, ServletException + public void testTerminatingEarly() throws Exception { - rewriteHandler.handle("/login.jsp", _request, _request, _response); - assertIsRequest("/login.jsp"); + String request = """ + GET /login.jsp HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.CREATED_201, response.getStatus()); + assertNull(response.get(HttpHeader.LOCATION)); } - @Test - public void testNoTerminationDo() throws IOException, ServletException + @ParameterizedTest + @ValueSource(strings = {"/login.do", "/login/"}) + public void testNonTerminating(String uri) throws Exception { - rewriteHandler.handle("/login.do", _request, _request, _response); - assertIsRedirect(HttpStatus.MOVED_TEMPORARILY_302, "http://login.company.com/"); - } + String request = """ + GET $U HTTP/1.1 + Host: localhost + + """.replace("$U", uri); - @Test - public void testNoTerminationDir() throws IOException, ServletException - { - rewriteHandler.handle("/login/", _request, _request, _response); - assertIsRedirect(HttpStatus.MOVED_TEMPORARILY_302, "http://login.company.com/"); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.SEE_OTHER_303, response.getStatus()); + assertEquals("http://login.company.com/", response.get(HttpHeader.LOCATION)); } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/TerminatingRegexRuleTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/TerminatingRegexRuleTest.java index ddff1cb9362..82004469b6d 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/TerminatingRegexRuleTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/TerminatingRegexRuleTest.java @@ -13,83 +13,68 @@ package org.eclipse.jetty.rewrite.handler; -import java.io.IOException; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; -public class TerminatingRegexRuleTest extends AbstractRuleTestCase +public class TerminatingRegexRuleTest extends AbstractRuleTest { - private RewriteHandler rewriteHandler; - @BeforeEach public void init() throws Exception { - rewriteHandler = new RewriteHandler(); - rewriteHandler.setServer(_server); - rewriteHandler.setHandler(new AbstractHandler() + TerminatingRegexRule rule1 = new TerminatingRegexRule("^/login.jsp$"); + _rewriteHandler.addRule(rule1); + RedirectRegexRule rule2 = new RedirectRegexRule("^/login.*$", "http://login.company.com/"); + rule2.setStatusCode(HttpStatus.SEE_OTHER_303); + _rewriteHandler.addRule(rule2); + start(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, - ServletException + public void process(Request request, Response response, Callback callback) { response.setStatus(HttpStatus.CREATED_201); - request.setAttribute("target", target); - request.setAttribute("URI", request.getRequestURI()); - request.setAttribute("info", request.getPathInfo()); + callback.succeeded(); } }); - rewriteHandler.start(); - - TerminatingRegexRule rule1 = new TerminatingRegexRule(); - rule1.setRegex("^/login.jsp$"); - rewriteHandler.addRule(rule1); - RedirectRegexRule rule2 = new RedirectRegexRule("^/login.*$", "http://login.company.com/"); - rewriteHandler.addRule(rule2); - - start(false); - } - - private void assertIsRedirect(int expectedStatus, String expectedLocation) - { - assertThat("Response Status", _response.getStatus(), is(expectedStatus)); - assertThat("Response Location Header", _response.getHeader(HttpHeader.LOCATION.asString()), is(expectedLocation)); - } - - private void assertIsRequest(String expectedRequestPath) - { - assertThat("Response Status", _response.getStatus(), is(HttpStatus.CREATED_201)); - assertThat("Request Target", _request.getAttribute("target"), is(expectedRequestPath)); } @Test - public void testTerminatingEarly() throws IOException, ServletException + public void testTerminatingEarly() throws Exception { - rewriteHandler.handle("/login.jsp", _request, _request, _response); - assertIsRequest("/login.jsp"); + String request = """ + GET /login.jsp HTTP/1.1 + Host: localhost + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.CREATED_201, response.getStatus()); + assertNull(response.get(HttpHeader.LOCATION)); } - @Test - public void testNoTerminationDo() throws IOException, ServletException + @ParameterizedTest + @ValueSource(strings = {"/login.do", "/login/"}) + public void testNonTerminating(String uri) throws Exception { - rewriteHandler.handle("/login.do", _request, _request, _response); - assertIsRedirect(HttpStatus.MOVED_TEMPORARILY_302, "http://login.company.com/"); - } + String request = """ + GET $U HTTP/1.1 + Host: localhost + + """.replace("$U", uri); - @Test - public void testNoTerminationDir() throws IOException, ServletException - { - rewriteHandler.handle("/login/", _request, _request, _response); - assertIsRedirect(HttpStatus.MOVED_TEMPORARILY_302, "http://login.company.com/"); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.SEE_OTHER_303, response.getStatus()); + assertEquals("http://login.company.com/", response.get(HttpHeader.LOCATION)); } } diff --git a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java index d057598244a..5c542ca3214 100644 --- a/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java +++ b/jetty-core/jetty-rewrite/src/test/java/org/eclipse/jetty/rewrite/handler/VirtualHostRuleContainerTest.java @@ -13,186 +13,226 @@ package org.eclipse.jetty.rewrite.handler; -import org.eclipse.jetty.http.HttpURI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -public class VirtualHostRuleContainerTest extends AbstractRuleTestCase +public class VirtualHostRuleContainerTest extends AbstractRuleTest { - private RewriteHandler _handler; - private RewritePatternRule _rule; - private RewritePatternRule _fooRule; - private VirtualHostRuleContainer _fooContainerRule; + private VirtualHostRuleContainer _virtualHostRules; @BeforeEach public void init() throws Exception { - _handler = new RewriteHandler(); - _handler.setRewriteRequestURI(true); - - _rule = new RewritePatternRule(); - _rule.setPattern("/cheese/*"); - _rule.setReplacement("/rule"); - - _fooRule = new RewritePatternRule(); - _fooRule.setPattern("/cheese/bar/*"); - _fooRule.setReplacement("/cheese/fooRule"); - - _fooContainerRule = new VirtualHostRuleContainer(); - _fooContainerRule.setVirtualHosts(new String[]{"foo.com"}); - _fooContainerRule.setRules(new Rule[]{_fooRule}); - - start(false); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/cheese/bar")); - - _handler.setServer(_server); - _handler.start(); + _virtualHostRules = new VirtualHostRuleContainer(); + start(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + response.setHeader("X-Path", request.getHttpURI().getPath()); + callback.succeeded(); + } + }); } @Test public void testArbitraryHost() throws Exception { - _request.setHttpURI(HttpURI.build(_request.getRequestURI()).authority("cheese.com", 0)); - _handler.setRules(new Rule[]{_rule, _fooContainerRule}); - handleRequest(); - assertEquals("/rule/bar", _request.getRequestURI(), "{_rule, _fooContainerRule, Host: cheese.com}: applied _rule"); + _rewriteHandler.addRule(new RewritePatternRule("/cheese/*", "/rule")); + _rewriteHandler.addRule(_virtualHostRules); + _virtualHostRules.setVirtualHosts(List.of("foo.com")); + _virtualHostRules.addRule(new RewritePatternRule("/cheese/bar/*", "/cheese/fooRule")); + + String request = """ + GET /cheese/bar HTTP/1.1 + Host: cheese.com + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + // VirtualHost rule does not apply, host does not match. + assertEquals("/rule/bar", response.get("X-Path")); } @Test public void testVirtualHost() throws Exception { - _request.setHttpURI(HttpURI.build(_request.getRequestURI()).authority("foo.com", 0)); - _handler.setRules(new Rule[]{_fooContainerRule}); - handleRequest(); - assertEquals("/cheese/fooRule", _request.getRequestURI(), "{_fooContainerRule, Host: foo.com}: applied _fooRule"); + _rewriteHandler.addRule(_virtualHostRules); + _virtualHostRules.setVirtualHosts(List.of("foo.com")); + _virtualHostRules.addRule(new RewritePatternRule("/cheese/bar/*", "/cheese/fooRule")); + + String request = """ + GET /cheese/bar HTTP/1.1 + Host: foo.com + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/cheese/fooRule", response.get("X-Path")); } @Test public void testCascadingRules() throws Exception { - _request.setHttpURI(HttpURI.build(_request.getRequestURI()).authority("foo.com", 0)); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/cheese/bar")); + RewritePatternRule rule = new RewritePatternRule("/cheese/*", "/rule"); + _rewriteHandler.addRule(rule); + _rewriteHandler.addRule(_virtualHostRules); + _virtualHostRules.setVirtualHosts(List.of("foo.com")); + _virtualHostRules.addRule(new RewritePatternRule("/cheese/bar/*", "/cheese/fooRule")); - _rule.setTerminating(false); - _fooRule.setTerminating(false); - _fooContainerRule.setTerminating(false); + String request = """ + GET /cheese/bar HTTP/1.1 + Host: foo.com + + """; - _handler.setRules(new Rule[]{_rule, _fooContainerRule}); - handleRequest(); - assertEquals("/rule/bar", _request.getRequestURI(), "{_rule, _fooContainerRule}: applied _rule, didn't match _fooRule"); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/rule/bar", response.get("X-Path")); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/cheese/bar")); - _handler.setRules(new Rule[]{_fooContainerRule, _rule}); - handleRequest(); - assertEquals("/rule/fooRule", _request.getRequestURI(), "{_fooContainerRule, _rule}: applied _fooRule, _rule"); + _rewriteHandler.setRules(List.of(_virtualHostRules, rule)); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/cheese/bar")); - _fooRule.setTerminating(true); - handleRequest(); - assertEquals("/rule/fooRule", _request.getRequestURI(), "{_fooContainerRule, _rule}: (_fooRule is terminating); applied _fooRule, _rule"); + request = """ + GET /cheese/bar HTTP/1.1 + Host: foo.com + + """; - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/cheese/bar")); - _fooRule.setTerminating(false); - _fooContainerRule.setTerminating(true); - handleRequest(); - assertEquals("/cheese/fooRule", _request.getRequestURI(), "{_fooContainerRule, _rule}: (_fooContainerRule is terminating); applied _fooRule, terminated before _rule"); + response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/rule/fooRule", response.get("X-Path")); + + _virtualHostRules.setTerminating(true); + + request = """ + GET /cheese/bar HTTP/1.1 + Host: foo.com + + """; + + response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/cheese/fooRule", response.get("X-Path")); } @Test public void testCaseInsensitiveHostname() throws Exception { - _request.setHttpURI(HttpURI.build(_request.getRequestURI()).authority("Foo.com", 0)); - _fooContainerRule.setVirtualHosts(new String[]{"foo.com"}); + _rewriteHandler.addRule(_virtualHostRules); + _virtualHostRules.setVirtualHosts(List.of("foo.com")); + _virtualHostRules.addRule(new RewritePatternRule("/cheese/bar/*", "/cheese/fooRule")); - _handler.setRules(new Rule[]{_fooContainerRule}); - handleRequest(); - assertEquals("/cheese/fooRule", _request.getRequestURI(), "Foo.com and foo.com are equivalent"); + String request = """ + GET /cheese/bar HTTP/1.1 + Host: Foo.com + + """; + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/cheese/fooRule", response.get("X-Path")); } @Test public void testEmptyVirtualHost() throws Exception { - _request.setHttpURI(HttpURI.build(_request.getRequestURI()).authority("cheese.com", 0)); + _rewriteHandler.addRule(_virtualHostRules); + _virtualHostRules.addRule(new RewritePatternRule("/cheese/bar/*", "/cheese/fooRule")); - _handler.setRules(new Rule[]{_fooContainerRule}); - _fooContainerRule.setVirtualHosts(null); - handleRequest(); - assertEquals("/cheese/fooRule", _request.getRequestURI(), "{_fooContainerRule: virtual hosts array is null, Host: cheese.com}: apply _fooRule"); + List> cases = Arrays.asList(null, List.of(), Collections.singletonList(null)); + for (List virtualHosts : cases) + { + _virtualHostRules.setVirtualHosts(virtualHosts); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/cheese/bar")); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/cheese/bar")); - _fooContainerRule.setVirtualHosts(new String[]{}); - handleRequest(); - assertEquals("/cheese/fooRule", _request.getRequestURI(), "{_fooContainerRule: virtual hosts array is empty, Host: cheese.com}: apply _fooRule"); + String request = """ + GET /cheese/bar HTTP/1.1 + Host: cheese.com + + """; - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/cheese/bar")); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/cheese/bar")); - _fooContainerRule.setVirtualHosts(new String[]{null}); - handleRequest(); - assertEquals("/cheese/fooRule", _request.getRequestURI(), "{_fooContainerRule: virtual host is null, Host: cheese.com}: apply _fooRule"); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/cheese/fooRule", response.get("X-Path")); + } } @Test public void testMultipleVirtualHosts() throws Exception { - _request.setHttpURI(HttpURI.build(_request.getRequestURI()).authority("foo.com", 0)); - _handler.setRules(new Rule[]{_fooContainerRule}); + _rewriteHandler.addRule(_virtualHostRules); + _virtualHostRules.setVirtualHosts(List.of("cheese.com")); + _virtualHostRules.addRule(new RewritePatternRule("/cheese/bar/*", "/cheese/fooRule")); - _fooContainerRule.setVirtualHosts(new String[]{"cheese.com"}); - handleRequest(); - assertEquals("/cheese/bar", _request.getRequestURI(), "{_fooContainerRule: vhosts[cheese.com], Host: foo.com}: no effect"); + String request = """ + GET /cheese/bar HTTP/1.1 + Host: foo.com + + """; - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/cheese/bar")); - _fooContainerRule.addVirtualHost("foo.com"); - handleRequest(); - assertEquals("/cheese/fooRule", _request.getRequestURI(), "{_fooContainerRule: vhosts[cheese.com, foo.com], Host: foo.com}: apply _fooRule"); + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/cheese/bar", response.get("X-Path")); + + _virtualHostRules.addVirtualHost("foo.com"); + + request = """ + GET /cheese/bar HTTP/1.1 + Host: foo.com + + """; + + response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + assertEquals("/cheese/fooRule", response.get("X-Path")); } @Test public void testWildcardVirtualHosts() throws Exception { - checkWildcardHost(true, null, new String[]{"foo.com", ".foo.com", "vhost.foo.com"}); - checkWildcardHost(true, new String[]{null}, new String[]{"foo.com", ".foo.com", "vhost.foo.com"}); - - checkWildcardHost(true, new String[]{"foo.com", "*.foo.com"}, new String[]{"foo.com", ".foo.com", "vhost.foo.com"}); - checkWildcardHost(false, new String[]{"foo.com", "*.foo.com"}, new String[]{ - "badfoo.com", ".badfoo.com", "vhost.badfoo.com" - }); - - checkWildcardHost(false, new String[]{"*."}, new String[]{"anything.anything"}); - - checkWildcardHost(true, new String[]{"*.foo.com"}, new String[]{"vhost.foo.com", ".foo.com"}); - checkWildcardHost(false, new String[]{"*.foo.com"}, new String[]{"vhost.www.foo.com", "foo.com", "www.vhost.foo.com"}); - - checkWildcardHost(true, new String[]{"*.sub.foo.com"}, new String[]{"vhost.sub.foo.com", ".sub.foo.com"}); - checkWildcardHost(false, new String[]{"*.sub.foo.com"}, new String[]{".foo.com", "sub.foo.com", "vhost.foo.com"}); - - checkWildcardHost(false, new String[]{"foo.*.com", "foo.com.*"}, new String[]{ - "foo.vhost.com", "foo.com.vhost", "foo.com" - }); + testWildcardVirtualHost(true, List.of("foo.com", "*.foo.com"), List.of("foo.com", ".foo.com", "vhost.foo.com")); + testWildcardVirtualHost(false, List.of("foo.com", "*.foo.com"), List.of("badfoo.com", ".badfoo.com", "vhost.badfoo.com")); + testWildcardVirtualHost(false, List.of("*."), List.of("anything.anything")); + testWildcardVirtualHost(true, List.of("*.foo.com"), List.of("vhost.foo.com", ".foo.com")); + testWildcardVirtualHost(false, List.of("*.foo.com"), List.of("vhost.www.foo.com", "foo.com", "www.vhost.foo.com")); + testWildcardVirtualHost(true, List.of("*.sub.foo.com"), List.of("vhost.sub.foo.com", ".sub.foo.com")); + testWildcardVirtualHost(false, List.of("*.sub.foo.com"), List.of(".foo.com", "sub.foo.com", "vhost.foo.com")); + testWildcardVirtualHost(false, List.of("foo.*.com", "foo.com.*"), List.of("foo.vhost.com", "foo.com.vhost", "foo.com")); } - private void checkWildcardHost(boolean succeed, String[] ruleHosts, String[] requestHosts) throws Exception + private void testWildcardVirtualHost(boolean succeed, List ruleHosts, List requestHosts) throws Exception { - _fooContainerRule.setVirtualHosts(ruleHosts); - _handler.setRules(new Rule[]{_fooContainerRule}); + _rewriteHandler.addRule(_virtualHostRules); + _virtualHostRules.setVirtualHosts(ruleHosts); + _virtualHostRules.addRule(new RewritePatternRule("/cheese/bar/*", "/cheese/fooRule")); - for (String host : requestHosts) + for (String requestHost : requestHosts) { - _request.setHttpURI(HttpURI.build(_request.getRequestURI()).authority(host, 0)); - _request.setHttpURI(HttpURI.build(_request.getHttpURI(), "/cheese/bar")); - handleRequest(); + String request = """ + GET /cheese/bar HTTP/1.1 + Host: $H + + """.replace("$H", requestHost); + + HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse(request)); + assertEquals(HttpStatus.OK_200, response.getStatus()); if (succeed) - assertEquals("/cheese/fooRule", _request.getRequestURI(), "{_fooContainerRule, Host: " + host + "}: should apply _fooRule"); + assertEquals("/cheese/fooRule", response.get("X-Path")); else - assertEquals("/cheese/bar", _request.getRequestURI(), "{_fooContainerRule, Host: " + host + "}: should not apply _fooRule"); + assertEquals("/cheese/bar", response.get("X-Path")); } } - - private void handleRequest() throws Exception - { - _handler.handle("/cheese/bar", _request, _request, _response); - } } diff --git a/jetty-core/jetty-rewrite/src/test/resources/jetty-logging.properties b/jetty-core/jetty-rewrite/src/test/resources/jetty-logging.properties new file mode 100644 index 00000000000..481be7a1eaf --- /dev/null +++ b/jetty-core/jetty-rewrite/src/test/resources/jetty-logging.properties @@ -0,0 +1,2 @@ +#org.eclipse.jetty.LEVEL=DEBUG +#org.eclipse.jetty.rewrite.LEVEL=DEBUG diff --git a/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml b/jetty-core/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml similarity index 99% rename from jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml rename to jetty-core/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml index 2688e21964c..4c1bfd390b5 100644 --- a/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml +++ b/jetty-core/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml @@ -53,7 +53,7 @@ AVAILABLE - // STARTING -----false--> UNAVAILABLE - // AVAILABLE ----false--> UNAVAILABLE - if (available) - { - while (true) - { - Availability availability = _availability.get(); - switch (availability) - { - case AVAILABLE: - break; - case UNAVAILABLE: - if (!_availability.compareAndSet(availability, Availability.AVAILABLE)) - continue; - break; - default: - throw new IllegalStateException(availability.toString()); - } - break; - } - } - else - { - while (true) - { - Availability availability = _availability.get(); - switch (availability) - { - case STARTING: - case AVAILABLE: - if (!_availability.compareAndSet(availability, Availability.UNAVAILABLE)) - continue; - break; - default: - break; - } - break; - } - } - } - - public Logger getLogger() - { - return _logger; - } - - public void setLogger(Logger logger) - { - _logger = logger; + // TODO } @Override protected void doStart() throws Exception { - if (_contextPath == null) + if (getContextPath() == null) throw new IllegalStateException("Null contextPath"); - if (getBaseResource() != null && getBaseResource().isAlias()) - LOG.warn("BaseResource {} is aliased to {} in {}. May not be supported in future releases.", - getBaseResource(), getBaseResource().getAlias(), this); - - _availability.set(Availability.STARTING); - - if (_logger == null) - _logger = LoggerFactory.getLogger(ContextHandler.class.getName() + getLogNameSuffix()); - - ClassLoader oldClassloader = null; - Thread currentThread = null; - Context oldContext = null; - - _attributes.setAttribute("org.eclipse.jetty.server.Executor", getServer().getThreadPool()); - - if (_mimeTypes == null) - _mimeTypes = new MimeTypes(); - - _durableListeners.addAll(getEventListeners()); - - try - { - // Set the classloader, context and enter scope - if (_classLoader != null) - { - currentThread = Thread.currentThread(); - oldClassloader = currentThread.getContextClassLoader(); - currentThread.setContextClassLoader(_classLoader); - } - oldContext = __context.get(); - __context.set(_scontext); - enterScope(null, getState()); - - // defers the calling of super.doStart() - startContext(); - - contextInitialized(); - - _availability.compareAndSet(Availability.STARTING, Availability.AVAILABLE); - LOG.info("Started {}", this); - } - finally - { - _availability.compareAndSet(Availability.STARTING, Availability.UNAVAILABLE); - exitScope(null); - __context.set(oldContext); - // reset the classloader - if (_classLoader != null && currentThread != null) - currentThread.setContextClassLoader(oldClassloader); - } - } - - private String getLogNameSuffix() - { - // Use display name first - String logName = getDisplayName(); - if (StringUtil.isBlank(logName)) - { - // try context path - logName = getContextPath(); - if (logName != null) - { - // Strip prefix slash - if (logName.startsWith("/")) - { - logName = logName.substring(1); - } - } - - if (StringUtil.isBlank(logName)) - { - // an empty context path is the ROOT context - logName = "ROOT"; - } - } - - // Replace bad characters. - return '.' + logName.replaceAll("\\W", "_"); - } - - /** - * Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to - * insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers. - * - * @throws Exception if unable to start the context - * @see ContextHandler.Context - */ - protected void startContext() throws Exception - { - String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES); - if (managedAttributes != null) - addEventListener(new ManagedAttributeListener(this, StringUtil.csvSplit(managedAttributes))); - - super.doStart(); - } - - /** - * Call the ServletContextListeners contextInitialized methods. - * This can be called from a ServletHandler during the proper sequence - * of initializing filters, servlets and listeners. However, if there is - * no ServletHandler, the ContextHandler will call this method during - * doStart(). - */ - public void contextInitialized() throws Exception - { - // Call context listeners - if (_contextStatus == ContextStatus.NOTSET) - { - _contextStatus = ContextStatus.INITIALIZED; - _destroyServletContextListeners.clear(); - if (!_servletContextListeners.isEmpty()) - { - ServletContextEvent event = new ServletContextEvent(_scontext); - for (ServletContextListener listener : _servletContextListeners) - { - callContextInitialized(listener, event); - _destroyServletContextListeners.add(listener); - } - } - } - } - - /** - * Call the ServletContextListeners with contextDestroyed. - * This method can be called from a ServletHandler in the - * proper sequence of destroying filters, servlets and listeners. - * If there is no ServletHandler, the ContextHandler must ensure - * these listeners are called instead. - */ - public void contextDestroyed() throws Exception - { - switch (_contextStatus) - { - case INITIALIZED: - { - try - { - //Call context listeners - MultiException ex = new MultiException(); - ServletContextEvent event = new ServletContextEvent(_scontext); - Collections.reverse(_destroyServletContextListeners); - for (ServletContextListener listener : _destroyServletContextListeners) - { - try - { - callContextDestroyed(listener, event); - } - catch (Exception x) - { - ex.add(x); - } - } - ex.ifExceptionThrow(); - } - finally - { - _contextStatus = ContextStatus.DESTROYED; - } - break; - } - default: - break; - } - } - - protected void stopContext() throws Exception - { - // stop all the handler hierarchy - super.doStop(); - } - - protected void callContextInitialized(ServletContextListener l, ServletContextEvent e) - { - if (getServer().isDryRun()) - return; - - if (LOG.isDebugEnabled()) - LOG.debug("contextInitialized: {}->{}", e, l); - l.contextInitialized(e); - } - - protected void callContextDestroyed(ServletContextListener l, ServletContextEvent e) - { - if (getServer().isDryRun()) - return; - - if (LOG.isDebugEnabled()) - LOG.debug("contextDestroyed: {}->{}", e, l); - l.contextDestroyed(e); + // TODO lots of stuff in previous doStart. Some might go here, but most probably goes to the ServletContentHandler ? + _context.call(super::doStart, null); } @Override protected void doStop() throws Exception { - // Should we attempt a graceful shutdown? - MultiException mex = null; - - _availability.set(Availability.STOPPED); - - ClassLoader oldClassloader = null; - ClassLoader oldWebapploader = null; - Thread currentThread = null; - Context oldContext = __context.get(); - enterScope(null, "doStop"); - __context.set(_scontext); - try - { - // Set the classloader - if (_classLoader != null) - { - oldWebapploader = _classLoader; - currentThread = Thread.currentThread(); - oldClassloader = currentThread.getContextClassLoader(); - currentThread.setContextClassLoader(_classLoader); - } - - stopContext(); - - contextDestroyed(); - - // retain only durable listeners - setEventListeners(_durableListeners); - _durableListeners.clear(); - - if (_errorHandler != null) - _errorHandler.stop(); - - for (EventListener l : _programmaticListeners) - { - removeEventListener(l); - if (l instanceof ContextScopeListener) - { - try - { - ((ContextScopeListener)l).exitScope(_scontext, null); - } - catch (Throwable e) - { - LOG.warn("Unable to exit scope", e); - } - } - } - _programmaticListeners.clear(); - } - catch (Throwable x) - { - if (mex == null) - mex = new MultiException(); - mex.add(x); - } - finally - { - _contextStatus = ContextStatus.NOTSET; - __context.set(oldContext); - exitScope(null); - LOG.info("Stopped {}", this); - // reset the classloader - if ((oldClassloader == null || (oldClassloader != oldWebapploader)) && currentThread != null) - currentThread.setContextClassLoader(oldClassloader); - - _scontext.clearAttributes(); - } - - if (mex != null) - mex.ifExceptionThrow(); + // TODO lots of stuff in previous doStart. Some might go here, but most probably goes to the ServletContentHandler ? + _context.call(super::doStop, null); } - public boolean checkVirtualHost(final Request baseRequest) + public boolean checkVirtualHost(Request request) { - if (_vhosts == null || _vhosts.length == 0) + if (_vhosts.isEmpty()) return true; - String vhost = normalizeHostname(baseRequest.getServerName()); - String connectorName = baseRequest.getHttpChannel().getConnector().getName(); + // TODO is this correct? + String host = request.getHttpURI().getHost(); - for (int i = 0; i < _vhosts.length; i++) + String connectorName = request.getConnectionMetaData().getConnector().getName(); + + for (VHost vhost : _vhosts) { - String contextVhost = _vhosts[i]; - String contextVConnector = _vconnectors[i]; + String contextVhost = vhost._vHost; + String contextVConnector = vhost._vConnector; if (contextVConnector != null) { @@ -1152,16 +503,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu if (contextVhost != null) { - if (_vhostswildcard[i]) + if (vhost._wild) { // wildcard only at the beginning, and only for one additional subdomain level - int index = vhost.indexOf("."); - if (index >= 0 && vhost.substring(index).equalsIgnoreCase(contextVhost)) + int index = host.indexOf("."); + if (index >= 0 && host.substring(index).equalsIgnoreCase(contextVhost)) { return true; } } - else if (vhost.equalsIgnoreCase(contextVhost)) + else if (host.equalsIgnoreCase(contextVhost)) { return true; } @@ -1170,451 +521,91 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu return false; } - public boolean checkContextPath(String uri) + protected String getPathInContext(Request request) { - // Are we not the root context? - if (_contextPath.length() > 1) - { - // reject requests that are not for us - if (!uri.startsWith(_contextPath)) - return false; - if (uri.length() > _contextPath.length() && uri.charAt(_contextPath.length()) != '/') - return false; - } - return true; - } - - /* - * @see org.eclipse.jetty.server.Handler#handle(jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse) - */ - public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException - { - DispatcherType dispatch = baseRequest.getDispatcherType(); - - // Check the vhosts - if (!checkVirtualHost(baseRequest)) - return false; - - if (!checkContextPath(target)) - return false; - - // Are we not the root context? - // redirect null path infos - if (!_allowNullPathInfo && _contextPath.length() == target.length() && _contextPath.length() > 1) - { - // context request must end with / - baseRequest.setHandled(true); - String queryString = baseRequest.getQueryString(); - baseRequest.getResponse().sendRedirect( - HttpServletResponse.SC_MOVED_TEMPORARILY, - baseRequest.getRequestURI() + (queryString == null ? "/" : ("/?" + queryString)), - true); - return false; - } - - switch (_availability.get()) - { - case STOPPED: - return false; - case SHUTDOWN: - case UNAVAILABLE: - baseRequest.setHandled(true); - response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - return false; - default: - if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled())) - return false; - } - - return true; + String path = request.getPathInContext(); + if (!path.startsWith(_context.getContextPath())) + return null; + if ("/".equals(_context.getContextPath())) + return path; + if (path.length() == _context.getContextPath().length()) + return ""; + if (path.charAt(_context.getContextPath().length()) != '/') + return null; + return path.substring(_context.getContextPath().length()); } @Override - public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void destroy() { - if (LOG.isDebugEnabled()) - LOG.debug("scope {}|{}|{} @ {}", baseRequest.getContextPath(), baseRequest.getServletPath(), baseRequest.getPathInfo(), this); - - final Thread currentThread = Thread.currentThread(); - final ClassLoader oldClassloader = currentThread.getContextClassLoader(); - Context oldContext; - String oldPathInContext = baseRequest.getPathInContext();; - String pathInContext = target; - - DispatcherType dispatch = baseRequest.getDispatcherType(); - - oldContext = baseRequest.getContext(); - - // Are we already in this context? - if (oldContext != _scontext) - { - // check the target. - if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch)) - { - // TODO: remove this once isCompact() has been deprecated for several releases. - if (isCompactPath()) - target = URIUtil.compactPath(target); - if (!checkContext(target, baseRequest, response)) - return; - - if (target.length() > _contextPath.length()) - { - if (_contextPath.length() > 1) - target = target.substring(_contextPath.length()); - pathInContext = target; - } - else if (_contextPath.length() == 1) - { - target = URIUtil.SLASH; - pathInContext = URIUtil.SLASH; - } - else - { - target = URIUtil.SLASH; - pathInContext = null; - } - } - } - - if (_classLoader != null) - currentThread.setContextClassLoader(_classLoader); - - try - { - // Update the paths - baseRequest.setContext(_scontext, - (DispatcherType.INCLUDE.equals(dispatch) || !target.startsWith("/")) ? oldPathInContext : pathInContext); - - if (oldContext != _scontext) - { - __context.set(_scontext); - enterScope(baseRequest, dispatch); - } - - if (LOG.isDebugEnabled()) - LOG.debug("context={}|{}|{} @ {}", baseRequest.getContextPath(), baseRequest.getServletPath(), baseRequest.getPathInfo(), this); - - nextScope(target, baseRequest, request, response); - } - finally - { - if (oldContext != _scontext) - { - exitScope(baseRequest); - - // reset the classloader - if (_classLoader != null) - currentThread.setContextClassLoader(oldClassloader); - - // reset the context - __context.set(oldContext); - } - - // reset pathInContext - baseRequest.setContext(oldContext, oldPathInContext); - } - } - - protected void requestInitialized(Request baseRequest, HttpServletRequest request) - { - // Handle the REALLY SILLY request events! - if (!_servletRequestAttributeListeners.isEmpty()) - for (ServletRequestAttributeListener l : _servletRequestAttributeListeners) - { - baseRequest.addEventListener(l); - } - - if (!_servletRequestListeners.isEmpty()) - { - final ServletRequestEvent sre = new ServletRequestEvent(_scontext, request); - for (ServletRequestListener l : _servletRequestListeners) - { - l.requestInitialized(sre); - } - } - } - - protected void requestDestroyed(Request baseRequest, HttpServletRequest request) - { - // Handle more REALLY SILLY request events! - if (!_servletRequestListeners.isEmpty()) - { - final ServletRequestEvent sre = new ServletRequestEvent(_scontext, request); - for (int i = _servletRequestListeners.size(); i-- > 0; ) - { - _servletRequestListeners.get(i).requestDestroyed(sre); - } - } - - if (!_servletRequestAttributeListeners.isEmpty()) - { - for (int i = _servletRequestAttributeListeners.size(); i-- > 0; ) - { - baseRequest.removeEventListener(_servletRequestAttributeListeners.get(i)); - } - } + _context.run(super::destroy); } @Override - public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public Request.Processor handle(Request request) throws Exception { - final DispatcherType dispatch = baseRequest.getDispatcherType(); - final boolean new_context = baseRequest.takeNewContext(); - try - { - if (new_context) - requestInitialized(baseRequest, request); - - if (dispatch == DispatcherType.REQUEST && isProtectedTarget(target)) - { - baseRequest.setHandled(true); - response.sendError(HttpServletResponse.SC_NOT_FOUND); - return; - } - - nextHandle(target, baseRequest, request, response); - } - finally - { - if (new_context) - requestDestroyed(baseRequest, request); - } - } - - /** - * @param request A request that is applicable to the scope, or null - * @param reason An object that indicates the reason the scope is being entered. - */ - protected void enterScope(Request request, Object reason) - { - if (!_contextListeners.isEmpty()) - { - for (ContextScopeListener listener : _contextListeners) - { - try - { - listener.enterScope(_scontext, request, reason); - } - catch (Throwable e) - { - LOG.warn("Unable to enter scope", e); - } - } - } - } - - /** - * @param request A request that is applicable to the scope, or null - */ - protected void exitScope(Request request) - { - if (!_contextListeners.isEmpty()) - { - for (int i = _contextListeners.size(); i-- > 0; ) - { - try - { - _contextListeners.get(i).exitScope(_scontext, request); - } - catch (Throwable e) - { - LOG.warn("Unable to exit scope", e); - } - } - } - } - - /** - * Handle a runnable in the scope of this context and a particular request - * - * @param request The request to scope the thread to (may be null if no particular request is in scope) - * @param runnable The runnable to run. - */ - public void handle(Request request, Runnable runnable) - { - ClassLoader oldClassloader = null; - Thread currentThread = null; - Context oldContext = __context.get(); - - // Are we already in the scope? - if (oldContext == _scontext) - { - runnable.run(); - return; - } - - // Nope, so enter the scope and then exit - try - { - __context.set(_scontext); - - // Set the classloader - if (_classLoader != null) - { - currentThread = Thread.currentThread(); - oldClassloader = currentThread.getContextClassLoader(); - currentThread.setContextClassLoader(_classLoader); - } - - enterScope(request, runnable); - runnable.run(); - } - finally - { - exitScope(request); - - __context.set(oldContext); - if (oldClassloader != null) - { - currentThread.setContextClassLoader(oldClassloader); - } - } - } - - /* - * Handle a runnable in the scope of this context - */ - public void handle(Runnable runnable) - { - handle(null, runnable); - } - - /** - * Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If - * the target is protected, 404 is returned. - * - * @param target the target to test - * @return true if target is a protected target - */ - public boolean isProtectedTarget(String target) - { - if (target == null || _protectedTargets.isEmpty()) - return false; - - if (target.startsWith("//")) - target = URIUtil.compactPath(target); - - ProtectedTargetType type = _protectedTargets.getBest(target); - - return type == ProtectedTargetType.PREFIX || - type == ProtectedTargetType.EXACT && _protectedTargets.get(target) == ProtectedTargetType.EXACT; - } - - /** - * @param targets Array of URL prefix. Each prefix is in the form /path and will match either /path exactly or /path/anything - */ - public void setProtectedTargets(String[] targets) - { - Index.Builder builder = new Index.Builder<>(); - if (targets != null) - { - for (String t : targets) - { - if (!t.startsWith("/")) - throw new IllegalArgumentException("Bad protected target: " + t); - - builder.with(t, ProtectedTargetType.EXACT); - builder.with(t + "/", ProtectedTargetType.PREFIX); - builder.with(t + "?", ProtectedTargetType.PREFIX); - builder.with(t + "#", ProtectedTargetType.PREFIX); - builder.with(t + ";", ProtectedTargetType.PREFIX); - } - } - _protectedTargets = builder.caseSensitive(false).build(); - } - - public String[] getProtectedTargets() - { - if (_protectedTargets == null) + if (getHandler() == null) return null; - return _protectedTargets.keySet().stream() - .filter(s -> _protectedTargets.get(s) == ProtectedTargetType.EXACT) - .toArray(String[]::new); + if (!checkVirtualHost(request)) + return null; + + String pathInContext = getPathInContext(request); + if (pathInContext == null) + return null; + + if (pathInContext.isEmpty() && !getAllowNullPathInContext()) + return this::processMovedPermanently; + + // TODO check availability and maybe return a 503 + if (!isAvailable() && isStarted()) + return this::processUnavailable; + + ContextRequest contextRequest = wrap(request, pathInContext); + // wrap might fail (eg ServletContextHandler could not match a servlet) + if (contextRequest == null) + return null; + + Request.Processor processor = processByContextHandler(contextRequest); + if (processor != null) + return processor; + + return contextRequest.wrapProcessor(_context.get(contextRequest, contextRequest)); } - @Override - public void removeAttribute(String name) + protected void processMovedPermanently(Request request, Response response, Callback callback) { - _attributes.removeAttribute(name); + String location = _contextPath + "/"; + if (request.getHttpURI().getParam() != null) + location += ";" + request.getHttpURI().getParam(); + if (request.getHttpURI().getQuery() != null) + location += ";" + request.getHttpURI().getQuery(); + + response.setStatus(HttpStatus.MOVED_PERMANENTLY_301); + response.getHeaders().add(new HttpField(HttpHeader.LOCATION, location)); + callback.succeeded(); } - /* - * Set a context attribute. Attributes set via this API cannot be overridden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of - * a context. No attribute listener events are triggered by this API. - * - * @see jakarta.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object) - */ - @Override - public void setAttribute(String name, Object value) + protected void processUnavailable(Request request, Response response, Callback callback) { - _attributes.setAttribute(name, value); + Response.writeError(request, response, callback, HttpStatus.SERVICE_UNAVAILABLE_503, null); } - /** - * @param attributes The attributes to set. - */ - public void setAttributes(Attributes attributes) + protected Request.Processor processByContextHandler(ContextRequest contextRequest) { - _attributes.clearAttributes(); - _attributes.addAll(attributes); - } - - @Override - public void clearAttributes() - { - _attributes.clearAttributes(); - } - - /** - * @param classLoader The classLoader to set. - */ - public void setClassLoader(ClassLoader classLoader) - { - if (isStarted()) - throw new IllegalStateException(getState()); - _classLoader = classLoader; - } - - /** - * Set the default context path. - * A default context path may be overriden by a default-context-path element - * in a web.xml - * - * @param contextPath The _contextPath to set. - */ - public void setDefaultContextPath(String contextPath) - { - setContextPath(contextPath); - _contextPathDefault = true; - } - - public void setDefaultRequestCharacterEncoding(String encoding) - { - _defaultRequestCharacterEncoding = encoding; - } - - public String getDefaultRequestCharacterEncoding() - { - return _defaultRequestCharacterEncoding; - } - - public void setDefaultResponseCharacterEncoding(String encoding) - { - _defaultResponseCharacterEncoding = encoding; - } - - public String getDefaultResponseCharacterEncoding() - { - return _defaultResponseCharacterEncoding; - } - - /** - * @return True if the current contextPath is from default settings - */ - public boolean isContextPathDefault() - { - return _contextPathDefault; + if (!_allowNullPathInContext && StringUtil.isEmpty(contextRequest.getPathInContext())) + { + return (request, response, callback) -> + { + // context request must end with / + String queryString = request.getHttpURI().getQuery(); + Response.sendRedirect(request, response, callback, + HttpStatus.MOVED_TEMPORARILY_302, + request.getHttpURI().getPath() + (queryString == null ? "/" : ("/?" + queryString)), + true); + }; + } + return null; } /** @@ -1622,42 +613,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu */ public void setContextPath(String contextPath) { - if (contextPath == null) - throw new IllegalArgumentException("null contextPath"); - - if (contextPath.endsWith("/*")) - { - LOG.warn("{} contextPath ends with /*", this); - contextPath = contextPath.substring(0, contextPath.length() - 2); - } - else if (contextPath.length() > 1 && contextPath.endsWith("/")) - { - LOG.warn("{} contextPath ends with /", this); - contextPath = contextPath.substring(0, contextPath.length() - 1); - } - - if (contextPath.length() == 0) - { - LOG.warn("Empty contextPath"); - contextPath = "/"; - } - + if (isStarted()) + throw new IllegalStateException(getState()); _contextPath = contextPath; - _contextPathEncoded = URIUtil.encodePath(contextPath); - _contextPathDefault = false; - - if (getServer() != null && (getServer().isStarting() || getServer().isStarted())) - { - Class handlerClass = ContextHandlerCollection.class; - Handler[] contextCollections = getServer().getChildHandlersByClass(handlerClass); - if (contextCollections != null) - { - for (Handler contextCollection : contextCollections) - { - handlerClass.cast(contextCollection).mapContexts(); - } - } - } } /** @@ -1668,384 +626,82 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu _displayName = servletContextName; } - /** - * @return Returns the resourceBase. - */ - public Resource getBaseResource() - { - if (_baseResource == null) - return null; - return _baseResource; - } - /** * @return Returns the base resource as a string. */ @ManagedAttribute("document root for context") - public String getResourceBase() + public Path getResourceBase() { - if (_baseResource == null) - return null; - return _baseResource.toString(); + return _resourceBase; } /** * Set the base resource for this context. * - * @param base The resource used as the base for all static content of this context. - * @see #setResourceBase(String) + * @param resourceBase The Path of the base resource for the context. */ - public void setBaseResource(Resource base) + public void setResourceBase(Path resourceBase) { if (isStarted()) - throw new IllegalStateException("Cannot call setBaseResource after starting"); - _baseResource = base; - } - - /** - * Set the base resource for this context. - * - * @param resourceBase A string representing the base resource for the context. Any string accepted by {@link Resource#newResource(String)} may be passed and the - * call is equivalent to setBaseResource(newResource(resourceBase)); - */ - public void setResourceBase(String resourceBase) - { - try - { - setBaseResource(newResource(resourceBase)); - } - catch (Exception e) - { - if (LOG.isDebugEnabled()) - LOG.warn("Unable to set baseResource: {}", resourceBase, e); - else - LOG.warn(e.toString()); - throw new IllegalArgumentException(resourceBase); - } - } - - /** - * @return Returns the mimeTypes. - */ - public MimeTypes getMimeTypes() - { - if (_mimeTypes == null) - _mimeTypes = new MimeTypes(); - return _mimeTypes; - } - - /** - * @param mimeTypes The mimeTypes to set. - */ - public void setMimeTypes(MimeTypes mimeTypes) - { - _mimeTypes = mimeTypes; - } - - public void setWelcomeFiles(String[] files) - { - _welcomeFiles = files; - } - - /** - * @return The names of the files which the server should consider to be welcome files in this context. - * @see The Servlet Specification - * @see #setWelcomeFiles - */ - @ManagedAttribute(value = "Partial URIs of directory welcome files", readonly = true) - public String[] getWelcomeFiles() - { - return _welcomeFiles; + throw new IllegalStateException(getState()); + _resourceBase = resourceBase; } /** * @return Returns the errorHandler. */ - @ManagedAttribute("The error handler to use for the context") - public ErrorHandler getErrorHandler() + @ManagedAttribute("The error processor to use for the context") + public Request.Processor getErrorProcessor() { - return _errorHandler; + // TODO, do we need to wrap this so that we can establish the context + // Classloader? Or will the caller already do that? + return _errorProcessor; } /** - * @param errorHandler The errorHandler to set. + * @param errorProcessor The error processor to set. */ - public void setErrorHandler(ErrorHandler errorHandler) + public void setErrorProcessor(Request.Processor errorProcessor) { - if (errorHandler != null) - errorHandler.setServer(getServer()); - updateBean(_errorHandler, errorHandler, true); - _errorHandler = errorHandler; + updateBean(_errorProcessor, errorProcessor, true); + _errorProcessor = errorProcessor; } - @ManagedAttribute("The maximum content size") - public int getMaxFormContentSize() + protected ContextRequest wrap(Request request, String pathInContext) { - return _maxFormContentSize; + return new ContextRequest(this, _context, request, pathInContext); } - /** - * Set the maximum size of a form post, to protect against DOS attacks from large forms. - * - * @param maxSize the maximum size of the form content (in bytes) - */ - public void setMaxFormContentSize(int maxSize) + @Override + public void clearAttributes() { - _maxFormContentSize = maxSize; - } - - public int getMaxFormKeys() - { - return _maxFormKeys; - } - - /** - * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys. - * - * @param max the maximum number of form keys - */ - public void setMaxFormKeys(int max) - { - _maxFormKeys = max; - } - - /** - * @return True if URLs are compacted to replace multiple '/'s with a single '/' - * @deprecated use {@code CompactPathRule} with {@code RewriteHandler} instead. - */ - @Deprecated - public boolean isCompactPath() - { - return _compactPath; - } - - /** - * @param compactPath True if URLs are compacted to replace multiple '/'s with a single '/' - */ - @Deprecated - public void setCompactPath(boolean compactPath) - { - _compactPath = compactPath; + _persistentAttributes.clearAttributes(); } @Override public String toString() { - final String[] vhosts = getVirtualHosts(); - + List vhosts = getVirtualHosts(); StringBuilder b = new StringBuilder(); - Package pkg = getClass().getPackage(); - if (pkg != null) - { - String p = pkg.getName(); - if (p != null && p.length() > 0) - { - String[] ss = p.split("\\."); - for (String s : ss) - { - b.append(s.charAt(0)).append('.'); - } - } - } - b.append(getClass().getSimpleName()).append('@').append(Integer.toString(hashCode(), 16)); + b.append(TypeUtil.toShortName(getClass())).append('@').append(Integer.toString(hashCode(), 16)); b.append('{'); if (getDisplayName() != null) b.append(getDisplayName()).append(','); - b.append(getContextPath()).append(',').append(getBaseResource()).append(',').append(_availability.get()); + b.append(getContextPath()).append(',').append(getResourceBase()).append(',').append(isAvailable()); - if (vhosts != null && vhosts.length > 0) - b.append(',').append(vhosts[0]); + for (String vh : vhosts) + { + b.append(',').append(vh); + } b.append('}'); return b.toString(); } - public Class loadClass(String className) throws ClassNotFoundException - { - if (className == null) - return null; - - if (_classLoader == null) - return Loader.loadClass(className); - - return _classLoader.loadClass(className); - } - - public void addLocaleEncoding(String locale, String encoding) - { - if (_localeEncodingMap == null) - _localeEncodingMap = new HashMap<>(); - _localeEncodingMap.put(locale, encoding); - } - - public String getLocaleEncoding(String locale) - { - if (_localeEncodingMap == null) - return null; - String encoding = _localeEncodingMap.get(locale); - return encoding; - } - - /** - * Get the character encoding for a locale. The full locale name is first looked up in the map of encodings. If no encoding is found, then the locale - * language is looked up. - * - * @param locale a Locale value - * @return a String representing the character encoding for the locale or null if none found. - */ - public String getLocaleEncoding(Locale locale) - { - if (_localeEncodingMap == null) - return null; - String encoding = _localeEncodingMap.get(locale.toString()); - if (encoding == null) - encoding = _localeEncodingMap.get(locale.getLanguage()); - return encoding; - } - - /** - * Get all of the locale encodings - * - * @return a map of all the locale encodings: key is name of the locale and value is the char encoding - */ - public Map getLocaleEncodings() - { - if (_localeEncodingMap == null) - return null; - return Collections.unmodifiableMap(_localeEncodingMap); - } - - /** - * Attempt to get a Resource from the Context. - * - * @param pathInContext the path within the base resource to attempt to get - * @return the resource, or null if not available. - * @throws MalformedURLException if unable to form a Resource from the provided path - */ - public Resource getResource(String pathInContext) throws MalformedURLException - { - if (pathInContext == null || !pathInContext.startsWith(URIUtil.SLASH)) - throw new MalformedURLException(pathInContext); - - if (_baseResource == null) - return null; - - try - { - // addPath with accept non-canonical paths that don't go above the root, - // but will treat them as aliases. So unless allowed by an AliasChecker - // they will be rejected below. - Resource resource = _baseResource.addPath(pathInContext); - - if (checkAlias(pathInContext, resource)) - return resource; - return null; - } - catch (Exception e) - { - LOG.trace("IGNORED", e); - } - - return null; - } - - /** - * @param path the path to check the alias for - * @param resource the resource - * @return True if the alias is OK - */ - public boolean checkAlias(String path, Resource resource) - { - // Is the resource aliased? - if (resource.isAlias()) - { - if (LOG.isDebugEnabled()) - LOG.debug("Aliased resource: {}~={}", resource, resource.getAlias()); - - // alias checks - for (AliasCheck check : getAliasChecks()) - { - if (check.check(path, resource)) - { - if (LOG.isDebugEnabled()) - LOG.debug("Aliased resource: {} approved by {}", resource, check); - return true; - } - } - return false; - } - return true; - } - - /** - * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations. - * - * @param url the url to convert to a Resource - * @return the Resource for that url - * @throws IOException if unable to create a Resource from the URL - */ - public Resource newResource(URL url) throws IOException - { - return Resource.newResource(url); - } - - /** - * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations. - * - * @param uri the URI to convert to a Resource - * @return the Resource for that URI - * @throws IOException if unable to create a Resource from the URL - */ - public Resource newResource(URI uri) throws IOException - { - return Resource.newResource(uri); - } - - /** - * Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}. - * - * @param urlOrPath The URL or path to convert - * @return The Resource for the URL/path - * @throws IOException The Resource could not be created. - */ - public Resource newResource(String urlOrPath) throws IOException - { - return Resource.newResource(urlOrPath); - } - - public Set getResourcePaths(String path) - { - try - { - Resource resource = getResource(path); - - if (resource != null && resource.exists()) - { - if (!path.endsWith(URIUtil.SLASH)) - path = path + URIUtil.SLASH; - - String[] l = resource.list(); - if (l != null) - { - HashSet set = new HashSet<>(); - for (int i = 0; i < l.length; i++) - { - set.add(path + l[i]); - } - return set; - } - } - } - catch (Exception e) - { - LOG.trace("IGNORED", e); - } - return Collections.emptySet(); - } - private String normalizeHostname(String host) { + // TODO is this needed? if so, should be it somewhere eles? if (host == null) return null; int connectorIndex = host.indexOf('@'); @@ -2064,964 +720,219 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu return host; } - /** - * Add an AliasCheck instance to possibly permit aliased resources - * - * @param check The alias checker - */ - public void addAliasCheck(AliasCheck check) + public class Context extends Attributes.Layer implements org.eclipse.jetty.server.Context { - _aliasChecks.add(check); - if (check instanceof LifeCycle) - addManaged((LifeCycle)check); - else - addBean(check); - } - - /** - * @return Immutable list of Alias checks - */ - public List getAliasChecks() - { - return Collections.unmodifiableList(_aliasChecks); - } - - /** - * @param checks list of AliasCheck instances - */ - public void setAliasChecks(List checks) - { - clearAliasChecks(); - checks.forEach(this::addAliasCheck); - } - - /** - * clear the list of AliasChecks - */ - public void clearAliasChecks() - { - _aliasChecks.forEach(this::removeBean); - _aliasChecks.clear(); - } - - /** - * Context. - *

    - * A partial implementation of {@link jakarta.servlet.ServletContext}. A complete implementation is provided by the - * derived {@link ContextHandler} implementations. - *

    - */ - public class Context extends StaticContext - { - protected boolean _enabled = true; // whether or not the dynamic API is enabled for callers - protected boolean _extendedListenerTypes = false; - - protected Context() + public Context() { + // TODO Should the ScopedContext attributes be a layer over the ServerContext attributes? + super(_persistentAttributes); } - public ContextHandler getContextHandler() + @SuppressWarnings("unchecked") + public H getContextHandler() { - return ContextHandler.this; - } - - @Override - public ServletContext getContext(String uripath) - { - List contexts = new ArrayList<>(); - Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class); - String matchedPath = null; - - for (Handler handler : handlers) - { - if (handler == null) - continue; - ContextHandler ch = (ContextHandler)handler; - String contextPath = ch.getContextPath(); - - if (uripath.equals(contextPath) || - (uripath.startsWith(contextPath) && uripath.charAt(contextPath.length()) == '/') || - "/".equals(contextPath)) - { - // look first for vhost matching context only - if (getVirtualHosts() != null && getVirtualHosts().length > 0) - { - if (ch.getVirtualHosts() != null && ch.getVirtualHosts().length > 0) - { - for (String h1 : getVirtualHosts()) - { - for (String h2 : ch.getVirtualHosts()) - { - if (h1.equals(h2)) - { - if (matchedPath == null || contextPath.length() > matchedPath.length()) - { - contexts.clear(); - matchedPath = contextPath; - } - - if (matchedPath.equals(contextPath)) - contexts.add(ch); - } - } - } - } - } - else - { - if (matchedPath == null || contextPath.length() > matchedPath.length()) - { - contexts.clear(); - matchedPath = contextPath; - } - - if (matchedPath.equals(contextPath)) - contexts.add(ch); - } - } - } - - if (contexts.size() > 0) - return contexts.get(0)._scontext; - - // try again ignoring virtual hosts - matchedPath = null; - for (Handler handler : handlers) - { - if (handler == null) - continue; - ContextHandler ch = (ContextHandler)handler; - String contextPath = ch.getContextPath(); - - if (uripath.equals(contextPath) || (uripath.startsWith(contextPath) && uripath.charAt(contextPath.length()) == '/') || "/".equals(contextPath)) - { - if (matchedPath == null || contextPath.length() > matchedPath.length()) - { - contexts.clear(); - matchedPath = contextPath; - } - - if (matchedPath.equals(contextPath)) - contexts.add(ch); - } - } - - if (contexts.size() > 0) - return contexts.get(0)._scontext; - return null; - } - - @Override - public String getMimeType(String file) - { - if (_mimeTypes == null) - return null; - return _mimeTypes.getMimeByExtension(file); - } - - @Override - public RequestDispatcher getRequestDispatcher(String uriInContext) - { - // uriInContext is encoded, potentially with query. - if (uriInContext == null) - return null; - - if (!uriInContext.startsWith("/")) - return null; - - try - { - String contextPath = getContextPath(); - // uriInContext is canonicalized by HttpURI. - HttpURI.Mutable uri = HttpURI.build(uriInContext); - String pathInfo = uri.getDecodedPath(); - if (StringUtil.isEmpty(pathInfo)) - return null; - - if (!StringUtil.isEmpty(contextPath)) - { - uri.path(URIUtil.addPaths(contextPath, uri.getPath())); - pathInfo = uri.getDecodedPath().substring(contextPath.length()); - } - return new Dispatcher(ContextHandler.this, uri, pathInfo); - } - catch (Exception e) - { - LOG.trace("IGNORED", e); - } - return null; - } - - @Override - public String getRealPath(String path) - { - // This is an API call from the application which may have arbitrary non canonical paths passed - // Thus we canonicalize here, to avoid the enforcement of only canonical paths in - // ContextHandler.this.getResource(path). - path = URIUtil.canonicalPath(path); - if (path == null) - return null; - if (path.length() == 0) - path = URIUtil.SLASH; - else if (path.charAt(0) != '/') - path = URIUtil.SLASH + path; - - try - { - Resource resource = ContextHandler.this.getResource(path); - if (resource != null) - { - File file = resource.getFile(); - if (file != null) - return file.getCanonicalPath(); - } - } - catch (Exception e) - { - LOG.trace("IGNORED", e); - } - - return null; - } - - @Override - public URL getResource(String path) throws MalformedURLException - { - // This is an API call from the application which may have arbitrary non canonical paths passed - // Thus we canonicalize here, to avoid the enforcement of only canonical paths in - // ContextHandler.this.getResource(path). - path = URIUtil.canonicalPath(path); - if (path == null) - return null; - Resource resource = ContextHandler.this.getResource(path); - if (resource != null && resource.exists()) - return resource.getURI().toURL(); - return null; - } - - @Override - public InputStream getResourceAsStream(String path) - { - try - { - URL url = getResource(path); - if (url == null) - return null; - Resource r = Resource.newResource(url); - // Cannot serve directories as an InputStream - if (r.isDirectory()) - return null; - return r.getInputStream(); - } - catch (Exception e) - { - LOG.trace("IGNORED", e); - return null; - } - } - - @Override - public Set getResourcePaths(String path) - { - // This is an API call from the application which may have arbitrary non canonical paths passed - // Thus we canonicalize here, to avoid the enforcement of only canonical paths in - // ContextHandler.this.getResource(path). - path = URIUtil.canonicalPath(path); - if (path == null) - return null; - return ContextHandler.this.getResourcePaths(path); - } - - @Override - public void log(Exception exception, String msg) - { - _logger.warn(msg, exception); - } - - @Override - public void log(String msg) - { - _logger.info(msg); - } - - @Override - public void log(String message, Throwable throwable) - { - if (throwable == null) - _logger.warn(message); - else - _logger.warn(message, throwable); - } - - @Override - public String getInitParameter(String name) - { - return ContextHandler.this.getInitParameter(name); - } - - @Override - public Enumeration getInitParameterNames() - { - return ContextHandler.this.getInitParameterNames(); + return (H)ContextHandler.this; } @Override public Object getAttribute(String name) { - Object o = ContextHandler.this.getAttribute(name); - if (o == null) - o = super.getAttribute(name); - return o; + // TODO the Attributes.Layer is a little different to previous + // behaviour. We need to verify if that is OK + return super.getAttribute(name); } @Override - public Enumeration getAttributeNames() + public Request.Processor getErrorProcessor() { - HashSet set = new HashSet<>(); - Enumeration e = super.getAttributeNames(); - while (e.hasMoreElements()) - { - set.add(e.nextElement()); - } - e = ContextHandler.this.getAttributeNames(); - while (e.hasMoreElements()) - { - set.add(e.nextElement()); - } - return Collections.enumeration(set); - } - - @Override - public void setAttribute(String name, Object value) - { - Object oldValue = super.getAttribute(name); - - if (value == null) - super.removeAttribute(name); - else - super.setAttribute(name, value); - - if (!_servletContextAttributeListeners.isEmpty()) - { - ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext, name, oldValue == null ? value : oldValue); - - for (ServletContextAttributeListener listener : _servletContextAttributeListeners) - { - if (oldValue == null) - listener.attributeAdded(event); - else if (value == null) - listener.attributeRemoved(event); - else - listener.attributeReplaced(event); - } - } - } - - @Override - public void removeAttribute(String name) - { - Object oldValue = super.getAttribute(name); - super.removeAttribute(name); - if (oldValue != null && !_servletContextAttributeListeners.isEmpty()) - { - ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext, name, oldValue); - for (ServletContextAttributeListener listener : _servletContextAttributeListeners) - { - listener.attributeRemoved(event); - } - } - } - - @Override - public String getServletContextName() - { - String name = ContextHandler.this.getDisplayName(); - if (name == null) - name = ContextHandler.this.getContextPath(); - return name; + Request.Processor processor = ContextHandler.this.getErrorProcessor(); + if (processor == null) + processor = getServer().getErrorProcessor(); + return processor; } @Override public String getContextPath() { - return getRequestContextPath(); + return _contextPath; } @Override public String toString() { - return "ServletContext@" + ContextHandler.this.toString(); - } - - @Override - public boolean setInitParameter(String name, String value) - { - if (ContextHandler.this.getInitParameter(name) != null) - return false; - ContextHandler.this.getInitParams().put(name, value); - return true; - } - - @Override - public void addListener(String className) - { - if (!_enabled) - throw new UnsupportedOperationException(); - - try - { - @SuppressWarnings( - {"unchecked", "rawtypes"}) - Class clazz = _classLoader == null ? Loader.loadClass(className) : (Class)_classLoader.loadClass(className); - addListener(clazz); - } - catch (ClassNotFoundException e) - { - throw new IllegalArgumentException(e); - } - } - - @Override - public void addListener(T t) - { - if (!_enabled) - throw new UnsupportedOperationException(); - - checkListener(t.getClass()); - - ContextHandler.this.addEventListener(t); - ContextHandler.this.addProgrammaticListener(t); - } - - @Override - public void addListener(Class listenerClass) - { - if (!_enabled) - throw new UnsupportedOperationException(); - - try - { - EventListener e = createListener(listenerClass); - addListener(e); - } - catch (ServletException e) - { - throw new IllegalArgumentException(e); - } - } - - public void checkListener(Class listener) throws IllegalStateException - { - boolean ok = false; - int startIndex = (isExtendedListenerTypes() ? EXTENDED_LISTENER_TYPE_INDEX : DEFAULT_LISTENER_TYPE_INDEX); - for (int i = startIndex; i < SERVLET_LISTENER_TYPES.length; i++) - { - if (SERVLET_LISTENER_TYPES[i].isAssignableFrom(listener)) - { - ok = true; - break; - } - } - if (!ok) - throw new IllegalArgumentException("Inappropriate listener class " + listener.getName()); - } - - public void setExtendedListenerTypes(boolean extended) - { - _extendedListenerTypes = extended; - } - - public boolean isExtendedListenerTypes() - { - return _extendedListenerTypes; + return "%s@%x".formatted(getClass().getSimpleName(), hashCode()); } @Override public ClassLoader getClassLoader() { - if (!_enabled) - throw new UnsupportedOperationException(); + return _classLoader; + } - // no security manager just return the classloader - if (!isUsingSecurityManager()) + @Override + public Path getResourceBase() + { + return _resourceBase; + } + + @Override + public List getVirtualHosts() + { + return ContextHandler.this.getVirtualHosts(); + } + + public T get(Supplier supplier, Request request) + { + Context lastContext = __context.get(); + if (lastContext == this) + return supplier.get(); + + ClassLoader loader = getClassLoader(); + ClassLoader lastLoader = Thread.currentThread().getContextClassLoader(); + try { - return _classLoader; + __context.set(this); + if (loader != null) + Thread.currentThread().setContextClassLoader(loader); + + enterScope(request); + return supplier.get(); } + finally + { + exitScope(request); + __context.set(lastContext); + if (loader != null) + Thread.currentThread().setContextClassLoader(lastLoader); + } + } + + public void call(Invocable.Callable callable, Request request) throws Exception + { + Context lastContext = __context.get(); + if (lastContext == this) + callable.call(); else { - // check to see if the classloader of the caller is the same as the context - // classloader, or a parent of it, as required by the javadoc specification. - - // Wrap in a PrivilegedAction so that only Jetty code will require the - // "createSecurityManager" permission, not also application code that calls this method. - Caller caller = AccessController.doPrivileged((PrivilegedAction)Caller::new); - ClassLoader callerLoader = caller.getCallerClassLoader(2); - while (callerLoader != null) + ClassLoader loader = getClassLoader(); + ClassLoader lastLoader = Thread.currentThread().getContextClassLoader(); + try { - if (callerLoader == _classLoader) - return _classLoader; - else - callerLoader = callerLoader.getParent(); + __context.set(this); + if (loader != null) + Thread.currentThread().setContextClassLoader(loader); + + enterScope(request); + callable.call(); + } + finally + { + exitScope(request); + __context.set(lastContext); + if (loader != null) + Thread.currentThread().setContextClassLoader(lastLoader); } - System.getSecurityManager().checkPermission(new RuntimePermission("getClassLoader")); - return _classLoader; } } - @Override - public JspConfigDescriptor getJspConfigDescriptor() + public void accept(Consumer consumer, Throwable t, Request request) { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getJspConfigDescriptor()"); - return null; - } - - public void setJspConfigDescriptor(JspConfigDescriptor d) - { - - } - - @Override - public void declareRoles(String... roleNames) - { - if (!isStarting()) - throw new IllegalStateException(); - if (!_enabled) - throw new UnsupportedOperationException(); - } - - public void setEnabled(boolean enabled) - { - _enabled = enabled; - } - - public boolean isEnabled() - { - return _enabled; - } - - @Override - public String getVirtualServerName() - { - String[] hosts = getVirtualHosts(); - if (hosts != null && hosts.length > 0) - return hosts[0]; - return null; - } - } - - /** - * A simple implementation of ServletContext that is used when there is no - * ContextHandler. This is also used as the base for all other ServletContext - * implementations. - */ - public static class StaticContext extends AttributesMap implements ServletContext - { - private int _effectiveMajorVersion = SERVLET_MAJOR_VERSION; - private int _effectiveMinorVersion = SERVLET_MINOR_VERSION; - - @Override - public ServletContext getContext(String uripath) - { - return null; - } - - @Override - public int getMajorVersion() - { - return SERVLET_MAJOR_VERSION; - } - - @Override - public String getMimeType(String file) - { - return null; - } - - @Override - public int getMinorVersion() - { - return SERVLET_MINOR_VERSION; - } - - @Override - public RequestDispatcher getNamedDispatcher(String name) - { - return null; - } - - @Override - public RequestDispatcher getRequestDispatcher(String uriInContext) - { - return null; - } - - @Override - public String getRealPath(String path) - { - return null; - } - - @Override - public URL getResource(String path) throws MalformedURLException - { - return null; - } - - @Override - public InputStream getResourceAsStream(String path) - { - return null; - } - - @Override - public Set getResourcePaths(String path) - { - return null; - } - - @Override - public String getServerInfo() - { - return ContextHandler.getServerInfo(); - } - - @Override - @Deprecated(since = "Servlet API 2.1") - public Servlet getServlet(String name) throws ServletException - { - return null; - } - - @Override - @Deprecated(since = "Servlet API 2.1") - public Enumeration getServletNames() - { - return Collections.enumeration(Collections.EMPTY_LIST); - } - - @Override - @Deprecated(since = "Servlet API 2.0") - public Enumeration getServlets() - { - return Collections.enumeration(Collections.EMPTY_LIST); - } - - @Override - @Deprecated(since = "Servlet API 2.1") - public void log(Exception exception, String msg) - { - LOG.warn(msg, exception); - } - - @Override - public void log(String msg) - { - LOG.info(msg); - } - - @Override - public void log(String message, Throwable throwable) - { - LOG.warn(message, throwable); - } - - @Override - public String getInitParameter(String name) - { - return null; - } - - @SuppressWarnings("unchecked") - @Override - public Enumeration getInitParameterNames() - { - return Collections.enumeration(Collections.EMPTY_LIST); - } - - @Override - public String getServletContextName() - { - return "No Context"; - } - - @Override - public String getContextPath() - { - return null; - } - - @Override - public boolean setInitParameter(String name, String value) - { - return false; - } - - @Override - public Dynamic addFilter(String filterName, Class filterClass) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addFilter(String, Class)"); - return null; - } - - @Override - public Dynamic addFilter(String filterName, Filter filter) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addFilter(String, Filter)"); - return null; - } - - @Override - public Dynamic addFilter(String filterName, String className) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addFilter(String, String)"); - return null; - } - - @Override - public jakarta.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class servletClass) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addServlet(String, Class)"); - return null; - } - - @Override - public jakarta.servlet.ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addServlet(String, Servlet)"); - return null; - } - - @Override - public jakarta.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addServlet(String, String)"); - return null; - } - - /** - * @since Servlet 4.0 - */ - @Override - public ServletRegistration.Dynamic addJspFile(String servletName, String jspFile) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addJspFile(String, String)"); - return null; - } - - @Override - public Set getDefaultSessionTrackingModes() - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getDefaultSessionTrackingModes()"); - return null; - } - - @Override - public Set getEffectiveSessionTrackingModes() - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getEffectiveSessionTrackingModes()"); - return null; - } - - @Override - public FilterRegistration getFilterRegistration(String filterName) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getFilterRegistration(String)"); - return null; - } - - @Override - public Map getFilterRegistrations() - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getFilterRegistrations()"); - return null; - } - - @Override - public ServletRegistration getServletRegistration(String servletName) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getServletRegistration(String)"); - return null; - } - - @Override - public Map getServletRegistrations() - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getServletRegistrations()"); - return null; - } - - @Override - public SessionCookieConfig getSessionCookieConfig() - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getSessionCookieConfig()"); - return null; - } - - @Override - public void setSessionTrackingModes(Set sessionTrackingModes) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setSessionTrackingModes(Set)"); - } - - @Override - public void addListener(String className) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addListener(String)"); - } - - @Override - public void addListener(T t) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addListener(T)"); - } - - @Override - public void addListener(Class listenerClass) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addListener(Class)"); - } - - public T createInstance(Class clazz) throws ServletException - { - try + Context lastContext = __context.get(); + if (lastContext == this) + consumer.accept(t); + else { - return clazz.getDeclaredConstructor().newInstance(); + ClassLoader loader = getClassLoader(); + ClassLoader lastLoader = Thread.currentThread().getContextClassLoader(); + try + { + __context.set(this); + if (loader != null) + Thread.currentThread().setContextClassLoader(loader); + enterScope(request); + consumer.accept(t); + } + finally + { + exitScope(request); + __context.set(lastContext); + if (loader != null) + Thread.currentThread().setContextClassLoader(lastLoader); + } } - catch (Exception e) + } + + @Override + public void run(Runnable runnable) + { + run(runnable, null); + } + + public void run(Runnable runnable, Request request) + { + Context lastContext = __context.get(); + if (lastContext == this) + runnable.run(); + else { - throw new ServletException(e); + ClassLoader loader = getClassLoader(); + ClassLoader lastLoader = Thread.currentThread().getContextClassLoader(); + try + { + __context.set(this); + if (loader != null) + Thread.currentThread().setContextClassLoader(loader); + enterScope(request); + runnable.run(); + } + finally + { + exitScope(request); + __context.set(lastContext); + if (loader != null) + Thread.currentThread().setContextClassLoader(lastLoader); + } } } @Override - public T createListener(Class clazz) throws ServletException + public void execute(Runnable runnable) { - return createInstance(clazz); + getServer().getContext().execute(() -> run(runnable)); + } + + protected DecoratedObjectFactory getDecoratedObjectFactory() + { + DecoratedObjectFactory factory = ContextHandler.this.getBean(DecoratedObjectFactory.class); + if (factory != null) + return factory; + return getServer().getBean(DecoratedObjectFactory.class); } @Override - public T createServlet(Class clazz) throws ServletException + public T decorate(T o) { - return createInstance(clazz); + DecoratedObjectFactory factory = getDecoratedObjectFactory(); + if (factory != null) + return factory.decorate(o); + return o; } @Override - public T createFilter(Class clazz) throws ServletException + public void destroy(Object o) { - return createInstance(clazz); - } - - @Override - public ClassLoader getClassLoader() - { - return ContextHandler.class.getClassLoader(); - } - - @Override - public int getEffectiveMajorVersion() - { - return _effectiveMajorVersion; - } - - @Override - public int getEffectiveMinorVersion() - { - return _effectiveMinorVersion; - } - - public void setEffectiveMajorVersion(int v) - { - _effectiveMajorVersion = v; - } - - public void setEffectiveMinorVersion(int v) - { - _effectiveMinorVersion = v; - } - - @Override - public JspConfigDescriptor getJspConfigDescriptor() - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getJspConfigDescriptor()"); - return null; - } - - @Override - public void declareRoles(String... roleNames) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "declareRoles(String...)"); - } - - @Override - public String getVirtualServerName() - { - return null; - } - - /** - * @since Servlet 4.0 - */ - @Override - public int getSessionTimeout() - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getSessionTimeout()"); - return 0; - } - - /** - * @since Servlet 4.0 - */ - @Override - public void setSessionTimeout(int sessionTimeout) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setSessionTimeout(int)"); - } - - /** - * @since Servlet 4.0 - */ - @Override - public String getRequestCharacterEncoding() - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getRequestCharacterEncoding()"); - return null; - } - - /** - * @since Servlet 4.0 - */ - @Override - public void setRequestCharacterEncoding(String encoding) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setRequestCharacterEncoding(String)"); - } - - /** - * @since Servlet 4.0 - */ - @Override - public String getResponseCharacterEncoding() - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "getResponseCharacterEncoding()"); - return null; - } - - /** - * @since Servlet 4.0 - */ - @Override - public void setResponseCharacterEncoding(String encoding) - { - LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "setResponseCharacterEncoding(String)"); + DecoratedObjectFactory factory = getDecoratedObjectFactory(); + if (factory != null) + factory.destroy(o); } } /** * Interface to check aliases + * TODO REVIEW!!! */ public interface AliasCheck { - /** * Check an alias * @@ -3032,78 +943,40 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu boolean check(String pathInContext, Resource resource); } - /** - * Approve all aliases. - * @deprecated use {@link org.eclipse.jetty.server.AllowedResourceAliasChecker} instead. - */ - @Deprecated - public static class ApproveAliases implements AliasCheck - { - public ApproveAliases() - { - LOG.warn("ApproveAliases is deprecated"); - } - - @Override - public boolean check(String pathInContext, Resource resource) - { - return true; - } - } - - /** - * Approve Aliases of a non existent directory. If a directory "/foobar/" does not exist, then the resource is aliased to "/foobar". Accept such aliases. - */ - @Deprecated - public static class ApproveNonExistentDirectoryAliases implements AliasCheck - { - @Override - public boolean check(String pathInContext, Resource resource) - { - if (resource.exists()) - return false; - - String a = resource.getAlias().toString(); - String r = resource.getURI().toString(); - - if (a.length() > r.length()) - return a.startsWith(r) && a.length() == r.length() + 1 && a.endsWith("/"); - if (a.length() < r.length()) - return r.startsWith(a) && r.length() == a.length() + 1 && r.endsWith("/"); - - return a.equals(r); - } - } - /** * Listener for all threads entering context scope, including async IO callbacks */ - public static interface ContextScopeListener extends EventListener + public interface ContextScopeListener extends EventListener { /** * @param context The context being entered * @param request A request that is applicable to the scope, or null - * @param reason An object that indicates the reason the scope is being entered. */ - void enterScope(Context context, Request request, Object reason); + default void enterScope(org.eclipse.jetty.server.Context context, Request request) {} /** * @param context The context being exited * @param request A request that is applicable to the scope, or null */ - void exitScope(Context context, Request request); + default void exitScope(org.eclipse.jetty.server.Context context, Request request) {} } - private static class Caller extends SecurityManager + private static class VHost { - public ClassLoader getCallerClassLoader(int depth) + private final String _vHost; + private final boolean _wild; + private final String _vConnector; + + private VHost(String vHost, boolean wild, String vConnector) { - if (depth < 0) - return null; - Class[] classContext = getClassContext(); - if (classContext.length <= depth) - return null; - return classContext[depth].getClassLoader(); + _vHost = vHost; + _wild = wild; + _vConnector = vConnector; + } + + String getVHost() + { + return _vHost; } } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java index bdabf31e19a..caa2bfe731b 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java @@ -13,21 +13,17 @@ package org.eclipse.jetty.server.handler; -import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.HandlerContainer; -import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.ArrayUtil; import org.eclipse.jetty.util.Callback; @@ -39,28 +35,23 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a + * Creates a * Map of contexts to it's contained handlers based - * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s. + * on the context path and virtual hosts of any contained {@link ContextHandler}s. * The contexts do not need to be directly contained, only children of the contained handlers. * Multiple contexts may have the same context path and they are called in order until one * handles the request. */ @ManagedObject("Context Handler Collection") -public class ContextHandlerCollection extends HandlerCollection +public class ContextHandlerCollection extends Handler.Collection { private static final Logger LOG = LoggerFactory.getLogger(ContextHandlerCollection.class); private final SerializedExecutor _serializedExecutor = new SerializedExecutor(); - public ContextHandlerCollection() - { - super(true); - } - public ContextHandlerCollection(ContextHandler... contexts) { - super(true); - setHandlers(contexts); + if (contexts.length > 0) + setHandlers(contexts); } /** @@ -78,20 +69,19 @@ public class ContextHandlerCollection extends HandlerCollection { while (true) { - Handlers handlers = _handlers.get(); + List handlers = getHandlers(); if (handlers == null) break; - if (updateHandlers(handlers, newHandlers(handlers.getHandlers()))) - break; + super.setHandlers(newHandlers(handlers)); } }); } @Override - protected Handlers newHandlers(Handler[] handlers) + protected List newHandlers(List handlers) { - if (handlers == null || handlers.length == 0) - return null; + if (handlers == null || handlers.size() == 0) + return Collections.emptyList(); // Create map of contextPath to handler Branch // A branch is a Handler that could contain 0 or more ContextHandlers @@ -132,88 +122,73 @@ public class ContextHandlerCollection extends HandlerCollection } @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public Request.Processor handle(Request request) throws Exception { - Mapping mapping = (Mapping)_handlers.get(); + List handlers = getHandlers(); // Handle no contexts - if (mapping == null) - return; - Handler[] handlers = mapping.getHandlers(); - if (handlers == null || handlers.length == 0) - return; + if (handlers == null || handlers.isEmpty()) + return null; + + if (!(handlers instanceof Mapping)) + return super.handle(request); + + Mapping mapping = (Mapping)getHandlers(); // handle only a single context. - if (handlers.length == 1) - { - handlers[0].handle(target, baseRequest, request, response); - return; - } - - // handle async dispatch to specific context - HttpChannelState async = baseRequest.getHttpChannelState(); - if (async.isAsync()) - { - ContextHandler context = async.getContextHandler(); - if (context != null) - { - Handler branch = mapping._contextBranches.get(context); - - if (branch == null) - context.handle(target, baseRequest, request, response); - else - branch.handle(target, baseRequest, request, response); - return; - } - } + if (handlers.size() == 1) + return handlers.get(0).handle(request); // handle many contexts - if (target.startsWith("/")) + Index> pathBranches = mapping._pathBranches; + if (pathBranches == null) + return null; + + String path = request.getPathInContext(); + if (!path.startsWith("/")) { - Index> pathBranches = mapping._pathBranches; - if (pathBranches == null) - return; + super.handle(request); + return null; + } - int limit = target.length() - 1; + int limit = path.length() - 1; - while (limit >= 0) + while (limit >= 0) + { + // Get best match + Map.Entry branches = pathBranches.getBest(path, 1, limit); + + if (branches == null) + break; + + int l = branches.getKey().length(); + if (l == 1 || path.length() == l || path.charAt(l) == '/') { - // Get best match - Map.Entry branches = pathBranches.getBest(target, 1, limit); - - if (branches == null) - break; - - int l = branches.getKey().length(); - if (l == 1 || target.length() == l || target.charAt(l) == '/') + for (Branch branch : branches.getValue()) { - for (Branch branch : branches.getValue()) + try { - branch.getHandler().handle(target, baseRequest, request, response); - if (baseRequest.isHandled()) - return; + Request.Processor processor = branch.getHandler().handle(request); + if (processor != null) + return processor; + } + catch (Throwable t) + { + LOG.warn("Unaccepted error {}", this, t); } } + } - limit = l - 2; - } - } - else - { - for (Handler handler : handlers) - { - handler.handle(target, baseRequest, request, response); - if (baseRequest.isHandled()) - return; - } + limit = l - 2; } + return null; } /** * Thread safe deploy of a Handler. *

    * This method is the equivalent of {@link #addHandler(Handler)}, - * but its execution is non-block and mutually excluded from all + * but its execution is non-blocking and mutually excluded from all * other calls to {@link #deployHandler(Handler, Callback)} and * {@link #undeployHandler(Handler, Callback)}. * The handler may be added after this call returns. @@ -224,9 +199,6 @@ public class ContextHandlerCollection extends HandlerCollection */ public void deployHandler(Handler handler, Callback callback) { - if (handler.getServer() != getServer()) - handler.setServer(getServer()); - _serializedExecutor.execute(new SerializedExecutor.ErrorHandlingTask() { @Override @@ -289,11 +261,11 @@ public class ContextHandlerCollection extends HandlerCollection { _contexts = new ContextHandler[]{(ContextHandler)handler}; } - else if (handler instanceof HandlerContainer) + else if (handler instanceof Handler.Container) { - Handler[] contexts = ((HandlerContainer)handler).getChildHandlersByClass(ContextHandler.class); - _contexts = new ContextHandler[contexts.length]; - System.arraycopy(contexts, 0, _contexts, 0, contexts.length); + List contexts = ((Handler.Container)handler).getDescendants(ContextHandler.class); + _contexts = new ContextHandler[contexts.size()]; + System.arraycopy(contexts, 0, _contexts, 0, contexts.size()); } else _contexts = new ContextHandler[0]; @@ -313,7 +285,7 @@ public class ContextHandlerCollection extends HandlerCollection { for (ContextHandler context : _contexts) { - if (context.getVirtualHosts() != null && context.getVirtualHosts().length > 0) + if (context.getVirtualHosts() != null && context.getVirtualHosts().size() > 0) return true; } return false; @@ -336,12 +308,12 @@ public class ContextHandlerCollection extends HandlerCollection } } - private static class Mapping extends Handlers + private static class Mapping extends ArrayList { private final Map _contextBranches; private final Index> _pathBranches; - private Mapping(Handler[] handlers, Map path2Branches) + private Mapping(List handlers, Map path2Branches) { super(handlers); _pathBranches = new Index.Builder>() diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextRequest.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextRequest.java new file mode 100644 index 00000000000..099d646b11c --- /dev/null +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextRequest.java @@ -0,0 +1,134 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.io.QuietException; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.thread.Invocable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ContextRequest extends Request.WrapperProcessor implements Invocable, Supplier, Runnable +{ + private static final Logger LOG = LoggerFactory.getLogger(ContextRequest.class); + private final String _pathInContext; + private final ContextHandler _contextHandler; + private final ContextHandler.Context _context; + private Response _response; + private Callback _callback; + + protected ContextRequest(ContextHandler contextHandler, ContextHandler.Context context, Request wrapped, String pathInContext) + { + super(wrapped); + _pathInContext = pathInContext; + _contextHandler = contextHandler; + _context = context; + } + + @Override + public Processor get() + { + try + { + return _contextHandler.getHandler().handle(this); + } + catch (Throwable t) + { + // Let's be less verbose with BadMessageExceptions & QuietExceptions + if (!LOG.isDebugEnabled() && (t instanceof BadMessageException || t instanceof QuietException)) + LOG.warn("context bad message {}", t.getMessage()); + else + LOG.warn("context handle failed {}", this, t); + } + return null; + } + + @Override + public void process(Request request, Response response, Callback callback) throws Exception + { + _response = response; + _callback = callback; + _context.run(this, this); + } + + public Callback getCallback() + { + return _callback; + } + + protected ContextResponse newContextResponse(Request request, Response response) + { + return new ContextResponse(_context, request, response); + } + + @Override + public void run() + { + try + { + super.process(this, newContextResponse(this, _response), _callback); + } + catch (Throwable t) + { + Response.writeError(this, _response, _callback, t); + } + } + + @Override + public void demandContent(Runnable onContentAvailable) + { + super.demandContent(() -> _context.run(onContentAvailable, this)); + } + + @Override + public boolean addErrorListener(Predicate onError) + { + return super.addErrorListener(t -> + { + // TODO: implement the line below + // return _context.apply(onError::test, t, ContextRequest.this); + _context.accept(onError::test, t, ContextRequest.this); + return true; + }); + } + + @Override + public org.eclipse.jetty.server.Context getContext() + { + return _context; + } + + public String getPathInContext() + { + return _pathInContext; + } + + @Override + public Object getAttribute(String name) + { + // return some hidden attributes for requestLog + return switch (name) + { + case "o.e.j.s.h.ScopedRequest.contextPath" -> _context.getContextPath(); + case "o.e.j.s.h.ScopedRequest.pathInContext" -> _pathInContext; + default -> super.getAttribute(name); + }; + } +} diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextResponse.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextResponse.java new file mode 100644 index 00000000000..129752b590f --- /dev/null +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextResponse.java @@ -0,0 +1,51 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +public class ContextResponse extends Response.Wrapper +{ + private final ContextHandler.Context _context; + + public ContextResponse(ContextHandler.Context context, Request request, Response response) + { + super(request, response); + _context = context; + } + + @Override + public void write(boolean last, Callback callback, ByteBuffer... content) + { + Callback contextCallback = new Callback() + { + @Override + public void succeeded() + { + _context.run(callback::succeeded, getRequest()); + } + + @Override + public void failed(Throwable t) + { + _context.accept(callback::failed, t, getRequest()); + } + }; + super.write(last, contextCallback, content); + } +} diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java index d3ba3831a1a..52a5451a0ed 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java @@ -13,19 +13,15 @@ package org.eclipse.jetty.server.handler; -import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.Locale; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.DateCache; import org.eclipse.jetty.util.RolloverFileOutputStream; @@ -36,12 +32,20 @@ import org.eclipse.jetty.util.RolloverFileOutputStream; * and the current thread name is updated with information that will link * to the details in that output. */ -public class DebugHandler extends HandlerWrapper implements Connection.Listener +public class DebugHandler extends Handler.Wrapper implements Connection.Listener { private DateCache _date = new DateCache("HH:mm:ss", Locale.US); private OutputStream _out; private PrintStream _print; + @Override + public Request.Processor handle(Request request) throws Exception + { + // TODO + return super.handle(request); + } + + /* @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException @@ -100,7 +104,7 @@ public class DebugHandler extends HandlerWrapper implements Connection.Listener print(name, "RESPONSE " + base_response.getStatus() + (ex == null ? "" : ("/" + ex)) + " " + base_response.getContentType()); } } - + */ private void print(String name, String message) { long now = System.currentTimeMillis(); diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java index 6b79269419c..56d3d185189 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java @@ -14,23 +14,29 @@ package org.eclipse.jetty.server.handler; import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.URL; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.List; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.DateGenerator; +import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.resource.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,12 +51,13 @@ import static java.nio.charset.StandardCharsets.UTF_8; * For requests to '/' a 404 with a list of known contexts is served. * For all other requests a normal 404 is served. */ -public class DefaultHandler extends AbstractHandler +public class DefaultHandler extends Handler.Processor { private static final Logger LOG = LoggerFactory.getLogger(DefaultHandler.class); - final long _faviconModified = (System.currentTimeMillis() / 1000) * 1000L; - final byte[] _favicon; + final long _faviconModifiedMs = (System.currentTimeMillis() / 1000) * 1000L; + final HttpField _faviconModified = new PreEncodedHttpField(HttpHeader.LAST_MODIFIED, DateGenerator.formatDate(_faviconModifiedMs)); + final ByteBuffer _favicon; boolean _serveIcon = true; boolean _showContexts = true; @@ -73,44 +80,41 @@ public class DefaultHandler extends AbstractHandler } finally { - _favicon = favbytes; + _favicon = BufferUtil.toBuffer(favbytes); } } @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) throws Exception { - if (response.isCommitted() || baseRequest.isHandled()) - return; - - baseRequest.setHandled(true); - String method = request.getMethod(); // little cheat for common request - if (_serveIcon && _favicon != null && HttpMethod.GET.is(method) && target.equals("/favicon.ico")) + if (isServeIcon() && _favicon != null && HttpMethod.GET.is(method) && request.getPathInContext().equals("/favicon.ico")) { - if (request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString()) == _faviconModified) - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + ByteBuffer content = BufferUtil.EMPTY_BUFFER; + if (_faviconModifiedMs > 0 && request.getHeaders().getDateField(HttpHeader.IF_MODIFIED_SINCE) == _faviconModifiedMs) + response.setStatus(HttpStatus.NOT_MODIFIED_304); else { - response.setStatus(HttpServletResponse.SC_OK); + response.setStatus(HttpStatus.OK_200); response.setContentType("image/x-icon"); - response.setContentLength(_favicon.length); - response.setDateHeader(HttpHeader.LAST_MODIFIED.toString(), _faviconModified); - response.setHeader(HttpHeader.CACHE_CONTROL.toString(), "max-age=360000,public"); - response.getOutputStream().write(_favicon); + response.setContentLength(_favicon.remaining()); + response.getHeaders().add(_faviconModified); + response.getHeaders().put(HttpHeader.CACHE_CONTROL.toString(), "max-age=360000,public"); + content = _favicon.slice(); } + response.write(true, callback, content); return; } - if (!_showContexts || !HttpMethod.GET.is(method) || !request.getRequestURI().equals("/")) + if (!isShowContexts() || !HttpMethod.GET.is(method) || !request.getPathInContext().equals("/")) { - response.sendError(HttpServletResponse.SC_NOT_FOUND); + Response.writeError(request, response, callback, HttpStatus.NOT_FOUND_404, null); return; } - response.setStatus(HttpServletResponse.SC_NOT_FOUND); + response.setStatus(HttpStatus.NOT_FOUND_404); response.setContentType(MimeTypes.Type.TEXT_HTML_UTF_8.toString()); try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); @@ -134,13 +138,11 @@ public class DefaultHandler extends AbstractHandler writer.append("\n"); Server server = getServer(); - Handler[] handlers = server == null ? null : server.getChildHandlersByClass(ContextHandler.class); + List handlers = server == null ? Collections.emptyList() : server.getDescendants(ContextHandler.class); - for (int i = 0; handlers != null && i < handlers.length; i++) + for (ContextHandler context : handlers) { writer.append(""); - // Context Path - ContextHandler context = (ContextHandler)handlers[i]; String contextPath = context.getContextPath(); String href = URIUtil.encodePath(contextPath); @@ -187,19 +189,17 @@ public class DefaultHandler extends AbstractHandler writer.append("Powered by Eclipse Jetty:// Server


    \n"); writer.append("\n\n"); writer.flush(); - byte[] content = outputStream.toByteArray(); - response.setContentLength(content.length); - try (OutputStream out = response.getOutputStream()) - { - out.write(content); - } + ByteBuffer content = BufferUtil.toBuffer(outputStream.toByteArray()); + response.setContentLength(content.remaining()); + response.write(true, callback, content); } } /** * @return Returns true if the handle can server the jetty favicon.ico */ - public boolean getServeIcon() + @ManagedAttribute("True if the favicon.ico should be served") + public boolean isServeIcon() { return _serveIcon; } @@ -212,7 +212,8 @@ public class DefaultHandler extends AbstractHandler _serveIcon = serveIcon; } - public boolean getShowContexts() + @ManagedAttribute("True if the contexts should be shown in the default 404 page") + public boolean isShowContexts() { return _showContexts; } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java new file mode 100644 index 00000000000..9f0f4c1807f --- /dev/null +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java @@ -0,0 +1,260 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.nio.charset.Charset; +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.function.BiConsumer; + +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.server.FutureFormFields; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Fields; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class DelayedHandler extends Handler.Wrapper +{ + private static final Logger LOG = LoggerFactory.getLogger(DelayedHandler.class); + + @Override + public Request.Processor handle(Request request) throws Exception + { + Request.Processor processor = super.handle(request); + if (processor == null) + return null; + return delayed(request, processor); + } + + protected abstract Request.Processor delayed(Request request, Request.Processor processor); + + public static class UntilContent extends DelayedHandler + { + @Override + protected Request.Processor delayed(Request request, Request.Processor processor) + { + // TODO remove this setting from HttpConfig? + if (!request.getConnectionMetaData().getHttpConfiguration().isDelayDispatchUntilContent()) + return processor; + if (request.getContentLength() <= 0 && !request.getHeaders().contains(HttpHeader.CONTENT_TYPE)) + return processor; + + return new UntilContentProcessor(request, processor); + } + } + + private static class UntilContentProcessor implements Request.Processor, Runnable + { + private final Request.Processor _processor; + private final Request _request; + private Response _response; + private Callback _callback; + + public UntilContentProcessor(Request request, Request.Processor processor) + { + _request = request; + _processor = processor; + } + + @Override + public void process(Request ignored, Response response, Callback callback) throws Exception + { + _response = response; + _callback = callback; + _request.demandContent(this); + } + + @Override + public void run() + { + try + { + _processor.process(_request, _response, _callback); + } + catch (Throwable t) + { + Response.writeError(_request, _response, _callback, t); + } + } + } + + public static class UntilFormFields extends DelayedHandler + { + @Override + protected Request.Processor delayed(Request request, Request.Processor processor) + { + if (!request.getConnectionMetaData().getHttpConfiguration().isDelayDispatchUntilContent()) + return processor; + + Charset charset = FutureFormFields.getFormEncodedCharset(request); + if (charset == null) + return processor; + + return new UntilFormFieldsProcessor(request, processor, charset); + } + } + + private static class UntilFormFieldsProcessor implements Request.Processor, BiConsumer + { + private final Request _request; + private final Request.Processor _processor; + private final Charset _charset; + private Response _response; + private Callback _callback; + + public UntilFormFieldsProcessor(Request request, Request.Processor processor, Charset charset) + { + _processor = processor; + _request = request; + _charset = charset; + } + + @Override + public void process(Request ignored, Response response, Callback callback) throws Exception + { + _response = response; + _callback = callback; + + // TODO get the max sizes + FutureFormFields futureFormFields = new FutureFormFields(_request, _charset, -1, -1); + _request.setAttribute(FutureFormFields.class.getName(), futureFormFields); + futureFormFields.run(); + + if (futureFormFields.isDone()) + _processor.process(_request, response, callback); + else + futureFormFields.whenComplete(this); + } + + @Override + public void accept(Fields fields, Throwable throwable) + { + try + { + _processor.process(_request, _response, _callback); + } + catch (Throwable t) + { + Response.writeError(_request, _response, _callback, t); + } + } + } + + public static class QualityOfService extends DelayedHandler + { + private final int _maxPermits; + private final Queue _queue = new ArrayDeque<>(); + private int _permits; + + public QualityOfService(int permits) + { + _maxPermits = permits; + } + + @Override + protected Request.Processor delayed(Request request, Request.Processor processor) + { + return new QualityOfServiceProcessor(request, processor); + } + + private class QualityOfServiceProcessor implements Request.Processor, Callback + { + private final Request.Processor _processor; + private final Request _request; + private Response _response; + private Callback _callback; + + private QualityOfServiceProcessor(Request request, Request.Processor processor) + { + _processor = processor; + _request = request; + } + + @Override + public void process(Request request, Response response, Callback callback) throws Exception + { + boolean accepted; + synchronized (QualityOfService.this) + { + _callback = callback; + accepted = _permits < _maxPermits; + if (accepted) + _permits++; + else + { + _response = response; + _queue.add(this); + } + } + if (accepted) + _processor.process(request, response, this); + } + + @Override + public void succeeded() + { + try + { + _callback.succeeded(); + release(); + } + finally + { + release(); + } + } + + @Override + public void failed(Throwable x) + { + try + { + _callback.failed(x); + release(); + } + finally + { + release(); + } + } + + private void release() + { + QualityOfServiceProcessor processor; + synchronized (QualityOfService.this) + { + processor = _queue.poll(); + if (processor == null) + _permits--; + } + + if (processor != null) + { + try + { + processor._processor.process(processor._request, processor._response, processor); + } + catch (Throwable t) + { + Response.writeError(processor._request, processor._response, processor, t); + } + } + } + } + } +} diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorProcessor.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorProcessor.java new file mode 100644 index 00000000000..a90ddae69bf --- /dev/null +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorProcessor.java @@ -0,0 +1,569 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.MimeTypes.Type; +import org.eclipse.jetty.http.PreEncodedHttpField; +import org.eclipse.jetty.http.QuotedQualityCSV; +import org.eclipse.jetty.io.ByteBufferOutputStream; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.StringUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handler for Error pages + * An ErrorHandler is registered with {@link Server#setErrorProcessor(Request.Processor)}. + * It is called by the {@link Response#writeError(Request, Response, Callback, int, String)} + * to generate an error page. + */ +public class ErrorProcessor implements Request.Processor +{ + // TODO This classes API needs to be majorly refactored/cleanup in jetty-10 + private static final Logger LOG = LoggerFactory.getLogger(ErrorProcessor.class); + public static final String ERROR_STATUS = "org.eclipse.jetty.server.error_status"; + public static final String ERROR_MESSAGE = "org.eclipse.jetty.server.error_message"; + public static final String ERROR_EXCEPTION = "org.eclipse.jetty.server.error_exception"; + public static final String ERROR_CONTEXT = "org.eclipse.jetty.server.error_context"; + public static final Set ERROR_METHODS = Set.of("GET", "POST", "HEAD"); + public static final HttpField ERROR_CACHE_CONTROL = new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store"); + + boolean _showServlet = true; + boolean _showStacks = true; + boolean _showMessageInTitle = true; + HttpField _cacheControl = new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store"); + + public ErrorProcessor() + { + } + + public boolean errorPageForMethod(String method) + { + return ERROR_METHODS.contains(method); + } + + @Override + public void process(Request request, Response response, Callback callback) + { + if (_cacheControl != null) + response.getHeaders().put(_cacheControl); + + int code = response.getStatus(); + String message = (String)request.getAttribute(ERROR_MESSAGE); + Throwable cause = (Throwable)request.getAttribute(ERROR_EXCEPTION); + if (cause instanceof BadMessageException bad) + { + code = bad.getCode(); + response.setStatus(code); + if (message == null) + message = bad.getReason(); + } + + if (!errorPageForMethod(request.getMethod()) || HttpStatus.hasNoBody(code)) + { + callback.succeeded(); + } + else + { + if (message == null) + message = cause == null ? HttpStatus.getMessage(code) : cause.toString(); + + try + { + generateAcceptableResponse(request, response, code, message, cause, callback); + } + catch (Throwable x) + { + // TODO: cannot write the error response, give up and close the stream. + x.printStackTrace(); + } + } + } + + protected void generateAcceptableResponse(Request request, Response response, int code, String message, Throwable cause, Callback callback) throws IOException + { + List acceptable = request.getHeaders().getQualityCSV(HttpHeader.ACCEPT, QuotedQualityCSV.MOST_SPECIFIC_MIME_ORDERING); + if (acceptable.isEmpty()) + { + if (request.getHeaders().contains(HttpHeader.ACCEPT)) + { + callback.succeeded(); + return; + } + acceptable = Collections.singletonList(Type.TEXT_HTML.asString()); + } + List charsets = request.getHeaders().getQualityCSV(HttpHeader.ACCEPT_CHARSET).stream() + .map(s -> + { + try + { + if ("*".equals(s)) + return StandardCharsets.UTF_8; + return Charset.forName(s); + } + catch (Throwable t) + { + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (charsets.isEmpty()) + { + charsets = List.of(StandardCharsets.ISO_8859_1, StandardCharsets.UTF_8); + if (request.getHeaders().contains(HttpHeader.ACCEPT_CHARSET)) + { + callback.succeeded(); + return; + } + } + + for (String mimeType : acceptable) + { + if (generateAcceptableResponse(request, response, callback, mimeType, charsets, code, message, cause)) + return; + } + callback.succeeded(); + } + + protected boolean generateAcceptableResponse(Request request, Response response, Callback callback, String contentType, List charsets, int code, String message, Throwable cause) throws IOException + { + Type type; + Charset charset; + switch (contentType) + { + case "text/html": + case "text/*": + case "*/*": + type = Type.TEXT_HTML; + charset = charsets.stream().findFirst().orElse(StandardCharsets.ISO_8859_1); + break; + + case "text/json": + case "application/json": + if (charsets.contains(StandardCharsets.UTF_8)) + charset = StandardCharsets.UTF_8; + else if (charsets.contains(StandardCharsets.ISO_8859_1)) + charset = StandardCharsets.ISO_8859_1; + else + return false; + type = Type.TEXT_JSON.is(contentType) ? Type.TEXT_JSON : Type.APPLICATION_JSON; + break; + + case "text/plain": + type = Type.TEXT_PLAIN; + charset = charsets.stream().findFirst().orElse(StandardCharsets.ISO_8859_1); + break; + + default: + return false; + } + + int bufferSize = request.getConnectionMetaData().getHttpConfiguration().getOutputBufferSize(); + bufferSize = Math.min(8192, bufferSize); // TODO ? + ByteBuffer buffer = request.getComponents().getByteBufferPool().acquire(bufferSize, false); + + // write into the response aggregate buffer and flush it asynchronously. + // Looping to reduce size if buffer overflows + boolean showStacks = _showStacks; + while (true) + { + try + { + BufferUtil.clear(buffer); + ByteBufferOutputStream out = new ByteBufferOutputStream(buffer); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, charset)); + + switch (type) + { + case TEXT_HTML -> writeErrorHtml(request, writer, charset, code, message, cause, showStacks); + case TEXT_JSON -> writeErrorJson(request, writer, code, message, cause, showStacks); + case TEXT_PLAIN -> writeErrorPlain(request, writer, code, message, cause, showStacks); + default -> throw new IllegalStateException(); + } + + writer.flush(); + break; + } + catch (BufferOverflowException e) + { + if (showStacks) + { + if (LOG.isDebugEnabled()) + LOG.debug("Disable stacks for " + e.toString()); + + showStacks = false; + continue; + } + if (LOG.isDebugEnabled()) + LOG.warn("Error page too large: >{} {} {} {}", bufferSize, code, message, request, e); + else + LOG.warn("Error page too large: >{} {} {} {}", bufferSize, code, message, request); + + break; + } + } + + if (!buffer.hasRemaining()) + { + callback.succeeded(); + return true; + } + + response.getHeaders().put(type.getContentTypeField(charset)); + response.write(true, new Callback.Nested(callback) + { + @Override + public void succeeded() + { + request.getComponents().getByteBufferPool().release(buffer); + super.succeeded(); + } + + @Override + public void failed(Throwable x) + { + request.getComponents().getByteBufferPool().release(buffer); + super.failed(x); + } + }, buffer); + + return true; + } + + protected void writeErrorHtml(Request request, Writer writer, Charset charset, int code, String message, Throwable cause, boolean showStacks) throws IOException + { + if (message == null) + message = HttpStatus.getMessage(code); + + writer.write("\n\n"); + writeErrorHtmlMeta(request, writer, charset); + writeErrorHtmlHead(request, writer, code, message); + writer.write("\n\n"); + writeErrorHtmlBody(request, writer, code, message, cause, showStacks); + writer.write("\n\n\n"); + } + + protected void writeErrorHtmlMeta(Request request, Writer writer, Charset charset) throws IOException + { + writer.write("\n"); + } + + protected void writeErrorHtmlHead(Request request, Writer writer, int code, String message) throws IOException + { + writer.write("Error "); + String status = Integer.toString(code); + writer.write(status); + if (message != null && !message.equals(status)) + { + writer.write(' '); + writer.write(StringUtil.sanitizeXmlString(message)); + } + writer.write("\n"); + } + + protected void writeErrorHtmlBody(Request request, Writer writer, int code, String message, Throwable cause, boolean showStacks) throws IOException + { + String uri = request.getHttpURI().toString(); + + writeErrorHtmlMessage(request, writer, code, message, cause, uri); + if (showStacks) + writeErrorHtmlStacks(request, writer); + + request.getConnectionMetaData().getHttpConfiguration() + .writePoweredBy(writer, "
    ", "
    \n"); + } + + protected void writeErrorHtmlMessage(Request request, Writer writer, int code, String message, Throwable cause, String uri) throws IOException + { + writer.write("

    HTTP ERROR "); + String status = Integer.toString(code); + writer.write(status); + if (message != null && !message.equals(status)) + { + writer.write(' '); + writer.write(StringUtil.sanitizeXmlString(message)); + } + writer.write("

    \n"); + writer.write("\n"); + htmlRow(writer, "URI", uri); + htmlRow(writer, "STATUS", status); + htmlRow(writer, "MESSAGE", message); + while (cause != null) + { + htmlRow(writer, "CAUSED BY", cause); + cause = cause.getCause(); + } + writer.write("
    \n"); + } + + private void htmlRow(Writer writer, String tag, Object value) throws IOException + { + writer.write(""); + writer.write(tag); + writer.write(":"); + if (value == null) + writer.write("-"); + else + writer.write(StringUtil.sanitizeXmlString(value.toString())); + writer.write("\n"); + } + + protected void writeErrorPlain(Request request, PrintWriter writer, int code, String message, Throwable cause, boolean showStacks) + { + writer.write("HTTP ERROR "); + writer.write(Integer.toString(code)); + writer.write(' '); + writer.write(StringUtil.sanitizeXmlString(message)); + writer.write("\n"); + writer.printf("URI: %s%n", request.getHttpURI()); + writer.printf("STATUS: %s%n", code); + writer.printf("MESSAGE: %s%n", message); + while (cause != null) + { + writer.printf("CAUSED BY %s%n", cause); + if (showStacks) + cause.printStackTrace(writer); + cause = cause.getCause(); + } + } + + private void writeErrorJson(Request request, PrintWriter writer, int code, String message, Throwable cause, boolean showStacks) + { + Map json = new HashMap<>(); + + json.put("url", request.getHttpURI().toString()); + json.put("status", Integer.toString(code)); + json.put("message", message); + int c = 0; + while (cause != null) + { + json.put("cause" + c++, cause.toString()); + cause = cause.getCause(); + } + + writer.append(json.entrySet().stream() + .map(e -> QuotedStringTokenizer.quote(e.getKey()) + + ":" + + QuotedStringTokenizer.quote(StringUtil.sanitizeXmlString((e.getValue())))) + .collect(Collectors.joining(",\n", "{\n", "\n}"))); + } + + protected void writeErrorHtmlStacks(Request request, Writer writer) throws IOException + { + Throwable th = (Throwable)request.getAttribute(ERROR_EXCEPTION); + if (th != null) + { + writer.write("

    Caused by:

    ");
    +            // You have to pre-generate and then use #write(writer, String)
    +            try (StringWriter sw = new StringWriter();
    +                 PrintWriter pw = new PrintWriter(sw))
    +            {
    +                th.printStackTrace(pw);
    +                pw.flush();
    +                write(writer, sw.getBuffer().toString()); // sanitize
    +            }
    +            writer.write("
    \n"); + } + } + + /** + * Bad Message Error body + *

    Generate an error response body to be sent for a bad message. + * In this case there is something wrong with the request, so either + * a request cannot be built, or it is not safe to build a request. + * This method allows for a simple error page body to be returned + * and some response headers to be set. + * + * @param status The error code that will be sent + * @param reason The reason for the error code (may be null) + * @param fields The header fields that will be sent with the response. + * @return The content as a ByteBuffer, or null for no body. + */ + public ByteBuffer badMessageError(int status, String reason, HttpFields.Mutable fields) + { + if (reason == null) + reason = HttpStatus.getMessage(status); + if (HttpStatus.hasNoBody(status)) + return BufferUtil.EMPTY_BUFFER; + fields.put(HttpHeader.CONTENT_TYPE, Type.TEXT_HTML_8859_1.asString()); + return BufferUtil.toBuffer("

    Bad Message " + status + "

    reason: " + reason + "
    "); + } + + /** + * Get the cacheControl. + * + * @return the cacheControl header to set on error responses. + */ + public String getCacheControl() + { + return _cacheControl == null ? null : _cacheControl.getValue(); + } + + /** + * Set the cacheControl. + * + * @param cacheControl the cacheControl header to set on error responses. + */ + public void setCacheControl(String cacheControl) + { + _cacheControl = cacheControl == null ? null : new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, cacheControl); + } + + /** + * @return True if the error page will show the Servlet that generated the error + */ + public boolean isShowServlet() + { + return _showServlet; + } + + /** + * @param showServlet True if the error page will show the Servlet that generated the error + */ + public void setShowServlet(boolean showServlet) + { + _showServlet = showServlet; + } + + /** + * @return True if stack traces are shown in the error pages + */ + public boolean isShowStacks() + { + return _showStacks; + } + + /** + * @param showStacks True if stack traces are shown in the error pages + */ + public void setShowStacks(boolean showStacks) + { + _showStacks = showStacks; + } + + /** + * @param showMessageInTitle if true, the error message appears in page title + */ + public void setShowMessageInTitle(boolean showMessageInTitle) + { + _showMessageInTitle = showMessageInTitle; + } + + public boolean getShowMessageInTitle() + { + return _showMessageInTitle; + } + + protected void write(Writer writer, String string) throws IOException + { + if (string == null) + return; + + writer.write(StringUtil.sanitizeXmlString(string)); + } + + public static Request.Processor getErrorProcessor(Server server, ContextHandler context) + { + Request.Processor errorProcessor = null; + if (context != null) + errorProcessor = context.getErrorProcessor(); + if (errorProcessor == null && server != null) + errorProcessor = server.getErrorProcessor(); + return errorProcessor; + } + + public static class ErrorRequest extends Request.Wrapper + { + private final int _status; + private final String _message; + private final Throwable _cause; + + public ErrorRequest(Request request, int status, String message, Throwable cause) + { + super(request); + _status = status; + _message = message; + _cause = cause; + } + + @Override + public Content readContent() + { + return Content.EOF; + } + + @Override + public void demandContent(Runnable onContentAvailable) + { + onContentAvailable.run(); + } + + @Override + public Object getAttribute(String name) + { + return switch (name) + { + case ERROR_MESSAGE -> _message; + case ERROR_EXCEPTION -> _cause; + case ERROR_STATUS -> _status; + default -> super.getAttribute(name); + }; + } + + @Override + public Set getAttributeNameSet() + { + Set names = new HashSet<>(super.getAttributeNameSet()); + if (_message != null) + names.add(ERROR_MESSAGE); + if (_status > 0) + names.add(ERROR_STATUS); + if (_cause != null) + names.add(ERROR_EXCEPTION); + return names; + } + } +} diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandler.java index cf1c52799c1..f26f6bfc283 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/FileBufferedResponseHandler.java @@ -14,26 +14,15 @@ package org.eclipse.jetty.server.handler; import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.util.Objects; -import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.HttpOutput.Interceptor; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.IteratingCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *

    - * A Handler that can apply a {@link org.eclipse.jetty.server.HttpOutput.Interceptor} + * A Handler that can apply a * mechanism to buffer the entire response content until the output is closed. * This allows the commit to be delayed until the response is complete and thus * headers and response status can be changed while writing the body. @@ -65,6 +54,8 @@ public class FileBufferedResponseHandler extends BufferedResponseHandler _tempDir = Objects.requireNonNull(tempDir); } + // TODO + /* @Override protected BufferedInterceptor newBufferedInterceptor(HttpChannel httpChannel, Interceptor interceptor) { @@ -231,4 +222,5 @@ public class FileBufferedResponseHandler extends BufferedResponseHandler icb.iterate(); } } + */ } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java index 84b011ad0d0..ed759314bfd 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java @@ -13,21 +13,14 @@ package org.eclipse.jetty.server.handler; -import java.io.IOException; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Request; /** * HandlerList. - * This extension of {@link HandlerCollection} will call - * each contained handler in turn until either an exception is thrown, the response - * is committed or a positive response status is set. + * @deprecated */ -public class HandlerList extends HandlerCollection +@Deprecated +public class HandlerList extends Handler.Collection { public HandlerList() { @@ -37,21 +30,4 @@ public class HandlerList extends HandlerCollection { super(handlers); } - - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException - { - Handler[] handlers = getHandlers(); - - if (handlers != null && isStarted()) - { - for (int i = 0; i < handlers.length; i++) - { - handlers[i].handle(target, baseRequest, request, response); - if (baseRequest.isHandled()) - return; - } - } - } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java index 6b9cdee07fc..a94c7f7e5d0 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java @@ -13,132 +13,14 @@ package org.eclipse.jetty.server.handler; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.HandlerContainer; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; -import org.eclipse.jetty.util.component.LifeCycle; /** - * A HandlerWrapper acts as a {@link Handler} but delegates the {@link Handler#handle handle} method and - * {@link LifeCycle life cycle} events to a delegate. This is primarily used to implement the Decorator pattern. + * @deprecated Use {@link Handler.Wrapper} */ @ManagedObject("Handler wrapping another Handler") -public class HandlerWrapper extends AbstractHandlerContainer +@Deprecated +public class HandlerWrapper extends Handler.Wrapper { - protected Handler _handler; - - /** - * - */ - public HandlerWrapper() - { - } - - /** - * @return Returns the handlers. - */ - @ManagedAttribute(value = "Wrapped Handler", readonly = true) - public Handler getHandler() - { - return _handler; - } - - /** - * @return Returns the handlers. - */ - @Override - public Handler[] getHandlers() - { - if (_handler == null) - return new Handler[0]; - return new Handler[]{_handler}; - } - - /** - * @param handler Set the {@link Handler} which should be wrapped. - */ - public void setHandler(Handler handler) - { - if (isStarted()) - throw new IllegalStateException(getState()); - - // check for loops - if (handler == this || (handler instanceof HandlerContainer && - Arrays.asList(((HandlerContainer)handler).getChildHandlers()).contains(this))) - throw new IllegalStateException("setHandler loop"); - - if (handler != null) - handler.setServer(getServer()); - - Handler old = _handler; - _handler = handler; - updateBean(old, _handler, true); - } - - /** - * Replace the current handler with another HandlerWrapper - * linked to the current handler. - *

    - * This is equivalent to: - *

    -     *   wrapper.setHandler(getHandler());
    -     *   setHandler(wrapper);
    -     * 
    - * - * @param wrapper the wrapper to insert - */ - public void insertHandler(HandlerWrapper wrapper) - { - if (wrapper == null) - throw new IllegalArgumentException(); - - HandlerWrapper tail = wrapper; - while (tail.getHandler() instanceof HandlerWrapper) - { - tail = (HandlerWrapper)tail.getHandler(); - } - if (tail.getHandler() != null) - throw new IllegalArgumentException("bad tail of inserted wrapper chain"); - - Handler next = getHandler(); - setHandler(wrapper); - tail.setHandler(next); - } - - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - Handler handler = _handler; - if (handler != null) - handler.handle(target, baseRequest, request, response); - } - - @Override - protected void expandChildren(List list, Class byClass) - { - expandHandler(_handler, list, byClass); - } - - @Override - public void destroy() - { - if (!isStopped()) - throw new IllegalStateException("!STOPPED"); - Handler child = getHandler(); - if (child != null) - { - setHandler(null); - child.destroy(); - } - super.destroy(); - } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HotSwapHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HotSwapHandler.java index 6d69db10245..cea6f17dc3e 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HotSwapHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HotSwapHandler.java @@ -13,110 +13,13 @@ package org.eclipse.jetty.server.handler; -import java.io.IOException; -import java.util.List; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; /** * A HandlerContainer that allows a hot swap of a wrapped handler. + * @deprecated */ -public class HotSwapHandler extends AbstractHandlerContainer +@Deprecated +public class HotSwapHandler extends Handler.Wrapper { - private volatile Handler _handler; - - /** - * - */ - public HotSwapHandler() - { - } - - /** - * @return Returns the handlers. - */ - public Handler getHandler() - { - return _handler; - } - - /** - * @return Returns the handlers. - */ - @Override - public Handler[] getHandlers() - { - Handler handler = _handler; - if (handler == null) - return new Handler[0]; - return new Handler[]{handler}; - } - - /** - * @param handler Set the {@link Handler} which should be wrapped. - */ - public void setHandler(Handler handler) - { - try - { - Server server = getServer(); - if (handler != null) - handler.setServer(server); - updateBean(_handler, handler, true); - _handler = handler; - } - catch (Exception e) - { - throw new RuntimeException(e); - } - } - - @Override - protected void doStart() throws Exception - { - super.doStart(); - } - - @Override - protected void doStop() throws Exception - { - super.doStop(); - } - - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - Handler handler = _handler; - if (handler != null && isStarted() && handler.isStarted()) - { - handler.handle(target, baseRequest, request, response); - } - } - - @Override - protected void expandChildren(List list, Class byClass) - { - Handler handler = _handler; - if (handler != null) - expandHandler(handler, list, byClass); - } - - @Override - public void destroy() - { - if (!isStopped()) - throw new IllegalStateException("!STOPPED"); - Handler child = getHandler(); - if (child != null) - { - setHandler(null); - child.destroy(); - } - super.destroy(); - } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java index 491c0c36706..17d9b7f1b84 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java @@ -13,15 +13,9 @@ package org.eclipse.jetty.server.handler; -import java.io.IOException; - -import jakarta.servlet.AsyncEvent; -import jakarta.servlet.AsyncListener; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.Callback; /** * Handler to adjust the idle timeout of requests while dispatched. @@ -36,7 +30,7 @@ import org.eclipse.jetty.server.Request; * </Set> * */ -public class IdleTimeoutHandler extends HandlerWrapper +public class IdleTimeoutHandler extends Handler.Wrapper { private long _idleTimeoutMs = 1000; private boolean _applyToAsync = false; @@ -70,47 +64,20 @@ public class IdleTimeoutHandler extends HandlerWrapper } @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public Request.Processor handle(Request request) throws Exception { - final HttpChannel channel = baseRequest.getHttpChannel(); - final long idle_timeout = baseRequest.getHttpChannel().getIdleTimeout(); - channel.setIdleTimeout(_idleTimeoutMs); + Request.Processor processor = super.handle(request); + if (processor == null) + return null; - try + return (rq, rs, cb) -> { - super.handle(target, baseRequest, request, response); - } - finally - { - if (_applyToAsync && request.isAsyncStarted()) + long idleTimeout = 0; // TODO rq.getHttpChannel().getIdleTimeout(); + // TODO rq.getHttpChannel().setIdleTimeout(_idleTimeoutMs); + processor.process(rq, rs, Callback.from(cb, () -> { - request.getAsyncContext().addListener(new AsyncListener() - { - @Override - public void onTimeout(AsyncEvent event) throws IOException - { - } - - @Override - public void onStartAsync(AsyncEvent event) throws IOException - { - } - - @Override - public void onError(AsyncEvent event) throws IOException - { - channel.setIdleTimeout(idle_timeout); - } - - @Override - public void onComplete(AsyncEvent event) throws IOException - { - channel.setIdleTimeout(idle_timeout); - } - }); - } - else - channel.setIdleTimeout(idle_timeout); - } + // TODO rq.getHttpChannel().setIdleTimeout(idleTimeout) + })); + }; } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java index 8ad57772cce..d4baf81b4e4 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessHandler.java @@ -16,13 +16,10 @@ package org.eclipse.jetty.server.handler; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.SocketAddress; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.pathmap.PathSpec; -import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.IncludeExcludeSet; import org.eclipse.jetty.util.InetAddressPattern; @@ -41,7 +38,7 @@ import static org.eclipse.jetty.server.handler.InetAccessSet.PatternTuple; * the forwarded for headers, as this cannot be as easily forged. *

    */ -public class InetAccessHandler extends HandlerWrapper +public class InetAccessHandler extends Handler.Wrapper { private final IncludeExcludeSet _set = new IncludeExcludeSet<>(InetAccessSet.class); @@ -205,41 +202,27 @@ public class InetAccessHandler extends HandlerWrapper } } - /** - * Checks the incoming request against the whitelist and blacklist - */ @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException + public Request.Processor handle(Request request) throws Exception { - // Get the real remote IP (not the one set by the forwarded headers (which may be forged)) - HttpChannel channel = baseRequest.getHttpChannel(); - if (channel != null) - { - InetSocketAddress address = channel.getRemoteAddress(); - if (address != null && !isAllowed(address.getAddress(), baseRequest, request)) - { - response.sendError(HttpStatus.FORBIDDEN_403); - baseRequest.setHandled(true); - return; - } - } - - getHandler().handle(target, baseRequest, request, response); + SocketAddress socketAddress = request.getConnectionMetaData().getRemoteSocketAddress(); + if (socketAddress instanceof InetSocketAddress inetSocketAddress && + !isAllowed(inetSocketAddress.getAddress(), request)) + return null; + return super.handle(request); } /** * Checks if specified address and request are allowed by current InetAddress rules. * * @param addr the inetAddress to check - * @param baseRequest the base request to check * @param request the HttpServletRequest request to check * @return true if inetAddress and request are allowed */ - protected boolean isAllowed(InetAddress addr, Request baseRequest, HttpServletRequest request) + protected boolean isAllowed(InetAddress addr, Request request) { - String connectorName = baseRequest.getHttpChannel().getConnector().getName(); - String path = baseRequest.getMetaData().getURI().getDecodedPath(); + String connectorName = request.getConnectionMetaData().getConnector().getName(); + String path = request.getPathInContext(); return _set.test(new AccessTuple(connectorName, addr, path)); } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ManagedAttributeListener.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ManagedAttributeListener.java index 56910e330b7..594f30c0a2e 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ManagedAttributeListener.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ManagedAttributeListener.java @@ -16,17 +16,13 @@ package org.eclipse.jetty.server.handler; import java.util.HashSet; import java.util.Set; -import jakarta.servlet.ServletContextAttributeEvent; -import jakarta.servlet.ServletContextAttributeListener; -import jakarta.servlet.ServletContextEvent; -import jakarta.servlet.ServletContextListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Enable Jetty style JMX MBeans from within a Context */ -public class ManagedAttributeListener implements ServletContextListener, ServletContextAttributeListener +public class ManagedAttributeListener { private static final Logger LOG = LoggerFactory.getLogger(ManagedAttributeListener.class); @@ -46,6 +42,7 @@ public class ManagedAttributeListener implements ServletContextListener, Servlet LOG.debug("managedAttributes {}", _managedAttributes); } + /* TODO @Override public void attributeReplaced(ServletContextAttributeEvent event) { @@ -94,4 +91,6 @@ public class ManagedAttributeListener implements ServletContextListener, Servlet LOG.debug("update {} {}->{} on {}", name, oldBean, newBean, _context); _context.updateBean(oldBean, newBean, false); } + + */ } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java index c3fa4da825d..9c28b730564 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java @@ -13,14 +13,13 @@ package org.eclipse.jetty.server.handler; -import java.io.IOException; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.server.HandlerContainer; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.URIUtil; /** @@ -42,12 +41,13 @@ public class MovedContextHandler extends ContextHandler { _redirector = new Redirector(); setHandler(_redirector); - setAllowNullPathInfo(true); + setAllowNullPathInContext(true); } - public MovedContextHandler(HandlerContainer parent, String contextPath, String newContextURL) + public MovedContextHandler(Handler.Collection parent, String contextPath, String newContextURL) { - super(parent, contextPath); + super(contextPath); + parent.addHandler(this); _newContextURL = newContextURL; _redirector = new Redirector(); setHandler(_redirector); @@ -93,37 +93,35 @@ public class MovedContextHandler extends ContextHandler _discardQuery = discardQuery; } - private class Redirector extends AbstractHandler + private class Redirector extends Handler.Processor { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) throws Exception { if (_newContextURL == null) return; String path = _newContextURL; - if (!_discardPathInfo && request.getPathInfo() != null) - path = URIUtil.addPaths(path, request.getPathInfo()); + if (!_discardPathInfo && request.getPathInContext() != null) + path = URIUtil.addPaths(path, request.getPathInContext()); - StringBuilder location = URIUtil.hasScheme(path) ? new StringBuilder() : baseRequest.getRootURL(); + HttpURI uri = request.getHttpURI(); + StringBuilder location = new StringBuilder(); + location.append(uri.getScheme()).append("://").append(uri.getAuthority()).append(path); - location.append(path); - if (!_discardQuery && request.getQueryString() != null) + if (!_discardQuery && uri.getQuery() != null) { location.append('?'); - String q = request.getQueryString(); + String q = uri.getQuery(); q = q.replaceAll("\r\n?&=", "!"); location.append(q); } - response.setHeader(HttpHeader.LOCATION.asString(), location.toString()); - + response.getHeaders().put(HttpHeader.LOCATION, location.toString()); if (_expires != null) - response.setHeader(HttpHeader.EXPIRES.asString(), _expires); - - response.setStatus(_permanent ? HttpServletResponse.SC_MOVED_PERMANENTLY : HttpServletResponse.SC_FOUND); - response.setContentLength(0); - baseRequest.setHandled(true); + response.getHeaders().put(HttpHeader.EXPIRES, _expires); + response.setStatus(_permanent ? HttpStatus.MOVED_PERMANENTLY_301 : HttpStatus.FOUND_302); + callback.succeeded(); } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxiedRequestHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxiedRequestHandler.java new file mode 100644 index 00000000000..a05e858e0e5 --- /dev/null +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ProxiedRequestHandler.java @@ -0,0 +1,77 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.net.SocketAddress; + +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.server.ConnectionMetaData; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.HostPort; + +public class ProxiedRequestHandler extends Handler.Wrapper +{ + @Override + public Request.Processor handle(Request request) throws Exception + { + ConnectionMetaData proxiedFor = new ConnectionMetaData.Wrapper(request.getConnectionMetaData()) + { + @Override + public boolean isSecure() + { + // TODO replace with value determined from headers + return super.isSecure(); + } + + @Override + public SocketAddress getRemoteSocketAddress() + { + // TODO replace with value determined from headers + return super.getRemoteSocketAddress(); + } + + @Override + public SocketAddress getLocalSocketAddress() + { + // TODO replace with value determined from headers + return super.getLocalSocketAddress(); + } + + @Override + public HostPort getServerAuthority() + { + // TODO replace with value determined from headers + return super.getServerAuthority(); + } + }; + + Request.WrapperProcessor wrapper = new Request.WrapperProcessor(request) + { + @Override + public HttpURI getHttpURI() + { + // TODO replace with any change in authority + return super.getHttpURI(); + } + + @Override + public ConnectionMetaData getConnectionMetaData() + { + return proxiedFor; + } + }; + return wrapper.wrapProcessor(super.handle(wrapper)); + } +} diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java index 0e9fde60561..e0f35350925 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java @@ -13,105 +13,140 @@ package org.eclipse.jetty.server.handler; -import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.text.DateFormat; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.CachingContentFactory; import org.eclipse.jetty.http.CompressedContentFormat; +import org.eclipse.jetty.http.DateGenerator; +import org.eclipse.jetty.http.DateParser; +import org.eclipse.jetty.http.HttpContent; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PreEncodedHttpField; +import org.eclipse.jetty.http.QuotedCSV; +import org.eclipse.jetty.http.QuotedQualityCSV; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Context; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.ResourceContentFactory; -import org.eclipse.jetty.server.ResourceService; -import org.eclipse.jetty.server.ResourceService.WelcomeFactory; -import org.eclipse.jetty.server.handler.ContextHandler.Context; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.IteratingCallback; +import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.resource.PathCollators; +import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.util.resource.ResourceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static java.util.Arrays.stream; + /** * Resource Handler. * * This handle will serve static content and handle If-Modified-Since headers. No caching is done. Requests for resources that do not exist are let pass (Eg no * 404's). + * TODO there is a lot of URI manipulation, this should be factored out in a utility class. + * + * TODO GW: Work out how this logic can be reused by the DefaultServlet... potentially for wrapped output streams + * + * Missing: + * - current context' mime types + * - getContent in HttpContent should go + * - Default stylesheet (needs Path impl for classpath resources) + * - request ranges + * - a way to configure caching or not */ -public class ResourceHandler extends HandlerWrapper implements ResourceFactory, WelcomeFactory +public class ResourceHandler extends Handler.Wrapper { private static final Logger LOG = LoggerFactory.getLogger(ResourceHandler.class); - Resource _baseResource; - ContextHandler _context; - Resource _defaultStylesheet; - MimeTypes _mimeTypes; - private final ResourceService _resourceService; - Resource _stylesheet; - String[] _welcomes = {"index.html"}; + private static final int NO_CONTENT_LENGTH = -1; + private static final int USE_KNOWN_CONTENT_LENGTH = -2; - public ResourceHandler(ResourceService resourceService) - { - _resourceService = resourceService; - } + private ContextHandler _context; + private Path _defaultStylesheet; + private MimeTypes _mimeTypes; + private Path _stylesheet; + private List _welcomes = List.of("index.html"); + private Path _baseResource; + private boolean _pathInfoOnly = false; + private CompressedContentFormat[] _precompressedFormats = new CompressedContentFormat[0]; + private Welcomer _welcomer; + private boolean _redirectWelcome = false; + private boolean _etags = false; + private List _gzipEquivalentFileExtensions; + private HttpContent.ContentFactory _contentFactory; + private final Map> _preferredEncodingOrderCache = new ConcurrentHashMap<>(); + private String[] _preferredEncodingOrder = new String[0]; + private int _encodingCacheSize = 100; + private boolean _dirAllowed = true; + private boolean _acceptRanges = true; + private HttpField _cacheControl; public ResourceHandler() { - this(new ResourceService() - { - @Override - protected void notFound(HttpServletRequest request, HttpServletResponse response) throws IOException - { - } - }); - _resourceService.setGzipEquivalentFileExtensions(new ArrayList<>(Arrays.asList(new String[]{".svgz"}))); - } - - @Override - public String getWelcomeFile(String pathInContext) throws IOException - { - if (_welcomes == null) - return null; - - for (int i = 0; i < _welcomes.length; i++) - { - String welcomeInContext = URIUtil.addPaths(pathInContext, _welcomes[i]); - Resource welcome = getResource(welcomeInContext); - if (welcome.exists()) - return welcomeInContext; - } - // not found - return null; } @Override public void doStart() throws Exception { - Context scontext = ContextHandler.getCurrentContext(); - _context = (scontext == null ? null : scontext.getContextHandler()); - if (_mimeTypes == null) - _mimeTypes = _context == null ? new MimeTypes() : _context.getMimeTypes(); + Context context = ContextHandler.getCurrentContext(); +// TODO _context = (context == null ? null : context.getContextHandler()); +// if (_mimeTypes == null) +// _mimeTypes = _context == null ? new MimeTypes() : _context.getMimeTypes(); - _resourceService.setContentFactory(new ResourceContentFactory(this, _mimeTypes, _resourceService.getPrecompressedFormats())); - _resourceService.setWelcomeFactory(this); + _mimeTypes = new MimeTypes(); + //_contentFactory = new PathContentFactory(); + // TODO make caching configurable and disabled by default + _contentFactory = new CachingContentFactory(new PathContentFactory()); + _welcomer = new DefaultWelcomer(); super.doStart(); } + // for testing only + HttpContent.ContentFactory getContentFactory() + { + return _contentFactory; + } + /** * @return Returns the resourceBase. */ - public Resource getBaseResource() + public Path getBaseResource() { - if (_baseResource == null) - return null; return _baseResource; } @@ -120,7 +155,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public String getCacheControl() { - return _resourceService.getCacheControl().getValue(); + return _cacheControl.getValue(); } /** @@ -128,7 +163,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public List getGzipEquivalentFileExtensions() { - return _resourceService.getGzipEquivalentFileExtensions(); + return _gzipEquivalentFileExtensions; } public MimeTypes getMimeTypes() @@ -136,65 +171,10 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, return _mimeTypes; } - @Override - public Resource getResource(String path) throws IOException - { - if (LOG.isDebugEnabled()) - LOG.debug("{} getResource({})", _context == null ? _baseResource : _context, path); - - if (StringUtil.isBlank(path)) - { - throw new IllegalArgumentException("Path is blank"); - } - - if (!path.startsWith("/")) - { - throw new IllegalArgumentException("Path reference invalid: " + path); - } - - Resource r = null; - - if (_baseResource != null) - { - r = _baseResource.addPath(path); - - if (r.isAlias() && (_context == null || !_context.checkAlias(path, r))) - { - if (LOG.isDebugEnabled()) - LOG.debug("Rejected alias resource={} alias={}", r, r.getAlias()); - throw new IllegalStateException("Rejected alias reference: " + path); - } - } - else if (_context != null) - { - r = _context.getResource(path); - } - - if ((r == null || !r.exists()) && path.endsWith("/jetty-dir.css")) - r = getStylesheet(); - - if (r == null) - { - throw new FileNotFoundException("Resource: " + path); - } - - return r; - } - - /** - * @return Returns the base resource as a string. - */ - public String getResourceBase() - { - if (_baseResource == null) - return null; - return _baseResource.toString(); - } - /** * @return Returns the stylesheet as a Resource. */ - public Resource getStylesheet() + public Path getStylesheet() { if (_stylesheet != null) { @@ -210,34 +190,795 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, } } - public static Resource getDefaultStylesheet() + public static Path getDefaultStylesheet() { - return Resource.newResource(ResourceHandler.class.getResource("/jetty-dir.css")); + // TODO the returned path should point to the classpath. + // This points to a non-existent file '/jetty-dir.css'. + return Path.of("/jetty-dir.css"); } - public String[] getWelcomeFiles() + public List getWelcomeFiles() { return _welcomes; } @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public Request.Processor handle(Request request) throws Exception { - if (baseRequest.isHandled()) - return; - if (!HttpMethod.GET.is(request.getMethod()) && !HttpMethod.HEAD.is(request.getMethod())) { // try another handler - super.handle(target, baseRequest, request, response); + return super.handle(request); + } + + HttpContent content = _contentFactory.getContent(request.getPathInContext(), request.getConnectionMetaData().getHttpConfiguration().getOutputBufferSize()); + if (content == null) + { + // no content - try other handlers + return super.handle(request); + } + else + { + // TODO is it possible to get rid of the lambda allocation? + // TODO GW: perhaps HttpContent can extend Request.Processor? + return (rq, rs, cb) -> doGet(rq, rs, cb, content); + } + } + + private void doGet(Request request, Response response, Callback callback, HttpContent content) throws Exception + { + String pathInContext = request.getPathInContext(); + + // Is this a Range request? + Enumeration reqRanges = request.getHeaders().getValues(HttpHeader.RANGE.asString()); + if (!hasDefinedRange(reqRanges)) + reqRanges = null; + + + boolean endsWithSlash = pathInContext.endsWith(URIUtil.SLASH); + boolean checkPrecompressedVariants = _precompressedFormats.length > 0 && !endsWithSlash && reqRanges == null; + + try + { + // Directory? + if (Files.isDirectory(content.getPath())) + { + sendWelcome(content, pathInContext, endsWithSlash, request, response, callback); + return; + } + + // Strip slash? + if (endsWithSlash && pathInContext.length() > 1) + { + // TODO need helper code to edit URIs + String q = request.getHttpURI().getQuery(); + pathInContext = pathInContext.substring(0, pathInContext.length() - 1); + if (q != null && q.length() != 0) + pathInContext += "?" + q; + Response.sendRedirect(request, response, callback, URIUtil.addPaths(request.getContext().getContextPath(), pathInContext)); + return; + } + + // Conditional response? + if (passConditionalHeaders(request, response, content, callback)) + return; + + // Precompressed variant available? + Map precompressedContents = checkPrecompressedVariants ? content.getPrecompressedContents() : null; + if (precompressedContents != null && precompressedContents.size() > 0) + { + // Tell caches that response may vary by accept-encoding + response.addHeader(HttpHeader.VARY.asString(), HttpHeader.ACCEPT_ENCODING.asString()); + + List preferredEncodings = getPreferredEncodingOrder(request); + CompressedContentFormat precompressedContentEncoding = getBestPrecompressedContent(preferredEncodings, precompressedContents.keySet()); + if (precompressedContentEncoding != null) + { + HttpContent precompressedContent = precompressedContents.get(precompressedContentEncoding); + if (LOG.isDebugEnabled()) + LOG.debug("precompressed={}", precompressedContent); + content = precompressedContent; + response.getHeaders().put(HttpHeader.CONTENT_ENCODING, precompressedContentEncoding.getEncoding()); + } + } + + // TODO this should be done by HttpContent#getContentEncoding + if (isGzippedContent(pathInContext)) + response.getHeaders().put(HttpHeader.CONTENT_ENCODING, "gzip"); + + // Send the data + sendData(request, response, callback, content, reqRanges); + } + // Can be thrown from contentFactory.getContent() call when using invalid characters + catch (InvalidPathException e) + { + if (LOG.isDebugEnabled()) + LOG.debug("InvalidPathException for pathInContext: {}", pathInContext, e); + Response.writeError(request, response, callback, HttpStatus.NOT_FOUND_404); + } + catch (IllegalArgumentException e) + { + LOG.warn("Failed to serve resource: {}", pathInContext, e); + if (!response.isCommitted()) + Response.writeError(request, response, callback, HttpStatus.INTERNAL_SERVER_ERROR_500); + } + } + + private List getPreferredEncodingOrder(Request request) + { + Enumeration headers = request.getHeaders().getValues(HttpHeader.ACCEPT_ENCODING.asString()); + if (!headers.hasMoreElements()) + return Collections.emptyList(); + + String key = headers.nextElement(); + if (headers.hasMoreElements()) + { + StringBuilder sb = new StringBuilder(key.length() * 2); + do + { + sb.append(',').append(headers.nextElement()); + } + while (headers.hasMoreElements()); + key = sb.toString(); + } + + List values = _preferredEncodingOrderCache.get(key); + if (values == null) + { + QuotedQualityCSV encodingQualityCSV = new QuotedQualityCSV(_preferredEncodingOrder); + encodingQualityCSV.addValue(key); + values = encodingQualityCSV.getValues(); + + // keep cache size in check even if we get strange/malicious input + if (_preferredEncodingOrderCache.size() > _encodingCacheSize) + _preferredEncodingOrderCache.clear(); + + _preferredEncodingOrderCache.put(key, values); + } + + return values; + } + + private boolean isGzippedContent(String path) + { + if (path == null || _gzipEquivalentFileExtensions == null) + return false; + + for (String suffix : _gzipEquivalentFileExtensions) + { + if (path.endsWith(suffix)) + return true; + } + return false; + } + + private CompressedContentFormat getBestPrecompressedContent(List preferredEncodings, java.util.Collection availableFormats) + { + if (availableFormats.isEmpty()) + return null; + + for (String encoding : preferredEncodings) + { + for (CompressedContentFormat format : availableFormats) + { + if (format.getEncoding().equals(encoding)) + return format; + } + + if ("*".equals(encoding)) + return availableFormats.iterator().next(); + + if (HttpHeaderValue.IDENTITY.asString().equals(encoding)) + return null; + } + return null; + } + + /** + * @return true if the request was processed, false otherwise. + */ + private boolean passConditionalHeaders(Request request, Response response, HttpContent content, Callback callback) throws IOException + { + try + { + String ifm = null; + String ifnm = null; + String ifms = null; + long ifums = -1; + + // Find multiple fields by iteration as an optimization + for (HttpField field : request.getHeaders()) + { + if (field.getHeader() != null) + { + switch (field.getHeader()) + { + case IF_MATCH -> ifm = field.getValue(); + case IF_NONE_MATCH -> ifnm = field.getValue(); + case IF_MODIFIED_SINCE -> ifms = field.getValue(); + case IF_UNMODIFIED_SINCE -> ifums = DateParser.parseDate(field.getValue()); + default -> + { + } + } + } + } + + if (_etags) + { + String etag = content.getETagValue(); + if (ifm != null) + { + boolean match = false; + if (etag != null && !etag.startsWith("W/")) + { + QuotedCSV quoted = new QuotedCSV(true, ifm); + for (String etagWithSuffix : quoted) + { + if (CompressedContentFormat.tagEquals(etag, etagWithSuffix)) + { + match = true; + break; + } + } + } + + if (!match) + { + Response.writeError(request, response, callback, HttpStatus.PRECONDITION_FAILED_412); + return true; + } + } + + if (ifnm != null && etag != null) + { + // Handle special case of exact match OR gzip exact match + if (CompressedContentFormat.tagEquals(etag, ifnm) && ifnm.indexOf(',') < 0) + { + Response.writeError(request, response, callback, HttpStatus.NOT_MODIFIED_304); + return true; + } + + // Handle list of tags + QuotedCSV quoted = new QuotedCSV(true, ifnm); + for (String tag : quoted) + { + if (CompressedContentFormat.tagEquals(etag, tag)) + { + Response.writeError(request, response, callback, HttpStatus.NOT_MODIFIED_304); + return true; + } + } + + // If etag requires content to be served, then do not check if-modified-since + return false; + } + } + + // Handle if modified since + if (ifms != null) + { + //Get jetty's Response impl + String mdlm = content.getLastModifiedValue(); + if (ifms.equals(mdlm)) + { + Response.writeError(request, response, callback, HttpStatus.NOT_MODIFIED_304); + return true; + } + + long ifmsl = request.getHeaders().getDateField(HttpHeader.IF_MODIFIED_SINCE.asString()); + if (ifmsl != -1 && Files.getLastModifiedTime(content.getPath()).toMillis() / 1000 <= ifmsl / 1000) + { + Response.writeError(request, response, callback, HttpStatus.NOT_MODIFIED_304); + return true; + } + } + + // Parse the if[un]modified dates and compare to resource + if (ifums != -1 && Files.getLastModifiedTime(content.getPath()).toMillis() / 1000 > ifums / 1000) + { + Response.writeError(request, response, callback, HttpStatus.PRECONDITION_FAILED_412); + return true; + } + } + catch (IllegalArgumentException iae) + { + if (!response.isCommitted()) + Response.writeError(request, response, callback, HttpStatus.BAD_REQUEST_400); + throw iae; + } + + return false; + } + + protected void sendWelcome(HttpContent content, String pathInContext, boolean endsWithSlash, Request request, Response response, Callback callback) throws Exception + { + // Redirect to directory + if (!endsWithSlash) + { + // TODO need helper code to edit URIs + StringBuilder buf = new StringBuilder(request.getHttpURI().asString()); + int param = buf.lastIndexOf(";"); + if (param < 0 || buf.lastIndexOf("/", param) > 0) + buf.append('/'); + else + buf.insert(param, '/'); + String q = request.getHttpURI().getQuery(); + if (q != null && q.length() != 0) + { + buf.append('?'); + buf.append(q); + } + response.setContentLength(0); + Response.sendRedirect(request, response, callback, buf.toString()); return; } - if (_resourceService.doGet(request, response)) - baseRequest.setHandled(true); + // look for a welcome file + if (_welcomer.welcome(request, response, callback)) + return; + + if (!passConditionalHeaders(request, response, content, callback)) + sendDirectory(request, response, content, callback, pathInContext); + } + + private void sendDirectory(Request request, Response response, HttpContent httpContent, Callback callback, String pathInContext) throws IOException + { + Path resource = httpContent.getPath(); + if (!_dirAllowed) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403); + return; + } + + String base = URIUtil.addEncodedPaths(request.getHttpURI().getPath(), URIUtil.SLASH); + String dir = getListHTML(resource, base, pathInContext.length() > 1, request.getHttpURI().getQuery()); + if (dir == null) + { + Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403); + return; + } + + byte[] data = dir.getBytes(StandardCharsets.UTF_8); + response.setContentType("text/html;charset=utf-8"); + response.setContentLength(data.length); + response.write(true, callback, ByteBuffer.wrap(data)); + } + + private String getListHTML(Path path, String base, boolean parent, String query) throws IOException + { + // This method doesn't check aliases, so it is OK to canonicalize here. + base = URIUtil.canonicalPath(base); + if (base == null || !Files.isDirectory(path)) + return null; + + List items; + try (DirectoryStream directoryStream = Files.newDirectoryStream(path)) + { + Stream stream = StreamSupport.stream(directoryStream.spliterator(), false); + items = stream.collect(Collectors.toCollection(ArrayList::new)); + } + catch (IOException e) + { + LOG.debug("Directory list access failure", e); + return null; + } + + boolean sortOrderAscending = true; + String sortColumn = "N"; // name (or "M" for Last Modified, or "S" for Size) + + // check for query + if (query != null) + { + MultiMap params = new MultiMap<>(); + UrlEncoded.decodeUtf8To(query, 0, query.length(), params); + + String paramO = params.getString("O"); + String paramC = params.getString("C"); + if (StringUtil.isNotBlank(paramO)) + { + if (paramO.equals("A")) + { + sortOrderAscending = true; + } + else if (paramO.equals("D")) + { + sortOrderAscending = false; + } + } + if (StringUtil.isNotBlank(paramC)) + { + if (paramC.equals("N") || paramC.equals("M") || paramC.equals("S")) + { + sortColumn = paramC; + } + } + } + + // Perform sort + if (sortColumn.equals("M")) + items.sort(PathCollators.byLastModified(sortOrderAscending)); + else if (sortColumn.equals("S")) + items.sort(PathCollators.bySize(sortOrderAscending)); else - // no resource - try other handlers - super.handle(target, baseRequest, request, response); + items.sort(PathCollators.byName(sortOrderAscending)); + + String decodedBase = URIUtil.decodePath(base); + String title = "Directory: " + StringUtil.sanitizeXmlString(decodedBase); + + StringBuilder buf = new StringBuilder(4096); + + // Doctype Declaration (HTML5) + buf.append("\n"); + buf.append("\n"); + + // HTML Header + buf.append("\n"); + buf.append("\n"); + buf.append("\n"); + buf.append(""); + buf.append(title); + buf.append("\n"); + buf.append("\n"); + + // HTML Body + buf.append("\n"); + buf.append("

    ").append(title).append("

    \n"); + + // HTML Table + final String ARROW_DOWN = "  ⇩"; + final String ARROW_UP = "  ⇧"; + + buf.append("\n"); + buf.append("\n"); + + String arrow = ""; + String order = "A"; + if (sortColumn.equals("N")) + { + if (sortOrderAscending) + { + order = "D"; + arrow = ARROW_UP; + } + else + { + order = "A"; + arrow = ARROW_DOWN; + } + } + + buf.append(""); + + arrow = ""; + order = "A"; + if (sortColumn.equals("M")) + { + if (sortOrderAscending) + { + order = "D"; + arrow = ARROW_UP; + } + else + { + order = "A"; + arrow = ARROW_DOWN; + } + } + + buf.append(""); + + arrow = ""; + order = "A"; + if (sortColumn.equals("S")) + { + if (sortOrderAscending) + { + order = "D"; + arrow = ARROW_UP; + } + else + { + order = "A"; + arrow = ARROW_DOWN; + } + } + buf.append("\n"); + buf.append("\n"); + + buf.append("\n"); + + String encodedBase = hrefEncodeURI(base); + + if (parent) + { + // Name + buf.append(""); + // Last Modified + buf.append(""); + // Size + buf.append(""); + buf.append("\n"); + } + + DateFormat dfmt = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); + for (Path item : items) + { + String name = item.getFileName().toString(); + if (StringUtil.isBlank(name)) + { + continue; // skip + } + + if (Files.isDirectory(item)) + { + name += URIUtil.SLASH; + } + + // Name + buf.append(""); + + // Last Modified + buf.append(""); + + // Size + buf.append("\n"); + } + buf.append("\n"); + buf.append("
    "); + buf.append("Name").append(arrow); + buf.append(""); + buf.append("Last Modified").append(arrow); + buf.append(""); + buf.append("Size").append(arrow); + buf.append("
    Parent Directory--
    "); + buf.append(StringUtil.sanitizeXmlString(name)); + buf.append(" "); + buf.append(""); + long lastModified = Files.getLastModifiedTime(item).toMillis(); + if (lastModified > 0) + { + buf.append(dfmt.format(new Date(lastModified))); + } + buf.append(" "); + long length = Files.size(item); + if (length >= 0) + { + buf.append(String.format("%,d bytes", length)); + } + buf.append(" 
    \n"); + buf.append("\n"); + + return buf.toString(); + } + + private static String hrefEncodeURI(String raw) + { + StringBuilder buf = null; + + loop: + for (int i = 0; i < raw.length(); i++) + { + char c = raw.charAt(i); + switch (c) + { + case '\'': + case '"': + case '<': + case '>': + buf = new StringBuilder(raw.length() << 1); + break loop; + default: + break; + } + } + if (buf == null) + return raw; + + for (int i = 0; i < raw.length(); i++) + { + char c = raw.charAt(i); + switch (c) + { + case '"' -> buf.append("%22"); + case '\'' -> buf.append("%27"); + case '<' -> buf.append("%3C"); + case '>' -> buf.append("%3E"); + default -> buf.append(c); + } + } + + return buf.toString(); + } + + private boolean sendData(Request request, Response response, Callback callback, HttpContent content, Enumeration reqRanges) throws IOException + { + long contentLength = content.getContentLengthValue(); + + if (LOG.isDebugEnabled()) + LOG.debug(String.format("sendData content=%s", content)); + + if (reqRanges == null || !reqRanges.hasMoreElements() || contentLength < 0) + { + // if there were no ranges, send entire entity + + // write the headers + putHeaders(response, content, USE_KNOWN_CONTENT_LENGTH); + + // write the content + writeContent(response, callback, content); + } + else + { + throw new UnsupportedOperationException("TODO"); + // TODO rewrite with ByteChannel only which should simplify HttpContentRangeWriter as HttpContent's Path always provides a SeekableByteChannel + // but MultiPartOutputStream also needs to be rewritten. +/* + // Parse the satisfiable ranges + List ranges = InclusiveByteRange.satisfiableRanges(reqRanges, contentLength); + + // if there are no satisfiable ranges, send 416 response + if (ranges == null || ranges.size() == 0) + { + putHeaders(response, content, USE_KNOWN_CONTENT_LENGTH); + response.getHeaders().put(HttpHeader.CONTENT_RANGE, + InclusiveByteRange.to416HeaderRangeString(contentLength)); + sendStatus(416, response, callback); + return true; + } + + // if there is only a single valid range (must be satisfiable + // since were here now), send that range with a 216 response + if (ranges.size() == 1) + { + InclusiveByteRange singleSatisfiableRange = ranges.iterator().next(); + long singleLength = singleSatisfiableRange.getSize(); + putHeaders(response, content, singleLength); + response.setStatus(206); + if (!response.getHeaders().contains(HttpHeader.DATE.asString())) + response.getHeaders().addDateField(HttpHeader.DATE.asString(), System.currentTimeMillis()); + response.getHeaders().put(HttpHeader.CONTENT_RANGE, + singleSatisfiableRange.toHeaderRangeString(contentLength)); + writeContent(content, out, singleSatisfiableRange.getFirst(), singleLength); + return true; + } + + // multiple non-overlapping valid ranges cause a multipart + // 216 response which does not require an overall + // content-length header + // + putHeaders(response, content, NO_CONTENT_LENGTH); + String mimetype = content.getContentTypeValue(); + if (mimetype == null) + LOG.warn("Unknown mimetype for {}", request.getHttpURI()); + response.setStatus(206); + if (!response.getHeaders().contains(HttpHeader.DATE.asString())) + response.getHeaders().addDateField(HttpHeader.DATE.asString(), System.currentTimeMillis()); + + // If the request has a "Request-Range" header then we need to + // send an old style multipart/x-byteranges Content-Type. This + // keeps Netscape and acrobat happy. This is what Apache does. + String ctp; + if (request.getHeaders().get(HttpHeader.REQUEST_RANGE.asString()) != null) + ctp = "multipart/x-byteranges; boundary="; + else + ctp = "multipart/byteranges; boundary="; + MultiPartOutputStream multi = new MultiPartOutputStream(out); + response.setContentType(ctp + multi.getBoundary()); + + // calculate the content-length + int length = 0; + String[] header = new String[ranges.size()]; + int i = 0; + final int CRLF = "\r\n".length(); + final int DASHDASH = "--".length(); + final int BOUNDARY = multi.getBoundary().length(); + final int FIELD_SEP = ": ".length(); + for (InclusiveByteRange ibr : ranges) + { + header[i] = ibr.toHeaderRangeString(contentLength); + if (i > 0) // in-part + length += CRLF; + length += DASHDASH + BOUNDARY + CRLF; + if (mimetype != null) + length += HttpHeader.CONTENT_TYPE.asString().length() + FIELD_SEP + mimetype.length() + CRLF; + length += HttpHeader.CONTENT_RANGE.asString().length() + FIELD_SEP + header[i].length() + CRLF; + length += CRLF; + length += ibr.getSize(); + i++; + } + length += CRLF + DASHDASH + BOUNDARY + DASHDASH + CRLF; + response.setContentLength(length); + + try (RangeWriter rangeWriter = HttpContentRangeWriter.newRangeWriter(content)) + { + i = 0; + for (InclusiveByteRange ibr : ranges) + { + multi.startPart(mimetype, new String[]{HttpHeader.CONTENT_RANGE + ": " + header[i]}); + rangeWriter.writeTo(multi, ibr.getFirst(), ibr.getSize()); + i++; + } + } + + multi.close(); + */ + } + return true; + } + + private void writeContent(Response response, Callback callback, HttpContent content) throws IOException + { + ByteBuffer buffer = content.getBuffer(); + if (buffer != null) + response.write(true, callback, buffer); + else + new ContentWriterIteratingCallback(content, response, callback).iterate(); + } + + private void putHeaders(Response response, HttpContent content, long contentLength) + { + HttpFields.Mutable headers = response.getHeaders(); + + // TODO it is very inefficient to do many put's to a HttpFields, as each put is a full iteration. + // it might be better remove headers en masse and then just add the extras: +// headers.remove(EnumSet.of( +// HttpHeader.LAST_MODIFIED, +// HttpHeader.CONTENT_LENGTH, +// HttpHeader.CONTENT_TYPE, +// HttpHeader.CONTENT_ENCODING, +// HttpHeader.ETAG, +// HttpHeader.ACCEPT_RANGES, +// HttpHeader.CACHE_CONTROL +// )); +// HttpField lm = content.getLastModified(); +// if (lm != null) +// headers.add(lm); +// etc. + + HttpField lm = content.getLastModified(); + if (lm != null) + headers.put(lm); + + if (contentLength == USE_KNOWN_CONTENT_LENGTH) + { + headers.put(content.getContentLength()); + } + else if (contentLength > NO_CONTENT_LENGTH) + { + headers.putLongField(HttpHeader.CONTENT_LENGTH, contentLength); + } + + HttpField ct = content.getContentType(); + if (ct != null) + headers.put(ct); + + HttpField ce = content.getContentEncoding(); + if (ce != null) + headers.put(ce); + + if (_etags) + { + HttpField et = content.getETag(); + if (et != null) + headers.put(et); + } + + HttpFields.Mutable fields = response.getHeaders(); + if (_acceptRanges && !fields.contains(HttpHeader.ACCEPT_RANGES)) + fields.add(new PreEncodedHttpField(HttpHeader.ACCEPT_RANGES, "bytes")); + if (_cacheControl != null && !fields.contains(HttpHeader.CACHE_CONTROL)) + fields.add(_cacheControl); + } + + private boolean hasDefinedRange(Enumeration reqRanges) + { + return (reqRanges != null && reqRanges.hasMoreElements()); } /** @@ -245,7 +986,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public boolean isAcceptRanges() { - return _resourceService.isAcceptRanges(); + return _acceptRanges; } /** @@ -253,17 +994,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public boolean isDirAllowed() { - return _resourceService.isDirAllowed(); - } - - /** - * Get the directory option. - * - * @return true if directories are listed. - */ - public boolean isDirectoriesListed() - { - return _resourceService.isDirAllowed(); + return _dirAllowed; } /** @@ -271,7 +1002,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public boolean isEtags() { - return _resourceService.isEtags(); + return _etags; } /** @@ -279,7 +1010,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public CompressedContentFormat[] getPrecompressedFormats() { - return _resourceService.getPrecompressedFormats(); + return _precompressedFormats; } /** @@ -287,7 +1018,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public boolean isPathInfoOnly() { - return _resourceService.isPathInfoOnly(); + return _pathInfoOnly; } /** @@ -295,7 +1026,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public boolean isRedirectWelcome() { - return _resourceService.isRedirectWelcome(); + return _redirectWelcome; } /** @@ -303,14 +1034,14 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public void setAcceptRanges(boolean acceptRanges) { - _resourceService.setAcceptRanges(acceptRanges); + _acceptRanges = acceptRanges; } /** * @param base The resourceBase to server content from. If null the * context resource base is used. */ - public void setBaseResource(Resource base) + public void setBaseResource(Path base) { _baseResource = base; } @@ -320,7 +1051,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public void setCacheControl(String cacheControl) { - _resourceService.setCacheControl(new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, cacheControl)); + _cacheControl = new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, cacheControl); } /** @@ -328,17 +1059,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public void setDirAllowed(boolean dirAllowed) { - _resourceService.setDirAllowed(dirAllowed); - } - - /** - * Set the directory. - * - * @param directory true if directories are listed. - */ - public void setDirectoriesListed(boolean directory) - { - _resourceService.setDirAllowed(directory); + _dirAllowed = dirAllowed; } /** @@ -346,7 +1067,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public void setEtags(boolean etags) { - _resourceService.setEtags(etags); + _etags = etags; } /** @@ -354,7 +1075,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public void setGzipEquivalentFileExtensions(List gzipEquivalentFileExtensions) { - _resourceService.setGzipEquivalentFileExtensions(gzipEquivalentFileExtensions); + _gzipEquivalentFileExtensions = gzipEquivalentFileExtensions; } /** @@ -363,7 +1084,20 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public void setPrecompressedFormats(CompressedContentFormat[] precompressedFormats) { - _resourceService.setPrecompressedFormats(precompressedFormats); + _precompressedFormats = precompressedFormats; + _preferredEncodingOrder = stream(_precompressedFormats).map(CompressedContentFormat::getEncoding).toArray(String[]::new); + } + + public void setEncodingCacheSize(int encodingCacheSize) + { + _encodingCacheSize = encodingCacheSize; + if (encodingCacheSize > _preferredEncodingOrderCache.size()) + _preferredEncodingOrderCache.clear(); + } + + public int getEncodingCacheSize() + { + return _encodingCacheSize; } public void setMimeTypes(MimeTypes mimeTypes) @@ -376,7 +1110,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public void setPathInfoOnly(boolean pathInfoOnly) { - _resourceService.setPathInfoOnly(pathInfoOnly); + _pathInfoOnly = pathInfoOnly; } /** @@ -386,34 +1120,19 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, */ public void setRedirectWelcome(boolean redirectWelcome) { - _resourceService.setRedirectWelcome(redirectWelcome); - } - - /** - * @param resourceBase The base resource as a string. - */ - public void setResourceBase(String resourceBase) - { - try - { - setBaseResource(Resource.newResource(resourceBase)); - } - catch (Exception e) - { - LOG.warn("Invalid Base Resource reference: {}", resourceBase, e); - throw new IllegalArgumentException(resourceBase); - } + _redirectWelcome = redirectWelcome; } /** * @param stylesheet The location of the stylesheet to be used as a String. */ + // TODO accept a Path instead of a String? public void setStylesheet(String stylesheet) { try { - _stylesheet = Resource.newResource(stylesheet); - if (!_stylesheet.exists()) + _stylesheet = Path.of(stylesheet); + if (!Files.exists(_stylesheet)) { LOG.warn("unable to find custom stylesheet: {}", stylesheet); _stylesheet = null; @@ -426,8 +1145,365 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory, } } - public void setWelcomeFiles(String[] welcomeFiles) + public void setWelcomeFiles(List welcomeFiles) { _welcomes = welcomeFiles; } + + private class PathContentFactory implements HttpContent.ContentFactory + { + @Override + public HttpContent getContent(String path, int maxBuffer) throws IOException + { + if (_precompressedFormats.length > 0) + { + // Is the precompressed content cached? + Map compressedContents = new HashMap<>(); + for (CompressedContentFormat format : _precompressedFormats) + { + String compressedPathInContext = path + format.getExtension(); + + // Is there a precompressed resource? + PathHttpContent compressedContent = load(compressedPathInContext, null); + if (compressedContent != null) + compressedContents.put(format, compressedContent); + } + if (!compressedContents.isEmpty()) + return load(path, compressedContents); + } + + return load(path, null); + } + + private PathHttpContent load(String path, Map compressedEquivalents) + { + if (path.startsWith("/")) + path = path.substring(1); + // TODO cache _baseResource.toUri() + Path resolved = Path.of(_baseResource.toUri().resolve(path)); + // TODO call alias checker + if (!Files.exists(resolved)) + return null; + String mimeType = _mimeTypes.getMimeByExtension(resolved.getFileName().toString()); + return new PathHttpContent(resolved, mimeType, compressedEquivalents); + } + } + + private static class PathHttpContent implements HttpContent + { + private final Path _path; + private final PreEncodedHttpField _contentType; + private final String _characterEncoding; + private final MimeTypes.Type _mimeType; + private final Map _compressedContents; + + public PathHttpContent(Path path, String contentType, Map compressedEquivalents) + { + _path = path; + _contentType = contentType == null ? null : new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, contentType); + _characterEncoding = _contentType == null ? null : MimeTypes.getCharsetFromContentType(contentType); + _mimeType = _contentType == null ? null : MimeTypes.CACHE.get(MimeTypes.getContentTypeWithoutCharset(contentType)); + _compressedContents = compressedEquivalents; + } + + @Override + public HttpField getContentType() + { + return _contentType; + } + + @Override + public String getContentTypeValue() + { + return _contentType.getValue(); + } + + @Override + public String getCharacterEncoding() + { + return _characterEncoding; + } + + @Override + public MimeTypes.Type getMimeType() + { + return _mimeType; + } + + @Override + public HttpField getContentEncoding() + { + return null; + } + + @Override + public String getContentEncodingValue() + { + return null; + } + + @Override + public HttpField getContentLength() + { + long cl = getContentLengthValue(); + return cl >= 0 ? new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH, cl) : null; + } + + @Override + public long getContentLengthValue() + { + try + { + if (Files.isDirectory(_path)) + return NO_CONTENT_LENGTH; + return Files.size(_path); + } + catch (IOException e) + { + return NO_CONTENT_LENGTH; + } + } + + @Override + public HttpField getLastModified() + { + String lm = getLastModifiedValue(); + return lm != null ? new HttpField(HttpHeader.LAST_MODIFIED, lm) : null; + } + + @Override + public String getLastModifiedValue() + { + try + { + long lm = Files.getLastModifiedTime(_path).toMillis(); + return DateGenerator.formatDate(lm); + } + catch (IOException e) + { + return null; + } + } + + @Override + public HttpField getETag() + { + String weakETag = getWeakETag(); + return weakETag == null ? null : new HttpField(HttpHeader.ETAG, weakETag); + } + + @Override + public String getETagValue() + { + return getWeakETag(); + } + + private String getWeakETag() + { + StringBuilder b = new StringBuilder(32); + b.append("W/\""); + + String name = _path.toAbsolutePath().toString(); + int length = name.length(); + long lhash = 0; + for (int i = 0; i < length; i++) + { + lhash = 31 * lhash + name.charAt(i); + } + + Base64.Encoder encoder = Base64.getEncoder().withoutPadding(); + try + { + long lastModifiedTime = Files.getLastModifiedTime(_path).toMillis(); + b.append(encoder.encodeToString(longToBytes(lastModifiedTime ^ lhash))); + } + catch (IOException e) + { + LOG.debug("Unable to get last modified time of {}", _path, e); + return null; + } + try + { + long contentLengthValue = Files.size(_path); + b.append(encoder.encodeToString(longToBytes(contentLengthValue ^ lhash))); + } + catch (IOException e) + { + LOG.debug("Unable to get size of {}", _path, e); + return null; + } + b.append('"'); + return b.toString(); + } + + private static byte[] longToBytes(long value) + { + byte[] result = new byte[Long.BYTES]; + for (int i = Long.BYTES - 1; i >= 0; i--) + { + result[i] = (byte)(value & 0xFF); + value >>= 8; + } + return result; + } + + @Override + public Path getPath() + { + return _path; + } + + @Override + public Resource getResource() + { + // TODO cache or create in constructor? + return new PathResource(_path); + } + + @Override + public Map getPrecompressedContents() + { + return _compressedContents; + } + + @Override + public ByteBuffer getBuffer() + { + return null; + } + + @Override + public void release() + { + } + } + + // TODO a ReadableByteChannel IteratingCallback that writes to a Response looks generic enough to be moved to some util module + private static class ContentWriterIteratingCallback extends IteratingCallback + { + private final ReadableByteChannel source; + private final Content.Writer target; + private final Callback callback; + private final ByteBuffer byteBuffer; + + public ContentWriterIteratingCallback(HttpContent content, Response target, Callback callback) throws IOException + { + // TODO: is it possible to do zero-copy transfer? +// WritableByteChannel c = Response.asWritableByteChannel(target); +// FileChannel fileChannel = (FileChannel) source; +// fileChannel.transferTo(0, contentLength, c); + + this.source = Files.newByteChannel(content.getPath()); + this.target = target; + this.callback = callback; + HttpConfiguration httpConfiguration = target.getRequest().getConnectionMetaData().getHttpConfiguration(); + int outputBufferSize = httpConfiguration.getOutputBufferSize(); + boolean useOutputDirectByteBuffers = httpConfiguration.isUseOutputDirectByteBuffers(); + this.byteBuffer = useOutputDirectByteBuffers ? ByteBuffer.allocateDirect(outputBufferSize) : ByteBuffer.allocate(outputBufferSize); // TODO use pool + } + + @Override + protected Action process() throws Throwable + { + if (!source.isOpen()) + return Action.SUCCEEDED; + byteBuffer.clear(); + int read = source.read(byteBuffer); + if (read == -1) + { + IO.close(source); + target.write(true, this, BufferUtil.EMPTY_BUFFER); + return Action.SCHEDULED; + } + byteBuffer.flip(); + target.write(false, this, byteBuffer); + return Action.SCHEDULED; + } + + @Override + protected void onCompleteSuccess() + { + callback.succeeded(); + } + + @Override + protected void onCompleteFailure(Throwable x) + { + callback.failed(x); + } + } + + public interface Welcomer + { + /** + * @return true if the request was processed, false otherwise. + */ + boolean welcome(Request request, Response response, Callback callback) throws Exception; + } + + private class DefaultWelcomer implements Welcomer + { + @Override + public boolean welcome(Request request, Response response, Callback callback) throws Exception + { + String pathInContext = request.getPathInContext(); + String welcome = getWelcomeFile(pathInContext); + if (welcome != null) + { + String contextPath = request.getContext().getContextPath(); + + if (_pathInfoOnly) + welcome = URIUtil.addPaths(contextPath, welcome); + + if (LOG.isDebugEnabled()) + LOG.debug("welcome={}", welcome); + + if (_redirectWelcome) + { + // Redirect to the index + response.setContentLength(0); + + // TODO need helper code to edit URIs + String uri = URIUtil.encodePath(URIUtil.addPaths(request.getContext().getContextPath(), welcome)); + String q = request.getHttpURI().getQuery(); + if (q != null && !q.isEmpty()) + uri += "?" + q; + + Response.sendRedirect(request, response, callback, uri); + return true; + } + + // Serve welcome file + HttpContent c = _contentFactory.getContent(welcome, request.getConnectionMetaData().getHttpConfiguration().getOutputBufferSize()); + sendData(request, response, callback, c, null); + return true; + } + return false; + } + + private String getWelcomeFile(String pathInContext) + { + if (_welcomes == null) + return null; + + for (String welcome : _welcomes) + { + // TODO this logic is similar to the one in PathContentFactory.getContent() + // TODO GW: This logic needs to be extensible so that a welcome file may be a servlet (yeah I know it shouldn't + // be called a welcome file then. So for example if /foo/index.jsp is the welcome file, we can't + // serve it's contents - rather we have to let the servlet layer to either a redirect or a RequestDispatcher to it. + // Worse yet, if there was a servlet mapped to /foo/index.html, then we need to be able to dispatch to it + // EVEN IF the file does not exist. + String welcomeInContext = URIUtil.addPaths(pathInContext, welcome); + String path = pathInContext; + if (path.startsWith("/")) + path = path.substring(1); + Path welcomePath = Path.of(_baseResource.toUri().resolve(path).resolve(welcome)); + if (Files.exists(welcomePath)) + return welcomeInContext; + } + // not found + return null; + } + } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java index aebdedf5839..c49b1df2d3c 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java @@ -13,15 +13,12 @@ package org.eclipse.jetty.server.handler; -import java.io.IOException; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.URIUtil; /** @@ -29,9 +26,9 @@ import org.eclipse.jetty.util.URIUtil; *

    SecuredRedirectHandler uses the information present in {@link HttpConfiguration} * attempting to redirect to the {@link HttpConfiguration#getSecureScheme()} and * {@link HttpConfiguration#getSecurePort()} for any request that - * {@link HttpServletRequest#isSecure()} is false.

    + * {@link Request#isSecure()} is false.

    */ -public class SecuredRedirectHandler extends HandlerWrapper +public class SecuredRedirectHandler extends Handler.Wrapper { /** * The redirect code to send in response. @@ -43,7 +40,7 @@ public class SecuredRedirectHandler extends HandlerWrapper */ public SecuredRedirectHandler() { - this(HttpServletResponse.SC_MOVED_TEMPORARILY); + this(HttpStatus.MOVED_TEMPORARILY_302); } /** @@ -60,36 +57,31 @@ public class SecuredRedirectHandler extends HandlerWrapper } @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public Request.Processor handle(Request request) throws Exception { - HttpChannel channel = baseRequest.getHttpChannel(); - if (baseRequest.isSecure() || channel == null) + if (request.isSecure()) { // Nothing to do here. - super.handle(target, baseRequest, request, response); - return; + return super.handle(request); } - baseRequest.setHandled(true); + return (rq, rs, cb) -> + { + HttpConfiguration httpConfig = rq.getConnectionMetaData().getHttpConfiguration(); - HttpConfiguration httpConfig = channel.getHttpConfiguration(); - if (httpConfig == null) - { - response.sendError(HttpStatus.FORBIDDEN_403, "Missing HttpConfiguration"); - return; - } - - int securePort = httpConfig.getSecurePort(); - if (securePort > 0) - { - String secureScheme = httpConfig.getSecureScheme(); - String url = URIUtil.newURI(secureScheme, baseRequest.getServerName(), securePort, baseRequest.getRequestURI(), baseRequest.getQueryString()); - response.setContentLength(0); - baseRequest.getResponse().sendRedirect(_redirectCode, url, true); - } - else - { - response.sendError(HttpStatus.FORBIDDEN_403, "HttpConfiguration.securePort not configured"); - } + int securePort = httpConfig.getSecurePort(); + if (securePort > 0) + { + String secureScheme = httpConfig.getSecureScheme(); + String url = URIUtil.newURI(secureScheme, Request.getServerName(request), securePort, request.getHttpURI().getPath(), request.getHttpURI().getQuery()); + rs.setHeader(HttpHeader.LOCATION, url); // TODO need a utility for this + rs.setStatus(_redirectCode); + rs.write(true, cb); + } + else + { + Response.writeError(rq, rs, cb, HttpStatus.FORBIDDEN_403, "HttpConfiguration.securePort not configured"); + } + }; } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java index 783d146a2c0..54038522e87 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java @@ -15,17 +15,13 @@ package org.eclipse.jetty.server.handler; import java.io.IOException; import java.net.HttpURLConnection; -import java.net.InetSocketAddress; import java.net.SocketException; import java.net.URL; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,7 +63,7 @@ import org.slf4j.LoggerFactory; * } * */ -public class ShutdownHandler extends HandlerWrapper +public class ShutdownHandler extends Handler.Wrapper { private static final Logger LOG = LoggerFactory.getLogger(ShutdownHandler.class); @@ -95,8 +91,11 @@ public class ShutdownHandler extends HandlerWrapper public ShutdownHandler(String shutdownToken, boolean exitJVM, boolean sendShutdownAtStart) { this._shutdownToken = shutdownToken; + /* TODO setExitJvm(exitJVM); setSendShutdownAtStart(sendShutdownAtStart); + + */ } public void sendShutdown() throws IOException @@ -148,8 +147,9 @@ public class ShutdownHandler extends HandlerWrapper } @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public Request.Processor handle(Request request) throws Exception { + /* TODO if (!target.equals("/shutdown")) { super.handle(target, baseRequest, request, response); @@ -176,8 +176,12 @@ public class ShutdownHandler extends HandlerWrapper LOG.info("Shutting down by request from {}", request.getRemoteAddr()); doShutdown(baseRequest, response); + + */ + return null; } + /* TODO protected void doShutdown(Request baseRequest, HttpServletResponse response) throws IOException { for (Connector connector : getServer().getConnectors()) @@ -263,4 +267,6 @@ public class ShutdownHandler extends HandlerWrapper { return _exitJvm; } + + */ } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java index 2bde850047b..e75fe93c716 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java @@ -13,604 +13,419 @@ package org.eclipse.jetty.server.handler; -import java.io.IOException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicLong; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.LongAdder; -import jakarta.servlet.AsyncEvent; -import jakarta.servlet.AsyncListener; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.AsyncContextEvent; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.server.Content; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.HttpChannelState; +import org.eclipse.jetty.server.HttpStream; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.annotation.ManagedAttribute; -import org.eclipse.jetty.util.annotation.ManagedObject; -import org.eclipse.jetty.util.annotation.ManagedOperation; -import org.eclipse.jetty.util.component.Graceful; import org.eclipse.jetty.util.statistic.CounterStatistic; import org.eclipse.jetty.util.statistic.SampleStatistic; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -@ManagedObject("Request Statistics Gathering") -public class StatisticsHandler extends HandlerWrapper implements Graceful +public class StatisticsHandler extends Handler.Wrapper { - private static final Logger LOG = LoggerFactory.getLogger(StatisticsHandler.class); - private final AtomicLong _statsStartedAt = new AtomicLong(); - private final Shutdown _shutdown; + private final ConcurrentHashMap _connectionStats = new ConcurrentHashMap<>(); private final CounterStatistic _requestStats = new CounterStatistic(); + private final CounterStatistic _handleStats = new CounterStatistic(); + private final CounterStatistic _processStats = new CounterStatistic(); private final SampleStatistic _requestTimeStats = new SampleStatistic(); - private final CounterStatistic _dispatchedStats = new CounterStatistic(); - private final SampleStatistic _dispatchedTimeStats = new SampleStatistic(); - private final CounterStatistic _asyncWaitStats = new CounterStatistic(); - - private final LongAdder _asyncDispatches = new LongAdder(); - private final LongAdder _expires = new LongAdder(); - private final LongAdder _errors = new LongAdder(); + private final SampleStatistic _handleTimeStats = new SampleStatistic(); + private final SampleStatistic _processTimeStats = new SampleStatistic(); private final LongAdder _responses1xx = new LongAdder(); private final LongAdder _responses2xx = new LongAdder(); private final LongAdder _responses3xx = new LongAdder(); private final LongAdder _responses4xx = new LongAdder(); private final LongAdder _responses5xx = new LongAdder(); - private final LongAdder _responsesTotalBytes = new LongAdder(); - private boolean _gracefulShutdownWaitsForRequests = true; - - private final AsyncListener _onCompletion = new AsyncListener() + @Override + public Request.Processor handle(Request request) throws Exception { - @Override - public void onStartAsync(AsyncEvent event) + long beginTimeStamp = System.nanoTime(); + _connectionStats.computeIfAbsent(request.getConnectionMetaData().getId(), id -> { - event.getAsyncContext().addListener(this); - } + // TODO test this with localconnector endpoint that has multiple requests per connection. + request.getConnectionMetaData().getConnection().addEventListener(new Connection.Listener() + { + @Override + public void onClosed(Connection connection) + { + // complete connections stats + _connectionStats.remove(id); + } + }); + return "SomeConnectionStatsObject"; + }); - @Override - public void onTimeout(AsyncEvent event) - { - _expires.increment(); - } + StatisticsRequest statisticsRequest = new StatisticsRequest(request); + _handleStats.increment(); + _requestStats.increment(); - @Override - public void onError(AsyncEvent event) - { - _errors.increment(); - } - - @Override - public void onComplete(AsyncEvent event) - { - Request request = ((AsyncContextEvent)event).getHttpChannelState().getBaseRequest(); - long elapsed = System.currentTimeMillis() - request.getTimeStamp(); - _requestStats.decrement(); - _requestTimeStats.record(elapsed); - updateResponse(request); - _asyncWaitStats.decrement(); - - if (_shutdown.isShutdown()) - _shutdown.check(); - } - }; - - public StatisticsHandler() - { - _shutdown = new Shutdown(this) + // TODO don't need to do this until we see a Processor + request.addHttpStreamWrapper(s -> new HttpStream.Wrapper(s) { @Override - public boolean isShutdownDone() + public void send(MetaData.Request request, MetaData.Response response, boolean last, Callback callback, ByteBuffer... content) { - if (_gracefulShutdownWaitsForRequests) - return _requestStats.getCurrent() == 0; - else - return _dispatchedStats.getCurrent() == 0; - } - }; - } - - /** - * Resets the current request statistics. - */ - @ManagedOperation(value = "resets statistics", impact = "ACTION") - public void statsReset() - { - _statsStartedAt.set(System.currentTimeMillis()); - - _requestStats.reset(); - _requestTimeStats.reset(); - _dispatchedStats.reset(); - _dispatchedTimeStats.reset(); - _asyncWaitStats.reset(); - - _asyncDispatches.reset(); - _expires.reset(); - _responses1xx.reset(); - _responses2xx.reset(); - _responses3xx.reset(); - _responses4xx.reset(); - _responses5xx.reset(); - _responsesTotalBytes.reset(); - } - - @Override - public void handle(String path, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - Handler handler = getHandler(); - if (handler == null || !isStarted() || isShutdown()) - { - if (!baseRequest.getResponse().isCommitted()) - response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503); - return; - } - - _dispatchedStats.increment(); - - final long start; - HttpChannelState state = baseRequest.getHttpChannelState(); - if (state.isInitial()) - { - // new request - _requestStats.increment(); - start = baseRequest.getTimeStamp(); - } - else - { - // resumed request - start = System.currentTimeMillis(); - _asyncDispatches.increment(); - } - - try - { - handler.handle(path, baseRequest, request, response); - } - finally - { - final long now = System.currentTimeMillis(); - final long dispatched = now - start; - - _dispatchedStats.decrement(); - _dispatchedTimeStats.record(dispatched); - - if (state.isInitial()) - { - if (state.isAsyncStarted()) + if (response != null) { - state.addListener(_onCompletion); - _asyncWaitStats.increment(); + switch (response.getStatus() / 100) + { + case 1 -> _responses1xx.increment(); + case 2 -> _responses2xx.increment(); + case 3 -> _responses3xx.increment(); + case 4 -> _responses4xx.increment(); + case 5 -> _responses5xx.increment(); + default -> + { + } + } } - else + + for (ByteBuffer b : content) { - _requestStats.decrement(); - _requestTimeStats.record(dispatched); - updateResponse(baseRequest); + statisticsRequest._bytesWritten.add(b.remaining()); } + + super.send(request, response, last, callback, content); } - if (_shutdown.isShutdown()) - _shutdown.check(); - } - } - - protected void updateResponse(Request request) - { - Response response = request.getResponse(); - if (request.isHandled()) - { - switch (response.getStatus() / 100) + @Override + public Content readContent() { - case 1: - _responses1xx.increment(); - break; - case 2: - _responses2xx.increment(); - break; - case 3: - _responses3xx.increment(); - break; - case 4: - _responses4xx.increment(); - break; - case 5: - _responses5xx.increment(); - break; - default: - break; + Content content = super.readContent(); + if (content != null) + statisticsRequest._bytesRead.add(content.remaining()); + return content; } - } - else + + @Override + public void succeeded() + { + super.succeeded(); + _processStats.decrement(); + _requestStats.decrement(); + _processTimeStats.record(System.nanoTime() - statisticsRequest._processStartTimeStamp); + _requestTimeStats.record(System.nanoTime() - getNanoTimeStamp()); + } + + @Override + public void failed(Throwable x) + { + super.failed(x); + _processStats.decrement(); + _requestStats.decrement(); + _processTimeStats.record(System.nanoTime() - statisticsRequest._processStartTimeStamp); + _requestTimeStats.record(System.nanoTime() - getNanoTimeStamp()); + } + }); + + Request.Processor processor = super.handle(statisticsRequest); + _handleTimeStats.record(System.nanoTime() - beginTimeStamp); + return processor == null ? null : statisticsRequest.wrapProcessor(processor); + } + + private class StatisticsRequest extends Request.WrapperProcessor + { + private final LongAdder _bytesRead = new LongAdder(); + private final LongAdder _bytesWritten = new LongAdder(); + private long _processStartTimeStamp; + + private StatisticsRequest(Request request) { - // will fall through to not found handler - _responses4xx.increment(); + super(request); } - _responsesTotalBytes.add(response.getContentCount()); + // TODO make this wrapper optional. Only needed if requestLog asks for these attributes. + @Override + public Object getAttribute(String name) + { + // return hidden attributes for requestLog + return switch (name) + { + // TODO class.getName + extra + case "o.e.j.s.h.StatsHandler.bytesRead" -> _bytesRead.longValue(); + case "o.e.j.s.h.StatsHandler.bytesWritten" -> _bytesWritten.longValue(); + case "o.e.j.s.h.StatsHandler.spentTime" -> spentTimeNs(); + case "o.e.j.s.h.StatsHandler.dataReadRate" -> dataRatePerSecond(_bytesRead.longValue()); + case "o.e.j.s.h.StatsHandler.dataWriteRate" -> dataRatePerSecond(_bytesWritten.longValue()); + default -> super.getAttribute(name); + }; + } + + private long dataRatePerSecond(long dataCount) + { + return (long)(dataCount / (spentTimeNs() / 1_000_000_000F)); + } + + private long spentTimeNs() + { + return System.nanoTime() - _processStartTimeStamp; + } + + @Override + public void process(Request ignored, Response response, Callback callback) throws Exception + { + _processStats.increment(); + _processStartTimeStamp = System.nanoTime(); + super.process(this, response, callback); + } } - @Override - protected void doStart() throws Exception - { - if (getHandler() == null) - throw new IllegalStateException("StatisticsHandler has no Wrapped Handler"); - _shutdown.cancel(); - super.doStart(); - statsReset(); - } - - @Override - protected void doStop() throws Exception - { - _shutdown.cancel(); - super.doStop(); - } - - /** - * Set whether the graceful shutdown should wait for all requests to complete including - * async requests which are not currently dispatched, or whether it should only wait for all the - * actively dispatched requests to complete. - * @param gracefulShutdownWaitsForRequests true to wait for async requests on graceful shutdown. - */ - public void setGracefulShutdownWaitsForRequests(boolean gracefulShutdownWaitsForRequests) - { - _gracefulShutdownWaitsForRequests = gracefulShutdownWaitsForRequests; - } - - /** - * @return whether the graceful shutdown will wait for all requests to complete including - * async requests which are not currently dispatched, or whether it will only wait for all the - * actively dispatched requests to complete. - * @see #getAsyncDispatches() - */ - @ManagedAttribute("if graceful shutdown will wait for all requests") - public boolean getGracefulShutdownWaitsForRequests() - { - return _gracefulShutdownWaitsForRequests; - } - - /** - * @return the number of requests handled by this handler - * since {@link #statsReset()} was last called, excluding - * active requests - * @see #getAsyncDispatches() - */ @ManagedAttribute("number of requests") public int getRequests() { return (int)_requestStats.getTotal(); } - /** - * @return the number of requests currently active. - * since {@link #statsReset()} was last called. - */ @ManagedAttribute("number of requests currently active") public int getRequestsActive() { return (int)_requestStats.getCurrent(); } - /** - * @return the maximum number of active requests - * since {@link #statsReset()} was last called. - */ @ManagedAttribute("maximum number of active requests") public int getRequestsActiveMax() { return (int)_requestStats.getMax(); } - /** - * @return the maximum time (in milliseconds) of request handling - * since {@link #statsReset()} was last called. - */ - @ManagedAttribute("maximum time spend handling requests (in ms)") - public long getRequestTimeMax() - { - return _requestTimeStats.getMax(); - } - - /** - * @return the total time (in milliseconds) of requests handling - * since {@link #statsReset()} was last called. - */ - @ManagedAttribute("total time spend in all request handling (in ms)") - public long getRequestTimeTotal() - { - return _requestTimeStats.getTotal(); - } - - /** - * @return the mean time (in milliseconds) of request handling - * since {@link #statsReset()} was last called. - * @see #getRequestTimeTotal() - * @see #getRequests() - */ - @ManagedAttribute("mean time spent handling requests (in ms)") - public double getRequestTimeMean() - { - return _requestTimeStats.getMean(); - } - - /** - * @return the standard deviation of time (in milliseconds) of request handling - * since {@link #statsReset()} was last called. - * @see #getRequestTimeTotal() - * @see #getRequests() - */ - @ManagedAttribute("standard deviation for request handling (in ms)") - public double getRequestTimeStdDev() - { - return _requestTimeStats.getStdDev(); - } - - /** - * @return the number of dispatches seen by this handler - * since {@link #statsReset()} was last called, excluding - * active dispatches - */ - @ManagedAttribute("number of dispatches") - public int getDispatched() - { - return (int)_dispatchedStats.getTotal(); - } - - /** - * @return the number of dispatches currently in this handler - * since {@link #statsReset()} was last called, including - * resumed requests - */ - @ManagedAttribute("number of dispatches currently active") - public int getDispatchedActive() - { - return (int)_dispatchedStats.getCurrent(); - } - - /** - * @return the max number of dispatches currently in this handler - * since {@link #statsReset()} was last called, including - * resumed requests - */ - @ManagedAttribute("maximum number of active dispatches being handled") - public int getDispatchedActiveMax() - { - return (int)_dispatchedStats.getMax(); - } - - /** - * @return the maximum time (in milliseconds) of request dispatch - * since {@link #statsReset()} was last called. - */ - @ManagedAttribute("maximum time spend in dispatch handling") - public long getDispatchedTimeMax() - { - return _dispatchedTimeStats.getMax(); - } - - /** - * @return the total time (in milliseconds) of requests handling - * since {@link #statsReset()} was last called. - */ - @ManagedAttribute("total time spent in dispatch handling (in ms)") - public long getDispatchedTimeTotal() - { - return _dispatchedTimeStats.getTotal(); - } - - /** - * @return the mean time (in milliseconds) of request handling - * since {@link #statsReset()} was last called. - * @see #getRequestTimeTotal() - * @see #getRequests() - */ - @ManagedAttribute("mean time spent in dispatch handling (in ms)") - public double getDispatchedTimeMean() - { - return _dispatchedTimeStats.getMean(); - } - - /** - * @return the standard deviation of time (in milliseconds) of request handling - * since {@link #statsReset()} was last called. - * @see #getRequestTimeTotal() - * @see #getRequests() - */ - @ManagedAttribute("standard deviation for dispatch handling (in ms)") - public double getDispatchedTimeStdDev() - { - return _dispatchedTimeStats.getStdDev(); - } - - /** - * @return the number of requests handled by this handler - * since {@link #statsReset()} was last called, including - * resumed requests - * @see #getAsyncDispatches() - */ - @ManagedAttribute("total number of async requests") - public int getAsyncRequests() - { - return (int)_asyncWaitStats.getTotal(); - } - - /** - * @return the number of requests currently suspended. - * since {@link #statsReset()} was last called. - */ - @ManagedAttribute("currently waiting async requests") - public int getAsyncRequestsWaiting() - { - return (int)_asyncWaitStats.getCurrent(); - } - - /** - * @return the maximum number of current suspended requests - * since {@link #statsReset()} was last called. - */ - @ManagedAttribute("maximum number of waiting async requests") - public int getAsyncRequestsWaitingMax() - { - return (int)_asyncWaitStats.getMax(); - } - - /** - * @return the number of requests that have been asynchronously dispatched - */ - @ManagedAttribute("number of requested that have been asynchronously dispatched") - public int getAsyncDispatches() - { - return _asyncDispatches.intValue(); - } - - /** - * @return the number of requests that expired while suspended. - * @see #getAsyncDispatches() - */ - @ManagedAttribute("number of async requests requests that have expired") - public int getExpires() - { - return _expires.intValue(); - } - - /** - * @return the number of async errors that occurred. - * @see #getAsyncDispatches() - */ - @ManagedAttribute("number of async errors that occurred") - public int getErrors() - { - return _errors.intValue(); - } - - /** - * @return the number of responses with a 1xx status returned by this context - * since {@link #statsReset()} was last called. - */ @ManagedAttribute("number of requests with 1xx response status") public int getResponses1xx() { return _responses1xx.intValue(); } - /** - * @return the number of responses with a 2xx status returned by this context - * since {@link #statsReset()} was last called. - */ @ManagedAttribute("number of requests with 2xx response status") public int getResponses2xx() { return _responses2xx.intValue(); } - /** - * @return the number of responses with a 3xx status returned by this context - * since {@link #statsReset()} was last called. - */ @ManagedAttribute("number of requests with 3xx response status") public int getResponses3xx() { return _responses3xx.intValue(); } - /** - * @return the number of responses with a 4xx status returned by this context - * since {@link #statsReset()} was last called. - */ @ManagedAttribute("number of requests with 4xx response status") public int getResponses4xx() { return _responses4xx.intValue(); } - /** - * @return the number of responses with a 5xx status returned by this context - * since {@link #statsReset()} was last called. - */ @ManagedAttribute("number of requests with 5xx response status") public int getResponses5xx() { return _responses5xx.intValue(); } - /** - * @return the milliseconds since the statistics were started with {@link #statsReset()}. - */ - @ManagedAttribute("time in milliseconds stats have been collected for") - public long getStatsOnMs() + @ManagedAttribute("") + public int getHandlings() { - return System.currentTimeMillis() - _statsStartedAt.get(); + return (int)_handleStats.getCurrent(); } - /** - * @return the total bytes of content sent in responses - */ - @ManagedAttribute("total number of bytes across all responses") - public long getResponsesBytesTotal() + @ManagedAttribute("") + public int getProcessings() { - return _responsesTotalBytes.longValue(); + return (int)_processStats.getTotal(); } - public String toStatsHTML() + @ManagedAttribute("") + public int getProcessingsActive() { - StringBuilder sb = new StringBuilder(); - - sb.append("

    Statistics:

    \n"); - sb.append("Statistics gathering started ").append(getStatsOnMs()).append("ms ago").append("
    \n"); - - sb.append("

    Requests:

    \n"); - sb.append("Total requests: ").append(getRequests()).append("
    \n"); - sb.append("Active requests: ").append(getRequestsActive()).append("
    \n"); - sb.append("Max active requests: ").append(getRequestsActiveMax()).append("
    \n"); - sb.append("Total requests time: ").append(getRequestTimeTotal()).append("
    \n"); - sb.append("Mean request time: ").append(getRequestTimeMean()).append("
    \n"); - sb.append("Max request time: ").append(getRequestTimeMax()).append("
    \n"); - sb.append("Request time standard deviation: ").append(getRequestTimeStdDev()).append("
    \n"); - - sb.append("

    Dispatches:

    \n"); - sb.append("Total dispatched: ").append(getDispatched()).append("
    \n"); - sb.append("Active dispatched: ").append(getDispatchedActive()).append("
    \n"); - sb.append("Max active dispatched: ").append(getDispatchedActiveMax()).append("
    \n"); - sb.append("Total dispatched time: ").append(getDispatchedTimeTotal()).append("
    \n"); - sb.append("Mean dispatched time: ").append(getDispatchedTimeMean()).append("
    \n"); - sb.append("Max dispatched time: ").append(getDispatchedTimeMax()).append("
    \n"); - sb.append("Dispatched time standard deviation: ").append(getDispatchedTimeStdDev()).append("
    \n"); - - sb.append("Total requests suspended: ").append(getAsyncRequests()).append("
    \n"); - sb.append("Total requests expired: ").append(getExpires()).append("
    \n"); - sb.append("Total requests resumed: ").append(getAsyncDispatches()).append("
    \n"); - - sb.append("

    Responses:

    \n"); - sb.append("1xx responses: ").append(getResponses1xx()).append("
    \n"); - sb.append("2xx responses: ").append(getResponses2xx()).append("
    \n"); - sb.append("3xx responses: ").append(getResponses3xx()).append("
    \n"); - sb.append("4xx responses: ").append(getResponses4xx()).append("
    \n"); - sb.append("5xx responses: ").append(getResponses5xx()).append("
    \n"); - sb.append("Bytes sent total: ").append(getResponsesBytesTotal()).append("
    \n"); - - return sb.toString(); + return (int)_processStats.getCurrent(); } - @Override - public CompletableFuture shutdown() + @ManagedAttribute("") + public int getProcessingsMax() { - return _shutdown.shutdown(); + return (int)_processStats.getMax(); } - @Override - public boolean isShutdown() + @ManagedAttribute("total time spend in all request execution (in ns)") + public long getRequestTimeTotal() { - return _shutdown.isShutdown(); + return _requestTimeStats.getTotal(); } - @Override - public String toString() + @ManagedAttribute("maximum time spend executing requests (in ns)") + public long getRequestTimeMax() { - return String.format("%s@%x{%s,r=%d,d=%d}", getClass().getSimpleName(), hashCode(), getState(), _requestStats.getCurrent(), _dispatchedStats.getCurrent()); + return _requestTimeStats.getMax(); } + @ManagedAttribute("mean time spent executing requests (in ns)") + public double getRequestTimeMean() + { + return _requestTimeStats.getMean(); + } + + @ManagedAttribute("standard deviation for request execution (in ns)") + public double getRequestTimeStdDev() + { + return _requestTimeStats.getStdDev(); + } + + @ManagedAttribute("(in ns)") + public long getHandlingTimeTotal() + { + return _handleTimeStats.getTotal(); + } + + @ManagedAttribute("(in ns)") + public long getHandlingTimeMax() + { + return _handleTimeStats.getMax(); + } + + @ManagedAttribute("(in ns)") + public double getHandlingTimeMean() + { + return _handleTimeStats.getMean(); + } + + @ManagedAttribute("(in ns)") + public double getHandlingTimeStdDev() + { + return _handleTimeStats.getStdDev(); + } + + @ManagedAttribute("(in ns)") + public long getProcessingTimeTotal() + { + return _processTimeStats.getTotal(); + } + + @ManagedAttribute("(in ns)") + public long getProcessingTimeMax() + { + return _processTimeStats.getMax(); + } + + @ManagedAttribute("(in ns)") + public double getProcessingTimeMean() + { + return _processTimeStats.getMean(); + } + + @ManagedAttribute("(in ns)") + public double getProcessingTimeStdDev() + { + return _processTimeStats.getStdDev(); + } + + public static class MinimumDataRateHandler extends StatisticsHandler + { + private final long _minimumReadRate; + private final long _minimumWriteRate; + + public MinimumDataRateHandler(long minimumReadRate, long minimumWriteRate) + { + _minimumReadRate = minimumReadRate; + _minimumWriteRate = minimumWriteRate; + } + + private class MinimumDataRateRequest extends Request.WrapperProcessor + { + private StatisticsRequest _statisticsRequest; + private Content.Error _errorContent; + + private MinimumDataRateRequest(Request request) + { + super(request); + } + + @Override + public Object getAttribute(String name) + { + return _statisticsRequest.getAttribute(name); + } + + @Override + public void demandContent(Runnable onContentAvailable) + { + if (_minimumReadRate > 0) + { + Long rr = (Long)getAttribute("o.e.j.s.h.StatsHandler.dataReadRate"); + if (rr < _minimumReadRate) + { + // TODO should this be a QuietException to reduce log verbosity from bad clients? + _errorContent = new Content.Error(new TimeoutException("read rate is too low: " + rr)); + onContentAvailable.run(); + return; + } + } + super.demandContent(onContentAvailable); + } + + @Override + public Content readContent() + { + return _errorContent != null ? _errorContent : super.readContent(); + } + + @Override + public WrapperProcessor wrapProcessor(Processor processor) + { + _statisticsRequest = (StatisticsRequest)processor; + return super.wrapProcessor(processor); + } + + @Override + public void process(Request ignored, Response response, Callback callback) throws Exception + { + super.process(ignored, new MinimumDataRateResponse(this, response), callback); + } + } + + private class MinimumDataRateResponse extends Response.Wrapper + { + private MinimumDataRateResponse(Request request, Response wrapped) + { + super(request, wrapped); + } + + @Override + public void write(boolean last, Callback callback, ByteBuffer... content) + { + if (_minimumWriteRate > 0) + { + MinimumDataRateRequest request = (MinimumDataRateRequest)getRequest(); + if (((Long)request.getAttribute("o.e.j.s.h.StatsHandler.bytesWritten")) > 0L) + { + Long wr = (Long)request.getAttribute("o.e.j.s.h.StatsHandler.dataWriteRate"); + if (wr < _minimumWriteRate) + { + TimeoutException cause = new TimeoutException("write rate is too low: " + wr); + request._errorContent = new Content.Error(cause); + callback.failed(cause); + return; + } + } + } + super.write(last, callback, content); + } + } + + @Override + public Request.Processor handle(Request request) throws Exception + { + MinimumDataRateRequest minimumDataRateRequest = new MinimumDataRateRequest(request); + Request.Processor processor = super.handle(minimumDataRateRequest); + if (processor == null) + return null; + return minimumDataRateRequest.wrapProcessor(processor); + } + } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java index 1af0bb261fb..b9d772f93fd 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java @@ -24,16 +24,14 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; -import jakarta.servlet.AsyncContext; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.QuotedCSV; import org.eclipse.jetty.server.ForwardedRequestCustomizer; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IncludeExcludeSet; import org.eclipse.jetty.util.InetAddressSet; import org.eclipse.jetty.util.StringUtil; @@ -64,12 +62,10 @@ import org.slf4j.LoggerFactory; * a thread is available. *

    This is a simpler alternative to DosFilter

    */ -public class ThreadLimitHandler extends HandlerWrapper +public class ThreadLimitHandler extends Handler.Wrapper { private static final Logger LOG = LoggerFactory.getLogger(ThreadLimitHandler.class); - private static final String REMOTE = "o.e.j.s.h.TLH.REMOTE"; - private static final String PERMIT = "o.e.j.s.h.TLH.PASS"; private final boolean _rfc7239; private final String _forwardedHeader; private final IncludeExcludeSet _includeExcludeSet = new IncludeExcludeSet<>(InetAddressSet.class); @@ -160,87 +156,73 @@ public class ThreadLimitHandler extends HandlerWrapper } @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public Request.Processor handle(Request request) throws Exception { + Request.Processor baseProcessor = super.handle(request); + if (baseProcessor == null) + { + // if no wrapped handler, do not handle + return null; + } + // Allow ThreadLimit to be enabled dynamically without restarting server if (!_enabled) { // if disabled, handle normally - super.handle(target, baseRequest, request, response); + return baseProcessor; + } + + // Get the remote address of the request + Remote remote = getRemote(request); + if (remote == null) + { + // if remote is not known, handle normally + return baseProcessor; + } + + CompletableFuture futurePermit = remote.acquire(); + // Did we get a permit? + if (futurePermit.isDone()) + { + if (LOG.isDebugEnabled()) + LOG.debug("Threadpermitted {}", remote); + return (rq, rs, cb) -> baseProcessor.process(rq, rs, Callback.from(cb, () -> getAndClose(futurePermit))); } else { - // Get the remote address of the request - Remote remote = getRemote(baseRequest); - if (remote == null) + if (LOG.isDebugEnabled()) + LOG.debug("Threadlimited {}", remote); + return (rq, rs, cb) -> futurePermit.thenAccept(c -> { - // if remote is not known, handle normally - super.handle(target, baseRequest, request, response); - } - else - { - // Do we already have a future permit from a previous invocation? - Closeable permit = (Closeable)baseRequest.getAttribute(PERMIT); try { - if (permit != null) - { - // Yes, remove it from any future async cycles. - baseRequest.removeAttribute(PERMIT); - } - else - { - // No, then lets try to acquire one - CompletableFuture futurePermit = remote.acquire(); - - // Did we get a permit? - if (futurePermit.isDone()) - { - // yes - permit = futurePermit.get(); - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Threadlimited {} {}", remote, target); - // No, lets asynchronously suspend the request - AsyncContext async = baseRequest.startAsync(); - // let's never timeout the async. If this is a DOS, then good to make them wait, if this is not - // then give them maximum time to get a thread. - async.setTimeout(0); - - // dispatch the request when we do eventually get a pass - futurePermit.thenAccept(c -> - { - baseRequest.setAttribute(PERMIT, c); - async.dispatch(); - }); - return; - } - } - - // Use the permit - super.handle(target, baseRequest, request, response); + baseProcessor.process(rq, rs, Callback.from(cb, () -> getAndClose(futurePermit))); } - catch (InterruptedException | ExecutionException e) + catch (Throwable x) { - throw new ServletException(e); + cb.failed(x); } - finally - { - if (permit != null) - permit.close(); - } - } + }); + } + } + + private static void getAndClose(CompletableFuture cf) + { + try + { + LOG.debug("getting {}", cf); + Closeable closeable = cf.get(); + LOG.debug("closing {}", closeable); + closeable.close(); + } + catch (IOException | InterruptedException | ExecutionException e) + { + LOG.warn("Error closing permit enclosed in {}", cf, e); } } private Remote getRemote(Request baseRequest) { - Remote remote = (Remote)baseRequest.getAttribute(REMOTE); - if (remote != null) - return remote; - String ip = getRemoteIP(baseRequest); LOG.debug("ip={}", ip); if (ip == null) @@ -250,7 +232,7 @@ public class ThreadLimitHandler extends HandlerWrapper if (limit <= 0) return null; - remote = _remotes.get(ip); + Remote remote = _remotes.get(ip); if (remote == null) { Remote r = new Remote(ip, limit); @@ -258,9 +240,6 @@ public class ThreadLimitHandler extends HandlerWrapper if (remote == null) remote = r; } - - baseRequest.setAttribute(REMOTE, remote); - return remote; } @@ -278,9 +257,12 @@ public class ThreadLimitHandler extends HandlerWrapper // If no remote IP from a header, determine it directly from the channel // Do not use the request methods, as they may have been lied to by the // RequestCustomizer! - InetSocketAddress inetAddr = baseRequest.getHttpChannel().getRemoteAddress(); - if (inetAddr != null && inetAddr.getAddress() != null) - return inetAddr.getAddress().getHostAddress(); + if (baseRequest.getConnectionMetaData().getRemoteSocketAddress() instanceof InetSocketAddress inetAddr) + { + // TODO ???? + if (inetAddr.getAddress() != null) + return inetAddr.getAddress().getHostAddress(); + } return null; } @@ -290,7 +272,7 @@ public class ThreadLimitHandler extends HandlerWrapper // This is the value from the closest proxy and the only one that // can be trusted. RFC7239 rfc7239 = new RFC7239(); - for (HttpField field : request.getHttpFields()) + for (HttpField field : request.getHeaders()) { if (_forwardedHeader.equalsIgnoreCase(field.getName())) rfc7239.addValue(field.getValue()); @@ -308,7 +290,7 @@ public class ThreadLimitHandler extends HandlerWrapper // This is the value from the closest proxy and the only one that // can be trusted. String forwardedFor = null; - for (HttpField field : request.getHttpFields()) + for (HttpField field : request.getHeaders()) { if (_forwardedHeader.equalsIgnoreCase(field.getName())) forwardedFor = field.getValue(); @@ -327,7 +309,7 @@ public class ThreadLimitHandler extends HandlerWrapper private final int _limit; private final AutoLock _lock = new AutoLock(); private int _permits; - private Deque> _queue = new ArrayDeque<>(); + private final Deque> _queue = new ArrayDeque<>(); private final CompletableFuture _permitted = CompletableFuture.completedFuture(this); public Remote(String ip, int limit) diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java index 12a4f735e56..9a8418dcd48 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java @@ -13,20 +13,11 @@ package org.eclipse.jetty.server.handler.gzip; -import java.io.IOException; -import java.util.Arrays; import java.util.EnumSet; import java.util.Set; import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; import java.util.zip.Deflater; -import jakarta.servlet.DispatcherType; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.CompressedContentFormat; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; @@ -35,119 +26,18 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http.pathmap.PathSpecSet; -import org.eclipse.jetty.server.HttpOutput; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.util.AsciiLowerCaseSet; import org.eclipse.jetty.util.IncludeExclude; import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.compression.CompressionPool; import org.eclipse.jetty.util.compression.DeflaterPool; import org.eclipse.jetty.util.compression.InflaterPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * A Handler that can dynamically GZIP uncompress requests, and compress responses. - *

    - * The GzipHandler can be applied to the entire server (a {@code gzip.mod} is included in - * the {@code jetty-home}) or it may be applied to individual contexts. - *

    - *

    - * Both Request uncompress and Response compress are gated by a configurable - * {@link DispatcherType} check on the GzipHandler. - * (This is similar in behavior to a {@link jakarta.servlet.Filter} configuration - * you would find in a Servlet Descriptor file ({@code WEB-INF/web.xml}) - *
    (Default: {@link DispatcherType#REQUEST}). - *

    - *

    - * Requests with a {@code Content-Encoding} header with the value {@code gzip} will - * be uncompressed by a {@link GzipHttpInputInterceptor} for any API that uses - * {@link HttpServletRequest#getInputStream()} or {@link HttpServletRequest#getReader()}. - *

    - *

    - * Response compression has a number of checks before GzipHandler will perform compression. - *

    - *
      - *
    1. - * Does the request contain a {@code Accept-Encoding} header that specifies - * {@code gzip} value? - *
    2. - *
    3. - * Is the {@link HttpServletRequest#getMethod()} allowed by the configured HTTP Method Filter. - *
      (Default: {@code GET}) - *
    4. - *
    5. - * Is the incoming Path allowed by the configured Path Specs filters? - *
      (Default: all paths are allowed) - *
    6. - *
    7. - * Is the Request User-Agent allowed by the configured User-Agent filters? - *
      (Default: MSIE 6 is excluded) - *
    8. - *
    9. - * Is the Response {@code Content-Length} header present, and does its - * value meet the minimum gzip size requirements (default 32 bytes)? - *
    10. - *
    11. - * Is the Request {@code Accept} header present and does it contain the - * required {@code gzip} value? - *
    12. - *
    - *

    - * When you encounter a configurable filter in the GzipHandler (method, paths, user-agent, - * mime-types, etc) that has both Included and Excluded values, note that the Included - * values always win over the Excluded values. - *

    - *

    - * Important note about Default Values: - * It is important to note that the GzipHandler will automatically configure itself from the - * MimeType present on the Server, System, and Contexts and the ultimate set of default values - * for the various filters (paths, methods, mime-types, etc) can be influenced by the - * available mime types to work with. - *

    - *

    - * ETag (or Entity Tag) information: any Request headers for {@code If-None-Match} or - * {@code If-Match} will be evaluated by the GzipHandler to determine if it was involved - * in compression of the response earlier. This is usually present as a {@code --gzip} suffix - * on the ETag that the Client User-Agent is tracking and handed to the Jetty server. - * The special {@code --gzip} suffix on the ETag is how GzipHandler knows that the content - * passed through itself, and this suffix will be stripped from the Request header values - * before the request is sent onwards to the specific webapp / servlet endpoint for - * handling. - * If a ETag is present in the Response headers, and GzipHandler is compressing the - * contents, it will add the {@code --gzip} suffix before the Response headers are committed - * and sent to the User Agent. - * Note that the suffix used is determined by {@link CompressedContentFormat#ETAG_SEPARATOR} - *

    - *

    - * This implementation relies on an Jetty internal {@link org.eclipse.jetty.server.HttpOutput.Interceptor} - * mechanism to allow for effective and efficient compression of the response on all Output API usages: - *

    - *
      - *
    • - * {@link jakarta.servlet.ServletOutputStream} - Obtained from {@link HttpServletResponse#getOutputStream()} - * using the traditional Blocking I/O techniques - *
    • - *
    • - * {@link jakarta.servlet.WriteListener} - Provided to - * {@link jakarta.servlet.ServletOutputStream#setWriteListener(jakarta.servlet.WriteListener)} - * using the new (since Servlet 3.1) Async I/O techniques - *
    • - *
    • - * {@link java.io.PrintWriter} - Obtained from {@link HttpServletResponse#getWriter()} - * using Blocking I/O techniques - *
    • - *
    - *

    - * Historically the compression of responses were accomplished via - * Servlet Filters (eg: {@code GzipFilter}) and usage of {@link jakarta.servlet.http.HttpServletResponseWrapper}. - * Since the introduction of Async I/O in Servlet 3.1, this older form of Gzip support - * in web applications has been problematic and bug ridden. - *

    - */ -public class GzipHandler extends HandlerWrapper implements GzipFactory +public class GzipHandler extends Handler.Wrapper implements GzipFactory { public static final EnumSet ETAG_HEADERS = EnumSet.of(HttpHeader.IF_MATCH, HttpHeader.IF_NONE_MATCH); public static final String GZIP_HANDLER_ETAGS = "o.e.j.s.h.gzip.GzipHandler.etag"; @@ -164,12 +54,11 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory private int _minGzipSize = DEFAULT_MIN_GZIP_SIZE; private boolean _syncFlush = false; private int _inflateBufferSize = -1; - private EnumSet _dispatchers = EnumSet.of(DispatcherType.REQUEST); // non-static, as other GzipHandler instances may have different configurations private final IncludeExclude _methods = new IncludeExclude<>(); private final IncludeExclude _paths = new IncludeExclude<>(PathSpecSet.class); private final IncludeExclude _mimeTypes = new IncludeExclude<>(AsciiLowerCaseSet.class); - private HttpField _vary = GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING; + private HttpField _vary = GzipResponse.VARY_ACCEPT_ENCODING; /** * Instantiates a new GzipHandler. @@ -269,36 +158,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory } } - /** - * Get the Set of {@link DispatcherType} that this Filter will operate on. - * - * @return the set of {@link DispatcherType} this filter will operate on - */ - public EnumSet getDispatcherTypes() - { - return _dispatchers; - } - - /** - * Set of supported {@link DispatcherType} that this filter will operate on. - * - * @param dispatchers the set of {@link DispatcherType} that this filter will operate on - */ - public void setDispatcherTypes(EnumSet dispatchers) - { - _dispatchers = dispatchers; - } - - /** - * Set the list of supported {@link DispatcherType} that this filter will operate on. - * - * @param dispatchers the list of {@link DispatcherType} that this filter will operate on - */ - public void setDispatcherTypes(DispatcherType... dispatchers) - { - _dispatchers = EnumSet.copyOf(Arrays.asList(dispatchers)); - } - /** * Adds excluded MIME types for response filtering. * @@ -450,7 +309,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory } // check the accept encoding header - if (!request.getHttpFields().contains(HttpHeader.ACCEPT_ENCODING, "gzip")) + if (!request.getHeaders().contains(HttpHeader.ACCEPT_ENCODING, "gzip")) { LOG.debug("{} excluded not gzip accept {}", this, request); return null; @@ -570,50 +429,29 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory } @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public Request.Processor handle(Request request) throws Exception { - final ServletContext context = baseRequest.getServletContext(); - final String path = baseRequest.getPathInContext(); - LOG.debug("{} handle {} in {}", this, baseRequest, context); + final String path = request.getPathInContext(); - if (!_dispatchers.contains(baseRequest.getDispatcherType())) - { - LOG.debug("{} excluded by dispatcherType {}", this, baseRequest.getDispatcherType()); - _handler.handle(target, baseRequest, request, response); - return; - } + if (LOG.isDebugEnabled()) + LOG.debug("{} handle {}", this, request); + // TODO: support more than GZIP. // Handle request inflation - HttpFields httpFields = baseRequest.getHttpFields(); + HttpFields httpFields = request.getHeaders(); boolean inflated = _inflateBufferSize > 0 && httpFields.contains(HttpHeader.CONTENT_ENCODING, "gzip"); - if (inflated) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} inflate {}", this, request); - GzipHttpInputInterceptor gzipHttpInputInterceptor = - new GzipHttpInputInterceptor(_inflaterPool, baseRequest.getHttpChannel().getByteBufferPool(), - _inflateBufferSize, baseRequest.getHttpChannel().isUseInputDirectByteBuffers()); - baseRequest.getHttpInput().addInterceptor(gzipHttpInputInterceptor); - } + // TODO: do we need this? // Are we already being gzipped? - HttpOutput out = baseRequest.getResponse().getHttpOutput(); - HttpOutput.Interceptor interceptor = out.getInterceptor(); - boolean alreadyGzipped = false; - while (interceptor != null) - { - if (interceptor instanceof GzipHttpOutputInterceptor) - { - alreadyGzipped = true; - break; - } - interceptor = interceptor.getNextInterceptor(); - } + GzipRequest gzipRequest = Request.as(request, GzipRequest.class); + boolean alreadyGzipped = gzipRequest != null; + // TODO: Move down. can we return super.handle(request) without these changes? // Update headers for etags and inflation + HttpFields.Mutable newFields = null; if (inflated || httpFields.contains(ETAG_HEADERS)) { - HttpFields.Mutable newFields = HttpFields.build(httpFields.size() + 1); + newFields = HttpFields.build(httpFields.size() + 1); for (HttpField field : httpFields) { if (field.getHeader() == null) @@ -634,7 +472,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory else { newFields.add(new HttpField(field.getHeader(), etagsNoSuffix)); - baseRequest.setAttribute(GZIP_HANDLER_ETAGS, etags); + request.setAttribute(GZIP_HANDLER_ETAGS, etags); } break; } @@ -665,23 +503,22 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory newFields.add(field); } } - baseRequest.setHttpFields(newFields); } // Don't gzip if already gzipped; if (alreadyGzipped) { LOG.debug("{} already intercepting {}", this, request); - _handler.handle(target, baseRequest, request, response); - return; + HeaderWrappingRequest wrappedRequest = new HeaderWrappingRequest(request, newFields); + return wrappedRequest.wrapProcessor(super.handle(wrappedRequest)); } // If not a supported method - no Vary because no matter what client, this URI is always excluded - if (!_methods.test(baseRequest.getMethod())) + if (!_methods.test(request.getMethod())) { LOG.debug("{} excluded by method {}", this, request); - _handler.handle(target, baseRequest, request, response); - return; + HeaderWrappingRequest wrappedRequest = new HeaderWrappingRequest(request, newFields); + return wrappedRequest.wrapProcessor(super.handle(wrappedRequest)); } // If not a supported URI- no Vary because no matter what client, this URI is always excluded @@ -689,12 +526,13 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory if (!isPathGzipable(path)) { LOG.debug("{} excluded by path {}", this, request); - _handler.handle(target, baseRequest, request, response); - return; + HeaderWrappingRequest wrappedRequest = new HeaderWrappingRequest(request, newFields); + return wrappedRequest.wrapProcessor(super.handle(wrappedRequest)); } + // TODO: get mimetype from context. // Exclude non compressible mime-types known from URI extension. - no Vary because no matter what client, this URI is always excluded - String mimeType = context == null ? MimeTypes.getDefaultMimeByExtension(path) : context.getMimeType(path); + String mimeType = MimeTypes.getDefaultMimeByExtension(path); if (mimeType != null) { mimeType = HttpField.valueParameters(mimeType, null); @@ -702,26 +540,13 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory { LOG.debug("{} excluded by path suffix mime type {}", this, request); // handle normally without setting vary header - _handler.handle(target, baseRequest, request, response); - return; + HeaderWrappingRequest wrappedRequest = new HeaderWrappingRequest(request, newFields); + return wrappedRequest.wrapProcessor(super.handle(wrappedRequest)); } } - HttpOutput.Interceptor origInterceptor = out.getInterceptor(); - try - { - // install interceptor and handle - out.setInterceptor(new GzipHttpOutputInterceptor(this, getVaryField(), baseRequest.getHttpChannel(), origInterceptor, isSyncFlush())); - - if (_handler != null) - _handler.handle(target, baseRequest, request, response); - } - finally - { - // reset interceptor if request not handled - if (!baseRequest.isHandled() && !baseRequest.isAsyncStarted()) - out.setInterceptor(origInterceptor); - } + gzipRequest = new GzipRequest(request, this, inflated, newFields); + return gzipRequest.wrapProcessor(super.handle(gzipRequest)); } /** @@ -799,19 +624,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory _paths.exclude(pathspecs); } - /** - * Set of supported {@link DispatcherType} that this filter will operate on. - * - * @param dispatchers the set of {@link DispatcherType} that this filter will operate on - */ - public void setDispatcherTypes(String... dispatchers) - { - _dispatchers = EnumSet.copyOf(Stream.of(dispatchers) - .flatMap(s -> Stream.of(StringUtil.csvSplit(s))) - .map(DispatcherType::valueOf) - .collect(Collectors.toSet())); - } - /** * Set the included filter list of HTTP methods (replacing any previously set) * @@ -969,55 +781,9 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory _inflaterPool = inflaterPool; } - /** - * Gets the maximum number of Deflaters that the DeflaterPool can hold. - * - * @return the Deflater pool capacity - * @deprecated for custom DeflaterPool settings use {@link #setDeflaterPool(DeflaterPool)}. - */ - @Deprecated - public int getDeflaterPoolCapacity() - { - return (_deflaterPool == null) ? CompressionPool.DEFAULT_CAPACITY : _deflaterPool.getCapacity(); - } - - /** - * Sets the maximum number of Deflaters that the DeflaterPool can hold. - * @deprecated for custom DeflaterPool settings use {@link #setDeflaterPool(DeflaterPool)}. - */ - @Deprecated - public void setDeflaterPoolCapacity(int capacity) - { - if (_deflaterPool != null) - _deflaterPool.setCapacity(capacity); - } - - /** - * Gets the maximum number of Inflaters that the InflaterPool can hold. - * - * @return the Inflater pool capacity - * @deprecated for custom InflaterPool settings use {@link #setInflaterPool(InflaterPool)}. - */ - @Deprecated - public int getInflaterPoolCapacity() - { - return (_inflaterPool == null) ? CompressionPool.DEFAULT_CAPACITY : _inflaterPool.getCapacity(); - } - - /** - * Sets the maximum number of Inflaters that the InflaterPool can hold. - * @deprecated for custom InflaterPool settings use {@link #setInflaterPool(InflaterPool)}. - */ - @Deprecated - public void setInflaterPoolCapacity(int capacity) - { - if (_inflaterPool != null) - _inflaterPool.setCapacity(capacity); - } - @Override public String toString() { return String.format("%s@%x{%s,min=%s,inflate=%s}", getClass().getSimpleName(), hashCode(), getState(), _minGzipSize, _inflateBufferSize); } -} +} \ No newline at end of file diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipRequest.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipRequest.java new file mode 100644 index 00000000000..14a46a70487 --- /dev/null +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipRequest.java @@ -0,0 +1,203 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server.handler.gzip; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.http.GZIPContentDecoder; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.server.Components; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.ContentProcessor; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.compression.InflaterPool; + +public class GzipRequest extends Request.WrapperProcessor +{ + // TODO: use InflaterPool from somewhere. + private static final InflaterPool __inflaterPool = new InflaterPool(-1, true); + + private final boolean _inflateInput; + private Decoder _decoder; + private GzipContentProcessor gzipContentProcessor; + private final int _inflateBufferSize; + private final GzipHandler _gzipHandler; + private final HttpFields _fields; + + public GzipRequest(Request wrapped, GzipHandler gzipHandler, boolean inflateInput, HttpFields fields) + { + super(wrapped); + _gzipHandler = gzipHandler; + _inflateInput = inflateInput; + _inflateBufferSize = gzipHandler.getInflateBufferSize(); + _fields = fields; + } + + @Override + public HttpFields getHeaders() + { + if (_fields == null) + return super.getHeaders(); + return _fields; + } + + @Override + public void process(Request request, Response response, Callback callback) throws Exception + { + if (_inflateInput) + { + Components components = request.getComponents(); + _decoder = new Decoder(__inflaterPool, components.getByteBufferPool(), _inflateBufferSize); + gzipContentProcessor = new GzipContentProcessor(request); + } + + int outputBufferSize = request.getConnectionMetaData().getHttpConfiguration().getOutputBufferSize(); + GzipResponse gzipResponse = new GzipResponse(this, response, _gzipHandler, _gzipHandler.getVary(), outputBufferSize, _gzipHandler.isSyncFlush()); + Callback cb = Callback.from(() -> destroy(gzipResponse), callback); + super.process(this, gzipResponse, cb); + } + + @Override + public void demandContent(Runnable onContentAvailable) + { + if (_inflateInput) + gzipContentProcessor.demandContent(onContentAvailable); + else + super.demandContent(onContentAvailable); + } + + @Override + public Content readContent() + { + if (_inflateInput) + return gzipContentProcessor.readContent(); + return super.readContent(); + } + + private void destroy(GzipResponse response) + { + // We need to do this to intercept the committing of the response + // and possibly change headers in case write is never called. + response.write(true, Callback.NOOP); + + if (_decoder != null) + { + _decoder.destroy(); + _decoder = null; + } + } + + private class GzipContentProcessor extends ContentProcessor + { + private Content _content; + + public GzipContentProcessor(Content.Reader reader) + { + super(reader); + } + + @Override + protected Content process(Content content) + { + try + { + if (_content == null) + _content = content; + if (_content == null) + return null; + if (_content.isSpecial()) + return _content; + + ByteBuffer decodedBuffer = _decoder.decode(_content); + if (BufferUtil.hasContent(decodedBuffer)) + return new DecodedContent(decodedBuffer, _content.isLast()); + return null; + } + finally + { + if (_content != null && !_content.hasRemaining()) + { + _content.release(); + _content = null; + } + } + } + } + + private class DecodedContent implements Content + { + final ByteBuffer _decodedContent; + final boolean _isLast; + + protected DecodedContent(ByteBuffer content, boolean isLast) + { + _decodedContent = content; + _isLast = isLast; + } + + @Override + public ByteBuffer getByteBuffer() + { + return _decodedContent; + } + + @Override + public boolean isLast() + { + return _isLast; + } + + @Override + public void release() + { + _decoder.release(_decodedContent); + } + } + + private static class Decoder extends GZIPContentDecoder + { + private ByteBuffer _chunk; + + private Decoder(InflaterPool inflaterPool, ByteBufferPool bufferPool, int bufferSize) + { + super(inflaterPool, bufferPool, bufferSize); + } + + public ByteBuffer decode(Content content) + { + decodeChunks(content.getByteBuffer()); + ByteBuffer chunk = _chunk; + _chunk = null; + return chunk; + } + + @Override + protected boolean decodedChunk(final ByteBuffer chunk) + { + _chunk = chunk; + return true; + } + + @Override + public void decodeChunks(ByteBuffer compressed) + { + _chunk = null; + super.decodeChunks(compressed); + } + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipResponse.java similarity index 59% rename from jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java rename to jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipResponse.java index 6aa9ad5a619..91673ed4a30 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipResponse.java @@ -15,6 +15,7 @@ package org.eclipse.jetty.server.handler.gzip; import java.nio.ByteBuffer; import java.nio.channels.WritePendingException; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.CRC32; import java.util.zip.Deflater; @@ -24,8 +25,7 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.PreEncodedHttpField; -import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.HttpOutput; +import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; @@ -36,9 +36,9 @@ import org.slf4j.LoggerFactory; import static org.eclipse.jetty.http.CompressedContentFormat.GZIP; -public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor +public class GzipResponse extends Response.Wrapper { - public static Logger LOG = LoggerFactory.getLogger(GzipHttpOutputInterceptor.class); + public static Logger LOG = LoggerFactory.getLogger(GzipResponse.class); private static final byte[] GZIP_HEADER = new byte[]{(byte)0x1f, (byte)0x8b, Deflater.DEFLATED, 0, 0, 0, 0, 0, 0, 0}; public static final HttpField VARY_ACCEPT_ENCODING = new PreEncodedHttpField(HttpHeader.VARY, HttpHeader.ACCEPT_ENCODING.asString()); @@ -52,8 +52,6 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor private final CRC32 _crc = new CRC32(); private final GzipFactory _factory; - private final HttpOutput.Interceptor _interceptor; - private final HttpChannel _channel; private final HttpField _vary; private final int _bufferSize; private final boolean _syncFlush; @@ -61,43 +59,27 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor private DeflaterPool.Entry _deflaterEntry; private ByteBuffer _buffer; - public GzipHttpOutputInterceptor(GzipFactory factory, HttpChannel channel, HttpOutput.Interceptor next, boolean syncFlush) + public GzipResponse(Request request, Response wrapped, GzipFactory factory, HttpField vary, int bufferSize, boolean syncFlush) { - this(factory, VARY_ACCEPT_ENCODING, channel.getHttpConfiguration().getOutputBufferSize(), channel, next, syncFlush); - } + super(request, wrapped); - public GzipHttpOutputInterceptor(GzipFactory factory, HttpField vary, HttpChannel channel, HttpOutput.Interceptor next, boolean syncFlush) - { - this(factory, vary, channel.getHttpConfiguration().getOutputBufferSize(), channel, next, syncFlush); - } - - public GzipHttpOutputInterceptor(GzipFactory factory, HttpField vary, int bufferSize, HttpChannel channel, HttpOutput.Interceptor next, boolean syncFlush) - { _factory = factory; - _channel = channel; - _interceptor = next; _vary = vary; _bufferSize = bufferSize; _syncFlush = syncFlush; } @Override - public HttpOutput.Interceptor getNextInterceptor() - { - return _interceptor; - } - - @Override - public void write(ByteBuffer content, boolean complete, Callback callback) + public void write(boolean last, Callback callback, ByteBuffer... content) { switch (_state.get()) { case MIGHT_COMPRESS: - commit(content, complete, callback); + commit(last, callback, content); break; case NOT_COMPRESSING: - _interceptor.write(content, complete, callback); + super.write(last, callback, content); return; case COMMITTING: @@ -105,7 +87,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor break; case COMPRESSING: - gzip(content, complete, callback); + gzip(last, callback, content); break; default: @@ -120,18 +102,19 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor BufferUtil.putIntLittleEndian(_buffer, _deflaterEntry.get().getTotalIn()); } - private void gzip(ByteBuffer content, boolean complete, final Callback callback) + private void gzip(boolean complete, final Callback callback, ByteBuffer... content) { - if (content.hasRemaining() || complete) - new GzipBufferCB(content, complete, callback).iterate(); + if (content != null || complete) + new GzipBufferCB(complete, callback, content).iterate(); else callback.succeeded(); } - protected void commit(ByteBuffer content, boolean complete, Callback callback) + protected void commit(boolean complete, Callback callback, ByteBuffer... content) { // Are we excluding because of status? - Response response = _channel.getResponse(); + Response response = GzipResponse.this; + Request request = response.getRequest(); int sc = response.getStatus(); if (sc > 0 && (sc < 200 || sc == 204 || sc == 205 || sc >= 300)) { @@ -140,22 +123,22 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor if (sc == HttpStatus.NOT_MODIFIED_304) { - String requestEtags = (String)_channel.getRequest().getAttribute(GzipHandler.GZIP_HANDLER_ETAGS); - String responseEtag = response.getHttpFields().get(HttpHeader.ETAG); + String requestEtags = (String)request.getAttribute(GzipHandler.GZIP_HANDLER_ETAGS); + String responseEtag = response.getHeaders().get(HttpHeader.ETAG); if (requestEtags != null && responseEtag != null) { String responseEtagGzip = etagGzip(responseEtag); if (requestEtags.contains(responseEtagGzip)) - response.getHttpFields().put(HttpHeader.ETAG, responseEtagGzip); + response.getHeaders().put(HttpHeader.ETAG, responseEtagGzip); } } - _interceptor.write(content, complete, callback); + super.write(complete, callback, content); return; } // Are we excluding because of mime-type? - String ct = response.getContentType(); + String ct = response.getHeaders().get(HttpHeader.CONTENT_TYPE); if (ct != null) { String baseType = HttpField.valueParameters(ct, null); @@ -163,19 +146,19 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor { LOG.debug("{} exclude by mimeType {}", this, ct); noCompression(); - _interceptor.write(content, complete, callback); + super.write(complete, callback, content); return; } } // Has the Content-Encoding header already been set? - HttpFields.Mutable fields = response.getHttpFields(); + HttpFields.Mutable fields = response.getHeaders(); String ce = fields.get(HttpHeader.CONTENT_ENCODING); if (ce != null) { LOG.debug("{} exclude by content-encoding {}", this, ce); noCompression(); - _interceptor.write(content, complete, callback); + super.write(complete, callback, content); return; } @@ -186,16 +169,16 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor if (_vary != null) fields.ensureField(_vary); - long contentLength = response.getContentLength(); + long contentLength = response.getHeaders().getLongField(HttpHeader.CONTENT_LENGTH); if (contentLength < 0 && complete) - contentLength = content.remaining(); + contentLength = Arrays.stream(content).map(BufferUtil::length).mapToLong(Long::valueOf).sum(); - _deflaterEntry = _factory.getDeflaterEntry(_channel.getRequest(), contentLength); + _deflaterEntry = _factory.getDeflaterEntry(request, contentLength); if (_deflaterEntry == null) { LOG.debug("{} exclude no deflater", this); _state.set(GZState.NOT_COMPRESSING); - _interceptor.write(content, complete, callback); + super.write(complete, callback, content); return; } @@ -214,11 +197,11 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor if (BufferUtil.isEmpty(content)) { // We are committing, but have no content to compress, so flush empty buffer to write headers. - _interceptor.write(BufferUtil.EMPTY_BUFFER, complete, callback); + super.write(complete, callback); } else { - gzip(content, complete, callback); + gzip(complete, callback, content); } } else @@ -257,22 +240,16 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor private class GzipBufferCB extends IteratingNestedCallback { - private final ByteBuffer _content; + private ByteBuffer _copy; + private final ByteBuffer[] _content; private final boolean _last; + private int _index = 0; - public GzipBufferCB(ByteBuffer content, boolean complete, Callback callback) + public GzipBufferCB(boolean complete, Callback callback, ByteBuffer... content) { super(callback); - _content = content; _last = complete; - - _crc.update(_content.slice()); - - Deflater deflater = _deflaterEntry.get(); - deflater.setInput(_content); - if (_last) - deflater.finish(); } @Override @@ -297,9 +274,14 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor // cleanup and succeed. if (_buffer != null) { - _channel.getByteBufferPool().release(_buffer); + getRequest().getComponents().getByteBufferPool().release(_buffer); _buffer = null; } + if (_copy != null) + { + getRequest().getComponents().getByteBufferPool().release(_copy); + _copy = null; + } return Action.SUCCEEDED; } @@ -307,7 +289,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor if (_buffer == null) { // allocate a buffer and add the gzip header - _buffer = _channel.getByteBufferPool().acquire(_bufferSize, _channel.isUseOutputDirectByteBuffers()); + _buffer = getRequest().getComponents().getByteBufferPool().acquire(_bufferSize, false); BufferUtil.fill(_buffer, GZIP_HEADER, 0, GZIP_HEADER.length); } else @@ -320,12 +302,68 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor Deflater deflater = _deflaterEntry.get(); if (!deflater.finished()) { - if (deflater.needsInput() && !_last) - return Action.SUCCEEDED; + if (deflater.needsInput()) + { + ByteBuffer content = null; + if (_content == null) + content = BufferUtil.EMPTY_BUFFER; + else if (_index < _content.length) + { + while (BufferUtil.isEmpty(content)) + { + if (_index >= _content.length) + break; + content = _content[_index++]; + } + } - int pos = BufferUtil.flipToFill(_buffer); - deflater.deflate(_buffer, _syncFlush ? Deflater.SYNC_FLUSH : Deflater.NO_FLUSH); - BufferUtil.flipToFlush(_buffer, pos); + // if there is no more content available to compress + // then we are either finished all content or just the current write. + if (BufferUtil.isEmpty(content)) + { + if (_last) + deflater.finish(); + else + return Action.SUCCEEDED; + } + else + { + // If there is more content available to compress, we have to make sure + // it is available in an array for the current deflator API, maybe slicing + // of content. + ByteBuffer slice; + if (content.hasArray()) + slice = content; + else + { + if (_copy == null) + _copy = getRequest().getComponents().getByteBufferPool().acquire(_bufferSize, false); + else + BufferUtil.clear(_copy); + slice = _copy; + BufferUtil.append(_copy, content); + } + + // transfer the data from the slice to the the deflator + byte[] array = slice.array(); + int off = slice.arrayOffset() + slice.position(); + int len = slice.remaining(); + _crc.update(array, off, len); + // Ideally we would want to use the ByteBuffer API for Deflaters. However due the the ByteBuffer implementation + // of the CRC32.update() it is less efficient for us to use this rather than to convert to array ourselves. + _deflaterEntry.get().setInput(array, off, len); + slice.position(slice.position() + len); + boolean lastOfCurrentContent = (_content == null) || _index >= _content.length; + if (_last && lastOfCurrentContent && BufferUtil.isEmpty(content)) + deflater.finish(); + } + } + + // deflate the content into the available space in the buffer + int off = _buffer.arrayOffset() + _buffer.limit(); + int len = BufferUtil.space(_buffer); + int produced = deflater.deflate(_buffer.array(), off, len, _syncFlush ? Deflater.SYNC_FLUSH : Deflater.NO_FLUSH); + _buffer.limit(_buffer.limit() + produced); } // If we have finished deflation and there is room for the trailer. @@ -339,17 +377,18 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor } // write the compressed buffer. - _interceptor.write(_buffer, _deflaterEntry == null, this); + GzipResponse.this.getWrapped().write(_deflaterEntry == null, this, _buffer); return Action.SCHEDULED; } @Override public String toString() { - return String.format("%s[content=%s last=%b buffer=%s deflate=%s %s]", + return String.format("%s[content=%s last=%b copy=%s buffer=%s deflate=%s %s]", super.toString(), BufferUtil.toDetailString(_content), _last, + BufferUtil.toDetailString(_copy), BufferUtil.toDetailString(_buffer), _deflaterEntry, _deflaterEntry != null && _deflaterEntry.get().finished() ? "(finished)" : ""); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/HeaderWrappingRequest.java similarity index 56% rename from jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java rename to jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/HeaderWrappingRequest.java index 753953286a2..95a9ead5c5d 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/HeaderWrappingRequest.java @@ -11,26 +11,26 @@ // ======================================================================== // -package org.eclipse.jetty.client.util; +package org.eclipse.jetty.server.handler.gzip; -import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.server.Request; -/** - * @deprecated use {@link AbstractRequestContent} instead. - */ -@Deprecated -public abstract class AbstractTypedContentProvider implements ContentProvider.Typed +public class HeaderWrappingRequest extends Request.WrapperProcessor { - private final String contentType; + private final HttpFields _fields; - protected AbstractTypedContentProvider(String contentType) + public HeaderWrappingRequest(Request request, HttpFields fields) { - this.contentType = contentType; + super(request); + _fields = fields; } @Override - public String getContentType() + public HttpFields getHeaders() { - return contentType; + if (_fields == null) + return super.getHeaders(); + return _fields; } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/package-info.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/package-info.java similarity index 100% rename from jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/package-info.java rename to jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/package-info.java diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java new file mode 100644 index 00000000000..4f4ac358fbd --- /dev/null +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java @@ -0,0 +1,97 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server.handler.jmx; + +import org.eclipse.jetty.jmx.ObjectMBean; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AbstractHandlerMBean extends ObjectMBean +{ + private static final Logger LOG = LoggerFactory.getLogger(AbstractHandlerMBean.class); + + public AbstractHandlerMBean(Object managedObject) + { + super(managedObject); + } + + @Override + public String getObjectContextBasis() + { + if (_managed != null) + { + String basis = null; + if (_managed instanceof ContextHandler) + { + ContextHandler handler = (ContextHandler)_managed; + String context = getContextName(handler); + if (context == null) + context = handler.getDisplayName(); + if (context != null) + return context; + } + else if (_managed instanceof Handler.Abstract handler) + { + Server server = handler.getServer(); + if (server != null) + { +// TODO ContextHandler context = +// AbstractHandlerContainer.findContainerOf(server, +// ContextHandler.class, handler); +// +// if (context != null) +// basis = getContextName(context); + } + } + if (basis != null) + return basis; + } + return super.getObjectContextBasis(); + } + + protected String getContextName(ContextHandler context) + { + String name = null; + + if (context.getContextPath() != null && context.getContextPath().length() > 0) + { + int idx = context.getContextPath().lastIndexOf('/'); + name = idx < 0 ? context.getContextPath() : context.getContextPath().substring(++idx); + if (name == null || name.length() == 0) + name = "ROOT"; + } + +// TODO if (name == null && context.getBaseResource() != null) +// { +// try +// { +// if (context.getBaseResource().getFile() != null) +// name = context.getBaseResource().getFile().getName(); +// } +// catch (IOException e) +// { +// LOG.trace("IGNORED", e); +// name = context.getBaseResource().getName(); +// } +// } + +// TODO if (context.getVirtualHosts() != null && context.getVirtualHosts().length > 0) +// name = '"' + name + "@" + context.getVirtualHosts()[0] + '"'; + + return name; + } +} diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java index d278a5a4e63..b91fc6d58b4 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java @@ -17,7 +17,7 @@ import java.util.HashMap; import java.util.Map; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.server.jmx.AbstractHandlerMBean; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedOperation; @@ -26,19 +26,21 @@ import org.eclipse.jetty.util.annotation.Name; @ManagedObject("ContextHandler mbean wrapper") public class ContextHandlerMBean extends AbstractHandlerMBean { + private ContextHandler _contextHandler; + public ContextHandlerMBean(Object managedObject) { super(managedObject); + _contextHandler = (ContextHandler)managedObject; } @ManagedAttribute("Map of context attributes") public Map getContextAttributes() { Map map = new HashMap(); - Attributes attrs = ((ContextHandler)_managed).getAttributes(); - for (String name : attrs.getAttributeNameSet()) + for (String name : _contextHandler.getContext().getAttributeNameSet()) { - Object value = attrs.getAttribute(name); + Object value = _contextHandler.getContext().getAttribute(name); map.put(name, value); } return map; @@ -47,21 +49,18 @@ public class ContextHandlerMBean extends AbstractHandlerMBean @ManagedOperation(value = "Set context attribute", impact = "ACTION") public void setContextAttribute(@Name(value = "name", description = "attribute name") String name, @Name(value = "value", description = "attribute value") Object value) { - Attributes attrs = ((ContextHandler)_managed).getAttributes(); - attrs.setAttribute(name, value); + _contextHandler.getContext().setAttribute(name, value); } @ManagedOperation(value = "Set context attribute", impact = "ACTION") public void setContextAttribute(@Name(value = "name", description = "attribute name") String name, @Name(value = "value", description = "attribute value") String value) { - Attributes attrs = ((ContextHandler)_managed).getAttributes(); - attrs.setAttribute(name, value); + _contextHandler.getContext().setAttribute(name, value); } @ManagedOperation(value = "Remove context attribute", impact = "ACTION") public void removeContextAttribute(@Name(value = "name", description = "attribute name") String name) { - Attributes attrs = ((ContextHandler)_managed).getAttributes(); - attrs.removeAttribute(name); + _contextHandler.getContext().removeAttribute(name); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/package-info.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/package-info.java similarity index 100% rename from jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/package-info.java rename to jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/package-info.java diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/package-info.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/package-info.java similarity index 100% rename from jetty-server/src/main/java/org/eclipse/jetty/server/handler/package-info.java rename to jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/package-info.java diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java index cdc0da88fe4..c0b0b5718d0 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java @@ -11,1381 +11,1433 @@ // ======================================================================== // -package org.eclipse.jetty.server; +package org.eclipse.jetty.server.internal; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; -import jakarta.servlet.AsyncListener; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.UnavailableException; import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.io.QuietException; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.ContextHandler.Context; -import org.eclipse.jetty.server.handler.ErrorHandler; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http.PreEncodedHttpField; +import org.eclipse.jetty.http.UriCompliance; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.Components; +import org.eclipse.jetty.server.ConnectionMetaData; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.Context; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpStream; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.RequestLog; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.thread.AutoLock; +import org.eclipse.jetty.util.thread.Invocable; import org.eclipse.jetty.util.thread.Scheduler; +import org.eclipse.jetty.util.thread.SerializedInvoker; +import org.eclipse.jetty.util.thread.ThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static jakarta.servlet.RequestDispatcher.ERROR_EXCEPTION; -import static jakarta.servlet.RequestDispatcher.ERROR_EXCEPTION_TYPE; -import static jakarta.servlet.RequestDispatcher.ERROR_MESSAGE; -import static jakarta.servlet.RequestDispatcher.ERROR_REQUEST_URI; -import static jakarta.servlet.RequestDispatcher.ERROR_SERVLET_NAME; -import static jakarta.servlet.RequestDispatcher.ERROR_STATUS_CODE; - /** - * Implementation of AsyncContext interface that holds the state of request-response cycle. + * A Channel represents a sequence of request cycles from the same connection. However only a single + * request cycle may be active at once for each channel. This is some, but not all of the + * behaviour of the current HttpChannel class, specifically it does not include the mutual exclusion + * of handling required by the servlet spec and currently encapsulated in HttpChannelState. + * + * Note how Runnables are returned to indicate that further work is needed. These + * can be given to an ExecutionStrategy instead of calling known methods like HttpChannel.handle(). */ -public class HttpChannelState +public class HttpChannelState implements HttpChannel, Components { private static final Logger LOG = LoggerFactory.getLogger(HttpChannelState.class); - - private static final long DEFAULT_TIMEOUT = Long.getLong("org.eclipse.jetty.server.HttpChannelState.DEFAULT_TIMEOUT", 30000L); - - /* - * The state of the HttpChannel,used to control the overall lifecycle. - *
    -     *     IDLE <-----> HANDLING ----> WAITING
    -     *       |                 ^       /
    -     *       |                  \     /
    -     *       v                   \   v
    -     *    UPGRADED               WOKEN
    -     * 
    - */ - public enum State + private static final HttpField CONTENT_LENGTH_0 = new PreEncodedHttpField(HttpHeader.CONTENT_LENGTH, "0") { - IDLE, // Idle request - HANDLING, // Request dispatched to filter/servlet or Async IO callback - WAITING, // Suspended and waiting - WOKEN, // Dispatch to handle from ASYNC_WAIT - UPGRADED // Request upgraded the connection - } + @Override + public int getIntValue() + { + return 0; + } - /* - * The state of the request processing lifecycle. - *
    -     *       BLOCKING <----> COMPLETING ---> COMPLETED
    -     *       ^  |  ^            ^
    -     *      /   |   \           |
    -     *     |    |    DISPATCH   |
    -     *     |    |    ^  ^       |
    -     *     |    v   /   |       |
    -     *     |  ASYNC -------> COMPLETE
    -     *     |    |       |       ^
    -     *     |    v       |       |
    -     *     |  EXPIRE    |       |
    -     *      \   |      /        |
    -     *       \  v     /         |
    -     *       EXPIRING ----------+
    -     * 
    - */ - private enum RequestState + @Override + public long getLongValue() + { + return 0L; + } + }; + private static final MetaData.Request ERROR_REQUEST = new MetaData.Request("GET", HttpURI.from("/"), HttpVersion.HTTP_1_0, HttpFields.EMPTY); + private static final HttpField SERVER_VERSION = new PreEncodedHttpField(HttpHeader.SERVER, HttpConfiguration.SERVER_VERSION); + private static final HttpField POWERED_BY = new PreEncodedHttpField(HttpHeader.X_POWERED_BY, HttpConfiguration.SERVER_VERSION); + private static final Map NULL_CACHE = new AbstractMap<>() { - BLOCKING, // Blocking request dispatched - ASYNC, // AsyncContext.startAsync() has been called - DISPATCH, // AsyncContext.dispatch() has been called - EXPIRE, // AsyncContext timeout has happened - EXPIRING, // AsyncListeners are being called - COMPLETE, // AsyncContext.complete() has been called - COMPLETING, // Request is being closed (maybe asynchronously) - COMPLETED // Response is completed - } + @Override + public Set> entrySet() + { + return Collections.emptySet(); + } - /* - * The input readiness state, which works together with {@link HttpInput.State} - */ - private enum InputState - { - IDLE, // No isReady; No data - UNREADY, // isReady()==false; No data - READY // isReady() was false; data is available - } + @Override + public Object put(String key, Object value) + { + return null; + } - /* - * The output committed state, which works together with {@link HttpOutput.State} - */ - private enum OutputState + @Override + public void putAll(Map m) + { + } + }; + + enum State { - OPEN, - COMMITTED, + /** Idle state */ + IDLE, + + /** The HandlerInvoker Runnable has been executed */ + HANDLING, + + /** A Request.Processor has been called. + * Any calls to {@link #onFailure(Throwable)} will fail the callback. */ + PROCESSING, + + /** The Request.Processor call has returned prior to callback completion. + * The Content.Reader APIs are enabled. */ + PROCESSED, + + /** Callback completion has been called prior to Request.Processor completion. */ COMPLETED, - ABORTED, - } - /** - * The actions to take as the channel moves from state to state. - */ - public enum Action - { - DISPATCH, // handle a normal request dispatch - ASYNC_DISPATCH, // handle an async request dispatch - SEND_ERROR, // Generate an error page or error dispatch - ASYNC_ERROR, // handle an async error - ASYNC_TIMEOUT, // call asyncContext onTimeout - WRITE_CALLBACK, // handle an IO write callback - READ_CALLBACK, // handle an IO read callback - COMPLETE, // Complete the response by closing output - TERMINATED, // No further actions - WAIT, // Wait for further events + /** The Request.Processor call has returned and the callback is complete */ + PROCESSED_AND_COMPLETED, } private final AutoLock _lock = new AutoLock(); - private final HttpChannel _channel; - private List _asyncListeners; - private State _state = State.IDLE; - private RequestState _requestState = RequestState.BLOCKING; - private OutputState _outputState = OutputState.OPEN; - private InputState _inputState = InputState.IDLE; - private boolean _initial = true; - private boolean _sendError; - private boolean _asyncWritePossible; - private long _timeoutMs = DEFAULT_TIMEOUT; - private AsyncContextEvent _event; - private Thread _onTimeoutThread; + private final HandlerInvoker _handlerInvoker = new HandlerInvoker(); + private final ConnectionMetaData _connectionMetaData; + private final SerializedInvoker _serializedInvoker; + private final Attributes _requestAttributes = new Attributes.Lazy(); + private final ResponseHttpFields _responseHeaders = new ResponseHttpFields(); + private State _state = State.IDLE; // TODO could this be an AtomicReference? + private boolean _lastWrite = false; + private Throwable _failure; + private ChannelRequest _request; + private HttpStream _stream; + private long _committedContentLength = -1; + private ResponseHttpFields _responseTrailers; + private Runnable _onContentAvailable; + private Callback _writeCallback; + private Content.Error _error; + private Predicate _onError; + private Map _cache; - protected HttpChannelState(HttpChannel channel) + public HttpChannelState(ConnectionMetaData connectionMetaData) { - _channel = channel; - } - - AutoLock lock() - { - return _lock.lock(); - } - - public State getState() - { - try (AutoLock l = lock()) + _connectionMetaData = connectionMetaData; + // The SerializedInvoker is used to prevent infinite recursion of callbacks calling methods calling callbacks etc. + _serializedInvoker = new SerializedInvoker() { - return _state; - } - } - - public void addListener(AsyncListener listener) - { - try (AutoLock l = lock()) - { - if (_asyncListeners == null) - _asyncListeners = new ArrayList<>(); - _asyncListeners.add(listener); - } - } - - public boolean hasListener(AsyncListener listener) - { - try (AutoLock ignored = lock()) - { - if (_asyncListeners == null) - return false; - for (AsyncListener l : _asyncListeners) + @Override + protected void onError(Runnable task, Throwable failure) { - if (l == listener) - return true; + ChannelRequest request; + Content.Error error; + boolean completed; + try (AutoLock ignore = _lock.lock()) + { + completed = _state.ordinal() >= State.COMPLETED.ordinal(); + request = _request; + error = _request == null ? null : _error; + } - if (l instanceof AsyncContextState.WrappedAsyncListener && ((AsyncContextState.WrappedAsyncListener)l).getListener() == listener) - return true; + if (request == null || completed) + { + // It is too late to handle error, so just log it + super.onError(task, failure); + } + else if (error == null) + { + // Try to fail the request, but we might lose a race. + try + { + request._callback.failed(failure); + } + catch (Throwable t) + { + if (!TypeUtil.isAssociated(failure, t)) + failure.addSuppressed(t); + super.onError(task, failure); + } + } + else + { + // We are already in error, so we will not handle this one, + // but we will add as suppressed if we have not seen it already. + Throwable cause = error.getCause(); + if (cause != null && !TypeUtil.isAssociated(cause, failure)) + error.getCause().addSuppressed(failure); + } + } + }; + } + + @Override + public void recycle() + { + try (AutoLock ignored = _lock.lock()) + { + if (LOG.isDebugEnabled()) + LOG.debug("recycling {}", this); + + // Break the link between request and channel, so that + // applications cannot use request/response/callback anymore. + _request._httpChannel = null; + + // Break the links with the upper and lower layers. + _request = null; + _stream = null; + + // Recycle. + _requestAttributes.clearAttributes(); + _responseHeaders.reset(); + _state = State.IDLE; + _lastWrite = false; + _failure = null; + _committedContentLength = -1; + if (_responseTrailers != null) + _responseTrailers.reset(); + _onContentAvailable = null; + _writeCallback = null; + _error = null; + _onError = null; + } + } + + public HttpConfiguration getHttpConfiguration() + { + return _connectionMetaData.getHttpConfiguration(); + } + + public HttpStream getHttpStream() + { + try (AutoLock ignored = _lock.lock()) + { + return _stream; + } + } + + public void setHttpStream(HttpStream stream) + { + try (AutoLock ignored = _lock.lock()) + { + _stream = stream; + } + } + + public Server getServer() + { + return _connectionMetaData.getConnector().getServer(); + } + + @Override + public ConnectionMetaData getConnectionMetaData() + { + return _connectionMetaData; + } + + // TODO: remove this + public Connection getConnection() + { + return _connectionMetaData.getConnection(); + } + + // TODO: remove this + public Connector getConnector() + { + return _connectionMetaData.getConnector(); + } + + // TODO: remove this + public EndPoint getEndPoint() + { + return getConnection().getEndPoint(); + } + + @Override + public ByteBufferPool getByteBufferPool() + { + return getConnectionMetaData().getConnector().getByteBufferPool(); + } + + @Override + public Scheduler getScheduler() + { + return getServer().getBean(Scheduler.class); + } + + @Override + public ThreadPool getThreadPool() + { + return getServer().getThreadPool(); + } + + @Override + public Map getCache() + { + if (_cache == null) + { + if (getConnectionMetaData().isPersistent()) + _cache = new HashMap<>(); + else + _cache = NULL_CACHE; + } + return _cache; + } + + /** + * Start request handling by returning a Runnable that will call {@link Handler#handle(Request)}. + * + * @param request The request metadata to handle. + * @return A Runnable that will call {@link Handler#handle(Request)}. Unlike all other Runnables + * returned by HttpChannel methods, this runnable is not mutually excluded or serialized against the other + * Runnables. + */ + public Runnable onRequest(MetaData.Request request) + { + if (LOG.isDebugEnabled()) + LOG.debug("onRequest {} {}", request, this); + + try (AutoLock ignored = _lock.lock()) + { + if (_stream == null) + throw new IllegalStateException("No HttpStream"); + if (_request != null) + throw new IllegalStateException("duplicate request"); + _request = new ChannelRequest(this, request); + + HttpFields.Mutable responseHeaders = _request._response.getHeaders(); + if (getHttpConfiguration().getSendServerVersion()) + responseHeaders.add(SERVER_VERSION); + if (getHttpConfiguration().getSendXPoweredBy()) + responseHeaders.add(POWERED_BY); + if (getHttpConfiguration().getSendDateHeader()) + responseHeaders.add(getConnectionMetaData().getConnector().getServer().getDateField()); + + if (!HttpMethod.PRI.is(request.getMethod()) && + !HttpMethod.CONNECT.is(request.getMethod()) && + !_request.getPathInContext().startsWith("/") && + !HttpMethod.OPTIONS.is(request.getMethod())) + throw new BadMessageException("Bad URI path"); + + HttpURI uri = request.getURI(); + if (uri.hasViolations()) + { + String badMessage = UriCompliance.checkUriCompliance(getConnectionMetaData().getHttpConfiguration().getUriCompliance(), uri); + if (badMessage != null) + return onFailure(new BadMessageException(badMessage)); } - return false; + // This is deliberately not serialized to allow a handler to block. + return _handlerInvoker; } } - public boolean isSendError() + public Request getRequest() { - try (AutoLock l = lock()) + try (AutoLock ignored = _lock.lock()) { - return _sendError; + return _request; } } - public void setTimeout(long ms) + public Response getResponse() { - try (AutoLock l = lock()) + try (AutoLock ignored = _lock.lock()) { - _timeoutMs = ms; + return _request == null ? null : _request._response; } } - public long getTimeout() + public boolean isRequestHandled() { - try (AutoLock l = lock()) + try (AutoLock ignored = _lock.lock()) { - return _timeoutMs; + return _state.ordinal() >= State.HANDLING.ordinal(); } } - public AsyncContextEvent getAsyncContextEvent() + public Runnable onContentAvailable() { - try (AutoLock l = lock()) + Runnable onContent; + try (AutoLock ignored = _lock.lock()) { - return _event; + if (_request == null) + return null; + onContent = _onContentAvailable; + _onContentAvailable = null; } + return _serializedInvoker.offer(onContent); + } + + @Override + public Invocable.InvocationType getInvocationType() + { + // TODO Can this actually be done, as we may need to invoke other Runnables after onContent? + // Could we at least avoid the lock??? + Runnable onContent; + try (AutoLock ignored = _lock.lock()) + { + if (_request == null) + return null; + onContent = _onContentAvailable; + } + return Invocable.getInvocationType(onContent); + } + + public Runnable onFailure(Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("onError {}", this, x); + + HttpStream stream; + Runnable task; + try (AutoLock ignored = _lock.lock()) + { + // If the channel doesn't have a stream, then the error is ignored. + if (_stream == null) + return null; + stream = _stream; + + if (_request == null) + { + // If the channel doesn't have a request, then the error must have occurred during the parsing of + // the request line / headers, so make a temp request for logging and producing an error response. + _request = new ChannelRequest(this, ERROR_REQUEST); + } + + // Remember the error and arrange for any subsequent reads, demands or writes to fail with this error. + if (_error == null) + { + _error = new Content.Error(x); + } + else if (_error.getCause() != x) + { + _error.getCause().addSuppressed(x); + return null; + } + + // Invoke onContentAvailable() if we are currently demanding. + Runnable invokeOnContentAvailable = _onContentAvailable; + _onContentAvailable = null; + + // If a write() is in progress, fail the write callback. + Callback writeCallback = _writeCallback; + _writeCallback = null; + Runnable invokeWriteFailure = writeCallback == null ? null : () -> writeCallback.failed(x); + + ChannelRequest request = _request; + Runnable invokeCallback = () -> + { + // Only fail the callback if the application was not invoked. + boolean handled; + try (AutoLock ignore = _lock.lock()) + { + handled = _state.ordinal() >= State.HANDLING.ordinal(); + if (!handled) + _state = State.PROCESSED; + } + if (handled) + { + if (LOG.isDebugEnabled()) + LOG.debug("already handled, skipping failing callback in {}", HttpChannelState.this); + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("failing callback in {}", this, x); + request._callback.failed(x); + } + }; + + // Invoke error listeners. + Predicate onError = _onError; + _onError = null; + Runnable invokeOnErrorAndCallback = onError == null ? invokeCallback : () -> + { + if (!onError.test(x)) + invokeCallback.run(); + }; + + // Serialize all the error actions. + task = _serializedInvoker.offer(invokeOnContentAvailable, invokeWriteFailure, invokeOnErrorAndCallback); + } + + // Consume content as soon as possible to open any flow control window. + Throwable unconsumed = stream.consumeAll(); + if (unconsumed != null && LOG.isDebugEnabled()) + LOG.debug("consuming content during error {}", unconsumed.toString()); + + return task; + } + + public void addHttpStreamWrapper(Function onStreamEvent) + { + while (true) + { + HttpStream stream; + try (AutoLock ignored = _lock.lock()) + { + stream = _stream; + } + if (_stream == null) + throw new IllegalStateException("No active stream"); + HttpStream.Wrapper combined = onStreamEvent.apply(stream); + if (combined == null || combined.getWrapped() != stream) + throw new IllegalArgumentException("Cannot remove stream"); + try (AutoLock ignored = _lock.lock()) + { + if (_stream != stream) + continue; + _stream = combined; + break; + } + } + } + + private void resetResponse() + { + try (AutoLock ignored = _lock.lock()) + { + if (_responseHeaders.isCommitted()) + throw new IllegalStateException("response committed"); + + _request._response._status = 0; + + _responseHeaders.clear(); + + if (_responseTrailers != null) + _responseTrailers.clear(); + } + } + + private void changeState(State from, State to) + { + try (AutoLock ignored = _lock.lock()) + { + changeStateLocked(from, to); + } + } + + private void changeStateLocked(State from, State to) + { + if (!_lock.isHeldByCurrentThread() || _state != from) // TODO do we need the lock check? + throw new IllegalStateException(String.valueOf(_state)); + _state = to; } @Override public String toString() { - try (AutoLock l = lock()) + try (AutoLock ignored = _lock.lock()) { - return toStringLocked(); + return String.format("%s@%x{s=%s,r=%s}", this.getClass().getSimpleName(), hashCode(), _state, _request); } } - private String toStringLocked() + private class HandlerInvoker implements Invocable.Task, Callback { - return String.format("%s@%x{%s}", - getClass().getSimpleName(), - hashCode(), - getStatusStringLocked()); - } - - private String getStatusStringLocked() - { - return String.format("s=%s rs=%s os=%s is=%s awp=%b se=%b i=%b al=%d", - _state, - _requestState, - _outputState, - _inputState, - _asyncWritePossible, - _sendError, - _initial, - _asyncListeners == null ? 0 : _asyncListeners.size()); - } - - public String getStatusString() - { - try (AutoLock l = lock()) + @Override + public void run() { - return getStatusStringLocked(); - } - } - - public boolean commitResponse() - { - try (AutoLock l = lock()) - { - switch (_outputState) + // Once we switch to HANDLING state and beyond, then we assume that the + // application will call the callback, and thus any onFailure reports will not. + // However, if a thread calling the application throws, then that exception will be reported + // to the callback. + ChannelRequest request; + try (AutoLock ignored = _lock.lock()) { - case OPEN: - _outputState = OutputState.COMMITTED; - return true; - - default: - return false; + changeStateLocked(State.IDLE, State.HANDLING); + request = _request; } - } - } - public boolean partialResponse() - { - try (AutoLock l = lock()) - { - switch (_outputState) - { - case COMMITTED: - _outputState = OutputState.OPEN; - return true; - - default: - return false; - } - } - } - - public boolean completeResponse() - { - try (AutoLock l = lock()) - { - switch (_outputState) - { - case OPEN: - case COMMITTED: - _outputState = OutputState.COMPLETED; - return true; - - default: - return false; - } - } - } - - public boolean isResponseCommitted() - { - try (AutoLock l = lock()) - { - switch (_outputState) - { - case OPEN: - return false; - default: - return true; - } - } - } - - public boolean isResponseCompleted() - { - try (AutoLock l = lock()) - { - return _outputState == OutputState.COMPLETED; - } - } - - public boolean abortResponse() - { - try (AutoLock l = lock()) - { - switch (_outputState) - { - case ABORTED: - return false; - - case OPEN: - _channel.getResponse().setStatus(500); - _outputState = OutputState.ABORTED; - return true; - - default: - _outputState = OutputState.ABORTED; - return true; - } - } - } - - /** - * @return Next handling of the request should proceed - */ - public Action handling() - { - try (AutoLock l = lock()) - { if (LOG.isDebugEnabled()) - LOG.debug("handling {}", toStringLocked()); + LOG.debug("invoking handler in {}", HttpChannelState.this); + Server server = _connectionMetaData.getConnector().getServer(); - switch (_state) + Request.Processor processor; + Request customized = request; + Throwable failure = null; + try { - case IDLE: - if (_requestState != RequestState.BLOCKING) - throw new IllegalStateException(getStatusStringLocked()); - _initial = true; - _state = State.HANDLING; - return Action.DISPATCH; + // Customize before accepting. + HttpConfiguration configuration = getHttpConfiguration(); - case WOKEN: - if (_event != null && _event.getThrowable() != null && !_sendError) + for (HttpConfiguration.Customizer customizer : configuration.getCustomizers()) + { + Request next = customizer.customize(request, ((Response)request._response).getHeaders()); + customized = next == null ? customized : next; + } + + if (customized != request && server.getRequestLog() != null) + request.setLoggedRequest(customized); + + processor = server.handle(customized); + if (processor == null) + processor = (req, res, cb) -> Response.writeError(req, res, cb, HttpStatus.NOT_FOUND_404); + } + catch (Throwable t) + { + processor = null; + failure = t; + } + + changeState(State.HANDLING, State.PROCESSING); + + try + { + if (processor != null) + processor.process(customized, request._response, request._callback); + } + catch (Throwable x) + { + failure = x; + } + if (failure != null) + ((Callback)request._callback).failed(failure); + + HttpStream stream; + boolean complete; + try (AutoLock ignored = _lock.lock()) + { + complete = _state == State.COMPLETED; + + if (_failure != null) + { + if (failure != null) { - _state = State.HANDLING; - return Action.ASYNC_ERROR; + if (!TypeUtil.isAssociated(failure, _failure)) + _failure.addSuppressed(failure); } + failure = _failure; + } - Action action = nextAction(true); + if (complete) + changeStateLocked(State.COMPLETED, State.PROCESSED_AND_COMPLETED); + else + changeStateLocked(State.PROCESSING, State.PROCESSED); + + stream = _stream; + } + if (complete) + complete(stream, failure); + } + + /** + * Called only as {@link Callback} by last write from {@link ChannelCallback#succeeded} + */ + @Override + public void succeeded() + { + HttpStream stream; + boolean complete; + boolean needLastWrite; + MetaData.Response responseMetaData = null; + try (AutoLock ignored = _lock.lock()) + { + needLastWrite = !_lastWrite; + _lastWrite = true; + if (needLastWrite && _responseHeaders.commit()) + responseMetaData = _request._response.lockedPrepareResponse(HttpChannelState.this, true); + complete = _state == State.PROCESSED_AND_COMPLETED; + stream = _stream; + } + + if (needLastWrite) + stream.send(_request._metaData, responseMetaData, true, this); + else if (complete) + complete(stream, null); + } + + /** + * Called only as {@link Callback} by last write from {@link ChannelCallback#succeeded} + */ + @Override + public void failed(Throwable failure) + { + HttpStream stream; + boolean complete; + try (AutoLock ignored = _lock.lock()) + { + complete = _state == State.PROCESSED_AND_COMPLETED; + stream = _stream; + if (_failure == null) + _failure = failure; + else if (!TypeUtil.isAssociated(_failure, failure)) + { + _failure.addSuppressed(failure); + failure = _failure; + } + } + if (complete) + complete(stream, failure); + } + + private void complete(HttpStream stream, Throwable failure) + { + try + { + RequestLog requestLog = getServer().getRequestLog(); + if (requestLog != null) + { if (LOG.isDebugEnabled()) - LOG.debug("nextAction(true) {} {}", action, toStringLocked()); - return action; + LOG.debug("logging {}", HttpChannelState.this); - default: - throw new IllegalStateException(getStatusStringLocked()); + requestLog.log(_request.getLoggedRequest(), _request._response); + } } - } - } - - /** - * Signal that the HttpConnection has finished handling the request. - * For blocking connectors, this call may block if the request has - * been suspended (startAsync called). - * - * @return next actions - * be handled again (eg because of a resume that happened before unhandle was called) - */ - protected Action unhandle() - { - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("unhandle {}", toStringLocked()); - - if (_state != State.HANDLING) - throw new IllegalStateException(this.getStatusStringLocked()); - - _initial = false; - - Action action = nextAction(false); - if (LOG.isDebugEnabled()) - LOG.debug("nextAction(false) {} {}", action, toStringLocked()); - return action; - } - } - - private Action nextAction(boolean handling) - { - // Assume we can keep going, but exceptions are below - _state = State.HANDLING; - - if (_sendError) - { - switch (_requestState) + finally { - case BLOCKING: - case ASYNC: - case COMPLETE: - case DISPATCH: - case COMPLETING: - _requestState = RequestState.BLOCKING; - _sendError = false; - return Action.SEND_ERROR; - - default: - break; + // This is THE ONLY PLACE the stream is succeeded or failed. + if (failure == null) + // TODO _serializedInvoker.run(stream::succeeded); + // That would wait for all callback to be completed. + stream.succeeded(); + else + stream.failed(_failure); } } - switch (_requestState) + @Override + public InvocationType getInvocationType() { - case BLOCKING: - if (handling) - throw new IllegalStateException(getStatusStringLocked()); - _requestState = RequestState.COMPLETING; - return Action.COMPLETE; + return getConnectionMetaData().getConnector().getServer().getInvocationType(); + } + } - case ASYNC: - switch (_inputState) + public static class ChannelRequest implements Attributes, Request + { + private final long _timeStamp = System.currentTimeMillis(); + private final ChannelCallback _callback = new ChannelCallback(this); + private final String _id; + private final ConnectionMetaData _connectionMetaData; + private final MetaData.Request _metaData; + private final ChannelResponse _response; + private final AutoLock _lock; + private final LongAdder _contentBytesRead = new LongAdder(); + private HttpChannelState _httpChannel; + private Request _loggedRequest; + + ChannelRequest(HttpChannelState httpChannel, MetaData.Request metaData) + { + _httpChannel = Objects.requireNonNull(httpChannel); + _id = httpChannel.getHttpStream().getId(); + _connectionMetaData = httpChannel.getConnectionMetaData(); + _metaData = Objects.requireNonNull(metaData); + _response = new ChannelResponse(this); + _lock = httpChannel._lock; + } + + public void setLoggedRequest(Request request) + { + _loggedRequest = request; + } + + public Request getLoggedRequest() + { + return _loggedRequest == null ? this : _loggedRequest; + } + + HttpStream getStream() + { + return getHttpChannel()._stream; + } + + public long getContentBytesRead() + { + return _contentBytesRead.longValue(); + } + + @Override + public Object getAttribute(String name) + { + HttpChannelState httpChannel = getHttpChannel(); + if (name.startsWith("org.eclipse.jetty")) + { + if (Server.class.getName().equals(name)) + return httpChannel.getConnectionMetaData().getConnector().getServer(); + if (HttpChannelState.class.getName().equals(name)) + return httpChannel; + // TODO: is the instanceof needed? + // TODO: possibly remove this if statement or move to Servlet. + if (HttpConnection.class.getName().equals(name) && + getConnectionMetaData().getConnection() instanceof HttpConnection) + return getConnectionMetaData().getConnection(); + } + return httpChannel._requestAttributes.getAttribute(name); + } + + @Override + public Object removeAttribute(String name) + { + return getHttpChannel()._requestAttributes.removeAttribute(name); + } + + @Override + public Object setAttribute(String name, Object attribute) + { + if (Server.class.getName().equals(name) || HttpChannelState.class.getName().equals(name) || HttpConnection.class.getName().equals(name)) + return null; + return getHttpChannel()._requestAttributes.setAttribute(name, attribute); + } + + @Override + public Set getAttributeNameSet() + { + return getHttpChannel()._requestAttributes.getAttributeNameSet(); + } + + @Override + public void clearAttributes() + { + getHttpChannel()._requestAttributes.clearAttributes(); + } + + @Override + public String getId() + { + return _id; + } + + @Override + public Components getComponents() + { + return getHttpChannel(); + } + + @Override + public ConnectionMetaData getConnectionMetaData() + { + return _connectionMetaData; + } + + HttpChannelState getHttpChannel() + { + try (AutoLock ignore = _lock.lock()) + { + return lockedGetHttpChannel(); + } + } + + private HttpChannelState lockedGetHttpChannel() + { + if (_httpChannel == null) + throw new IllegalStateException("channel already completed"); + return _httpChannel; + } + + @Override + public String getMethod() + { + return _metaData.getMethod(); + } + + @Override + public HttpURI getHttpURI() + { + return _metaData.getURI(); + } + + @Override + public Context getContext() + { + return getConnectionMetaData().getConnector().getServer().getContext(); + } + + @Override + public String getPathInContext() + { + return _metaData.getURI().getCanonicalPath(); + } + + @Override + public HttpFields getHeaders() + { + return _metaData.getFields(); + } + + @Override + public long getTimeStamp() + { + return _timeStamp; + } + + @Override + public boolean isSecure() + { + return HttpScheme.HTTPS.is(getHttpURI().getScheme()); + } + + @Override + public long getContentLength() + { + return _metaData.getContentLength(); + } + + @Override + public Content readContent() + { + HttpStream stream; + try (AutoLock ignored = _lock.lock()) + { + HttpChannelState httpChannel = lockedGetHttpChannel(); + + Content error = httpChannel._error; + if (error != null) + return error; + + if (httpChannel._state.ordinal() < State.PROCESSING.ordinal()) + return new Content.Error(new IllegalStateException("not processing")); + + stream = httpChannel._stream; + } + + Content content = stream.readContent(); + if (content != null && content.hasRemaining()) + _contentBytesRead.add(content.remaining()); + + return content; + } + + @Override + public void demandContent(Runnable onContentAvailable) + { + boolean error; + HttpStream stream; + try (AutoLock ignored = _lock.lock()) + { + HttpChannelState httpChannel = lockedGetHttpChannel(); + + error = httpChannel._error != null || httpChannel._state.ordinal() < State.PROCESSING.ordinal(); + if (!error) { - case IDLE: - case UNREADY: - break; - case READY: - _inputState = InputState.IDLE; - return Action.READ_CALLBACK; - - default: - throw new IllegalStateException(getStatusStringLocked()); + if (httpChannel._onContentAvailable != null) + throw new IllegalArgumentException("demand pending"); + httpChannel._onContentAvailable = onContentAvailable; } - if (_asyncWritePossible) - { - _asyncWritePossible = false; - return Action.WRITE_CALLBACK; - } - - Scheduler scheduler = _channel.getScheduler(); - if (scheduler != null && _timeoutMs > 0 && !_event.hasTimeoutTask()) - _event.setTimeoutTask(scheduler.schedule(_event, _timeoutMs, TimeUnit.MILLISECONDS)); - _state = State.WAITING; - return Action.WAIT; - - case DISPATCH: - _requestState = RequestState.BLOCKING; - return Action.ASYNC_DISPATCH; - - case EXPIRE: - _requestState = RequestState.EXPIRING; - return Action.ASYNC_TIMEOUT; - - case EXPIRING: - if (handling) - throw new IllegalStateException(getStatusStringLocked()); - sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, "AsyncContext timeout"); - // handle sendError immediately - _requestState = RequestState.BLOCKING; - _sendError = false; - return Action.SEND_ERROR; - - case COMPLETE: - _requestState = RequestState.COMPLETING; - return Action.COMPLETE; - - case COMPLETING: - _state = State.WAITING; - return Action.WAIT; - - case COMPLETED: - _state = State.IDLE; - return Action.TERMINATED; - - default: - throw new IllegalStateException(getStatusStringLocked()); - } - } - - public void startAsync(AsyncContextEvent event) - { - final List lastAsyncListeners; - - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("startAsync {}", toStringLocked()); - if (_state != State.HANDLING || _requestState != RequestState.BLOCKING) - throw new IllegalStateException(this.getStatusStringLocked()); - - _requestState = RequestState.ASYNC; - _event = event; - lastAsyncListeners = _asyncListeners; - _asyncListeners = null; - } - - if (lastAsyncListeners != null) - { - Runnable callback = new Runnable() - { - @Override - public void run() - { - for (AsyncListener listener : lastAsyncListeners) - { - try - { - listener.onStartAsync(event); - } - catch (Throwable e) - { - // TODO Async Dispatch Error - LOG.warn("Async dispatch error", e); - } - } - } - - @Override - public String toString() - { - return "startAsync"; - } - }; - - runInContext(event, callback); - } - } - - public void dispatch(ServletContext context, String path) - { - boolean dispatch = false; - AsyncContextEvent event; - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("dispatch {} -> {}", toStringLocked(), path); - - switch (_requestState) - { - case ASYNC: - break; - case EXPIRING: - if (Thread.currentThread() != _onTimeoutThread) - throw new IllegalStateException(this.getStatusStringLocked()); - break; - default: - throw new IllegalStateException(this.getStatusStringLocked()); + stream = httpChannel._stream; } - if (context != null) - _event.setDispatchContext(context); - if (path != null) - _event.setDispatchPath(path); - - if (_requestState == RequestState.ASYNC && _state == State.WAITING) - { - _state = State.WOKEN; - dispatch = true; - } - _requestState = RequestState.DISPATCH; - event = _event; - } - - cancelTimeout(event); - if (dispatch) - scheduleDispatch(); - } - - protected void timeout() - { - boolean dispatch = false; - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("Timeout {}", toStringLocked()); - - if (_requestState != RequestState.ASYNC) - return; - _requestState = RequestState.EXPIRE; - - if (_state == State.WAITING) - { - _state = State.WOKEN; - dispatch = true; - } - } - - if (dispatch) - { - if (LOG.isDebugEnabled()) - LOG.debug("Dispatch after async timeout {}", this); - scheduleDispatch(); - } - } - - protected void onTimeout() - { - final List listeners; - AsyncContextEvent event; - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("onTimeout {}", toStringLocked()); - if (_requestState != RequestState.EXPIRING || _state != State.HANDLING) - throw new IllegalStateException(toStringLocked()); - event = _event; - listeners = _asyncListeners; - _onTimeoutThread = Thread.currentThread(); - } - - try - { - if (listeners != null) - { - Runnable task = new Runnable() - { - @Override - public void run() - { - for (AsyncListener listener : listeners) - { - try - { - listener.onTimeout(event); - } - catch (Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.warn("{} while invoking onTimeout listener {}", x.toString(), listener, x); - else - LOG.warn("{} while invoking onTimeout listener {}", x.toString(), listener); - } - } - } - - @Override - public String toString() - { - return "onTimeout"; - } - }; - - runInContext(event, task); - } - } - finally - { - try (AutoLock l = lock()) - { - _onTimeoutThread = null; - } - } - } - - public void complete() - { - boolean handle = false; - AsyncContextEvent event; - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("complete {}", toStringLocked()); - - event = _event; - switch (_requestState) - { - case EXPIRING: - if (Thread.currentThread() != _onTimeoutThread) - throw new IllegalStateException(this.getStatusStringLocked()); - _requestState = _sendError ? RequestState.BLOCKING : RequestState.COMPLETE; - break; - - case ASYNC: - _requestState = _sendError ? RequestState.BLOCKING : RequestState.COMPLETE; - break; - - case COMPLETE: - return; - default: - throw new IllegalStateException(this.getStatusStringLocked()); - } - if (_state == State.WAITING) - { - handle = true; - _state = State.WOKEN; - } - } - - cancelTimeout(event); - if (handle) - runInContext(event, _channel); - } - - public void asyncError(Throwable failure) - { - // This method is called when an failure occurs asynchronously to - // normal handling. If the request is async, we arrange for the - // exception to be thrown from the normal handling loop and then - // actually handled by #thrownException - - AsyncContextEvent event = null; - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("asyncError {}", toStringLocked(), failure); - - if (_state == State.WAITING && _requestState == RequestState.ASYNC) - { - _state = State.WOKEN; - _event.addThrowable(failure); - event = _event; - } + if (error) + // TODO: can we avoid re-grabbing the lock to get the HttpChannel? + getHttpChannel()._serializedInvoker.run(onContentAvailable); else + stream.demandContent(); + } + + @Override + public void push(MetaData.Request request) + { + getStream().push(request); + } + + @Override + public boolean addErrorListener(Predicate onError) + { + try (AutoLock ignored = _lock.lock()) { - if (!(failure instanceof QuietException)) - LOG.warn(failure.toString()); - if (LOG.isDebugEnabled()) - LOG.debug("Async error", failure); + HttpChannelState httpChannel = lockedGetHttpChannel(); + + if (httpChannel._error != null) + return false; + + if (httpChannel._onError == null) + { + httpChannel._onError = onError; + } + else + { + Predicate previous = httpChannel._onError; + httpChannel._onError = throwable -> + { + if (!previous.test(throwable)) + return onError.test(throwable); + return true; + }; + } + return true; } } - if (event != null) + @Override + public void addHttpStreamWrapper(Function wrapper) { - cancelTimeout(event); - runInContext(event, _channel); + getHttpChannel().addHttpStreamWrapper(wrapper); + } + + @Override + public String toString() + { + return String.format("%s@%x %s %s", getMethod(), hashCode(), getHttpURI(), _metaData.getHttpVersion()); } } - protected void onError(Throwable th) + public static class ChannelResponse implements Response, Callback { - final AsyncContextEvent asyncEvent; - final List asyncListeners; - try (AutoLock l = lock()) + private final ChannelRequest _request; + private int _status; + private long _contentBytesWritten; + + private ChannelResponse(ChannelRequest request) { - if (LOG.isDebugEnabled()) - LOG.debug("thrownException {}", getStatusStringLocked(), th); + _request = request; + } - // This can only be called from within the handle loop - if (_state != State.HANDLING) - throw new IllegalStateException(getStatusStringLocked()); + public long getContentBytesWritten() + { + return _contentBytesWritten; + } - // If sendError has already been called, we can only handle one failure at a time! - if (_sendError) + @Override + public Request getRequest() + { + return _request; + } + + @Override + public int getStatus() + { + return _status; + } + + @Override + public void setStatus(int code) + { + if (!isCommitted()) + _status = code; + } + + @Override + public HttpFields.Mutable getHeaders() + { + return _request.getHttpChannel()._responseHeaders; + } + + @Override + public HttpFields.Mutable getTrailers() + { + // TODO: getter with side effects, perhaps use a Supplier like in Jetty 11? + try (AutoLock ignored = _request._lock.lock()) { - LOG.warn("unhandled due to prior sendError", th); - return; - } + HttpChannelState httpChannel = _request.lockedGetHttpChannel(); - // Check async state to determine type of handling - switch (_requestState) - { - case BLOCKING: - // handle the exception with a sendError - sendError(th); - return; - - case DISPATCH: // Dispatch has already been called but we ignore and handle exception below - case COMPLETE: // Complete has already been called but we ignore and handle exception below - case ASYNC: - if (_asyncListeners == null || _asyncListeners.isEmpty()) - { - sendError(th); - return; - } - asyncEvent = _event; - asyncEvent.addThrowable(th); - asyncListeners = _asyncListeners; - break; - - default: - LOG.warn("unhandled in state {}", _requestState, new IllegalStateException(th)); - return; + // TODO check if trailers allowed in version and transport? + HttpFields.Mutable trailers = httpChannel._responseTrailers; + if (trailers == null) + trailers = httpChannel._responseTrailers = new ResponseHttpFields(); + return trailers; } } - // If we are async and have async listeners - // call onError - runInContext(asyncEvent, () -> + private HttpFields takeTrailers() { - for (AsyncListener listener : asyncListeners) + ResponseHttpFields trailers = _request.getHttpChannel()._responseTrailers; + if (trailers != null) + trailers.commit(); + return trailers; + } + + @Override + public void write(boolean last, Callback callback, ByteBuffer... content) + { + long written; + HttpChannelState httpChannel; + HttpStream stream = null; + Throwable failure = null; + MetaData.Response responseMetaData = null; + boolean noop = false; + try (AutoLock ignored = _request._lock.lock()) { - try + httpChannel = _request.lockedGetHttpChannel(); + + if (httpChannel._writeCallback != null) + failure = new IllegalStateException("write pending"); + else if (httpChannel._state.ordinal() < State.PROCESSING.ordinal()) + failure = new IllegalStateException("not processing"); + else if (httpChannel._error != null) + failure = httpChannel._error.getCause(); + else if (last && httpChannel._lastWrite) { - listener.onError(asyncEvent); - } - catch (Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.warn("{} while invoking onError listener {}", x.toString(), listener, x); + if (BufferUtil.remaining(content) > 0) + failure = new IllegalStateException("last already written"); else - LOG.warn("{} while invoking onError listener {}", x.toString(), listener); + noop = true; } - } - }); - // check the actions of the listeners - try (AutoLock l = lock()) - { - if (_requestState == RequestState.ASYNC && !_sendError) - { - // The listeners did not invoke API methods and the - // container must provide a default error dispatch. - sendError(th); - } - else if (_requestState != RequestState.COMPLETE) - { - LOG.warn("unhandled in state {}", _requestState, new IllegalStateException(th)); - } - } - } - - private void sendError(Throwable th) - { - // No sync as this is always called with lock held - - // Determine the actual details of the exception - final Request request = _channel.getRequest(); - final int code; - final String message; - Throwable cause = _channel.unwrap(th, BadMessageException.class, UnavailableException.class); - if (cause == null) - { - code = HttpStatus.INTERNAL_SERVER_ERROR_500; - message = th.toString(); - } - else if (cause instanceof BadMessageException) - { - BadMessageException bme = (BadMessageException)cause; - code = bme.getCode(); - message = bme.getReason(); - } - else if (cause instanceof UnavailableException) - { - message = cause.toString(); - if (((UnavailableException)cause).isPermanent()) - code = HttpStatus.NOT_FOUND_404; - else - code = HttpStatus.SERVICE_UNAVAILABLE_503; - } - else - { - code = HttpStatus.INTERNAL_SERVER_ERROR_500; - message = null; - } - - sendError(code, message); - - // No ISE, so good to modify request/state - request.setAttribute(ERROR_EXCEPTION, th); - request.setAttribute(ERROR_EXCEPTION_TYPE, th.getClass()); - // Ensure any async lifecycle is ended! - _requestState = RequestState.BLOCKING; - } - - public void sendError(int code, String message) - { - // This method is called by Response.sendError to organise for an error page to be generated when it is possible: - // + The response is reset and temporarily closed. - // + The details of the error are saved as request attributes - // + The _sendError boolean is set to true so that an ERROR_DISPATCH action will be generated: - // - after unhandle for sync - // - after both unhandle and complete for async - - final Request request = _channel.getRequest(); - final Response response = _channel.getResponse(); - if (message == null) - message = HttpStatus.getMessage(code); - - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("sendError {}", toStringLocked()); - - if (_outputState != OutputState.OPEN) - throw new IllegalStateException(_outputState.toString()); - - switch (_state) - { - case HANDLING: - case WOKEN: - case WAITING: - break; - default: - throw new IllegalStateException(getStatusStringLocked()); - } - - response.setStatus(code); - response.errorClose(); - - request.setAttribute(ErrorHandler.ERROR_CONTEXT, request.getErrorContext()); - request.setAttribute(ERROR_REQUEST_URI, request.getRequestURI()); - request.setAttribute(ERROR_SERVLET_NAME, request.getServletName()); - request.setAttribute(ERROR_STATUS_CODE, code); - request.setAttribute(ERROR_MESSAGE, message); - - _sendError = true; - if (_event != null) - { - Throwable cause = (Throwable)request.getAttribute(ERROR_EXCEPTION); - if (cause != null) - _event.addThrowable(cause); - } - } - } - - protected void completing() - { - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("completing {}", toStringLocked()); - - switch (_requestState) - { - case COMPLETED: - throw new IllegalStateException(getStatusStringLocked()); - default: - _requestState = RequestState.COMPLETING; - } - } - } - - protected void completed(Throwable failure) - { - final List aListeners; - final AsyncContextEvent event; - boolean handle = false; - - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("completed {}", toStringLocked()); - - if (_requestState != RequestState.COMPLETING) - throw new IllegalStateException(this.getStatusStringLocked()); - - if (_event == null) - { - _requestState = RequestState.COMPLETED; - aListeners = null; - event = null; - if (_state == State.WAITING) + if (failure == null && !noop) { - _state = State.WOKEN; - handle = true; - } - } - else - { - aListeners = _asyncListeners; - event = _event; - } - } + httpChannel._writeCallback = callback; + for (ByteBuffer b : content) + _contentBytesWritten += b.remaining(); - // release any aggregate buffer from a closing flush - _channel.getResponse().getHttpOutput().completed(failure); + httpChannel._lastWrite |= last; - if (event != null) - { - cancelTimeout(event); - if (aListeners != null) - { - runInContext(event, () -> - { - for (AsyncListener listener : aListeners) + stream = httpChannel._stream; + written = _contentBytesWritten; + + if (httpChannel._responseHeaders.commit()) + responseMetaData = lockedPrepareResponse(httpChannel, last); + + // If the content length were not compatible with what was written, then we need to abort. + long committedContentLength = httpChannel._committedContentLength; + if (committedContentLength >= 0) { - try - { - listener.onComplete(event); - } - catch (Throwable x) + String lengthError = (written > committedContentLength) ? "written %d > %d content-length" + : (last && written < committedContentLength) ? "written %d < %d content-length" : null; + if (lengthError != null) { + String message = lengthError.formatted(written, committedContentLength); if (LOG.isDebugEnabled()) - LOG.warn("{} while invoking onComplete listener {}", x.toString(), listener, x); - else - LOG.warn("{} while invoking onComplete listener {}", x.toString(), listener); + LOG.debug("fail {} {}", callback, message); + failure = new IOException(message); } } - }); - } - event.completed(); - - try (AutoLock l = lock()) - { - _requestState = RequestState.COMPLETED; - if (_state == State.WAITING) - { - _state = State.WOKEN; - handle = true; } } - } - if (handle) - _channel.handle(); - } - - protected void recycle() - { - cancelTimeout(); - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("recycle {}", toStringLocked()); - - switch (_state) + if (failure != null) { - case HANDLING: - throw new IllegalStateException(getStatusStringLocked()); - case UPGRADED: - return; - default: - break; + Throwable t = failure; + httpChannel._serializedInvoker.run(() -> callback.failed(t)); } - _asyncListeners = null; - _state = State.IDLE; - _requestState = RequestState.BLOCKING; - _outputState = OutputState.OPEN; - _initial = true; - _inputState = InputState.IDLE; - _asyncWritePossible = false; - _timeoutMs = DEFAULT_TIMEOUT; - _event = null; - } - } - - public void upgrade() - { - cancelTimeout(); - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("upgrade {}", toStringLocked()); - - switch (_state) + else if (noop) { - case IDLE: - break; - default: - throw new IllegalStateException(getStatusStringLocked()); + httpChannel._serializedInvoker.run(callback::succeeded); } - _asyncListeners = null; - _state = State.UPGRADED; - _requestState = RequestState.BLOCKING; - _initial = true; - _inputState = InputState.IDLE; - _asyncWritePossible = false; - _timeoutMs = DEFAULT_TIMEOUT; - _event = null; - } - } - - protected void scheduleDispatch() - { - _channel.execute(_channel); - } - - protected void cancelTimeout() - { - cancelTimeout(getAsyncContextEvent()); - } - - protected void cancelTimeout(AsyncContextEvent event) - { - if (event != null) - event.cancelTimeoutTask(); - } - - public boolean isIdle() - { - try (AutoLock l = lock()) - { - return _state == State.IDLE; - } - } - - public boolean isExpired() - { - try (AutoLock l = lock()) - { - // TODO review - return _requestState == RequestState.EXPIRE || _requestState == RequestState.EXPIRING; - } - } - - public boolean isInitial() - { - try (AutoLock l = lock()) - { - return _initial; - } - } - - public boolean isSuspended() - { - try (AutoLock l = lock()) - { - return _state == State.WAITING || _state == State.HANDLING && _requestState == RequestState.ASYNC; - } - } - - boolean isCompleted() - { - try (AutoLock l = lock()) - { - return _requestState == RequestState.COMPLETED; - } - } - - public boolean isAsyncStarted() - { - try (AutoLock l = lock()) - { - if (_state == State.HANDLING) - return _requestState != RequestState.BLOCKING; - return _requestState == RequestState.ASYNC || _requestState == RequestState.EXPIRING; - } - } - - public boolean isAsync() - { - try (AutoLock l = lock()) - { - return !_initial || _requestState != RequestState.BLOCKING; - } - } - - public Request getBaseRequest() - { - return _channel.getRequest(); - } - - public HttpChannel getHttpChannel() - { - return _channel; - } - - public ContextHandler getContextHandler() - { - return getContextHandler(getAsyncContextEvent()); - } - - ContextHandler getContextHandler(AsyncContextEvent event) - { - if (event != null) - { - Context context = ((Context)event.getServletContext()); - if (context != null) - return context.getContextHandler(); - } - return null; - } - - public ServletResponse getServletResponse() - { - return getServletResponse(getAsyncContextEvent()); - } - - public ServletResponse getServletResponse(AsyncContextEvent event) - { - if (event != null && event.getSuppliedResponse() != null) - return event.getSuppliedResponse(); - return _channel.getResponse(); - } - - void runInContext(AsyncContextEvent event, Runnable runnable) - { - ContextHandler contextHandler = getContextHandler(event); - if (contextHandler == null) - runnable.run(); - else - contextHandler.handle(_channel.getRequest(), runnable); - } - - public Object getAttribute(String name) - { - return _channel.getRequest().getAttribute(name); - } - - public void removeAttribute(String name) - { - _channel.getRequest().removeAttribute(name); - } - - public void setAttribute(String name, Object attribute) - { - _channel.getRequest().setAttribute(name, attribute); - } - - /** - * Called to signal that the channel is ready for a callback. - * - * @return true if woken - */ - public boolean onReadReady() - { - boolean woken = false; - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("onReadReady {}", toStringLocked()); - - switch (_inputState) + else { - case READY: - _inputState = InputState.READY; - break; - case IDLE: - case UNREADY: - _inputState = InputState.READY; - if (_state == State.WAITING) - { - woken = true; - _state = State.WOKEN; - } - break; - - default: - throw new IllegalStateException(toStringLocked()); - } - } - return woken; - } - - public boolean onReadEof() - { - boolean woken = false; - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("onReadEof {}", toStringLocked()); - - switch (_inputState) - { - case IDLE: - case READY: - case UNREADY: - _inputState = InputState.READY; - if (_state == State.WAITING) - { - woken = true; - _state = State.WOKEN; - } - break; - - default: - throw new IllegalStateException(toStringLocked()); - } - } - return woken; - } - - /** - * Called to indicate that some content was produced and is - * ready for consumption. - */ - public void onContentAdded() - { - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("onContentAdded {}", toStringLocked()); - - switch (_inputState) - { - case IDLE: - case UNREADY: - case READY: - _inputState = InputState.READY; - break; - - default: - throw new IllegalStateException(toStringLocked()); - } - } - } - - /** - * Called to indicate that the content is being consumed. - */ - public void onReadIdle() - { - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("onReadIdle {}", toStringLocked()); - - switch (_inputState) - { - case UNREADY: - case READY: - case IDLE: - _inputState = InputState.IDLE; - break; - - default: - throw new IllegalStateException(toStringLocked()); - } - } - } - - /** - * Called to indicate that no content is currently available, - * more content has been demanded and may be available, but - * that a handling thread may need to produce (fill/parse) it. - */ - public void onReadUnready() - { - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("onReadUnready {}", toStringLocked()); - - switch (_inputState) - { - case IDLE: - case UNREADY: - case READY: // READY->UNREADY is needed by AsyncServletIOTest.testStolenAsyncRead - _inputState = InputState.UNREADY; - break; - - default: - throw new IllegalStateException(toStringLocked()); - } - } - } - - public boolean isInputUnready() - { - try (AutoLock l = lock()) - { - return _inputState == InputState.UNREADY; - } - } - - public boolean onWritePossible() - { - boolean wake = false; - - try (AutoLock l = lock()) - { - if (LOG.isDebugEnabled()) - LOG.debug("onWritePossible {}", toStringLocked()); - - _asyncWritePossible = true; - if (_state == State.WAITING) - { - _state = State.WOKEN; - wake = true; + stream.send(_request._metaData, responseMetaData, last, this, content); } } - return wake; + @Override + public void succeeded() + { + // Called when an individual write succeeds. + Callback callback; + HttpChannelState httpChannel; + try (AutoLock ignored = _request._lock.lock()) + { + httpChannel = _request.lockedGetHttpChannel(); + callback = httpChannel._writeCallback; + httpChannel._writeCallback = null; + } + if (LOG.isDebugEnabled()) + LOG.debug("write succeeded {}", callback); + if (callback != null) + httpChannel._serializedInvoker.run(callback::succeeded); + } + + @Override + public void failed(Throwable x) + { + // Called when an individual write fails. + Callback callback; + HttpChannelState httpChannel; + try (AutoLock ignored = _request._lock.lock()) + { + httpChannel = _request.lockedGetHttpChannel(); + callback = httpChannel._writeCallback; + httpChannel._writeCallback = null; + } + if (LOG.isDebugEnabled()) + LOG.debug("write failed {}", callback, x); + if (callback != null) + httpChannel._serializedInvoker.run(() -> callback.failed(x)); + } + + @Override + public InvocationType getInvocationType() + { + return Invocable.getInvocationType(_request.getHttpChannel()._writeCallback); + } + + @Override + public boolean isCommitted() + { + return _request.getHttpChannel()._responseHeaders.isCommitted(); + } + + @Override + public boolean isCompletedSuccessfully() + { + try (AutoLock ignored = _request._lock.lock()) + { + if (_request._httpChannel == null) + return false; + return switch (_request._httpChannel._state) + { + case COMPLETED, PROCESSED_AND_COMPLETED -> _request._httpChannel._failure == null; + default -> false; + }; + } + } + + @Override + public void reset() + { + _request.getHttpChannel().resetResponse(); + } + + private MetaData.Response lockedPrepareResponse(HttpChannelState httpChannel, boolean last) + { + // Assume 200 unless told otherwise. + if (_status == 0) + _status = HttpStatus.OK_200; + + // Can we set the content length? + HttpFields.Mutable mutableHeaders = httpChannel._responseHeaders.getMutableHttpFields(); + httpChannel._committedContentLength = mutableHeaders.getLongField(HttpHeader.CONTENT_LENGTH); + if (last && httpChannel._committedContentLength < 0L) + { + httpChannel._committedContentLength = _contentBytesWritten; + if (httpChannel._committedContentLength == 0) + mutableHeaders.put(CONTENT_LENGTH_0); + else + mutableHeaders.putLongField(HttpHeader.CONTENT_LENGTH, httpChannel._committedContentLength); + } + + httpChannel._stream.prepareResponse(mutableHeaders); + + // Provide trailers if they exist + Supplier trailers = httpChannel._responseTrailers == null ? null : this::takeTrailers; + + return new MetaData.Response( + httpChannel.getConnectionMetaData().getHttpVersion(), + _status, + null, + httpChannel._responseHeaders, + httpChannel._committedContentLength, + trailers + ); + } + } + + private static class ChannelCallback implements Callback + { + private final ChannelRequest _request; + private Throwable _completedBy; + + private ChannelCallback(ChannelRequest request) + { + _request = request; + } + + @Override + public void succeeded() + { + // Called when the request/response cycle is completing successfully. + HttpStream stream; + boolean needLastWrite; + HttpChannelState httpChannelState; + Throwable failure = null; + MetaData.Response responseMetaData = null; + boolean complete; + try (AutoLock ignored = _request._lock.lock()) + { + complete = complete(); + httpChannelState = _request._httpChannel; + needLastWrite = !httpChannelState._lastWrite; + + // We are being tough on handler implementations and expect them + // to not have pending operations when calling succeeded or failed. + if (httpChannelState._onContentAvailable != null) + throw new IllegalStateException("demand pending"); + if (httpChannelState._writeCallback != null) + throw new IllegalStateException("write pending"); + if (httpChannelState._error != null) + throw new IllegalStateException("error " + httpChannelState._error, httpChannelState._error.getCause()); + + stream = httpChannelState._stream; + + if (httpChannelState._responseHeaders.commit()) + responseMetaData = _request._response.lockedPrepareResponse(httpChannelState, true); + + long written = _request._response._contentBytesWritten; + long committedContentLength = httpChannelState._committedContentLength; + + if (committedContentLength >= 0 && committedContentLength != written) + failure = httpChannelState._failure = new IOException("content-length %d != %d written".formatted(committedContentLength, written)); + + // is the request fully consumed? + Throwable unconsumed = stream.consumeAll(); + if (LOG.isDebugEnabled()) + LOG.debug("consumeAll: {} {} ", unconsumed == null, httpChannelState); + + if (unconsumed != null && httpChannelState.getConnectionMetaData().isPersistent()) + { + if (failure == null) + failure = httpChannelState._failure = unconsumed; + else if (!TypeUtil.isAssociated(failure, unconsumed)) + failure.addSuppressed(unconsumed); + } + } + + if (failure == null && needLastWrite) + stream.send(_request._metaData, responseMetaData, true, httpChannelState._handlerInvoker); + else if (complete) + httpChannelState._handlerInvoker.complete(stream, failure); + } + + @Override + public void failed(Throwable failure) + { + // Called when the request/response cycle is completing with a failure. + HttpStream stream; + boolean writeErrorResponse; + ChannelRequest request; + HttpChannelState httpChannelState; + boolean complete; + try (AutoLock ignored = _request._lock.lock()) + { + complete = complete(); + httpChannelState = _request._httpChannel; + httpChannelState._failure = failure; + + // Verify whether we can write an error response. + writeErrorResponse = !httpChannelState._stream.isCommitted(); + stream = httpChannelState._stream; + request = _request; + + // Consume any input. + Throwable unconsumed = stream.consumeAll(); + if (unconsumed != null && !TypeUtil.isAssociated(unconsumed, failure)) + failure.addSuppressed(unconsumed); + + if (writeErrorResponse) + { + // Cannot log or recycle just yet, since we need to generate the error response. + _request._response._status = HttpStatus.INTERNAL_SERVER_ERROR_500; + httpChannelState._responseHeaders.reset(); + } + } + + if (LOG.isDebugEnabled()) + LOG.debug("failed {}", httpChannelState, failure); + + if (writeErrorResponse) + { + ErrorResponse response = new ErrorResponse(request, stream); + Response.writeError(request, response, httpChannelState._handlerInvoker, failure); + } + else if (complete) + { + httpChannelState._handlerInvoker.complete(stream, failure); + } + } + + private boolean complete() + { + HttpChannelState httpChannelState = _request._httpChannel; + if (httpChannelState == null) + { + if (LOG.isDebugEnabled()) + LOG.warn("already recycled after completion {} by", _request, _completedBy); + throw new IllegalStateException("channel already completed"); + } + + return switch (httpChannelState._state) + { + case PROCESSING -> + { + if (LOG.isDebugEnabled()) + _completedBy = new Throwable(Thread.currentThread().getName()); + httpChannelState._state = State.COMPLETED; + yield false; + } + case PROCESSED -> + { + if (LOG.isDebugEnabled()) + _completedBy = new Throwable(Thread.currentThread().getName()); + httpChannelState._state = State.PROCESSED_AND_COMPLETED; + yield true; + } + case PROCESSED_AND_COMPLETED, COMPLETED -> + { + if (LOG.isDebugEnabled()) + LOG.warn("already completed {} by", _request, _completedBy); + throw new IllegalStateException("already completed"); + } + default -> throw new IllegalStateException("not processing"); + }; + } + + @Override + public InvocationType getInvocationType() + { + // TODO review this as it is probably not correct + return _request.getStream().getInvocationType(); + } + } + + private static class ErrorResponse extends Response.Wrapper + { + private final ChannelRequest _request; + private final ChannelResponse _response; + private final HttpStream _stream; + + public ErrorResponse(ChannelRequest request, HttpStream stream) + { + super(request, request._response); + _request = request; + _response = request._response; + _stream = stream; + } + + @Override + public void write(boolean last, Callback callback, ByteBuffer... content) + { + MetaData.Response responseMetaData = null; + try (AutoLock ignored = _request._lock.lock()) + { + HttpChannelState httpChannel = _request.lockedGetHttpChannel(); + + httpChannel._writeCallback = callback; + for (ByteBuffer b : content) + _response._contentBytesWritten += b.remaining(); + + httpChannel._lastWrite |= last; + + if (httpChannel._responseHeaders.commit()) + responseMetaData = _request._response.lockedPrepareResponse(httpChannel, last); + } + _stream.send(_request._metaData, responseMetaData, last, callback, content); + } } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java index aaf11cf501c..41042d5d2ba 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java @@ -11,24 +11,38 @@ // ======================================================================== // -package org.eclipse.jetty.server; +package org.eclipse.jetty.server.internal; import java.io.IOException; +import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.WritePendingException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.LongAdder; import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.ComplianceViolation; +import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpParser; +import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; -import org.eclipse.jetty.http.PreEncodedHttpField; +import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Connection; @@ -37,9 +51,22 @@ import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.RetainableByteBuffer; import org.eclipse.jetty.io.RetainableByteBufferPool; import org.eclipse.jetty.io.WriteFlusher; +import org.eclipse.jetty.io.ssl.SslConnection; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.ConnectionMetaData; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Content; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpStream; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.HostPort; import org.eclipse.jetty.util.IteratingCallback; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.thread.Invocable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,26 +75,36 @@ import static org.eclipse.jetty.http.HttpStatus.INTERNAL_SERVER_ERROR_500; /** *

    A {@link Connection} that handles the HTTP protocol.

    */ -public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport, WriteFlusher.Listener, Connection.UpgradeFrom, Connection.UpgradeTo +public class HttpConnection extends AbstractConnection implements Runnable, WriteFlusher.Listener, Connection.UpgradeFrom, Connection.UpgradeTo, ConnectionMetaData { private static final Logger LOG = LoggerFactory.getLogger(HttpConnection.class); - public static final HttpField CONNECTION_CLOSE = new PreEncodedHttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); private static final ThreadLocal __currentConnection = new ThreadLocal<>(); + private static final AtomicLong __connectionIdGenerator = new AtomicLong(); - private final HttpConfiguration _config; + private final AtomicLong _streamIdGenerator = new AtomicLong(); + private final long _id; + private final HttpConfiguration _configuration; private final Connector _connector; + private final HttpChannel _httpChannel; + private final RequestHandler _requestHandler; + private final HttpParser _parser; + private final HttpGenerator _generator; private final ByteBufferPool _bufferPool; private final RetainableByteBufferPool _retainableByteBufferPool; - private final HttpInput _input; - private final HttpGenerator _generator; - private final HttpChannelOverHttp _channel; - private final HttpParser _parser; - private volatile RetainableByteBuffer _retainableByteBuffer; - private final AsyncReadCallback _asyncReadCallback = new AsyncReadCallback(); + private final AtomicReference _stream = new AtomicReference<>(); + private final Lazy _attributes = new Lazy(); + private final DemandContentCallback _demandContentCallback = new DemandContentCallback(); private final SendCallback _sendCallback = new SendCallback(); private final boolean _recordHttpComplianceViolations; private final LongAdder bytesIn = new LongAdder(); private final LongAdder bytesOut = new LongAdder(); + private final AtomicBoolean _handling = new AtomicBoolean(false); + private final HttpFields.Mutable _headerBuilder = HttpFields.build(); + private volatile RetainableByteBuffer _retainableByteBuffer; + private HttpFields.Mutable _trailers; + private Runnable _onRequest; + private long _requests; + // TODO why is this not on HttpConfiguration? private boolean _useInputDirectByteBuffers; private boolean _useOutputDirectByteBuffers; @@ -91,25 +128,27 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http return last; } - public HttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint, boolean recordComplianceViolations) + public HttpConnection(HttpConfiguration configuration, Connector connector, EndPoint endPoint, boolean recordComplianceViolations) { super(endPoint, connector.getExecutor()); - _config = config; + _id = __connectionIdGenerator.getAndIncrement(); + _configuration = configuration; _connector = connector; _bufferPool = _connector.getByteBufferPool(); _retainableByteBufferPool = RetainableByteBufferPool.findOrAdapt(connector, _bufferPool); _generator = newHttpGenerator(); - _channel = newHttpChannel(); - _input = _channel.getRequest().getHttpInput(); - _parser = newHttpParser(config.getHttpCompliance()); + _httpChannel = newHttpChannel(connector.getServer(), configuration); + _requestHandler = newRequestHandler(); + _parser = newHttpParser(configuration.getHttpCompliance()); _recordHttpComplianceViolations = recordComplianceViolations; if (LOG.isDebugEnabled()) LOG.debug("New HTTP Connection {}", this); } - public HttpConfiguration getHttpConfiguration() + @Override + public InvocationType getInvocationType() { - return _config; + return getServer().getInvocationType(); } public boolean isRecordHttpComplianceViolations() @@ -119,25 +158,30 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http protected HttpGenerator newHttpGenerator() { - return new HttpGenerator(_config.getSendServerVersion(), _config.getSendXPoweredBy()); - } - - protected HttpChannelOverHttp newHttpChannel() - { - return new HttpChannelOverHttp(this, _connector, _config, getEndPoint(), this); + return new HttpGenerator(_configuration.getSendServerVersion(), _configuration.getSendXPoweredBy()); } protected HttpParser newHttpParser(HttpCompliance compliance) { - HttpParser parser = new HttpParser(newRequestHandler(), getHttpConfiguration().getRequestHeaderSize(), compliance); + HttpParser parser = new HttpParser(_requestHandler, getHttpConfiguration().getRequestHeaderSize(), compliance); parser.setHeaderCacheSize(getHttpConfiguration().getHeaderCacheSize()); parser.setHeaderCacheCaseSensitive(getHttpConfiguration().isHeaderCacheCaseSensitive()); return parser; } - protected HttpParser.RequestHandler newRequestHandler() + protected HttpChannel newHttpChannel(Server server, HttpConfiguration configuration) { - return _channel; + return new HttpChannelState(this); + } + + protected HttpStreamOverHTTP1 newHttpStream(String method, String uri, HttpVersion version) + { + return new HttpStreamOverHTTP1(method, uri, version); + } + + protected RequestHandler newRequestHandler() + { + return new RequestHandler(); } public Server getServer() @@ -152,7 +196,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http public HttpChannel getHttpChannel() { - return _channel; + return _httpChannel; } public HttpParser getParser() @@ -165,16 +209,117 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http return _generator; } + @Override + public String getId() + { + return "%s@%x#%d".formatted(getEndPoint().getRemoteSocketAddress(), hashCode(), _id); + } + + @Override + public HttpConfiguration getHttpConfiguration() + { + return _configuration; + } + + @Override + public HttpVersion getHttpVersion() + { + HttpStreamOverHTTP1 stream = _stream.get(); + return (stream != null) ? stream._version : HttpVersion.HTTP_1_1; + } + + @Override + public String getProtocol() + { + return getHttpVersion().asString(); + } + + @Override + public Connection getConnection() + { + return this; + } + + @Override + public boolean isPersistent() + { + return _generator.isPersistent(getHttpVersion()); + } + + @Override + public boolean isSecure() + { + return getEndPoint() instanceof SslConnection.DecryptedEndPoint; + } + + @Override + public SocketAddress getRemoteSocketAddress() + { + return getEndPoint().getRemoteSocketAddress(); + } + + @Override + public SocketAddress getLocalSocketAddress() + { + HttpConfiguration config = getHttpConfiguration(); + if (config != null) + { + SocketAddress override = config.getLocalAddress(); + if (override != null) + return override; + } + return getEndPoint().getLocalSocketAddress(); + } + + @Override + public HostPort getServerAuthority() + { + HostPort authority = ConnectionMetaData.getServerAuthority(getHttpConfiguration(), this); + if (authority == null) + authority = new HostPort(getLocalSocketAddress().toString(), -1); + return authority; + } + + @Override + public Object removeAttribute(String name) + { + return _attributes.removeAttribute(name); + } + + @Override + public Object setAttribute(String name, Object attribute) + { + return _attributes.setAttribute(name, attribute); + } + + @Override + public Object getAttribute(String name) + { + return _attributes.getAttribute(name); + } + + @Override + public Set getAttributeNameSet() + { + return _attributes.getAttributeNameSet(); + } + + @Override + public void clearAttributes() + { + _attributes.clearAttributes(); + } + @Override public long getMessagesIn() { - return getHttpChannel().getRequests(); + return _requests; } @Override public long getMessagesOut() { - return getHttpChannel().getRequests(); + return _requests; // TODO not strictly correct } public boolean isUseInputDirectByteBuffers() @@ -184,6 +329,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers) { + // TODO why is this not on HttpConfiguration? _useInputDirectByteBuffers = useInputDirectByteBuffers; } @@ -194,6 +340,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers) { + // TODO why is this not on HttpConfiguration? _useOutputDirectByteBuffers = useOutputDirectByteBuffers; } @@ -220,9 +367,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http @Override public void onFlushed(long bytes) throws IOException { - // Unfortunately cannot distinguish between header and content - // bytes, and for content bytes whether they are chunked or not. - _channel.getResponse().getHttpOutput().onFlushed(bytes); + // TODO is this callback still needed? Couldn't we wrap send callback instead? + // Either way, the dat rate calculations from HttpOutput.onFlushed should be moved to Channel. } void releaseRequestBuffer() @@ -254,13 +400,16 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http public void onFillable() { if (LOG.isDebugEnabled()) - LOG.debug("{} onFillable enter {} {}", this, _channel.getState(), _retainableByteBuffer); + LOG.debug(">>onFillable enter {} {} {}", this, _httpChannel, _retainableByteBuffer); HttpConnection last = setCurrentConnection(this); try { while (getEndPoint().isOpen()) { + if (LOG.isDebugEnabled()) + LOG.debug("onFillable fill and parse {} {} {}", this, _httpChannel, _retainableByteBuffer); + // Fill the request buffer (if needed). int filled = fillRequestBuffer(); if (filled < 0 && getEndPoint().isOutputShutdown()) @@ -276,26 +425,52 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http if (getEndPoint().getConnection() != this) break; - // Handle channel event + // Handle channel event. This will only be true when the headers of a request have been received. if (handle) { - boolean suspended = !_channel.handle(); + Request request = _httpChannel.getRequest(); + if (LOG.isDebugEnabled()) + LOG.debug("HANDLE {} {}", request, this); - // We should break iteration if we have suspended or upgraded the connection. - if (suspended || getEndPoint().getConnection() != this) + // handle the request by running the task obtained from onRequest + _handling.set(true); + Runnable onRequest = _onRequest; + _onRequest = null; + onRequest.run(); + + // If the _handling boolean has already been CaS'd to false, then stream is completed and we are no longer + // handling, so the caller can continue to fill and parse more connections. If it is still true, then some + // thread is still handling the request and they will need to organize more filling and parsing once complete. + if (_handling.compareAndSet(true, false)) + { + if (LOG.isDebugEnabled()) + LOG.debug("request !complete {} {}", request, this); break; + } + + // If the request is complete, but has been upgraded, then break + if (getEndPoint().getConnection() != this) + { + if (LOG.isDebugEnabled()) + LOG.debug("upgraded {} -> {}", this, getEndPoint().getConnection()); + break; + } + } + else if (filled < 0) + { + getEndPoint().shutdownOutput(); + break; + } + else if (_requestHandler._failure != null) + { + // There was an error, don't fill more. + break; } else if (filled == 0) { fillInterested(); break; } - else if (filled < 0) - { - if (_channel.getState().isIdle()) - getEndPoint().shutdownOutput(); - break; - } } } catch (Throwable x) @@ -303,7 +478,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http try { if (LOG.isDebugEnabled()) - LOG.debug("{} caught exception {}", this, _channel.getState(), x); + LOG.debug("caught exception {} {}", this, _httpChannel, x); if (_retainableByteBuffer != null) { _retainableByteBuffer.clear(); @@ -319,7 +494,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http { setCurrentConnection(last); if (LOG.isDebugEnabled()) - LOG.debug("{} onFillable exit {} {}", this, _channel.getState(), _retainableByteBuffer); + LOG.debug("<= _config.getResponseHeaderSize()) + if (_header.capacity() >= _configuration.getResponseHeaderSize()) throw new BadMessageException(INTERNAL_SERVER_ERROR_500, "Response header too large"); releaseHeader(); - _header = _bufferPool.acquire(_config.getResponseHeaderSize(), useDirectByteBuffers); + _header = _bufferPool.acquire(_configuration.getResponseHeaderSize(), useDirectByteBuffers); continue; } case NEED_CHUNK: @@ -779,7 +823,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http case NEED_CHUNK_TRAILER: { releaseChunk(); - _chunk = _bufferPool.acquire(_config.getResponseHeaderSize(), useDirectByteBuffers); + _chunk = _bufferPool.acquire(_configuration.getResponseHeaderSize(), useDirectByteBuffers); continue; } case FLUSH: @@ -893,7 +937,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http @Override protected void onCompleteSuccess() { - boolean upgrading = _channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE) != null; + // TODO is this too late to get the request? And is that the right attribute and the right thing to do? + boolean upgrading = _httpChannel.getRequest() != null && _httpChannel.getRequest().getAttribute(ConnectionMetaData.UPGRADE_CONNECTION_ATTRIBUTE) != null; release().succeeded(); // If successfully upgraded it is responsibility of the next protocol to close the connection. if (_shutdownOut && !upgrading) @@ -914,4 +959,695 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http return String.format("%s[i=%s,cb=%s]", super.toString(), _info, _callback); } } + + protected class RequestHandler implements HttpParser.RequestHandler, ComplianceViolation.Listener + { + private Throwable _failure; + + protected RequestHandler() + { + } + + @Override + public void startRequest(String method, String uri, HttpVersion version) + { + HttpStreamOverHTTP1 stream = newHttpStream(method, uri, version); + if (!_stream.compareAndSet(null, stream)) + throw new IllegalStateException("Stream pending"); + _headerBuilder.clear(); + _httpChannel.setHttpStream(stream); + } + + @Override + public void parsedHeader(HttpField field) + { + _stream.get().parsedHeader(field); + } + + @Override + public boolean headerComplete() + { + _onRequest = _stream.get().headerComplete(); + return true; + } + + @Override + public boolean content(ByteBuffer buffer) + { + HttpStreamOverHTTP1 stream = _stream.get(); + if (stream == null || stream._content != null || _retainableByteBuffer == null) + throw new IllegalStateException(); + + _retainableByteBuffer.retain(); + + if (LOG.isDebugEnabled()) + LOG.debug("content {}/{} for {}", BufferUtil.toDetailString(buffer), _retainableByteBuffer, HttpConnection.this); + + stream._content = new Content.Abstract(false, false) + { + final RetainableByteBuffer _retainable = _retainableByteBuffer; + + @Override + public void release() + { + _retainable.release(); + if (LOG.isDebugEnabled()) + LOG.debug("release {}/{} for {}", BufferUtil.toDetailString(buffer), _retainable, HttpConnection.this); + } + + @Override + public ByteBuffer getByteBuffer() + { + return buffer; + } + }; + return true; + } + + @Override + public boolean contentComplete() + { + // Do nothing at this point. + // Wait for messageComplete so any trailers can be sent as special content + return false; + } + + @Override + public boolean messageComplete() + { + HttpStreamOverHTTP1 stream = _stream.get(); + stream._content = Content.last(stream._content); + if (_trailers != null && (stream._content == null || stream._content == Content.EOF)) + stream._content = new Content.Trailers(_trailers.asImmutable()); + else + stream._content = Content.last(stream._content); + return false; + } + + @Override + public void parsedTrailer(HttpField field) + { + if (_trailers == null) + _trailers = HttpFields.build(); + _trailers.add(field); + } + + @Override + public void badMessage(BadMessageException failure) + { + if (LOG.isDebugEnabled()) + LOG.debug("badMessage {} {}", HttpConnection.this, failure); + + _failure = failure; + _generator.setPersistent(false); + + HttpStreamOverHTTP1 stream = _stream.get(); + if (stream == null) + { + stream = newHttpStream("GET", "/badMessage", HttpVersion.HTTP_1_0); + _stream.set(stream); + _httpChannel.setHttpStream(stream); + } + + if (_httpChannel.getRequest() == null) + { + HttpURI uri = stream._uri; + if (uri.hasViolations()) + uri = HttpURI.from("/badURI"); + _httpChannel.onRequest(new MetaData.Request(stream._method, uri, stream._version, HttpFields.EMPTY)); + } + + Runnable todo = _httpChannel.onFailure(failure); + if (todo != null) + getServer().getThreadPool().execute(todo); + } + + @Override + public void earlyEOF() + { + if (LOG.isDebugEnabled()) + LOG.debug("early EOF {}", HttpConnection.this); + _generator.setPersistent(false); + HttpStreamOverHTTP1 stream = _stream.get(); + if (stream != null) + { + BadMessageException bad = new BadMessageException("Early EOF"); + + if (stream._content instanceof Error error) + error.getCause().addSuppressed(bad); + else + { + if (stream._content != null) + stream._content.release(); + stream._content = new Content.Error(bad); + } + + Runnable todo = _httpChannel.onFailure(bad); + if (todo != null) + getServer().getThreadPool().execute(todo); + } + } + + @Override + public void onComplianceViolation(ComplianceViolation.Mode mode, ComplianceViolation violation, String details) + { + //TODO configure this somewhere else + //TODO what about cookie compliance + //TODO what about http2 & 3 + //TODO test this in core + if (isRecordHttpComplianceViolations()) + { + HttpStreamOverHTTP1 stream = _stream.get(); + if (stream != null) + { + if (stream._complianceViolations == null) + { + stream._complianceViolations = new ArrayList<>(); + } + String record = String.format("%s (see %s) in mode %s for %s in %s", + violation.getDescription(), violation.getURL(), mode, details, HttpConnection.this); + stream._complianceViolations.add(record); + if (LOG.isDebugEnabled()) + LOG.debug(record); + } + } + } + } + + private static final HttpField PREAMBLE_UPGRADE_H2C = new HttpField(HttpHeader.UPGRADE, "h2c"); + + protected class HttpStreamOverHTTP1 implements HttpStream + { + private final long _nanoTimestamp = System.nanoTime(); + private final long _id; + private final String _method; + private final HttpURI.Mutable _uri; + private final HttpVersion _version; + private long _contentLength = -1; + private HostPortHttpField _hostField; + private MetaData.Request _request; + private HttpField _upgrade = null; + private Connection _upgradeConnection; + + Content _content; + private boolean _connectionClose = false; + private boolean _connectionKeepAlive = false; + private boolean _connectionUpgrade = false; + private boolean _unknownExpectation = false; + private boolean _expect100Continue = false; + private boolean _expect102Processing = false; + private List _complianceViolations; + + protected HttpStreamOverHTTP1(String method, String uri, HttpVersion version) + { + _id = _streamIdGenerator.getAndIncrement(); + _method = method; + _uri = uri == null ? null : HttpURI.build(method, uri); + _version = version; + + if (_uri != null && _uri.getPath() == null && _uri.getScheme() != null && _uri.hasAuthority()) + _uri.path("/"); + } + + public void parsedHeader(HttpField field) + { + HttpHeader header = field.getHeader(); + String value = field.getValue(); + if (header != null) + { + switch (header) + { + case CONNECTION: + _connectionClose |= field.contains(HttpHeaderValue.CLOSE.asString()); + if (HttpVersion.HTTP_1_0.equals(_version)) + _connectionKeepAlive |= field.contains(HttpHeader.KEEP_ALIVE.asString()); + _connectionUpgrade |= field.contains(HttpHeaderValue.UPGRADE.asString()); + break; + + case HOST: + if (value == null) + value = ""; + if (field instanceof HostPortHttpField) + _hostField = (HostPortHttpField)field; + else + field = _hostField = new HostPortHttpField(value); + break; + + case EXPECT: + { + if (!HttpHeaderValue.parseCsvIndex(value, t -> + { + switch (t) + { + case CONTINUE: + _expect100Continue = true; + return true; + case PROCESSING: + _expect102Processing = true; + return true; + default: + return false; + } + }, s -> false)) + { + _unknownExpectation = true; + _expect100Continue = false; + _expect102Processing = false; + } + break; + } + + case UPGRADE: + _upgrade = field; + break; + + case CONTENT_LENGTH: + _contentLength = field.getLongValue(); + break; + + default: + break; + } + } + _headerBuilder.add(field); + } + + public Runnable headerComplete() + { + UriCompliance compliance; + if (_uri.hasViolations()) + { + compliance = _configuration.getUriCompliance(); + String badMessage = UriCompliance.checkUriCompliance(compliance, _uri); + if (badMessage != null) + throw new BadMessageException(badMessage); + } + + // Check host field matches the authority in the any absolute URI or is not blank + if (_hostField != null) + { + if (_uri.isAbsolute()) + { + if (!_hostField.getValue().equals(_uri.getAuthority())) + throw new BadMessageException("Authority!=Host "); + } + else + { + if (StringUtil.isBlank(_hostField.getHostPort().getHost())) + throw new BadMessageException("Blank Host"); + } + } + + // Set the scheme in the URI + if (!_uri.isAbsolute()) + _uri.scheme(getEndPoint() instanceof SslConnection.DecryptedEndPoint ? HttpScheme.HTTPS : HttpScheme.HTTP); + + // Set the authority (if not already set) in the URI + if (!HttpMethod.CONNECT.is(_method) && _uri.getAuthority() == null) + { + HostPort hostPort = _hostField == null ? getServerAuthority() : _hostField.getHostPort(); + int port = hostPort.getPort(); + if (port == HttpScheme.getDefaultPort(_uri.getScheme())) + port = -1; + _uri.authority(hostPort.getHost(), port); + } + + _request = new MetaData.Request(_method, _uri.asImmutable(), _version, _headerBuilder, _contentLength); + + Runnable handle = _httpChannel.onRequest(_request); + ++_requests; + + if (_complianceViolations != null && !_complianceViolations.isEmpty()) + { + _httpChannel.getRequest().setAttribute(HttpCompliance.VIOLATIONS_ATTR, _complianceViolations); + _complianceViolations = null; + } + + boolean persistent; + + switch (_request.getHttpVersion()) + { + case HTTP_0_9: + { + persistent = false; + break; + } + case HTTP_1_0: + { + persistent = getHttpConfiguration().isPersistentConnectionsEnabled() && + _connectionKeepAlive && + !_connectionClose || + HttpMethod.CONNECT.is(_method); + + // Since persistent status is now exposed in the application API, we need to be more definitive earlier + // if we are persistent or not. + _generator.setPersistent(persistent); + if (!persistent) + _connectionKeepAlive = false; + + break; + } + + case HTTP_1_1: + { + if (_unknownExpectation) + { + _requestHandler.badMessage(new BadMessageException(HttpStatus.EXPECTATION_FAILED_417)); + return null; // TODO Is this enough ??? + } + + persistent = getHttpConfiguration().isPersistentConnectionsEnabled() && + !_connectionClose || + HttpMethod.CONNECT.is(_method); + + // Since persistent status is now exposed in the application API, we need to be more definitive earlier + // if we are persistent or not. + _generator.setPersistent(persistent); + + if (_upgrade != null && HttpConnection.this.upgrade(_stream.get())) + return null; // TODO do we need to return a runnable to complete the upgrade ??? + + break; + } + + case HTTP_2: + { + // Allow direct "upgrade" to HTTP_2_0 only if the connector supports h2c. + _upgrade = PREAMBLE_UPGRADE_H2C; + + if (HttpMethod.PRI.is(_method) && + "*".equals(_uri.getPath()) && + _headerBuilder.size() == 0 && + HttpConnection.this.upgrade(_stream.get())) + return null; // TODO do we need to return a runnable to complete the upgrade ??? + + // TODO is this sufficient? + _parser.close(); + throw new BadMessageException(HttpStatus.UPGRADE_REQUIRED_426, "Upgrade Required"); + } + + default: + { + throw new IllegalStateException("unsupported version " + _version); + } + } + + if (!persistent) + _generator.setPersistent(false); + + return handle; + } + + @Override + public String getId() + { + return "%s#%d".formatted(_version, _id); + } + + @Override + public long getNanoTimeStamp() + { + return _nanoTimestamp; + } + + @Override + public Content readContent() + { + if (_content == null) + { + if (_parser.isTerminated()) + _content = Content.EOF; + else + parseAndFillForContent(); + } + + Content content = _content; + _content = Content.next(content); + if (content != null && _expect100Continue && content.hasRemaining()) + _expect100Continue = false; + + return content; + } + + @Override + public void demandContent() + { + if (_content != null) + { + Runnable onContentAvailable = _httpChannel.onContentAvailable(); + if (onContentAvailable != null) + onContentAvailable.run(); + return; + } + parseAndFillForContent(); + if (_content != null) + { + Runnable onContentAvailable = _httpChannel.onContentAvailable(); + if (onContentAvailable != null) + onContentAvailable.run(); + return; + } + + if (_expect100Continue) + { + _expect100Continue = false; + send(_request, HttpGenerator.CONTINUE_100_INFO, false, Callback.NOOP); + } + + tryFillInterested(_demandContentCallback); + } + + @Override + public void prepareResponse(HttpFields.Mutable headers) + { + if (_connectionKeepAlive && _version == HttpVersion.HTTP_1_0 && !headers.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())) + headers.add(HttpFields.CONNECTION_KEEPALIVE); + } + + @Override + public void send(MetaData.Request request, MetaData.Response response, boolean last, Callback callback, ByteBuffer... content) + { + if (response == null) + { + if (!last && BufferUtil.isEmpty(content)) + { + callback.succeeded(); + return; + } + } + else if (_generator.isCommitted()) + { + callback.failed(new IllegalStateException("Committed")); + } + else if (response.getStatus() == 102 && !_expect102Processing) + { + // silently discard + callback.succeeded(); + } + else if (response.getStatus() != 100 && _expect100Continue) + { + // If we are still expecting a 100 continues when we commit then we can't be persistent + _generator.setPersistent(false); + } + + // TODO support gather write + if (content.length > 1) + throw new UnsupportedOperationException("Gather write!"); + if (_sendCallback.reset(_request, response, content.length == 0 ? null : content[0], last, callback)) + _sendCallback.iterate(); + } + + @Override + public boolean isPushSupported() + { + return false; + } + + @Override + public void push(MetaData.Request request) + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isCommitted() + { + return _stream.get() != this || _generator.isCommitted(); + } + + @Override + public boolean isComplete() + { + return _stream.get() != this; + } + + @Override + public void setUpgradeConnection(Connection connection) + { + _upgradeConnection = connection; + if (_httpChannel.getRequest() != null) + _httpChannel.getRequest().setAttribute(ConnectionMetaData.UPGRADE_CONNECTION_ATTRIBUTE, connection); + } + + @Override + public Connection upgrade() + { + if (LOG.isDebugEnabled()) + LOG.debug("upgrade {} {}", this, _upgrade); + + // If Upgrade attribute already set then we don't need to do anything here. + if (_upgradeConnection != null) + return _upgradeConnection; + + // If no upgrade headers there is nothing to do. + if (!_connectionUpgrade && (_upgrade == null)) + return null; + + @SuppressWarnings("ReferenceEquality") + boolean isUpgradedH2C = (_upgrade == PREAMBLE_UPGRADE_H2C); + if (!isUpgradedH2C && !_connectionUpgrade) + throw new BadMessageException(HttpStatus.BAD_REQUEST_400); + + // Find the upgrade factory + ConnectionFactory.Upgrading factory = getConnector().getConnectionFactories().stream() + .filter(f -> f instanceof ConnectionFactory.Upgrading) + .map(ConnectionFactory.Upgrading.class::cast) + .filter(f -> f.getProtocols().contains(_upgrade.getValue())) + .findAny() + .orElse(null); + + if (factory == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("No factory for {} in {}", _upgrade, getConnector()); + return null; + } + + // Create new connection + HttpFields.Mutable response101 = HttpFields.build(); + Connection upgradeConnection = factory.upgradeConnection(getConnector(), getEndPoint(), _request, response101); + if (upgradeConnection == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("Upgrade ignored for {} by {}", _upgrade, factory); + return null; + } + + // Send 101 if needed + if (!isUpgradedH2C) + send(_request, new MetaData.Response(HttpVersion.HTTP_1_1, HttpStatus.SWITCHING_PROTOCOLS_101, response101, 0), false, Callback.NOOP); + + if (LOG.isDebugEnabled()) + LOG.debug("Upgrade from {} to {}", getEndPoint().getConnection(), upgradeConnection); + //getHttpTransport().onCompleted(); // TODO: succeed callback instead? + return upgradeConnection; + } + + @Override + public void succeeded() + { + HttpStreamOverHTTP1 stream = _stream.getAndSet(null); + if (stream == null) + return; + + if (LOG.isDebugEnabled()) + LOG.debug("succeeded {}", HttpConnection.this); + // If we are fill interested, then a read is pending and we must abort + if (isFillInterested()) + { + LOG.warn("Read pending {} {}", this, getEndPoint()); + failed(new IOException("Pending read in onCompleted")); + return; + } + + _httpChannel.recycle(); + + if (HttpConnection.this.upgrade(stream)) + return; + + // Finish consuming the request + // If we are still expecting + if (_expect100Continue) + { + // close to seek EOF + _parser.close(); + } + + // Reset the channel, parsers and generator + if (!_parser.isClosed()) + { + if (_generator.isPersistent()) + _parser.reset(); + else + _parser.close(); + } + + _generator.reset(); + + // Can the onFillable thread continue processing + if (_handling.compareAndSet(true, false)) + return; + + // we need to organized further processing + if (LOG.isDebugEnabled()) + LOG.debug("non-current completion {}", this); + + // TODO what about upgrade???? + + // If we are looking for the next request + if (_parser.isStart()) + { + // if the buffer is empty + if (isRequestBufferEmpty()) + { + // look for more data + fillInterested(); + } + // else if we are still running + else if (getConnector().isRunning()) + { + // Dispatched to handle a pipelined request + try + { + getExecutor().execute(HttpConnection.this); + } + catch (RejectedExecutionException e) + { + if (getConnector().isRunning()) + LOG.warn("Failed dispatch of {}", this, e); + else + LOG.trace("IGNORED", e); + getEndPoint().close(); + } + } + else + { + getEndPoint().close(); + } + } + // else the parser must be closed, so seek the EOF if we are still open + else if (getEndPoint().isOpen()) + fillInterested(); + } + + @Override + public void failed(Throwable x) + { + HttpStreamOverHTTP1 stream = _stream.getAndSet(null); + if (stream == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("ignored", x); + return; + } + + getEndPoint().close(); + } + + @Override + public InvocationType getInvocationType() + { + return HttpStream.super.getInvocationType(); + } + } } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/MultiPartParser.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/MultiPartParser.java index e08f0d1422d..d8c7f2291ac 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/MultiPartParser.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/MultiPartParser.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.server; +package org.eclipse.jetty.server.internal; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -20,6 +20,7 @@ import java.util.EnumSet; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpParser.RequestHandler; import org.eclipse.jetty.http.HttpTokens; +import org.eclipse.jetty.server.Content; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.SearchPattern; import org.eclipse.jetty.util.Utf8StringBuilder; @@ -31,6 +32,8 @@ import org.slf4j.LoggerFactory; * * @see https://tools.ietf.org/html/rfc2046#section-5.1 * @see https://tools.ietf.org/html/rfc2045 + * + * TODO convert to use a {@link Content.Reader} and to be async */ public class MultiPartParser { diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/ResponseHttpFields.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/ResponseHttpFields.java new file mode 100644 index 00000000000..74f95201e5f --- /dev/null +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/ResponseHttpFields.java @@ -0,0 +1,336 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server.internal; + +import java.util.EnumSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; + +// TODO: review whether it needs to override these many methods, as it may be enough to override iterator(). +public class ResponseHttpFields implements HttpFields.Mutable +{ + private final Mutable _fields = HttpFields.build(); + private final AtomicBoolean _committed = new AtomicBoolean(); + + public HttpFields.Mutable getMutableHttpFields() + { + return _fields; + } + + public boolean commit() + { + return _committed.compareAndSet(false, true); + } + + public boolean isCommitted() + { + return _committed.get(); + } + + public void reset() + { + _committed.set(false); + _fields.clear(); + } + + @Override + public HttpField getField(int index) + { + return _fields.getField(index); + } + + @Override + public int size() + { + return _fields.size(); + } + + @Override + public Stream stream() + { + return _fields.stream(); + } + + @Override + public HttpFields takeAsImmutable() + { + if (_committed.get()) + return this; + return _fields.asImmutable(); + } + + @Override + public Mutable add(String name, String value) + { + return _committed.get() ? this : _fields.add(name, value); + } + + @Override + public Mutable add(HttpHeader header, HttpHeaderValue value) + { + return _fields.add(header, value); + } + + @Override + public Mutable add(HttpHeader header, String value) + { + return _committed.get() ? this : _fields.add(header, value); + } + + @Override + public Mutable add(HttpField field) + { + return _committed.get() ? this : _fields.add(field); + } + + @Override + public Mutable add(HttpFields fields) + { + return _committed.get() ? this : _fields.add(fields); + } + + @Override + public Mutable addCSV(HttpHeader header, String... values) + { + return _committed.get() ? this : _fields.addCSV(header, values); + } + + @Override + public Mutable addCSV(String name, String... values) + { + return _committed.get() ? this : _fields.addCSV(name, values); + } + + @Override + public Mutable addDateField(String name, long date) + { + return _committed.get() ? this : _fields.addDateField(name, date); + } + + @Override + public HttpFields asImmutable() + { + return _committed.get() ? this : _fields.asImmutable(); + } + + @Override + public Mutable clear() + { + return _committed.get() ? this : _fields.clear(); + } + + @Override + public void ensureField(HttpField field) + { + if (!_committed.get()) + _fields.ensureField(field); + } + + @Override + public Iterator iterator() + { + Iterator i = _fields.iterator(); + return new Iterator<>() + { + @Override + public boolean hasNext() + { + return i.hasNext(); + } + + @Override + public HttpField next() + { + return i.next(); + } + + @Override + public void remove() + { + if (_committed.get()) + throw new UnsupportedOperationException("Read Only"); + i.remove(); + } + }; + } + + @Override + public ListIterator listIterator() + { + ListIterator i = _fields.listIterator(); + return new ListIterator<>() + { + @Override + public boolean hasNext() + { + return i.hasNext(); + } + + @Override + public HttpField next() + { + return i.next(); + } + + @Override + public boolean hasPrevious() + { + return i.hasPrevious(); + } + + @Override + public HttpField previous() + { + return i.previous(); + } + + @Override + public int nextIndex() + { + return i.nextIndex(); + } + + @Override + public int previousIndex() + { + return i.previousIndex(); + } + + @Override + public void remove() + { + if (_committed.get()) + throw new UnsupportedOperationException("Read Only"); + i.remove(); + } + + @Override + public void set(HttpField httpField) + { + if (_committed.get()) + throw new UnsupportedOperationException("Read Only"); + i.set(httpField); + } + + @Override + public void add(HttpField httpField) + { + if (_committed.get()) + throw new UnsupportedOperationException("Read Only"); + i.add(httpField); + } + }; + } + + @Override + public Mutable put(HttpField field) + { + return _committed.get() ? this : _fields.put(field); + } + + @Override + public Mutable put(String name, String value) + { + return _committed.get() ? this : _fields.put(name, value); + } + + @Override + public Mutable put(HttpHeader header, HttpHeaderValue value) + { + return _committed.get() ? this : _fields.put(header, value); + } + + @Override + public Mutable put(HttpHeader header, String value) + { + return _committed.get() ? this : _fields.put(header, value); + } + + @Override + public Mutable put(String name, List list) + { + return _committed.get() ? this : _fields.put(name, list); + } + + @Override + public Mutable putDateField(HttpHeader name, long date) + { + return _committed.get() ? this : _fields.putDateField(name, date); + } + + @Override + public Mutable putDateField(String name, long date) + { + return _committed.get() ? this : _fields.putDateField(name, date); + } + + @Override + public Mutable putLongField(HttpHeader name, long value) + { + return _committed.get() ? this : _fields.putLongField(name, value); + } + + @Override + public Mutable putLongField(String name, long value) + { + return _committed.get() ? this : _fields.putLongField(name, value); + } + + @Override + public void computeField(HttpHeader header, BiFunction, HttpField> computeFn) + { + if (_committed.get()) + _fields.computeField(header, computeFn); + } + + @Override + public void computeField(String name, BiFunction, HttpField> computeFn) + { + if (_committed.get()) + _fields.computeField(name, computeFn); + } + + @Override + public Mutable remove(HttpHeader name) + { + return _committed.get() ? this : _fields.remove(name); + } + + @Override + public Mutable remove(EnumSet fields) + { + return _committed.get() ? this : _fields.remove(fields); + } + + @Override + public Mutable remove(String name) + { + return _committed.get() ? this : _fields.remove(name); + } + + @Override + public String toString() + { + return _fields.toString(); + } +} diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/AbstractHandlerMBean.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/AbstractHandlerMBean.java new file mode 100644 index 00000000000..a875871442f --- /dev/null +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/AbstractHandlerMBean.java @@ -0,0 +1,82 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server.jmx; + +import java.util.List; + +import org.eclipse.jetty.jmx.ObjectMBean; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; + +// TODO: can this handle inner classes like Handler.Abstract, etc.? +public class AbstractHandlerMBean extends ObjectMBean +{ + public AbstractHandlerMBean(Object managedObject) + { + super(managedObject); + } + + @Override + public String getObjectContextBasis() + { + if (_managed != null) + { + String basis = null; + if (_managed instanceof ContextHandler contextHandler) + { + String contextName = getContextName(contextHandler); + if (contextName == null) + contextName = contextHandler.getDisplayName(); + if (contextName != null) + return contextName; + } + else if (_managed instanceof Handler.Abstract handler) + { + Server server = handler.getServer(); + if (server != null) + { + ContextHandler context = server.getContainer(handler, ContextHandler.class); + if (context != null) + basis = getContextName(context); + } + } + if (basis != null) + return basis; + } + return super.getObjectContextBasis(); + } + + protected String getContextName(ContextHandler context) + { + String name = null; + + if (context.getContextPath() != null && context.getContextPath().length() > 0) + { + int idx = context.getContextPath().lastIndexOf('/'); + name = idx < 0 ? context.getContextPath() : context.getContextPath().substring(++idx); + if (name == null || name.length() == 0) + name = "ROOT"; + } + + if (name == null && context.getResourceBase() != null) + name = context.getResourceBase().toFile().getName(); + + List vhosts = context.getVirtualHosts(); + if (vhosts.size() > 0) + name = '"' + name + "@" + vhosts.get(0) + '"'; + + return name; + } +} diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/ServerMBean.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/ServerMBean.java index 396cade86e9..d16b32e9bf8 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/ServerMBean.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/ServerMBean.java @@ -13,16 +13,14 @@ package org.eclipse.jetty.server.jmx; +import java.util.List; + import org.eclipse.jetty.jmx.ObjectMBean; -import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; -/** - * - */ @ManagedObject("MBean Wrapper for Server") public class ServerMBean extends ObjectMBean { @@ -36,13 +34,13 @@ public class ServerMBean extends ObjectMBean server = (Server)managedObject; } - @ManagedAttribute("contexts on this server") - public Handler[] getContexts() + @ManagedAttribute("The contexts on this server") + public List getContexts() { - return server.getChildHandlersByClass(ContextHandler.class); + return server.getDescendants(ContextHandler.class); } - @ManagedAttribute("the startup time since January 1st, 1970 (in ms)") + @ManagedAttribute("The startup time since January 1st, 1970 (in ms)") public long getStartupTime() { return startupTime; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/package-info.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/package-info.java similarity index 93% rename from jetty-server/src/main/java/org/eclipse/jetty/server/jmx/package-info.java rename to jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/package-info.java index 16720e197a4..874c1cd13f3 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/package-info.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/jmx/package-info.java @@ -12,7 +12,7 @@ // /** - * Jetty Server : Server JMX Integration + * Jetty Core Server : JMX Integration */ package org.eclipse.jetty.server.jmx; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/package-info.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/package-info.java similarity index 100% rename from jetty-server/src/main/java/org/eclipse/jetty/server/package-info.java rename to jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/package-info.java diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/FixJPMS.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/FixJPMS.java new file mode 100644 index 00000000000..d5108df0430 --- /dev/null +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ssl/FixJPMS.java @@ -0,0 +1,20 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server.ssl; + +public class FixJPMS +{ + // This class only exists to make the tests in org.eclipse.jetty.server.ssl work + // These tests probably should not be in that package, but leaving them there for now to make sure we don't miss them +} diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java index ce9e02c7cfa..779aa9e3fb6 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java @@ -22,14 +22,12 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.ArrayByteBufferPool; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.internal.HttpChannelState; +import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -52,7 +50,7 @@ public abstract class AbstractHttpTest connector.setIdleTimeout(100000); server.addConnector(connector); - stacklessChannelLogging = new StacklessLogging(HttpChannel.class); + stacklessChannelLogging = new StacklessLogging(HttpChannelState.class); } @AfterEach @@ -99,7 +97,7 @@ public abstract class AbstractHttpTest } } - protected class ThrowExceptionOnDemandHandler extends AbstractHandler + protected class ThrowExceptionOnDemandHandler extends Handler.Processor { private final boolean throwException; private volatile Throwable failure; @@ -110,10 +108,11 @@ public abstract class AbstractHttpTest } @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) throws Exception { if (throwException) throw new TestCommitException(); + callback.succeeded(); } protected void markFailed(Throwable x) diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java index c79fc1846c8..cf46b2b9c93 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java @@ -23,13 +23,11 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.io.Connection; -import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -46,10 +44,10 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest @Test public void testOpenClose() throws Exception { - server.setHandler(new AbstractHandler() + server.setHandler(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { throw new IllegalStateException(); } @@ -59,7 +57,7 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest final AtomicInteger callbacks = new AtomicInteger(); final CountDownLatch openLatch = new CountDownLatch(1); final CountDownLatch closeLatch = new CountDownLatch(1); - connector.addBean(new Connection.Listener.Adapter() + connector.addBean(new Connection.Listener() { @Override public void onOpened(Connection connection) @@ -95,12 +93,12 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest @Test public void testOpenRequestClose() throws Exception { - server.setHandler(new AbstractHandler() + server.setHandler(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); + callback.succeeded(); } }); server.start(); @@ -108,7 +106,7 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest final AtomicInteger callbacks = new AtomicInteger(); final CountDownLatch openLatch = new CountDownLatch(1); final CountDownLatch closeLatch = new CountDownLatch(1); - connector.addBean(new Connection.Listener.Adapter() + connector.addBean(new Connection.Listener() { @Override public void onOpened(Connection connection) @@ -166,12 +164,12 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest connector = new ServerConnector(server, sslContextFactory); server.addConnector(connector); - server.setHandler(new AbstractHandler() + server.setHandler(new Handler.Processor() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + public void process(Request request, Response response, Callback callback) { - baseRequest.setHandled(true); + callback.succeeded(); } }); server.start(); @@ -179,7 +177,7 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest final AtomicInteger callbacks = new AtomicInteger(); final CountDownLatch openLatch = new CountDownLatch(2); final CountDownLatch closeLatch = new CountDownLatch(2); - connector.addBean(new Connection.Listener.Adapter() + connector.addBean(new Connection.Listener() { @Override public void onOpened(Connection connection) diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorCloseTestBase.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorCloseTestBase.java index 812819a3e6a..fd9237736a6 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorCloseTestBase.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorCloseTestBase.java @@ -23,6 +23,8 @@ import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.server.handler.EchoHandler; +import org.eclipse.jetty.server.handler.HelloHandler; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -53,7 +55,7 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture final int requestCount = 32; final CountDownLatch latch = new CountDownLatch(requestCount); - configureServer(new HelloWorldHandler()); + startServer(new HelloHandler()); URI uri = _server.getURI(); try (Socket client = newSocket(uri.getHost(), uri.getPort())) @@ -123,7 +125,7 @@ public abstract class ConnectorCloseTestBase extends HttpServerTestFixture @Test public void testCloseBetweenChunks() throws Exception { - configureServer(new EchoHandler()); + startServer(new EchoHandler()); URI uri = _server.getURI(); diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java index a077df255c7..d6defc5c1cd 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java @@ -27,14 +27,14 @@ import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLHandshakeException; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.EchoHandler; +import org.eclipse.jetty.server.internal.HttpChannelState; +import org.eclipse.jetty.util.Blocking; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -66,11 +66,6 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture private int minimumTestRuntime = MAX_IDLE_TIME - MAX_IDLE_TIME / 5; private int maximumTestRuntime = MAX_IDLE_TIME * 10; - static - { - System.setProperty("org.eclipse.jetty.io.nio.IDLE_TICK", "500"); - } - @BeforeEach @Override public void before() @@ -86,7 +81,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleWithRequest10() throws Exception { - configureServer(new HelloWorldHandler()); + startServer(new HelloWorldHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); @@ -120,7 +115,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleWithRequest11() throws Exception { - configureServer(new EchoHandler()); + startServer(new EchoHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); @@ -158,21 +153,20 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture public void testMaxIdleWithRequest10NoClientClose() throws Exception { final Exchanger exchanger = new Exchanger<>(); - configureServer(new HelloWorldHandler() + startServer(new HelloWorldHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, - ServletException + public void process(Request request, Response response, Callback callback) throws Exception { try { - exchanger.exchange(baseRequest.getHttpChannel().getEndPoint()); + exchanger.exchange(request.getConnectionMetaData().getConnection().getEndPoint()); } catch (Exception e) { e.printStackTrace(); } - super.handle(target, baseRequest, request, response); + super.process(request, response, callback); } }); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); @@ -217,21 +211,20 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture public void testMaxIdleWithRequest11NoClientClose() throws Exception { final Exchanger exchanger = new Exchanger<>(); - configureServer(new EchoHandler() + startServer(new EchoHandler() { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, - ServletException + public void process(Request request, Response response, Callback callback) { try { - exchanger.exchange(baseRequest.getHttpChannel().getEndPoint()); + exchanger.exchange(request.getConnectionMetaData().getConnection().getEndPoint()); } catch (Exception e) { e.printStackTrace(); } - super.handle(target, baseRequest, request, response); + super.process(request, response, callback); } }); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); @@ -281,7 +274,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Disabled // TODO make more stable public void testNoBlockingTimeoutRead() throws Exception { - configureServer(new EchoHandler()); + startServer(new EchoHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); InputStream is = client.getInputStream(); @@ -342,7 +335,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Disabled // TODO make more stable public void testBlockingTimeoutRead() throws Exception { - configureServer(new EchoHandler()); + startServer(new EchoHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); InputStream is = client.getInputStream(); @@ -362,7 +355,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture .getBytes("utf-8")); os.flush(); - try (StacklessLogging stackless = new StacklessLogging(HttpChannel.class)) + try (StacklessLogging stackless = new StacklessLogging(HttpChannelState.class)) { Thread.sleep(300); os.write("1".getBytes("utf-8")); @@ -398,7 +391,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Disabled // TODO make more stable public void testNoBlockingTimeoutWrite() throws Exception { - configureServer(new HugeResponseHandler()); + startServer(new HugeResponseHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); @@ -441,7 +434,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Disabled // TODO make more stable public void testBlockingTimeoutWrite() throws Exception { - configureServer(new HugeResponseHandler()); + startServer(new HugeResponseHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); @@ -467,7 +460,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture } long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); - try (StacklessLogging stackless = new StacklessLogging(HttpChannel.class, AbstractConnection.class)) + try (StacklessLogging stackless = new StacklessLogging(HttpChannelState.class, AbstractConnection.class)) { for (int i = 0; i < (128 * 1024); i++) { @@ -489,7 +482,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleNoRequest() throws Exception { - configureServer(new EchoHandler()); + startServer(new EchoHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); InputStream is = client.getInputStream(); @@ -520,7 +513,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleNothingSent() throws Exception { - configureServer(new EchoHandler()); + startServer(new EchoHandler()); long start = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); @@ -551,7 +544,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleDelayedDispatch() throws Exception { - configureServer(new EchoHandler()); + startServer(new EchoHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); InputStream is = client.getInputStream(); @@ -590,7 +583,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleDispatch() throws Exception { - configureServer(new EchoHandler()); + startServer(new EchoHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); InputStream is = client.getInputStream(); @@ -630,7 +623,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleWithSlowRequest() throws Exception { - configureServer(new EchoHandler()); + startServer(new EchoHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); @@ -673,7 +666,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleWithSlowResponse() throws Exception { - configureServer(new SlowResponseHandler()); + startServer(new SlowResponseHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); @@ -705,7 +698,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture @Test public void testMaxIdleWithWait() throws Exception { - configureServer(new WaitHandler()); + startServer(new WaitHandler()); Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()); client.setSoTimeout(10000); @@ -729,40 +722,37 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture }); } - protected static class SlowResponseHandler extends AbstractHandler + protected static class SlowResponseHandler extends Handler.Processor { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); response.setStatus(200); - OutputStream out = response.getOutputStream(); + + Blocking.Shared blocker = new Blocking.Shared(); for (int i = 0; i < 20; i++) { - out.write("Hello World\r\n".getBytes()); - out.flush(); - try + try (Blocking.Callback block = blocker.callback()) { Thread.sleep(50); + response.write(false, block, "Hello World\r\n"); } catch (Exception e) { e.printStackTrace(); } } - out.close(); + response.write(true, callback); } } - protected static class HugeResponseHandler extends AbstractHandler + protected static class HugeResponseHandler extends Handler.Processor { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); response.setStatus(200); - OutputStream out = response.getOutputStream(); byte[] buffer = new byte[128 * 1024 * 1024]; Arrays.fill(buffer, (byte)'x'); for (int i = 0; i < 128 * 1024; i++) @@ -770,20 +760,16 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture buffer[i * 1024 + 1022] = '\r'; buffer[i * 1024 + 1023] = '\n'; } - ByteBuffer bb = ByteBuffer.wrap(buffer); - ((HttpOutput)out).sendContent(bb); - out.close(); + response.write(true, callback, ByteBuffer.wrap(buffer)); } } - protected static class WaitHandler extends AbstractHandler + protected static class WaitHandler extends Handler.Processor { @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void process(Request request, Response response, Callback callback) throws Exception { - baseRequest.setHandled(true); response.setStatus(200); - OutputStream out = response.getOutputStream(); try { Thread.sleep(2000); @@ -792,8 +778,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture { e.printStackTrace(); } - out.write("Hello World\r\n".getBytes()); - out.flush(); + response.write(true, callback, "Hello World\r\n"); } } } diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ContentTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ContentTest.java new file mode 100644 index 00000000000..02fe9444dfe --- /dev/null +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ContentTest.java @@ -0,0 +1,910 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; + +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Fields; +import org.eclipse.jetty.util.FutureCallback; +import org.eclipse.jetty.util.FuturePromise; +import org.eclipse.jetty.util.IO; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ContentTest +{ + TestReader _provider; + TestProcessor _processor; + + @BeforeEach + public void beforeEach() + { + _provider = new TestReader(); + _processor = new TestProcessor(_provider); + } + + @AfterEach + public void afterEach() + { + _provider.leakCheck(); + } + + @Test + public void testSimple() + { + assertNull(_provider.readContent()); + _provider.add("hello", false); + Content content = _provider.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), equalTo("hello")); + content.release(); + } + + @Test + public void testReadBytes() throws Exception + { + FuturePromise promise = new FuturePromise<>(); + Content.readAllBytes(_provider, promise); + + Runnable todo = _provider.takeDemand(); + assertNotNull(todo); + _provider.add("hello", false); + todo.run(); + assertFalse(promise.isDone()); + + todo = _provider.takeDemand(); + assertNotNull(todo); + _provider.add(" cruel", false); + _provider.add(" world", true); + todo.run(); + + todo = _provider.takeDemand(); + assertNull(todo); + assertTrue(promise.isDone()); + ByteBuffer output = promise.get(10, TimeUnit.SECONDS); + assertNotNull(output); + assertThat(BufferUtil.toString(output), equalTo("hello cruel world")); + } + + @Test + public void testReadUtf8() throws Exception + { + FuturePromise promise = new FuturePromise<>(); + Content.readAll(_provider, promise); + + Runnable todo = _provider.takeDemand(); + assertNotNull(todo); + _provider.add("hello", false); + todo.run(); + assertFalse(promise.isDone()); + + todo = _provider.takeDemand(); + assertNotNull(todo); + _provider.add(" cruel", false); + _provider.add(" world", true); + todo.run(); + + todo = _provider.takeDemand(); + assertNull(todo); + assertTrue(promise.isDone()); + String output = promise.get(10, TimeUnit.SECONDS); + assertNotNull(output); + assertThat(output, equalTo("hello cruel world")); + } + + @Test + public void testConsumeAll() throws Exception + { + FutureCallback callback = new FutureCallback(); + Content.consumeAll(_provider, callback); + Runnable todo = _provider.takeDemand(); + assertNotNull(todo); + _provider.add("hello", false); + todo.run(); + assertFalse(callback.isDone()); + + todo = _provider.takeDemand(); + assertNotNull(todo); + _provider.add(" cruel", false); + _provider.add(" world", true); + todo.run(); + + todo = _provider.takeDemand(); + assertNull(todo); + assertTrue(callback.isDone()); + callback.get(); + } + + @Test + public void testConsumeAllFailed() throws Exception + { + FutureCallback callback = new FutureCallback(); + Content.consumeAll(_provider, callback); + Runnable todo = _provider.takeDemand(); + assertNotNull(todo); + _provider.add("hello", false); + todo.run(); + assertFalse(callback.isDone()); + + todo = _provider.takeDemand(); + assertNotNull(todo); + + Throwable cause = new Throwable("test cause"); + _provider.add(new Content.Error(cause)); + todo.run(); + + todo = _provider.takeDemand(); + assertNull(todo); + assertTrue(callback.isDone()); + assertThrows(ExecutionException.class, callback::get); + } + + @Test + public void testInputStream() throws Exception + { + InputStream in = Content.asInputStream(_provider); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + AtomicReference throwable = new AtomicReference<>(); + CountDownLatch complete = new CountDownLatch(1); + new Thread(() -> + { + try + { + IO.copy(in, out); + } + catch (Throwable t) + { + throwable.set(t); + } + finally + { + complete.countDown(); + } + }).start(); + + long wait = System.currentTimeMillis() + 1000; + Runnable todo = _provider.takeDemand(); + while (todo == null && System.currentTimeMillis() < wait) + todo = _provider.takeDemand(); + assertNotNull(todo); + _provider.add("hello", false); + todo.run(); + + wait = System.currentTimeMillis() + 1000; + todo = _provider.takeDemand(); + while (todo == null && System.currentTimeMillis() < wait) + todo = _provider.takeDemand(); + assertNotNull(todo); + + _provider.add(" cruel", false); + _provider.add(" world", true); + todo.run(); + assertTrue(complete.await(10, TimeUnit.SECONDS)); + + assertNull(throwable.get()); + assertThat(out.toString(StandardCharsets.UTF_8), equalTo("hello cruel world")); + } + + @Test + public void testInputStreamFailed() throws Exception + { + InputStream in = Content.asInputStream(_provider); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + AtomicReference throwable = new AtomicReference<>(); + CountDownLatch complete = new CountDownLatch(1); + new Thread(() -> + { + try + { + IO.copy(in, out); + } + catch (Throwable t) + { + throwable.set(t); + } + finally + { + complete.countDown(); + } + }).start(); + + long wait = System.currentTimeMillis() + 1000; + Runnable todo = _provider.takeDemand(); + while (todo == null && System.currentTimeMillis() < wait) + todo = _provider.takeDemand(); + assertNotNull(todo); + _provider.add("hello", false); + todo.run(); + + wait = System.currentTimeMillis() + 1000; + todo = _provider.takeDemand(); + while (todo == null && System.currentTimeMillis() < wait) + todo = _provider.takeDemand(); + assertNotNull(todo); + + Throwable cause = new Throwable("test cause"); + _provider.add(new Content.Error(cause)); + todo.run(); + + assertTrue(complete.await(10, TimeUnit.SECONDS)); + + assertNotNull(throwable.get()); + assertThat(out.toString(StandardCharsets.UTF_8), equalTo("hello")); + } + + @Test + public void testFields() throws Exception + { + FutureFormFields future = new FutureFormFields(_provider); + future.run(); + Runnable todo = _provider.takeDemand(); + assertNotNull(todo); + _provider.add("one=1", false); + todo.run(); + assertFalse(future.isDone()); + + todo = _provider.takeDemand(); + assertNotNull(todo); + _provider.add("&two=2&", false); + _provider.add("three=3", true); + todo.run(); + + todo = _provider.takeDemand(); + assertNull(todo); + assertTrue(future.isDone()); + Fields fields = future.get(10, TimeUnit.SECONDS); + assertNotNull(fields); + assertThat(fields.getSize(), equalTo(3)); + assertThat(fields.get("one").getValue(), equalTo("1")); + assertThat(fields.get("two").getValue(), equalTo("2")); + assertThat(fields.get("three").getValue(), equalTo("3")); + } + + @Test + public void testProcessorNoContent() + { + assertThat(_processor.readContent(), nullValue()); + assertThat(_provider.takeDemand(), nullValue()); + assertThat(_processor.readContent(), nullValue()); + assertThat(_provider.takeDemand(), nullValue()); + + FutureCallback oca = new FutureCallback(); + _processor.demandContent(oca::succeeded); + assertFalse(oca.isDone()); + Runnable demand = _provider.takeDemand(); + assertNotNull(demand); + assertThat(_provider.takeDemand(), nullValue()); + + demand.run(); // spurious wakeup! + assertFalse(oca.isDone()); + demand = _provider.takeDemand(); + assertNotNull(demand); + + assertThat(_processor.readContent(), nullValue()); + assertThat(_provider.takeDemand(), nullValue()); + + _provider.add(Content.EOF); + demand.run(); + assertTrue(oca.isDone()); + assertThat(_processor.readContent(), sameInstance(Content.EOF)); + } + + @Test + public void testProcessorContentAvailableEOF() + { + _provider.add("one", false); + _provider.add("two", false); + _provider.add("three", false); + _provider.add(Content.EOF); + + Content content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("one")); + content.release(); + + content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("two")); + content.release(); + + FutureCallback oca = new FutureCallback(); + _processor.demandContent(oca::succeeded); + assertTrue(oca.isDone()); + + content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("three")); + content.release(); + + assertThat(_processor.readContent(), sameInstance(Content.EOF)); + } + + @Test + public void testProcessorContentAvailableLast() + { + _provider.add("one", false); + _provider.add("two", false); + _provider.add("three", true); + + Content content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("one")); + content.release(); + + content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("two")); + content.release(); + + FutureCallback oca = new FutureCallback(); + _processor.demandContent(oca::succeeded); + assertTrue(oca.isDone()); + + content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("three")); + assertTrue(content.isLast()); + content.release(); + + assertThat(_processor.readContent(), sameInstance(Content.EOF)); + } + + public static Stream consumers() + { + return Stream.of(new OneByOneConsumer(), new IteratingConsumer()); + } + + @ParameterizedTest + @MethodSource("consumers") + public void testProcessorAvailableNoRecursion(Consumer consumer) + { + consumer.setProcessor(_processor); + _provider.add("one", false); + _provider.add("NOOP", false); + _provider.add("NOOP TWO NOOP", false); + _provider.add("THREE", false); + _provider.add("four five", true); + _processor.demandContent(consumer); + assertTrue(consumer.last.get()); + assertFalse(consumer.notReEntrant.get()); + assertThat(consumer.output, contains("one", "two", "three", "four", "five")); + } + + @ParameterizedTest + @MethodSource("consumers") + public void testProcessorDemandedNoRecursion(Consumer consumer) + { + consumer.setProcessor(_processor); + _processor.demandContent(consumer); + + _provider.add("one", false); + _provider.takeDemand().run(); + _provider.add("NOOP", false); + _provider.takeDemand().run(); + _provider.add("NOOP TWO NOOP", false); + _provider.takeDemand().run(); + _provider.add("THREE", false); + _provider.takeDemand().run(); + _provider.add("four five", true); + _provider.takeDemand().run(); + + assertTrue(consumer.last.get()); + assertFalse(consumer.notReEntrant.get()); + assertThat(consumer.output, contains("one", "two", "three", "four", "five")); + } + + @Test + public void testProcessorProducerThrowsInRead() + { + _provider.add("one", false); + _provider.add("THROW", false); + _provider.add("two", false); + _provider.add("THROW", true); + + Content content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("one")); + content.release(); + + content = _processor.readContent(); + assertNotNull(content); + assertThat(content, instanceOf(Content.Error.class)); + + content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("two")); + content.release(); + + content = _processor.readContent(); + assertNotNull(content); + assertThat(content, instanceOf(Content.Error.class)); + + assertThat(_processor.readContent(), sameInstance(Content.EOF)); + } + + @Test + public void testProcessorProducerThrowsInDemand() + { + _provider.add("one", false); + _provider.add("THROW", false); + _provider.add("two", false); + _provider.add("THROW", true); + + Content content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("one")); + content.release(); + + FutureCallback oca = new FutureCallback(); + _processor.demandContent(oca::succeeded); + assertTrue(oca.isDone()); + content = _processor.readContent(); + assertNotNull(content); + assertThat(content, instanceOf(Content.Error.class)); + + content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("two")); + content.release(); + + oca = new FutureCallback(); + _processor.demandContent(oca::succeeded); + assertTrue(oca.isDone()); + content = _processor.readContent(); + assertNotNull(content); + assertThat(content, instanceOf(Content.Error.class)); + + assertThat(_processor.readContent(), sameInstance(Content.EOF)); + } + + @Test + public void testProcessorProducerThrowsInAvailable() + { + _provider.add("one", false); + + Content content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("one")); + content.release(); + + FutureCallback oca = new FutureCallback(); + _processor.demandContent(oca::succeeded); + assertFalse(oca.isDone()); + Runnable demand = _provider.takeDemand(); + assertNotNull(demand); + + _provider.add("THROW", false); + _provider.add("two", false); + demand.run(); + assertTrue(oca.isDone()); + content = _processor.readContent(); + assertNotNull(content); + assertThat(content, instanceOf(Content.Error.class)); + + content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is("two")); + content.release(); + + oca = new FutureCallback(); + _processor.demandContent(oca::succeeded); + assertFalse(oca.isDone()); + demand = _provider.takeDemand(); + assertNotNull(demand); + + _provider.add("THROW", true); + demand.run(); + assertTrue(oca.isDone()); + content = _processor.readContent(); + assertNotNull(content); + assertThat(content, instanceOf(Content.Error.class)); + + assertThat(_processor.readContent(), sameInstance(Content.EOF)); + } + + @Test + public void testProcessorAvailableThrows() + { + AtomicReference error = new AtomicReference<>(); + Deque output = new ConcurrentLinkedDeque<>(); + Runnable onAvailable = new Runnable() + { + @Override + public void run() + { + Content content = _processor.readContent(); + if (content != null) + { + if (content.hasRemaining()) + { + String s = BufferUtil.toString(content.getByteBuffer()); + content.release(); + if ("throw".equals(s)) + throw new RuntimeException("testing"); + if ("dthrow".equals(s)) + { + _processor.demandContent(this); + throw new RuntimeException("testing"); + } + output.add(s); + } + if (content instanceof Content.Error) + error.set(((Content.Error)content).getCause()); + if (content.isLast()) + return; + } + _processor.demandContent(this); + } + }; + + _processor.demandContent(onAvailable); + Runnable demand = _provider.takeDemand(); + assertNotNull(demand); + _provider.add("one", false); + demand.run(); + assertThat(output, contains("one")); + assertNull(error.get()); + + demand = _provider.takeDemand(); + assertNotNull(demand); + _provider.add("throw", false); + demand.run(); + assertThat(output, contains("one")); + assertNull(error.get()); + + _processor.demandContent(onAvailable); + assertThat(output, contains("one")); + assertThat(error.getAndSet(null), instanceOf(RuntimeException.class)); + + _provider.add("two", false); + _provider.takeDemand().run(); + assertThat(output, contains("one", "two")); + assertNull(error.get()); + + _provider.add("dthrow", true); + _provider.takeDemand().run(); + assertThat(output, contains("one", "two")); + assertThat(error.getAndSet(null), instanceOf(RuntimeException.class)); + + assertThat(_processor.readContent(), sameInstance(Content.EOF)); + } + + @Test + public void testProcessorExampleAvailable() + { + _provider.add("one", false); + _provider.add("TWO", false); + _provider.add("three four", false); + _provider.add("NOOP", false); + _provider.add("five NOOP six", false); + _provider.add(Content.EOF); + + for (String s : List.of("one", "two", "three", "four", "five", "six")) + { + Content content = _processor.readContent(); + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is(s)); + content.release(); + } + assertThat(_processor.readContent(), sameInstance(Content.EOF)); + } + + @Test + public void testProcessorExampleDemandedBefore() + { + Deque input = new ArrayDeque<>(List.of("one", "TWO", "three four", "NOOP", "five NOOP six")); + + for (String s : List.of("one", "two", "three", "four", "five", "six")) + { + Content content = _processor.readContent(); + if (content == null) + { + FutureCallback oca = new FutureCallback(); + _processor.demandContent(oca::succeeded); + assertFalse(oca.isDone()); + Runnable demand = _provider.takeDemand(); + assertNotNull(demand); + String word = input.poll(); + _provider.add(word, false); + demand.run(); + if ("NOOP".equals(word)) + { + assertFalse(oca.isDone()); + demand = _provider.takeDemand(); + assertNotNull(demand); + word = input.poll(); + _provider.add(word, false); + demand.run(); + } + assertTrue(oca.isDone()); + content = _processor.readContent(); + } + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is(s)); + content.release(); + } + + _provider.add(Content.EOF); + assertThat(_processor.readContent(), sameInstance(Content.EOF)); + } + + @Test + public void testProcessorExampleDemandedAfter() + { + Deque input = new ArrayDeque<>(List.of("one", "TWO", "three four", "NOOP", "five NOOP six")); + + for (String s : List.of("one", "two", "three", "four", "five", "six")) + { + Content content = _processor.readContent(); + if (content == null) + { + FutureCallback oca = new FutureCallback(); + String word = input.poll(); + _provider.add(word, false); + + _processor.demandContent(oca::succeeded); + if ("NOOP".equals(word)) + { + assertFalse(oca.isDone()); + word = input.poll(); + _provider.add(word, false); + _provider.takeDemand().run(); + } + assertTrue(oca.isDone()); + assertNull(_provider.takeDemand()); + content = _processor.readContent(); + } + assertNotNull(content); + assertThat(BufferUtil.toString(content.getByteBuffer()), is(s)); + content.release(); + } + + _provider.add(Content.EOF); + assertThat(_processor.readContent(), sameInstance(Content.EOF)); + } + + static class TestReader implements Content.Reader + { + final AtomicReference _demand = new AtomicReference<>(); + final Deque _content = new ConcurrentLinkedDeque<>(); + final Deque _references = new ConcurrentLinkedDeque<>(); + + Runnable takeDemand() + { + return _demand.getAndSet(null); + } + + void add(Content content) + { + _content.add(content); + } + + void add(String content, boolean last) + { + AtomicBoolean reference = new AtomicBoolean(); + _references.add(reference); + ByteBuffer buffer = BufferUtil.toBuffer(content); + _content.add(new Content.Abstract(false, last) + { + @Override + public void release() + { + reference.set(true); + } + + @Override + public ByteBuffer getByteBuffer() + { + return buffer; + } + }); + } + + @Override + public Content readContent() + { + Content content = _content.poll(); + Content next = Content.next(content); + if (next != null) + { + _content.clear(); + _content.add(next); + } + return content; + } + + @Override + public void demandContent(Runnable onContentAvailable) + { + if (!_demand.compareAndSet(null, onContentAvailable)) + throw new IllegalStateException(); + } + + public void leakCheck() + { + _references.forEach(b -> assertTrue(b.get())); + } + } + + static class TestProcessor extends ContentProcessor + { + Deque _words = new ArrayDeque<>(); + boolean _last; + + public TestProcessor(Content.Reader reader) + { + super(reader); + } + + @Override + protected Content process(Content content) + { + if (content != null) + { + if (!_words.isEmpty()) + throw new IllegalStateException("enough already!"); + + _last |= content.isLast(); + + if (content.isSpecial() || content.isEmpty()) + return content; + + ByteBuffer buffer = content.getByteBuffer(); + boolean space = false; + boolean upper = false; + for (int i = buffer.position(); i < buffer.limit(); i++) + { + byte b = buffer.get(i); + space |= Character.isWhitespace(b); + upper |= Character.isUpperCase(b); + } + if (!space && !upper) + return content; + + String s = BufferUtil.toString(buffer); + content.release(); + + if (space) + _words.addAll(Arrays.asList(s.split("\\s+"))); + else + _words.add(s); + } + + while (true) + { + String word = _words.poll(); + if (word == null) + return null; + if ("NOOP".equals(word)) + continue; + if ("THROW".equals(word)) + throw new RuntimeException("testing"); + return Content.from(BufferUtil.toBuffer(word.toLowerCase()), _last && _words.isEmpty()); + } + } + } + + private abstract static class Consumer implements Runnable + { + final AtomicBoolean last = new AtomicBoolean(); + final List output = new ArrayList<>(); + final AtomicBoolean notReEntrant = new AtomicBoolean(); + + Content.Processor _processor; + + void setProcessor(Content.Processor processor) + { + _processor = processor; + } + } + + private static class OneByOneConsumer extends Consumer + { + @Override + public void run() + { + if (!notReEntrant.compareAndSet(false, true)) + throw new IllegalStateException("Reentered!!!!"); + + boolean okExit; + try + { + Content content = _processor.readContent(); + if (content != null) + { + if (content.isLast()) + last.set(true); + if (content.hasRemaining()) + output.add(BufferUtil.toString(content.getByteBuffer())); + content.release(); + } + if (!last.get()) + _processor.demandContent(this); + } + finally + { + okExit = notReEntrant.compareAndSet(true, false); + } + + if (!okExit) + throw new IllegalStateException("Reexited!?!?"); + } + } + + private static class IteratingConsumer extends Consumer + { + @Override + public void run() + { + if (!notReEntrant.compareAndSet(false, true)) + throw new IllegalStateException("Reentered!!!!"); + + boolean okExit; + try + { + Content content = _processor.readContent(); + while (content != null && !last.get()) + { + if (content.isLast()) + last.set(true); + if (content.hasRemaining()) + output.add(BufferUtil.toString(content.getByteBuffer())); + content.release(); + content = _processor.readContent(); + } + if (!last.get()) + _processor.demandContent(this); + } + finally + { + okExit = notReEntrant.compareAndSet(true, false); + } + + if (!okExit) + throw new IllegalStateException("Reexited!?!?"); + } + } +} diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomRequestLogTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomRequestLogTest.java new file mode 100644 index 00000000000..f6fee6d7f72 --- /dev/null +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomRequestLogTest.java @@ -0,0 +1,663 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.Socket; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.io.QuietException; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.Blocking; +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.DateCache; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +public class CustomRequestLogTest +{ + private final BlockingQueue _entries = new BlockingArrayQueue<>(); + private final BlockingQueue requestTimes = new BlockingArrayQueue<>(); + private CustomRequestLog _log; + private Server _server; + private LocalConnector _connector; + private ServerConnector _serverConnector; + private URI _serverURI; + + private static final long DELAY = 2000; + + @BeforeEach + public void before() + { + _server = new Server(); + _connector = new LocalConnector(_server); + _serverConnector = new ServerConnector(_server); + _server.addConnector(_connector); + _server.addConnector(_serverConnector); + } + + void testHandlerServerStart(String formatString) throws Exception + { + _serverConnector.setPort(0); + _serverConnector.getBean(HttpConnectionFactory.class).getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer()); + TestRequestLogWriter writer = new TestRequestLogWriter(); + _log = new CustomRequestLog(writer, formatString); + _server.setRequestLog(_log); + ContextHandler contextHandler = new ContextHandler(); + contextHandler.setHandler(new TestHandler()); + _server.setHandler(contextHandler); + _server.start(); + + String host = _serverConnector.getHost(); + if (host == null) + host = "localhost"; + + int localPort = _serverConnector.getLocalPort(); + _serverURI = new URI(String.format("http://%s:%d/", host, localPort)); + } + + @AfterEach + public void after() throws Exception + { + _server.stop(); + } + + @Test + public void testRequestFilter() throws Exception + { + AtomicReference logRequest = new AtomicReference<>(); + testHandlerServerStart("RequestPath: %U"); + _log.setFilter((request, response) -> logRequest.get()); + + logRequest.set(true); + _connector.getResponse("GET /path HTTP/1.0\n\n"); + assertThat(_entries.poll(5, TimeUnit.SECONDS), is("RequestPath: /path")); + + logRequest.set(false); + _connector.getResponse("GET /path HTTP/1.0\n\n"); + assertNull(_entries.poll(1, TimeUnit.SECONDS)); + } + + @Test + @Disabled // TODO + public void testLogRemoteUser() throws Exception + { + String authHeader = HttpHeader.AUTHORIZATION + ": Basic " + Base64.getEncoder().encodeToString("username:password".getBytes()); + testHandlerServerStart("%u %{d}u"); + + _connector.getResponse("GET / HTTP/1.0\n\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("- -")); + + _connector.getResponse("GET / HTTP/1.0\n" + authHeader + "\n\n\n"); + log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("- username")); + + _connector.getResponse("GET /secure HTTP/1.0\n" + authHeader + "\n\n\n"); + log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("username username")); + } + + @Test + public void testModifier() throws Exception + { + testHandlerServerStart("%s: %!404,301{Referer}i"); + + _connector.getResponse("GET /error404 HTTP/1.0\nReferer: testReferer\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("404: -")); + + _connector.getResponse("GET /error301 HTTP/1.0\nReferer: testReferer\n\n"); + log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("301: -")); + + _connector.getResponse("GET /success HTTP/1.0\nReferer: testReferer\n\n"); + log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("200: testReferer")); + } + + @Test + public void testDoublePercent() throws Exception + { + testHandlerServerStart("%%%%%%a"); + + _connector.getResponse("GET / HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("%%%a")); + } + + @Test + public void testLogAddress() throws Exception + { + testHandlerServerStart("%{local}a|%{local}p|" + + "%{remote}a|%{remote}p|" + + "%{server}a|%{server}p|" + + "%{client}a|%{client}p"); + + Enumeration e = NetworkInterface.getNetworkInterfaces(); + while (e.hasMoreElements()) + { + NetworkInterface n = e.nextElement(); + if (n.isLoopback()) + { + Enumeration ee = n.getInetAddresses(); + while (ee.hasMoreElements()) + { + InetAddress i = ee.nextElement(); + try (Socket client = newSocket(i.getHostAddress(), _serverURI.getPort())) + { + OutputStream os = client.getOutputStream(); + String request = "GET / HTTP/1.0\n" + + "Host: webtide.com:1234\n" + + "Forwarded: For=10.1.2.3:1337\n" + + "\n\n"; + os.write(request.getBytes(StandardCharsets.ISO_8859_1)); + os.flush(); + + String[] log = Objects.requireNonNull(_entries.poll(5, TimeUnit.SECONDS)).split("\\|"); + assertThat(log.length, is(8)); + + String localAddr = log[0]; + String localPort = log[1]; + String remoteAddr = log[2]; + String remotePort = log[3]; + String serverAddr = log[4]; + String serverPort = log[5]; + String clientAddr = log[6]; + String clientPort = log[7]; + + assertThat(serverPort, is("1234")); + assertThat(clientPort, is("1337")); + assertThat(remotePort, not(clientPort)); + assertThat(localPort, not(serverPort)); + + assertThat(serverAddr, is("webtide.com")); + assertThat(clientAddr, is("10.1.2.3")); + assertThat(InetAddress.getByName(remoteAddr), is(client.getInetAddress())); + assertThat(InetAddress.getByName(localAddr), is(i)); + } + } + } + } + } + + @Test + public void testLogBytesSent() throws Exception + { + testHandlerServerStart("BytesSent: %O"); + + _connector.getResponse("GET / HTTP/1.0\necho: hello world\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("BytesSent: 11")); + } + + @Test + public void testLogBytesReceived() throws Exception + { + testHandlerServerStart("BytesReceived: %I"); + + _connector.getResponse("GET / HTTP/1.0\n" + + "Content-Length: 11\n\n" + + "hello world"); + + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("BytesReceived: 11")); + } + + @Test + public void testLogBytesTransferred() throws Exception + { + testHandlerServerStart("BytesTransferred: %S"); + + _connector.getResponse("GET / HTTP/1.0\n" + + "echo: hello world\n" + + "Content-Length: 11\n\n" + + "hello world"); + + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("BytesTransferred: 22")); + } + + @Test + public void testLogRequestCookie() throws Exception + { + testHandlerServerStart("RequestCookies: %{cookieName}C, %{cookie2}C, %{cookie3}C"); + + _connector.getResponse("GET / HTTP/1.0\nCookie: cookieName=cookieValue; cookie2=value2\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("RequestCookies: cookieValue, value2, -")); + } + + @Test + public void testLogRequestCookies() throws Exception + { + testHandlerServerStart("RequestCookies: %C"); + + _connector.getResponse("GET / HTTP/1.0\nCookie: cookieName=cookieValue; cookie2=value2\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("RequestCookies: cookieName=cookieValue;cookie2=value2")); + } + + @Test + public void testLogEnvironmentVar() throws Exception + { + testHandlerServerStart("EnvironmentVar: %{JAVA_HOME}e"); + + _connector.getResponse("GET / HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + + String envVar = System.getenv("JAVA_HOME"); + assertThat(log, is("EnvironmentVar: " + ((envVar == null) ? "-" : envVar))); + } + + @Test + public void testLogRequestProtocol() throws Exception + { + testHandlerServerStart("%H"); + + _connector.getResponse("GET / HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("HTTP/1.0")); + } + + @Test + public void testLogRequestHeader() throws Exception + { + testHandlerServerStart("RequestHeader: %{Header1}i, %{Header2}i, %{Header3}i"); + + _connector.getResponse("GET / HTTP/1.0\nHeader1: value1\nHeader2: value2\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("RequestHeader: value1, value2, -")); + } + + @Test + public void testLogKeepAliveRequests() throws Exception + { + testHandlerServerStart("KeepAliveRequests: %k"); + + LocalConnector.LocalEndPoint connect = _connector.connect(); + connect.addInput(""" + GET /a HTTP/1.0 + Connection: keep-alive + + """); + connect.addInput(""" + GET /a HTTP/1.1 + Host: localhost + + """); + + assertThat(connect.getResponse(), containsString("200 OK")); + assertThat(connect.getResponse(), containsString("200 OK")); + + connect.addInput("GET /a HTTP/1.0\n\n"); + assertThat(connect.getResponse(), containsString("200 OK")); + + assertThat(_entries.poll(5, TimeUnit.SECONDS), is("KeepAliveRequests: 1")); + assertThat(_entries.poll(5, TimeUnit.SECONDS), is("KeepAliveRequests: 2")); + assertThat(_entries.poll(5, TimeUnit.SECONDS), is("KeepAliveRequests: 3")); + } + + @Disabled + @Test + public void testLogKeepAliveRequestsHttp2() throws Exception + { + testHandlerServerStart("KeepAliveRequests: %k"); + fail(); + } + + @Test + public void testLogRequestMethod() throws Exception + { + testHandlerServerStart("RequestMethod: %m"); + + _connector.getResponse("GET / HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("RequestMethod: GET")); + } + + @Test + public void testLogResponseHeader() throws Exception + { + testHandlerServerStart("ResponseHeader: %{Header1}o, %{Header2}o, %{Header3}o"); + + String response = _connector.getResponse("GET /responseHeaders HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("ResponseHeader: value1, value2, -")); + } + + @Test + public void testLogQueryString() throws Exception + { + testHandlerServerStart("QueryString: %q"); + + _connector.getResponse("GET /path?queryString HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("QueryString: ?queryString")); + } + + @Test + public void testLogRequestFirstLine() throws Exception + { + testHandlerServerStart("RequestFirstLin: %r"); + + _connector.getResponse("GET /path?query HTTP/1.0\nHeader: null\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("RequestFirstLin: GET /path?query HTTP/1.0")); + } + + @Test + public void testLogResponseStatus() throws Exception + { + testHandlerServerStart("LogResponseStatus: %s"); + + _connector.getResponse("GET /error404 HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("LogResponseStatus: 404")); + + _connector.getResponse("GET /error301 HTTP/1.0\n\n"); + log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("LogResponseStatus: 301")); + + _connector.getResponse("GET / HTTP/1.0\n\n"); + log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("LogResponseStatus: 200")); + } + + @Test + public void testLogRequestTime() throws Exception + { + testHandlerServerStart("RequestTime: %t"); + + _connector.getResponse("GET / HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + long requestTime = getTimeRequestReceived(); + DateCache dateCache = new DateCache(CustomRequestLog.DEFAULT_DATE_FORMAT, Locale.getDefault(), "GMT"); + assertThat(log, is("RequestTime: [" + dateCache.format(requestTime) + "]")); + } + + @Test + public void testLogRequestTimeCustomFormats() throws Exception + { + testHandlerServerStart("%{EEE MMM dd HH:mm:ss zzz yyyy}t\n" + + "%{EEE MMM dd HH:mm:ss zzz yyyy|EST}t\n" + + "%{EEE MMM dd HH:mm:ss zzz yyyy|EST|ja}t"); + + _connector.getResponse("GET / HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertNotNull(log); + long requestTime = getTimeRequestReceived(); + + DateCache dateCache1 = new DateCache("EEE MMM dd HH:mm:ss zzz yyyy", Locale.getDefault(), "GMT"); + DateCache dateCache2 = new DateCache("EEE MMM dd HH:mm:ss zzz yyyy", Locale.getDefault(), "EST"); + DateCache dateCache3 = new DateCache("EEE MMM dd HH:mm:ss zzz yyyy", Locale.forLanguageTag("ja"), "EST"); + + String[] logs = log.split("\n"); + assertThat(logs[0], is("[" + dateCache1.format(requestTime) + "]")); + assertThat(logs[1], is("[" + dateCache2.format(requestTime) + "]")); + assertThat(logs[2], is("[" + dateCache3.format(requestTime) + "]")); + } + + @Test + public void testLogLatencyMicroseconds() throws Exception + { + testHandlerServerStart("%{us}T"); + + _connector.getResponse("GET /delay HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertNotNull(log); + long lowerBound = getTimeRequestReceived(); + long upperBound = System.currentTimeMillis(); + + long measuredDuration = Long.parseLong(log); + long durationLowerBound = TimeUnit.MILLISECONDS.toMicros(DELAY); + long durationUpperBound = TimeUnit.MILLISECONDS.toMicros(upperBound - lowerBound); + + assertThat(measuredDuration, greaterThanOrEqualTo(durationLowerBound)); + assertThat(measuredDuration, lessThanOrEqualTo(durationUpperBound)); + } + + @Test + public void testLogLatencyMilliseconds() throws Exception + { + testHandlerServerStart("%{ms}T"); + + _connector.getResponse("GET /delay HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertNotNull(log); + long lowerBound = getTimeRequestReceived(); + long upperBound = System.currentTimeMillis(); + + long measuredDuration = Long.parseLong(log); + long durationLowerBound = DELAY; + long durationUpperBound = upperBound - lowerBound; + + assertThat(measuredDuration, greaterThanOrEqualTo(durationLowerBound)); + assertThat(measuredDuration, lessThanOrEqualTo(durationUpperBound)); + } + + @Test + public void testLogLatencySeconds() throws Exception + { + testHandlerServerStart("%{s}T"); + + _connector.getResponse("GET /delay HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertNotNull(log); + long lowerBound = getTimeRequestReceived(); + long upperBound = System.currentTimeMillis(); + + long measuredDuration = Long.parseLong(log); + long durationLowerBound = TimeUnit.MILLISECONDS.toSeconds(DELAY); + long durationUpperBound = TimeUnit.MILLISECONDS.toSeconds(upperBound - lowerBound); + + assertThat(measuredDuration, greaterThanOrEqualTo(durationLowerBound)); + assertThat(measuredDuration, lessThanOrEqualTo(durationUpperBound)); + } + + @Test + public void testLogUrlRequestPath() throws Exception + { + testHandlerServerStart("UrlRequestPath: %U"); + + _connector.getResponse("GET /path?query HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + assertThat(log, is("UrlRequestPath: /path")); + } + + @Test + public void testLogConnectionStatus() throws Exception + { + testHandlerServerStart("%U ConnectionStatus: %s %X"); + + _connector.getResponse("GET /one HTTP/1.0\n\n"); + assertThat(_entries.poll(5, TimeUnit.SECONDS), is("/one ConnectionStatus: 200 -")); + + _connector.getResponse(""" + GET /two HTTP/1.1 + Host: localhost + Connection: close + + """); + assertThat(_entries.poll(5, TimeUnit.SECONDS), is("/two ConnectionStatus: 200 -")); + + LocalConnector.LocalEndPoint connect = _connector.connect(); + connect.addInput(""" + GET /three HTTP/1.0 + Connection: keep-alive + + """); + connect.addInput(""" + GET /four HTTP/1.1 + Host: localhost + + """); + connect.addInput(""" + GET /five HTTP/1.1 + Host: localhost + Connection: close + + """); + assertThat(connect.getResponse(), containsString("200 OK")); + assertThat(connect.getResponse(), containsString("200 OK")); + assertThat(connect.getResponse(), containsString("200 OK")); + assertThat(_entries.poll(5, TimeUnit.SECONDS), is("/three ConnectionStatus: 200 +")); + assertThat(_entries.poll(5, TimeUnit.SECONDS), is("/four ConnectionStatus: 200 +")); + assertThat(_entries.poll(5, TimeUnit.SECONDS), is("/five ConnectionStatus: 200 -")); + + _connector.getResponse(""" + GET /no/host HTTP/1.1 + + """); + connect.getResponse(); + assertThat(_entries.poll(5, TimeUnit.SECONDS), is("/no/host ConnectionStatus: 400 X")); + + _connector.getResponse(""" + GET /abort HTTP/1.1 + Host: localhost + + """); + connect.getResponse(); + assertThat(_entries.poll(5, TimeUnit.SECONDS), is("/abort ConnectionStatus: 200 X")); + } + + @Disabled // TODO + @Test + public void testLogRequestTrailer() throws Exception + { + testHandlerServerStart("%{trailerName}ti"); + + _connector.getResponse("GET / HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + fail(log); + } + + @Disabled // TODO + @Test + public void testLogResponseTrailer() throws Exception + { + testHandlerServerStart("%{trailerName}to"); + + _connector.getResponse("GET / HTTP/1.0\n\n"); + String log = _entries.poll(5, TimeUnit.SECONDS); + fail(log); + } + + protected Socket newSocket(String host, int port) throws Exception + { + Socket socket = new Socket(host, port); + socket.setSoTimeout(10000); + socket.setTcpNoDelay(true); + return socket; + } + + class TestRequestLogWriter implements RequestLog.Writer + { + @Override + public void write(String requestEntry) + { + try + { + _entries.add(requestEntry); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + + private long getTimeRequestReceived() throws InterruptedException + { + Long requestTime = requestTimes.poll(5, TimeUnit.SECONDS); + assertNotNull(requestTime); + return requestTime; + } + + private class TestHandler extends Handler.Processor + { + @Override + public void process(Request request, Response response, Callback callback) throws Exception + { + requestTimes.offer(request.getTimeStamp()); + + if (request.getPathInContext().contains("error404")) + { + response.setStatus(404); + } + else if (request.getPathInContext().contains("error301")) + { + response.setStatus(301); + } + else if (request.getHeaders().get("echo") != null) + { + try (Blocking.Callback blocker = Blocking.callback()) + { + response.write(false, blocker, String.valueOf(request.getHeaders().get("echo"))); + blocker.block(); + } + } + else if (request.getPathInContext().contains("responseHeaders")) + { + response.addHeader("Header1", "value1"); + response.addHeader("Header2", "value2"); + } + else if (request.getPathInContext().contains("/abort")) + { + response.write(false, Callback.from(() -> callback.failed(new QuietException.Exception("test fail")), callback::failed), "data"); + return; + } + else if (request.getPathInContext().contains("delay")) + { + try + { + Thread.sleep(DELAY); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + + if (request.getContentLength() > 0) + Content.readAllBytes(request); + + callback.succeeded(); + } + } +} diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomResourcesMonitorTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomResourcesMonitorTest.java index e66f1c4a98f..676baf4c967 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomResourcesMonitorTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomResourcesMonitorTest.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.eclipse.jetty.server.handler.DumpHandler; import org.eclipse.jetty.util.thread.TimerScheduler; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; @@ -76,6 +77,7 @@ public class CustomResourcesMonitorTest @Test public void testFileOnDirectoryMonitor() throws Exception { + // TODO this test is too slow int monitorPeriod = _lowResourceMonitor.getPeriod(); int lowResourcesIdleTimeout = _lowResourceMonitor.getLowResourcesIdleTimeout(); assertThat(lowResourcesIdleTimeout, Matchers.lessThanOrEqualTo(monitorPeriod)); diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/DelayedServerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/DelayedServerTest.java index 75b2b467b3a..9147183f752 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/DelayedServerTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/DelayedServerTest.java @@ -15,11 +15,12 @@ package org.eclipse.jetty.server; import java.nio.ByteBuffer; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.internal.HttpConnection; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.thread.ThreadPool; import org.junit.jupiter.api.BeforeEach; /** @@ -30,12 +31,15 @@ public class DelayedServerTest extends HttpServerTestBase @BeforeEach public void init() throws Exception { - startServer(new ServerConnector(_server, new HttpConnectionFactory() + initServer(new ServerConnector(_server, new HttpConnectionFactory() { @Override public Connection newConnection(Connector connector, EndPoint endPoint) { - return configure(new DelayedHttpConnection(getHttpConfiguration(), connector, endPoint), connector, endPoint); + DelayedHttpConnection connection = new DelayedHttpConnection(getHttpConfiguration(), connector, endPoint); + connection.setUseInputDirectByteBuffers(isUseInputDirectByteBuffers()); + connection.setUseOutputDirectByteBuffers(isUseOutputDirectByteBuffers()); + return configure(connection, connector, endPoint); } })); } @@ -48,31 +52,36 @@ public class DelayedServerTest extends HttpServerTestBase } @Override - public void send(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback) + protected HttpStreamOverHTTP1 newHttpStream(String method, String uri, HttpVersion version) { - DelayedCallback delay = new DelayedCallback(callback, getServer().getThreadPool()); - super.send(request, response, content, lastContent, delay); + return new HttpStreamOverHTTP1(method, uri, version) + { + @Override + public void send(MetaData.Request request, MetaData.Response response, boolean last, Callback callback, ByteBuffer... content) + { + DelayedCallback delay = new DelayedCallback(callback); + super.send(request, response, last, delay, content); + } + }; } } private static class DelayedCallback extends Callback.Nested { - final ThreadPool pool; - - public DelayedCallback(Callback callback, ThreadPool threadPool) + public DelayedCallback(Callback callback) { super(callback); - pool = threadPool; } @Override public void succeeded() { - pool.execute(() -> + new Thread(() -> { try { - Thread.sleep(10); + Thread.sleep(2); + Thread.yield(); } catch (InterruptedException ignored) { @@ -82,13 +91,13 @@ public class DelayedServerTest extends HttpServerTestBase { super.succeeded(); } - }); + }).start(); } @Override public void failed(Throwable x) { - pool.execute(() -> + new Thread(() -> { try { @@ -102,7 +111,13 @@ public class DelayedServerTest extends HttpServerTestBase { super.failed(x); } - }); + }).start(); + } + + @Override + public InvocationType getInvocationType() + { + return InvocationType.NON_BLOCKING; } } } diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/DetectorConnectionTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/DetectorConnectionTest.java index 27310f6c3ed..e7169e3cf3e 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/DetectorConnectionTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/DetectorConnectionTest.java @@ -30,12 +30,16 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.logging.StacklessLogging; +import org.eclipse.jetty.server.handler.DumpHandler; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; @@ -214,6 +218,7 @@ public class DetectorConnectionTest } @Test + @Disabled // TODO public void testDetectingSslProxyToHttpNoSslWithProxy() throws Exception { String keystore = MavenTestingUtils.getTestResourceFile("keystore.p12").getAbsolutePath(); @@ -236,7 +241,7 @@ public class DetectorConnectionTest String response = getResponse(request); assertThat(response, Matchers.containsString("HTTP/1.1 200")); - assertThat(response, Matchers.containsString("pathInfo=/path")); + assertThat(response, Matchers.containsString("pathInContext=/path")); assertThat(response, Matchers.containsString("servername=server")); assertThat(response, Matchers.containsString("serverport=80")); assertThat(response, Matchers.containsString("localname=5.6.7.8")); @@ -343,7 +348,7 @@ public class DetectorConnectionTest // SSL matched, so the upgrade was made to proxy which itself upgraded to HTTP assertThat(response, Matchers.containsString("HTTP/1.1 200")); - assertThat(response, Matchers.containsString("pathInfo=/path")); + assertThat(response, Matchers.containsString("pathInContext=/path")); assertThat(response, Matchers.containsString("local=5.6.7.8:222")); assertThat(response, Matchers.containsString("remote=1.2.3.4:111")); } @@ -484,14 +489,17 @@ public class DetectorConnectionTest "3039" + // 12345 "1F90"; // 8080 - String httpReq = - "GET /path HTTP/1.1\n" + - "Host: server:80\n" + - "Connection: close\n" + - "\n"; - String response = getResponse(TypeUtil.fromHexString(proxyReq), httpReq.getBytes(StandardCharsets.US_ASCII)); + String httpReq = """ + GET /path HTTP/1.1 + Host: server:80 + Connection: close - assertThat(response, Matchers.nullValue()); + """; + try (StacklessLogging ignore = new StacklessLogging(DetectorConnectionFactory.class)) + { + String response = getResponse(StringUtil.fromHexString(proxyReq), httpReq.getBytes(StandardCharsets.US_ASCII)); + assertThat(response, Matchers.nullValue()); + } } @Test diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorProcessorTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorProcessorTest.java new file mode 100644 index 00000000000..cf65d8ea7bb --- /dev/null +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorProcessorTest.java @@ -0,0 +1,699 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.io.QuietException; +import org.eclipse.jetty.logging.StacklessLogging; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.ErrorProcessor; +import org.eclipse.jetty.server.internal.HttpChannelState; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.ajax.JSON; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.w3c.dom.Document; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ErrorProcessorTest +{ + StacklessLogging stacklessLogging; + Server server; + LocalConnector connector; + + @BeforeEach + public void before() throws Exception + { + stacklessLogging = new StacklessLogging(HttpChannelState.class); + server = new Server(); + connector = new LocalConnector(server); + server.addConnector(connector); + + server.setHandler(new Handler.Processor() + { + @Override + public void process(Request request, Response response, Callback callback) + { + if (request.getPathInContext().startsWith("/badmessage/")) + { + int code = Integer.parseInt(request.getPathInContext().substring(request.getPathInContext().lastIndexOf('/') + 1)); + throw new BadMessageException(code); + } + + // produce an exception with an JSON formatted cause message + if (request.getPathInContext().startsWith("/jsonmessage/")) + { + String message = "\"}, \"glossary\": {\n \"title\": \"example\"\n }\n {\""; + throw new TestException(message); + } + + // produce an exception with an XML cause message + if (request.getPathInContext().startsWith("/xmlmessage/")) + { + String message = + "\n" + + " \n" + + " example glossary\n" + + " "; + throw new TestException(message); + } + + // produce an exception with an HTML cause message + if (request.getPathInContext().startsWith("/htmlmessage/")) + { + String message = "
    %3Cscript%3E"; + throw new TestException(message); + } + + // produce an exception with a UTF-8 cause message + if (request.getPathInContext().startsWith("/utf8message/")) + { + // @checkstyle-disable-check : AvoidEscapedUnicodeCharacters + String message = "Euro is € and \u20AC and %E2%82%AC"; + // @checkstyle-enable-check : AvoidEscapedUnicodeCharacters + throw new TestException(message); + } + + Response.writeError(request, response, callback, 404); + } + }); + server.start(); + } + + @AfterEach + public void after() throws Exception + { + server.stop(); + stacklessLogging.close(); + } + + @Test + public void test404NoAccept() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=ISO-8859-1")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=ISO-8859-1\"")); + + assertContent(response); + } + + @Test + public void test404EmptyAccept() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Accept: \r\n" + + "Host: Localhost\r\n" + + "\r\n"); + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), is(0)); + assertThat("Response Content-Type", response.getField(HttpHeader.CONTENT_TYPE), is(nullValue())); + } + + @Test + public void test404UnAccept() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Accept: text/*;q=0\r\n" + + "Host: Localhost\r\n" + + "\r\n"); + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), is(0)); + assertThat("Response Content-Type", response.getField(HttpHeader.CONTENT_TYPE), is(nullValue())); + } + + @Test + public void test404AllAccept() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: */*\r\n" + + "\r\n"); + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=ISO-8859-1")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=ISO-8859-1\"")); + assertContent(response); + } + + @Test + public void test404HtmlAccept() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=ISO-8859-1")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=ISO-8859-1\"")); + assertContent(response); + } + + @Test + public void test404PostHttp10() throws Exception + { + String rawResponse = connector.getResponse( + "POST / HTTP/1.0\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html\r\n" + + "Content-Length: 10\r\n" + + "Connection: keep-alive\r\n" + + "\r\n" + + "0123456789"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat(response.getStatus(), is(404)); + assertThat(response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat(response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=ISO-8859-1")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=ISO-8859-1\"")); + assertThat(response.get(HttpHeader.CONNECTION), is("keep-alive")); + assertContent(response); + } + + @Test + public void test404PostHttp11() throws Exception + { + String rawResponse = connector.getResponse( + "POST / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html\r\n" + + "Content-Length: 10\r\n" + + "Connection: keep-alive\r\n" + // This is not need by HTTP/1.1 but sometimes sent anyway + "\r\n" + + "0123456789"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat(response.getStatus(), is(404)); + assertThat(response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat(response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=ISO-8859-1")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=ISO-8859-1\"")); + assertThat(response.getField(HttpHeader.CONNECTION), nullValue()); + assertContent(response); + } + + @Test + public void testMoreSpecificAccept() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html, some/other;specific=true\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=ISO-8859-1")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=ISO-8859-1\"")); + + assertContent(response); + } + + @Test + public void test404HtmlAcceptAnyCharset() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html\r\n" + + "Accept-Charset: *\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=UTF-8")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=UTF-8\"")); + + assertContent(response); + } + + @Test + public void test404HtmlAcceptUtf8Charset() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html\r\n" + + "Accept-Charset: utf-8\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=UTF-8")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=UTF-8\"")); + + assertContent(response); + } + + @Test + public void test404HtmlAcceptNotUtf8Charset() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html\r\n" + + "Accept-Charset: utf-8;q=0\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), is(0)); + } + + @Test + public void test404HtmlAcceptNotUtf8UnknownCharset() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html\r\n" + + "Accept-Charset: utf-8;q=0,unknown\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), is(0)); + assertThat("Response Content-Type", response.getField(HttpHeader.CONTENT_TYPE), is(nullValue())); + } + + @Test + public void test404HtmlAcceptUnknownUtf8Charset() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html\r\n" + + "Accept-Charset: utf-8;q=0.1,unknown\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=UTF-8")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=UTF-8\"")); + + assertContent(response); + } + + @Test + public void test404PreferHtml() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html;q=1.0,text/json;q=0.5,*/*\r\n" + + "Accept-Charset: *\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=UTF-8")); + assertThat(response.getContent(), containsString("Error 404 Not Found")); + + assertContent(response); + } + + @Test + public void test404PreferJson() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html;q=0.5,text/json;q=1.0,*/*\r\n" + + "Accept-Charset: *\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(404)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/json")); + + assertContent(response); + } + + @Test + public void testThrowBadMessage() throws Exception + { + String rawResponse = connector.getResponse( + "GET /badmessage/444 HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(444)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=ISO-8859-1")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=ISO-8859-1\"")); + + assertContent(response); + } + + @Test + public void testBadMessage() throws Exception + { + String rawResponse = connector.getResponse( + "GET / HTTP/1.1\r\n" + + "Host:\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(400)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=ISO-8859-1")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=ISO-8859-1\"")); + + assertContent(response); + } + + @Test + public void testNoBodyErrorHandler() throws Exception + { + server.setErrorProcessor((request, response, callback) -> + { + response.getHeaders().put(HttpHeader.LOCATION, "/error"); + response.getHeaders().put("X-Error-Message", String.valueOf(request.getAttribute(ErrorProcessor.ERROR_MESSAGE))); + response.getHeaders().put("X-Error-Status", Integer.toString(response.getStatus())); + response.setStatus(302); + response.write(true, callback); + }); + String rawResponse = connector.getResponse(""" + GET /no/host HTTP/1.1 + + """); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat(response.getStatus(), is(302)); + assertThat(response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), is(0)); + assertThat(response.get(HttpHeader.LOCATION), is("/error")); + assertThat(response.get("X-Error-Status"), is("400")); + assertThat(response.get("X-Error-Message"), is("No Host")); + } + + @ParameterizedTest + @ValueSource(strings = { + "/jsonmessage/", + "/xmlmessage/", + "/htmlmessage/", + "/utf8message/", + }) + public void testComplexCauseMessageNoAcceptHeader(String path) throws Exception + { + String rawResponse = connector.getResponse( + "GET " + path + " HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(500)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=ISO-8859-1")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=ISO-8859-1\"")); + + String content = assertContent(response); + + if (path.startsWith("/utf8")) + { + // we are Not expecting UTF-8 output, look for mangled ISO-8859-1 version + assertThat("content", content, containsString("Euro is &euro; and ? and %E2%82%AC")); + } + } + + @ParameterizedTest + @ValueSource(strings = { + "/jsonmessage/", + "/xmlmessage/", + "/htmlmessage/", + "/utf8message/", + }) + public void testComplexCauseMessageAcceptUtf8Header(String path) throws Exception + { + String rawResponse = connector.getResponse( + "GET " + path + " HTTP/1.1\r\n" + + "Host: Localhost\r\n" + + "Accept: text/html\r\n" + + "Accept-Charset: utf-8\r\n" + + "\r\n"); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + assertThat("Response status code", response.getStatus(), is(500)); + assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0)); + assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("text/html;charset=UTF-8")); + assertThat(response.getContent(), containsString("content=\"text/html;charset=UTF-8\"")); + + String content = assertContent(response); + + if (path.startsWith("/utf8")) + { + // @checkstyle-disable-check : AvoidEscapedUnicodeCharacters + // we are Not expecting UTF-8 output, look for mangled ISO-8859-1 version + assertThat("content", content, containsString("Euro is &euro; and \u20AC and %E2%82%AC")); + // @checkstyle-enabled-check : AvoidEscapedUnicodeCharacters + } + } + + private String assertContent(HttpTester.Response response) throws Exception + { + String contentType = response.get(HttpHeader.CONTENT_TYPE); + String content = response.getContent(); + + if (contentType.contains("text/html")) + { + assertThat(content, not(containsString("