├── netty-http-server ├── src │ ├── test │ │ ├── resources │ │ │ └── cl │ │ │ │ └── test.txt │ │ └── java │ │ │ └── org │ │ │ └── xbib │ │ │ └── netty │ │ │ └── http │ │ │ └── server │ │ │ └── test │ │ │ ├── package-info.java │ │ │ ├── hacks │ │ │ └── package-info.java │ │ │ ├── BindExceptionTest.java │ │ │ ├── NettyHttpTestExtension.java │ │ │ ├── ThreadLeakTest.java │ │ │ ├── http1 │ │ │ ├── BasicAuthTest.java │ │ │ └── StreamTest.java │ │ │ ├── ws1 │ │ │ └── EchoTest.java │ │ │ ├── MultiDomainServerTest.java │ │ │ ├── http2 │ │ │ └── StreamTest.java │ │ │ └── MultiDomainSecureServerTest.java │ ├── docs │ │ ├── asciidoclet │ │ │ └── overview.adoc │ │ └── asciidoc │ │ │ └── index.adoc │ └── main │ │ ├── java │ │ ├── org │ │ │ └── xbib │ │ │ │ └── netty │ │ │ │ └── http │ │ │ │ └── server │ │ │ │ ├── package-info.java │ │ │ │ ├── util │ │ │ │ ├── package-info.java │ │ │ │ ├── ExceptionFormatter.java │ │ │ │ └── MimeTypeUtils.java │ │ │ │ ├── handler │ │ │ │ ├── package-info.java │ │ │ │ ├── IdleTimeoutHandler.java │ │ │ │ ├── TrafficLoggingHandler.java │ │ │ │ └── ExtendedSNIHandler.java │ │ │ │ ├── protocol │ │ │ │ ├── ws2 │ │ │ │ │ ├── Http2WebSocketPathNotFoundException.java │ │ │ │ │ ├── Http2WebSocketChannelFutureListener.java │ │ │ │ │ ├── PathAcceptor.java │ │ │ │ │ ├── PathSubprotocolAcceptor.java │ │ │ │ │ ├── Http2WebSocketAcceptor.java │ │ │ │ │ ├── Http2WebSocketChannelInitializer.java │ │ │ │ │ └── Http2WebSocketServerHandler.java │ │ │ │ ├── http1 │ │ │ │ │ ├── Http1.java │ │ │ │ │ ├── HttpPipelinedRequest.java │ │ │ │ │ ├── HttpPipelinedResponse.java │ │ │ │ │ └── Http1Transport.java │ │ │ │ └── http2 │ │ │ │ │ └── Http2.java │ │ │ │ ├── AcceptState.java │ │ │ │ ├── ServerName.java │ │ │ │ ├── endpoint │ │ │ │ ├── HttpEndpointDescriptor.java │ │ │ │ └── service │ │ │ │ │ ├── MethodService.java │ │ │ │ │ └── FileService.java │ │ │ │ ├── security │ │ │ │ └── CertificateUtils.java │ │ │ │ └── BaseTransport.java │ │ └── module-info.java │ │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.xbib.netty.http.server.api.ServerProtocolProvider └── build.gradle ├── netty-http-client-api ├── build.gradle └── src │ └── main │ └── java │ ├── org │ └── xbib │ │ └── netty │ │ └── http │ │ └── client │ │ └── api │ │ ├── package-info.java │ │ ├── TimeoutListener.java │ │ ├── ExceptionListener.java │ │ ├── ResponseListener.java │ │ ├── WebSocketResponseListener.java │ │ ├── Pool.java │ │ ├── ClientProtocolProvider.java │ │ ├── UserAgent.java │ │ ├── BackOff.java │ │ └── ClientTransport.java │ └── module-info.java ├── netty-http-server-api ├── build.gradle └── src │ └── main │ └── java │ ├── org │ └── xbib │ │ └── netty │ │ └── http │ │ └── server │ │ └── api │ │ ├── FilterConfig.java │ │ ├── EndpointDescriptor.java │ │ ├── Resource.java │ │ ├── ServerProtocolProvider.java │ │ ├── EndpointResolver.java │ │ ├── Filter.java │ │ ├── Endpoint.java │ │ ├── Domain.java │ │ ├── ServerTransport.java │ │ ├── ServerCertificateProvider.java │ │ ├── ServerResponse.java │ │ └── ServerRequest.java │ └── module-info.java ├── netty-http-common ├── build.gradle └── src │ ├── main │ └── java │ │ ├── org │ │ └── xbib │ │ │ └── netty │ │ │ └── http │ │ │ └── common │ │ │ ├── Transport.java │ │ │ ├── package-info.java │ │ │ ├── security │ │ │ ├── Codec.java │ │ │ ├── HMac.java │ │ │ ├── Algo.java │ │ │ └── SecurityUtil.java │ │ │ ├── cookie │ │ │ ├── SameSite.java │ │ │ ├── CookieBox.java │ │ │ ├── CookieHeaderNames.java │ │ │ ├── CookieEncoder.java │ │ │ └── CookieDecoder.java │ │ │ ├── HttpMethod.java │ │ │ ├── PoolKey.java │ │ │ ├── NetworkClass.java │ │ │ ├── mime │ │ │ ├── MimeMultipartListener.java │ │ │ ├── MimeMultipart.java │ │ │ └── MimePart.java │ │ │ ├── NetworkProtocolVersion.java │ │ │ ├── HttpChannelInitializer.java │ │ │ ├── HttpHeaders.java │ │ │ ├── util │ │ │ ├── LRUCache.java │ │ │ ├── LimitedTreeMap.java │ │ │ ├── LimitedLinkedHashMap.java │ │ │ ├── LinkedHashSetMultiMap.java │ │ │ ├── LimitedSet.java │ │ │ ├── MultiMap.java │ │ │ ├── LimitedConcurrentHashMap.java │ │ │ ├── ParameterMap.java │ │ │ ├── HtmlUtils.java │ │ │ └── DateTimeUtil.java │ │ │ ├── TransportProvider.java │ │ │ ├── HttpResponse.java │ │ │ ├── HttpRequest.java │ │ │ ├── HttpStatus.java │ │ │ ├── DefaultHttpHeaders.java │ │ │ ├── ws │ │ │ ├── Http2WebSocketMessages.java │ │ │ ├── Preconditions.java │ │ │ ├── Http2WebSocket.java │ │ │ └── Http2WebSocketProtocol.java │ │ │ └── DefaultHttpResponse.java │ │ └── module-info.java │ └── test │ └── java │ └── org │ └── xbib │ └── netty │ └── http │ └── common │ └── test │ ├── NetworkUtilsTest.java │ ├── security │ └── CryptUtilTest.java │ └── HttpParametersTest.java ├── netty-http-client ├── src │ ├── docs │ │ ├── asciidoclet │ │ │ └── overview.adoc │ │ └── asciidoc │ │ │ └── index.adoc │ ├── main │ │ ├── java │ │ │ ├── org │ │ │ │ └── xbib │ │ │ │ │ └── netty │ │ │ │ │ └── http │ │ │ │ │ └── client │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── handler │ │ │ │ │ ├── http │ │ │ │ │ │ ├── package-info.java │ │ │ │ │ │ ├── HttpChunkContentCompressor.java │ │ │ │ │ │ ├── HttpResponseHandler.java │ │ │ │ │ │ └── TrafficLoggingHandler.java │ │ │ │ │ ├── http2 │ │ │ │ │ │ ├── package-info.java │ │ │ │ │ │ └── Http2ResponseHandler.java │ │ │ │ │ └── ws1 │ │ │ │ │ │ └── Http1WebSocketClientHandler.java │ │ │ │ │ ├── transport │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── Flow.java │ │ │ │ │ ├── ClientAuthMode.java │ │ │ │ │ ├── Http1.java │ │ │ │ │ └── Http2.java │ │ │ └── module-info.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── services │ │ │ └── org.xbib.netty.http.client.api.ClientProtocolProvider │ └── test │ │ ├── resources │ │ └── logging.properties │ │ └── java │ │ └── org │ │ └── xbib │ │ └── netty │ │ └── http │ │ └── client │ │ └── test │ │ ├── retry │ │ ├── MockBackOffTest.java │ │ └── MockBackOff.java │ │ ├── http2 │ │ ├── XbibTest.java │ │ └── GoogleTest.java │ │ ├── ThreadLeakTest.java │ │ ├── http2push │ │ └── Http2PushTest.java │ │ ├── NettyHttpTestExtension.java │ │ ├── http1 │ │ └── FileDescriptorLeakTest.java │ │ ├── conscrypt │ │ └── ConscryptTest.java │ │ ├── cookie │ │ ├── ClientCookieEncoderTest.java │ │ └── CookieSetterHttpBinTest.java │ │ ├── webtide │ │ └── WebtideTest.java │ │ ├── akamai │ │ └── AkamaiTest.java │ │ ├── CompletableFutureTest.java │ │ └── pool │ │ └── PooledClientTest.java ├── NOTICE.txt └── build.gradle ├── gradle.properties ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── ide │ └── idea.gradle ├── publishing │ ├── sonatype.gradle │ └── publication.gradle ├── documentation │ └── asciidoc.gradle ├── compile │ └── java.gradle └── test │ └── junit5.gradle ├── netty-http-bouncycastle ├── build.gradle └── src │ ├── main │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── org.xbib.netty.http.server.api.ServerCertificateProvider │ └── java │ │ ├── module-info.java │ │ └── org │ │ └── xbib │ │ └── netty │ │ └── http │ │ └── bouncycastle │ │ └── BouncyCastleSelfSignedCertificateProvider.java │ └── test │ └── java │ └── org │ └── xbib │ └── netty │ └── http │ └── bouncycastle │ └── SelfSignedCertificateTest.java ├── netty-http-server-rest ├── build.gradle └── src │ └── main │ └── java │ ├── org │ └── xbib │ │ └── netty │ │ └── http │ │ └── server │ │ └── rest │ │ ├── Rest.java │ │ ├── RestBuilder.java │ │ ├── RestConfig.java │ │ └── RestName.java │ └── module-info.java ├── netty-http-epoll ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── org.xbib.netty.http.common.TransportProvider │ │ └── java │ │ ├── module-info.java │ │ └── org │ │ └── xbib │ │ └── netty │ │ └── http │ │ └── epoll │ │ └── EpollTransportProvider.java └── build.gradle ├── netty-http-kqueue ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── org.xbib.netty.http.common.TransportProvider │ │ └── java │ │ ├── module-info.java │ │ └── org │ │ └── xbib │ │ └── netty │ │ └── http │ │ └── kqueue │ │ └── KqueueTransportProvider.java └── build.gradle ├── .gitignore ├── netty-http-client-rest ├── build.gradle └── src │ ├── main │ └── java │ │ ├── module-info.java │ │ └── org │ │ └── xbib │ │ └── netty │ │ └── http │ │ └── client │ │ └── rest │ │ └── RestClient.java │ └── test │ └── java │ └── org │ └── xbib │ └── netty │ └── http │ └── client │ └── rest │ ├── RestClientTest.java │ └── NettyHttpTestExtension.java ├── settings.gradle └── gradlew.bat /netty-http-server/src/test/resources/cl/test.txt: -------------------------------------------------------------------------------- 1 | Hello Jörg -------------------------------------------------------------------------------- /netty-http-server/src/docs/asciidoclet/overview.adoc: -------------------------------------------------------------------------------- 1 | = Netty HTTP server 2 | Jörg Prante 3 | 4 | -------------------------------------------------------------------------------- /netty-http-client-api/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api project(":netty-http-common") 3 | } 4 | -------------------------------------------------------------------------------- /netty-http-server-api/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api project(":netty-http-common") 3 | } 4 | -------------------------------------------------------------------------------- /netty-http-common/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api libs.net 3 | api libs.netty.http2 4 | } 5 | -------------------------------------------------------------------------------- /netty-http-client/src/docs/asciidoclet/overview.adoc: -------------------------------------------------------------------------------- 1 | = Netty HTTP client 2 | Jörg Prante 3 | Version 4.1.22.0 4 | 5 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group = org.xbib 2 | name = netty-http 3 | version = 4.1.85.0 4 | 5 | org.gradle.warning.mode = ALL 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jprante/netty-http/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /netty-http-bouncycastle/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api project(":netty-http-server-api") 3 | api libs.bouncycastle 4 | } 5 | -------------------------------------------------------------------------------- /netty-http-server-rest/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api project(":netty-http-server") 3 | implementation libs.guice 4 | } 5 | -------------------------------------------------------------------------------- /netty-http-epoll/src/main/resources/META-INF/services/org.xbib.netty.http.common.TransportProvider: -------------------------------------------------------------------------------- 1 | org.xbib.netty.http.epoll.EpollTransportProvider -------------------------------------------------------------------------------- /netty-http-kqueue/src/main/resources/META-INF/services/org.xbib.netty.http.common.TransportProvider: -------------------------------------------------------------------------------- 1 | org.xbib.netty.http.kqueue.KqueueTransportProvider -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/Transport.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | public interface Transport { 4 | } 5 | -------------------------------------------------------------------------------- /netty-http-server-rest/src/main/java/org/xbib/netty/http/server/rest/Rest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.rest; 2 | 3 | public class Rest { 4 | } 5 | -------------------------------------------------------------------------------- /netty-http-epoll/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api project(":netty-http-common") 3 | api(variantOf(libs.netty.epoll) { classifier('linux-x86_64') }) 4 | } 5 | -------------------------------------------------------------------------------- /netty-http-kqueue/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api project(":netty-http-common") 3 | api(variantOf(libs.netty.kqueue) { classifier('osx-x86_64') }) 4 | } 5 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes for Netty HTTP client. 3 | */ 4 | package org.xbib.netty.http.client; 5 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities for Netty HTTP client. 3 | */ 4 | package org.xbib.netty.http.common; 5 | -------------------------------------------------------------------------------- /netty-http-server-rest/src/main/java/org/xbib/netty/http/server/rest/RestBuilder.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.rest; 2 | 3 | public class RestBuilder { 4 | } 5 | -------------------------------------------------------------------------------- /netty-http-server-rest/src/main/java/org/xbib/netty/http/server/rest/RestConfig.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.rest; 2 | 3 | public class RestConfig { 4 | } 5 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes for Netty HTTP server. 3 | */ 4 | package org.xbib.netty.http.server; 5 | -------------------------------------------------------------------------------- /netty-http-client/src/main/resources/META-INF/services/org.xbib.netty.http.client.api.ClientProtocolProvider: -------------------------------------------------------------------------------- 1 | org.xbib.netty.http.client.Http1 2 | org.xbib.netty.http.client.Http2 -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/FilterConfig.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | public interface FilterConfig { 4 | } 5 | -------------------------------------------------------------------------------- /netty-http-server/src/test/java/org/xbib/netty/http/server/test/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests for Netty HTTP server. 3 | */ 4 | package org.xbib.netty.http.server.test; 5 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/security/Codec.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.security; 2 | 3 | public enum Codec { 4 | BASE64, HEX 5 | } 6 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/util/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities for Netty HTTP server. 3 | */ 4 | package org.xbib.netty.http.server.util; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /data 2 | /work 3 | /logs 4 | /.idea 5 | /target 6 | .DS_Store 7 | /.settings 8 | /.classpath 9 | /.project 10 | /.gradle 11 | build 12 | out 13 | *~ 14 | *.iml 15 | -------------------------------------------------------------------------------- /netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Listeners for Netty HTTP client. 3 | */ 4 | package org.xbib.netty.http.client.api; 5 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/handler/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Handlers for Netty HTTP server. 3 | */ 4 | package org.xbib.netty.http.server.handler; 5 | -------------------------------------------------------------------------------- /netty-http-bouncycastle/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ServerCertificateProvider: -------------------------------------------------------------------------------- 1 | org.xbib.netty.http.bouncycastle.BouncyCastleSelfSignedCertificateProvider -------------------------------------------------------------------------------- /netty-http-client-rest/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api project(':netty-http-client') 3 | api project(':netty-http-client-api') 4 | api project(':netty-http-common') 5 | } 6 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/SameSite.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.cookie; 2 | 3 | public enum SameSite { 4 | STRICT, LAX, NONE 5 | } 6 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * HTTP handlers for Netty HTTP client. 3 | */ 4 | package org.xbib.netty.http.client.handler.http; 5 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/transport/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes for transports in the Netty client. 3 | */ 4 | package org.xbib.netty.http.client.transport; 5 | -------------------------------------------------------------------------------- /gradle/ide/idea.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'idea' 2 | 3 | idea { 4 | module { 5 | outputDir file('build/classes/java/main') 6 | testOutputDir file('build/classes/java/test') 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http2/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * HTTP/2 handlers for Netty HTTP client. 3 | */ 4 | package org.xbib.netty.http.client.handler.http2; 5 | -------------------------------------------------------------------------------- /netty-http-server/src/test/java/org/xbib/netty/http/server/test/hacks/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Hacking Netty for showing server functions. 3 | */ 4 | package org.xbib.netty.http.server.test.hacks; 5 | -------------------------------------------------------------------------------- /netty-http-server/src/main/resources/META-INF/services/org.xbib.netty.http.server.api.ServerProtocolProvider: -------------------------------------------------------------------------------- 1 | org.xbib.netty.http.server.protocol.http1.Http1 2 | org.xbib.netty.http.server.protocol.http2.Http2 3 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/EndpointDescriptor.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | public interface EndpointDescriptor { 4 | 5 | String getSortKey(); 6 | } 7 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/HttpMethod.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | public enum HttpMethod { 4 | 5 | GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH 6 | } 7 | -------------------------------------------------------------------------------- /netty-http-client/NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | http2 web socket implementation based on the work of Maksym Ostroverkhov 3 | 4 | https://github.com/jauntsdn/netty-websocket-http2/ 5 | 6 | Apache License 2.0 7 | 8 | forked at 20 October 2021 9 | -------------------------------------------------------------------------------- /netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/TimeoutListener.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.api; 2 | 3 | @FunctionalInterface 4 | public interface TimeoutListener { 5 | 6 | void onTimeout(Request request); 7 | } 8 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/PoolKey.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | import java.net.InetSocketAddress; 4 | 5 | public interface PoolKey { 6 | 7 | InetSocketAddress getInetSocketAddress(); 8 | } 9 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/NetworkClass.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | /** 4 | * The network classes. 5 | */ 6 | public enum NetworkClass { 7 | 8 | ANY, LOOPBACK, LOCAL, PUBLIC 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ExceptionListener.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.api; 2 | 3 | @FunctionalInterface 4 | public interface ExceptionListener { 5 | 6 | void onException(Throwable throwable); 7 | } 8 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/ClientAuthMode.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client; 2 | 3 | /** 4 | * Client authentication modes, useful for SSL channels. 5 | */ 6 | public enum ClientAuthMode { 7 | NONE, WANT, NEED 8 | } 9 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/mime/MimeMultipartListener.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.mime; 2 | 3 | public interface MimeMultipartListener { 4 | 5 | void handle(String type, String subtype, MimeMultipart part); 6 | } 7 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/NetworkProtocolVersion.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | /** 4 | * The TCP/IP network protocol versions. 5 | */ 6 | public enum NetworkProtocolVersion { 7 | 8 | IPV4, IPV6, IPV46, NONE 9 | } 10 | -------------------------------------------------------------------------------- /netty-http-server-rest/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.xbib.netty.http.server.rest { 2 | exports org.xbib.netty.http.server.rest; 3 | exports org.xbib.netty.http.server.rest.util; 4 | requires org.xbib.netty.http.server; 5 | requires io.netty.transport; 6 | } 7 | -------------------------------------------------------------------------------- /netty-http-server/src/docs/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = Netty HTTP server 2 | Jörg Prante 3 | :sectnums: 4 | :toc: preamble 5 | :toclevels: 4 6 | :!toc-title: Content 7 | :experimental: 8 | :description: Netty HTTP server for Java 9 | :keywords: Java, Netty, HTTP, server 10 | :icons: font 11 | -------------------------------------------------------------------------------- /netty-http-client/src/docs/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = Netty HTTP client 2 | Jörg Prante 3 | Version 4.1.9.0 4 | :sectnums: 5 | :toc: preamble 6 | :toclevels: 4 7 | :!toc-title: Content 8 | :experimental: 9 | :description: asynchronous Netty HTTP client for Java 10 | :keywords: Java, Netty, HTTP, client 11 | :icons: font 12 | -------------------------------------------------------------------------------- /netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ResponseListener.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.api; 2 | 3 | import org.xbib.netty.http.common.HttpResponse; 4 | 5 | @FunctionalInterface 6 | public interface ResponseListener { 7 | 8 | void onResponse(R response); 9 | } 10 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/HttpChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandler; 5 | 6 | public interface HttpChannelInitializer extends ChannelHandler { 7 | 8 | void initChannel(Channel channel); 9 | } 10 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/mime/MimeMultipart.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.mime; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import java.util.Map; 5 | 6 | public interface MimeMultipart { 7 | 8 | Map headers(); 9 | 10 | ByteBuf body(); 11 | 12 | int length(); 13 | } 14 | -------------------------------------------------------------------------------- /netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/WebSocketResponseListener.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.api; 2 | 3 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 4 | 5 | @FunctionalInterface 6 | public interface WebSocketResponseListener { 7 | 8 | void onResponse(F frame); 9 | } 10 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/CookieBox.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.cookie; 2 | 3 | import org.xbib.netty.http.common.util.LRUCache; 4 | 5 | @SuppressWarnings("serial") 6 | public class CookieBox extends LRUCache { 7 | 8 | public CookieBox(int cacheSize) { 9 | super(cacheSize); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /netty-http-epoll/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.xbib.netty.http.epoll { 2 | exports org.xbib.netty.http.epoll; 3 | requires org.xbib.netty.http.common; 4 | requires io.netty.transport; 5 | requires io.netty.transport.classes.epoll; 6 | provides org.xbib.netty.http.common.TransportProvider with 7 | org.xbib.netty.http.epoll.EpollTransportProvider; 8 | } 9 | -------------------------------------------------------------------------------- /netty-http-client-rest/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.xbib.netty.http.client.rest { 2 | exports org.xbib.netty.http.client.rest; 3 | requires org.xbib.netty.http.common; 4 | requires org.xbib.netty.http.client; 5 | requires org.xbib.netty.http.client.api; 6 | requires io.netty.buffer; 7 | requires io.netty.codec.http; 8 | requires org.xbib.net; 9 | } 10 | -------------------------------------------------------------------------------- /netty-http-kqueue/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.xbib.netty.http.kqueue { 2 | exports org.xbib.netty.http.kqueue; 3 | requires org.xbib.netty.http.common; 4 | requires io.netty.transport; 5 | requires io.netty.transport.classes.kqueue; 6 | provides org.xbib.netty.http.common.TransportProvider with 7 | org.xbib.netty.http.kqueue.KqueueTransportProvider; 8 | } 9 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/security/HMac.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.security; 2 | 3 | public enum HMac { 4 | HMAC_SHA1("HMacSHA1"), 5 | HMAC_SHA256("HMacSHA256"); 6 | 7 | String algo; 8 | 9 | HMac(String algo) { 10 | this.algo = algo; 11 | } 12 | 13 | public String getAlgo() { 14 | return algo; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gradle/publishing/sonatype.gradle: -------------------------------------------------------------------------------- 1 | import java.time.Duration 2 | 3 | if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) { 4 | 5 | apply plugin: 'io.codearte.nexus-staging' 6 | 7 | nexusStaging { 8 | username = project.property('ossrhUsername') 9 | password = project.property('ossrhPassword') 10 | packageGroup = "org.xbib" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /netty-http-client-api/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.xbib.netty.http.client.api { 2 | exports org.xbib.netty.http.client.api; 3 | requires org.xbib.netty.http.common; 4 | requires org.xbib.net; 5 | requires io.netty.buffer; 6 | requires io.netty.common; 7 | requires io.netty.transport; 8 | requires io.netty.codec.http; 9 | requires io.netty.codec.http2; 10 | } 11 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/HttpHeaders.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | import java.util.Iterator; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | public interface HttpHeaders { 8 | 9 | String getHeader(CharSequence header); 10 | 11 | List getAllHeaders(CharSequence header); 12 | 13 | Iterator> iterator(); 14 | } 15 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.xbib.netty.http.server.api { 2 | exports org.xbib.netty.http.server.api; 3 | requires org.xbib.netty.http.common; 4 | requires org.xbib.net; 5 | requires io.netty.buffer; 6 | requires io.netty.common; 7 | requires io.netty.handler; 8 | requires io.netty.transport; 9 | requires io.netty.codec.http; 10 | requires io.netty.codec.http2; 11 | } 12 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Resource.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | import java.net.URL; 4 | import java.time.Instant; 5 | 6 | public interface Resource { 7 | 8 | String getResourcePath(); 9 | 10 | URL getURL(); 11 | 12 | Instant getLastModified(); 13 | 14 | long getLength(); 15 | 16 | boolean isDirectory(); 17 | 18 | String indexFileName(); 19 | } 20 | -------------------------------------------------------------------------------- /netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/Pool.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.api; 2 | 3 | import java.io.Closeable; 4 | 5 | public interface Pool extends Closeable { 6 | 7 | void prepare(int count) throws Exception; 8 | 9 | T acquire() throws Exception; 10 | 11 | void release(T t, boolean close) throws Exception; 12 | 13 | enum PoolKeySelectorType { 14 | RANDOM, ROUNDROBIN 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ClientProtocolProvider.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.api; 2 | 3 | import org.xbib.netty.http.common.HttpChannelInitializer; 4 | 5 | public interface ClientProtocolProvider { 6 | 7 | boolean supportsMajorVersion(int majorVersion); 8 | 9 | Class initializerClass(); 10 | 11 | Class transportClass(); 12 | } 13 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerProtocolProvider.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | import org.xbib.netty.http.common.HttpChannelInitializer; 4 | 5 | public interface ServerProtocolProvider { 6 | 7 | boolean supportsMajorVersion(int majorVersion); 8 | 9 | Class initializerClass(); 10 | 11 | Class transportClass(); 12 | } 13 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/CookieHeaderNames.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.cookie; 2 | 3 | public interface CookieHeaderNames { 4 | 5 | String PATH = "Path"; 6 | 7 | String EXPIRES = "Expires"; 8 | 9 | String MAX_AGE = "Max-Age"; 10 | 11 | String DOMAIN = "Domain"; 12 | 13 | String SECURE = "Secure"; 14 | 15 | String HTTPONLY = "HTTPOnly"; 16 | 17 | String SAMESITE = "SameSite"; 18 | } 19 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/ws2/Http2WebSocketPathNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.ws2; 2 | 3 | import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException; 4 | 5 | @SuppressWarnings("serial") 6 | public final class Http2WebSocketPathNotFoundException extends WebSocketHandshakeException { 7 | 8 | public Http2WebSocketPathNotFoundException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /netty-http-bouncycastle/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | import org.xbib.netty.http.server.api.ServerCertificateProvider; 2 | 3 | module org.xbib.netty.http.bouncycastle { 4 | exports org.xbib.netty.http.bouncycastle; 5 | requires org.xbib.netty.http.server.api; 6 | requires org.bouncycastle.pkix; 7 | requires org.bouncycastle.provider; 8 | provides ServerCertificateProvider with 9 | org.xbib.netty.http.bouncycastle.BouncyCastleSelfSignedCertificateProvider; 10 | } 11 | -------------------------------------------------------------------------------- /netty-http-common/src/test/java/org/xbib/netty/http/common/test/NetworkUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.xbib.netty.http.common.NetworkUtils; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | 8 | class NetworkUtilsTest { 9 | 10 | @Test 11 | void testInterfaces() throws InterruptedException { 12 | Logger.getLogger("test").log(Level.INFO, NetworkUtils.getNetworkInterfacesAsString()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/security/Algo.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.security; 2 | 3 | public enum Algo { 4 | MD5("MD5", "md5"), 5 | SHA("SHA","sha"), 6 | SHA256("SHA-256","sha256"), 7 | SHA512("SHA-512", "sha512"), 8 | SSHA("SHA1", "ssha"), 9 | SSHA256("SHA-256", "ssha"), 10 | SSHA512("SHA-512", "ssha"); 11 | 12 | String algo; 13 | 14 | String prefix; 15 | 16 | Algo(String algo, String prefix) { 17 | this.algo = algo; 18 | this.prefix = prefix; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/EndpointResolver.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | import org.xbib.netty.http.common.HttpMethod; 4 | import java.io.IOException; 5 | import java.util.List; 6 | 7 | public interface EndpointResolver> { 8 | 9 | List matchingEndpointsFor(String path, HttpMethod method, String contentType); 10 | 11 | void handle(E matchingEndpoint, 12 | ServerRequest serverRequest, 13 | ServerResponse serverResponse) throws IOException; 14 | } 15 | -------------------------------------------------------------------------------- /netty-http-client/src/test/resources/logging.properties: -------------------------------------------------------------------------------- 1 | handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler 2 | .level=ALL 3 | java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n 4 | java.util.logging.ConsoleHandler.level=ALL 5 | java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter 6 | java.util.logging.FileHandler.level=ALL 7 | java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter 8 | java.util.logging.FileHandler.pattern=build/test.log 9 | jdk.event.security.level=INFO 10 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/util/LRUCache.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.util; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | @SuppressWarnings("serial") 7 | public class LRUCache extends LinkedHashMap { 8 | 9 | private final int cacheSize; 10 | 11 | public LRUCache(int cacheSize) { 12 | super(16, 0.75f, true); 13 | this.cacheSize = cacheSize; 14 | } 15 | 16 | @Override 17 | protected boolean removeEldestEntry(Map.Entry eldest) { 18 | return size() >= cacheSize; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/TransportProvider.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | import io.netty.channel.EventLoopGroup; 4 | import io.netty.channel.socket.ServerSocketChannel; 5 | import io.netty.channel.socket.SocketChannel; 6 | import java.util.concurrent.ThreadFactory; 7 | 8 | public interface TransportProvider { 9 | 10 | EventLoopGroup createEventLoopGroup(int nThreads, ThreadFactory threadFactory); 11 | 12 | Class createSocketChannelClass(); 13 | 14 | Class createServerSocketChannelClass(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /netty-http-client/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api project(":netty-http-client-api") 3 | api project(":netty-http-common") 4 | api libs.netty.handler.proxy 5 | testImplementation libs.jackson 6 | testImplementation libs.conscrypt 7 | testRuntimeOnly libs.bouncycastle 8 | if (System.properties.get('os.name').toLowerCase().contains('linux') && 9 | System.properties.get('os.arch').toLowerCase().contains('amd64')) { 10 | testRuntimeOnly(variantOf(libs.netty.boringssl) { classifier('linux-x86_64') }) 11 | } else { 12 | testRuntimeOnly libs.netty.boringssl 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/HttpResponse.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | import org.xbib.netty.http.common.cookie.CookieBox; 6 | import java.io.InputStream; 7 | import java.nio.charset.Charset; 8 | 9 | public interface HttpResponse { 10 | 11 | HttpAddress getAddress(); 12 | 13 | HttpStatus getStatus(); 14 | 15 | HttpHeaders getHeaders(); 16 | 17 | CookieBox getCookies(); 18 | 19 | ByteBuf getBody(); 20 | 21 | InputStream getBodyAsStream(); 22 | 23 | String getBodyAsString(Charset charset); 24 | 25 | void release(); 26 | } 27 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedTreeMap.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.util; 2 | 3 | import java.util.SortedSet; 4 | import java.util.TreeMap; 5 | 6 | @SuppressWarnings("serial") 7 | public class LimitedTreeMap extends TreeMap> { 8 | 9 | private final int limit; 10 | 11 | public LimitedTreeMap(int limit) { 12 | this.limit = limit; 13 | } 14 | 15 | @Override 16 | public SortedSet put(K key, SortedSet value) { 17 | if (size() < limit) { 18 | return super.put(key, value); 19 | } 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /netty-http-client-rest/src/test/java/org/xbib/netty/http/client/rest/RestClientTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.rest; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | 6 | import java.io.IOException; 7 | import java.util.logging.Logger; 8 | 9 | @ExtendWith(NettyHttpTestExtension.class) 10 | class RestClientTest { 11 | 12 | private static final Logger logger = Logger.getLogger(RestClientTest.class.getName()); 13 | 14 | @Test 15 | void testSimpleGet() throws IOException { 16 | String result = RestClient.get("https://xbib.org").asString(); 17 | logger.info(result); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle/documentation/asciidoc.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.xbib.gradle.plugin.asciidoctor' 2 | 3 | asciidoctor { 4 | backends 'html5' 5 | outputDir = file("${rootProject.projectDir}/docs") 6 | separateOutputDirs = false 7 | attributes 'source-highlighter': 'coderay', 8 | idprefix: '', 9 | idseparator: '-', 10 | toc: 'left', 11 | doctype: 'book', 12 | icons: 'font', 13 | encoding: 'utf-8', 14 | sectlink: true, 15 | sectanchors: true, 16 | linkattrs: true, 17 | imagesdir: 'img', 18 | stylesheet: "${projectDir}/src/docs/asciidoc/css/foundation.css" 19 | } 20 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.xbib.netty.http.common { 2 | exports org.xbib.netty.http.common; 3 | exports org.xbib.netty.http.common.cookie; 4 | exports org.xbib.netty.http.common.mime; 5 | exports org.xbib.netty.http.common.security; 6 | exports org.xbib.netty.http.common.util; 7 | exports org.xbib.netty.http.common.ws; 8 | requires org.xbib.net; 9 | requires io.netty.buffer; 10 | requires io.netty.common; 11 | requires io.netty.transport; 12 | requires io.netty.handler; 13 | requires io.netty.codec; 14 | requires io.netty.codec.http; 15 | requires io.netty.codec.http2; 16 | requires java.logging; 17 | } 18 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/HttpRequest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | import java.io.InputStream; 4 | import java.util.Map; 5 | 6 | /** 7 | * A representation of an HTTP request. Contains methods to access all those parts of an HTTP request. 8 | */ 9 | public interface HttpRequest { 10 | 11 | String getMethod(); 12 | 13 | String getRequestUrl(); 14 | 15 | void setRequestUrl(String url); 16 | 17 | void setHeader(String name, String value); 18 | 19 | String getHeader(String name); 20 | 21 | Map getHeaders(); 22 | 23 | InputStream getContent(); 24 | 25 | String getContentType(); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedLinkedHashMap.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.util; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | @SuppressWarnings("serial") 6 | public class LimitedLinkedHashMap extends LinkedHashMap { 7 | 8 | private final int limit; 9 | 10 | public LimitedLinkedHashMap(int limit) { 11 | super(16, 0.75f, true); 12 | this.limit = limit; 13 | } 14 | 15 | @Override 16 | public V put(K key, V value) { 17 | if (size() < limit) { 18 | return super.put(key, value); 19 | } 20 | throw new IllegalArgumentException("size limit exceeded: " + limit); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Filter.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * A {@code Filter} is capable of serving requests for resources within its context. 7 | */ 8 | @FunctionalInterface 9 | public interface Filter { 10 | 11 | /** 12 | * Handles the given request by building and returning a response. 13 | * 14 | * @param serverRequest the request to be served 15 | * @param serverResponse the response to be written 16 | * @throws IOException if an IO error occurs 17 | */ 18 | void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException; 19 | } 20 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.http1; 2 | 3 | import org.xbib.netty.http.server.api.ServerProtocolProvider; 4 | 5 | public class Http1 implements ServerProtocolProvider { 6 | 7 | @Override 8 | public boolean supportsMajorVersion(int majorVersion) { 9 | return majorVersion == 1; 10 | } 11 | 12 | @Override 13 | public Class initializerClass() { 14 | return Http1ChannelInitializer.class; 15 | } 16 | 17 | @Override 18 | public Class transportClass() { 19 | return Http1Transport.class; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http2/Http2.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.http2; 2 | 3 | import org.xbib.netty.http.server.api.ServerProtocolProvider; 4 | 5 | public class Http2 implements ServerProtocolProvider { 6 | 7 | @Override 8 | public boolean supportsMajorVersion(int majorVersion) { 9 | return majorVersion == 2; 10 | } 11 | 12 | @Override 13 | public Class initializerClass() { 14 | return Http2ChannelInitializer.class; 15 | } 16 | 17 | @Override 18 | public Class transportClass() { 19 | return Http2Transport.class; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/util/LinkedHashSetMultiMap.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.util; 2 | 3 | import java.util.Collection; 4 | import java.util.LinkedHashMap; 5 | import java.util.LinkedHashSet; 6 | import java.util.Map; 7 | 8 | /** 9 | * Linked multi map. 10 | * 11 | * @param the key type parameter 12 | * @param the value type parameter 13 | */ 14 | public class LinkedHashSetMultiMap extends AbstractMultiMap { 15 | 16 | public LinkedHashSetMultiMap() { 17 | super(); 18 | } 19 | 20 | @Override 21 | Collection newValues() { 22 | return new LinkedHashSet<>(); 23 | } 24 | 25 | @Override 26 | Map> newMap() { 27 | return new LinkedHashMap<>(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /netty-http-server/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | api project(":netty-http-server-api") 4 | api project(":netty-http-common") 5 | implementation libs.net.path 6 | testImplementation project(":netty-http-client") 7 | testImplementation project(":netty-http-bouncycastle") 8 | testRuntimeOnly libs.javassist 9 | testRuntimeOnly libs.bouncycastle 10 | if (System.properties.get('os.name').toLowerCase().contains('linux') && 11 | System.properties.get('os.arch').toLowerCase().contains('amd64')) { 12 | testRuntimeOnly(variantOf(libs.netty.boringssl) { classifier('linux-x86_64') }) 13 | } else { 14 | testRuntimeOnly libs.netty.boringssl 15 | } 16 | testRuntimeOnly project(":netty-http-epoll") 17 | testRuntimeOnly project(":netty-http-kqueue") 18 | } 19 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Endpoint.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | import java.io.IOException; 4 | 5 | public interface Endpoint { 6 | 7 | String getPrefix(); 8 | 9 | String getPath(); 10 | 11 | boolean matches(D descriptor); 12 | 13 | ServerRequest resolveRequest(ServerRequest.Builder serverRequestBuilder, 14 | Domain>> domain, 15 | EndpointResolver> endpointResolver); 16 | 17 | void before(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException; 18 | 19 | void after(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException; 20 | } 21 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/Http1.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client; 2 | 3 | import org.xbib.netty.http.client.api.ClientProtocolProvider; 4 | import org.xbib.netty.http.client.handler.http.Http1ChannelInitializer; 5 | import org.xbib.netty.http.client.transport.Http1Transport; 6 | 7 | public class Http1 implements ClientProtocolProvider { 8 | 9 | @Override 10 | public boolean supportsMajorVersion(int majorVersion) { 11 | return majorVersion == 1; 12 | } 13 | 14 | @Override 15 | public Class initializerClass() { 16 | return Http1ChannelInitializer.class; 17 | } 18 | 19 | @Override 20 | public Class transportClass() { 21 | return Http1Transport.class; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/Http2.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client; 2 | 3 | import org.xbib.netty.http.client.api.ClientProtocolProvider; 4 | import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer; 5 | import org.xbib.netty.http.client.transport.Http2Transport; 6 | 7 | public class Http2 implements ClientProtocolProvider { 8 | 9 | @Override 10 | public boolean supportsMajorVersion(int majorVersion) { 11 | return majorVersion == 2; 12 | } 13 | 14 | @Override 15 | public Class initializerClass() { 16 | return Http2ChannelInitializer.class; 17 | } 18 | 19 | @Override 20 | public Class transportClass() { 21 | return Http2Transport.class; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gradle/compile/java.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'java-library' 3 | 4 | java { 5 | modularity.inferModulePath.set(true) 6 | } 7 | 8 | compileJava { 9 | sourceCompatibility = JavaVersion.VERSION_17 10 | targetCompatibility = JavaVersion.VERSION_17 11 | } 12 | 13 | compileTestJava { 14 | sourceCompatibility = JavaVersion.VERSION_17 15 | targetCompatibility = JavaVersion.VERSION_17 16 | } 17 | 18 | jar { 19 | manifest { 20 | attributes('Implementation-Title': project.name) 21 | attributes('Implementation-Version': project.version) 22 | } 23 | } 24 | 25 | tasks.withType(JavaCompile) { 26 | options.compilerArgs.add('-Xlint:all,-exports') 27 | options.encoding = 'UTF-8' 28 | } 29 | 30 | tasks.withType(Javadoc) { 31 | options.addStringOption('Xdoclint:none', '-quiet') 32 | options.encoding = 'UTF-8' 33 | } 34 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/HttpStatus.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus; 4 | 5 | public class HttpStatus { 6 | 7 | private final HttpResponseStatus httpResponseStatus; 8 | 9 | public HttpStatus(HttpResponseStatus httpResponseStatus) { 10 | this.httpResponseStatus = httpResponseStatus; 11 | } 12 | 13 | public int getCode() { 14 | return httpResponseStatus.code(); 15 | } 16 | 17 | public String getMessage() { 18 | return httpResponseStatus.codeAsText().toString(); 19 | } 20 | 21 | public String getReasonPhrase() { 22 | return httpResponseStatus.reasonPhrase(); 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return httpResponseStatus.toString(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedSet.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.util; 2 | 3 | import java.util.Objects; 4 | import java.util.TreeSet; 5 | 6 | @SuppressWarnings("serial") 7 | public class LimitedSet extends TreeSet { 8 | 9 | private final int sizeLimit; 10 | 11 | private final int elementMaximumLength; 12 | 13 | public LimitedSet(int sizeLimit, int elementMaximumLength) { 14 | this.sizeLimit = sizeLimit; 15 | this.elementMaximumLength = elementMaximumLength; 16 | } 17 | 18 | @Override 19 | public boolean add(T t) { 20 | Objects.requireNonNull(t); 21 | if (size() < sizeLimit && t.length() <= elementMaximumLength) { 22 | return super.add(t); 23 | } 24 | throw new IllegalArgumentException("limit exceeded"); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/AcceptState.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server; 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus; 4 | 5 | public enum AcceptState { 6 | 7 | OK(HttpResponseStatus.OK, null, null), 8 | MISSING_HOST_HEADER(HttpResponseStatus.BAD_REQUEST, "application/octet-stream", "missing 'Host' header"), 9 | EXPECTATION_FAILED(HttpResponseStatus.EXPECTATION_FAILED, null, null), 10 | UNSUPPORTED_HTTP_VERSION( HttpResponseStatus.BAD_REQUEST, "application/octet-stream", "unsupported HTTP version"); 11 | 12 | HttpResponseStatus status; 13 | 14 | String contentType; 15 | 16 | String content; 17 | 18 | AcceptState(HttpResponseStatus status, String contentType, String content) { 19 | this.status = status; 20 | this.contentType = contentType; 21 | this.content = content; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Domain.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | import io.netty.handler.ssl.SslContext; 4 | import org.xbib.netty.http.common.HttpAddress; 5 | import java.io.IOException; 6 | import java.security.cert.X509Certificate; 7 | import java.util.Collection; 8 | 9 | public interface Domain> { 10 | 11 | HttpAddress getHttpAddress(); 12 | 13 | String getName(); 14 | 15 | Collection getHttpEndpointResolvers(); 16 | 17 | SslContext getSslContext(); 18 | 19 | Collection getCertificateChain(); 20 | 21 | void handle(ServerRequest.Builder serverRequestBuilder, ServerResponse.Builder serverResponseBuilder) throws IOException; 22 | 23 | void handleAfterError(ServerRequest.Builder serverRequestBuilder, ServerResponse.Builder serverResponseBuilder, Throwable throwable); 24 | } 25 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerTransport.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http2.Http2Settings; 6 | import io.netty.util.AttributeKey; 7 | import org.xbib.netty.http.common.Transport; 8 | import java.io.IOException; 9 | 10 | public interface ServerTransport extends Transport { 11 | 12 | AttributeKey TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport"); 13 | 14 | void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException; 15 | 16 | void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) throws Exception; 17 | 18 | void exceptionReceived(ChannelHandlerContext ctx, Throwable throwable) throws IOException; 19 | } 20 | -------------------------------------------------------------------------------- /gradle/test/junit5.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | testImplementation libs.junit.jupiter.api 3 | testImplementation libs.junit.jupiter.params 4 | testImplementation libs.hamcrest 5 | testRuntimeOnly libs.junit.jupiter.engine 6 | } 7 | 8 | test { 9 | useJUnitPlatform() 10 | failFast = true 11 | maxHeapSize '1g' 12 | systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties' 13 | testLogging { 14 | events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED' 15 | } 16 | afterSuite { desc, result -> 17 | if (!desc.parent) { 18 | println "\nTest result: ${result.resultType}" 19 | println "Test summary: ${result.testCount} tests, " + 20 | "${result.successfulTestCount} succeeded, " + 21 | "${result.failedTestCount} failed, " + 22 | "${result.skippedTestCount} skipped" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerCertificateProvider.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | import java.io.InputStream; 4 | 5 | public interface ServerCertificateProvider { 6 | 7 | /** 8 | * Prepare the server certificate, if appropriate. 9 | * 10 | * @param fqdn the full qualified domain name. 11 | */ 12 | void prepare(String fqdn); 13 | 14 | /** 15 | * Returns the generated RSA private key file in PEM format. 16 | * @return input stream of private key 17 | */ 18 | InputStream getPrivateKey(); 19 | 20 | /** 21 | * Returns the generated X.509 certificate file in PEM format. 22 | * @return input stream of certificate 23 | */ 24 | InputStream getCertificateChain(); 25 | 26 | /** 27 | * A key password or null if key password is not required. 28 | * @return key password 29 | */ 30 | String getKeyPassword(); 31 | } 32 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/HttpChunkContentCompressor.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.handler.http; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelPromise; 6 | import io.netty.handler.codec.http.DefaultHttpContent; 7 | import io.netty.handler.codec.http.HttpContentCompressor; 8 | 9 | /** 10 | * Be sure you place the HttpChunkContentCompressor before the ChunkedWriteHandler. 11 | */ 12 | public class HttpChunkContentCompressor extends HttpContentCompressor { 13 | 14 | @Override 15 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 16 | if (msg instanceof ByteBuf) { 17 | ByteBuf byteBuf = (ByteBuf) msg; 18 | if (byteBuf.isReadable()) { 19 | msg = new DefaultHttpContent(byteBuf); 20 | } 21 | } 22 | super.write(ctx, msg, promise); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/retry/MockBackOffTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.retry; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.xbib.netty.http.client.api.BackOff; 5 | 6 | import java.io.IOException; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | /** 11 | * Tests {@link MockBackOff}. 12 | */ 13 | class MockBackOffTest { 14 | 15 | @Test 16 | void testNextBackOffMillis() throws IOException { 17 | subtestNextBackOffMillis(0, new MockBackOff()); 18 | subtestNextBackOffMillis(BackOff.STOP, new MockBackOff().setBackOffMillis(BackOff.STOP)); 19 | subtestNextBackOffMillis(42, new MockBackOff().setBackOffMillis(42)); 20 | } 21 | 22 | private void subtestNextBackOffMillis(long expectedValue, BackOff backOffPolicy) throws IOException { 23 | for (int i = 0; i < 10; i++) { 24 | assertEquals(expectedValue, backOffPolicy.nextBackOffMillis()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/DefaultHttpHeaders.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | import java.util.Iterator; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | public class DefaultHttpHeaders implements HttpHeaders { 8 | 9 | private final io.netty.handler.codec.http.HttpHeaders httpHeaders; 10 | 11 | public DefaultHttpHeaders(io.netty.handler.codec.http.HttpHeaders headers) { 12 | this.httpHeaders = headers; 13 | } 14 | 15 | @Override 16 | public String getHeader(CharSequence header) { 17 | return httpHeaders.get(header); 18 | } 19 | 20 | @Override 21 | public List getAllHeaders(CharSequence header) { 22 | return httpHeaders.getAll(header); 23 | } 24 | 25 | @Override 26 | public Iterator> iterator() { 27 | return httpHeaders.iteratorCharSequence(); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return httpHeaders.entries().toString(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/util/MultiMap.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.util; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | /** 8 | * MultiMap interface. 9 | * 10 | * @param the key type parameter 11 | * @param the value type parameter 12 | */ 13 | public interface MultiMap { 14 | 15 | void clear(); 16 | 17 | int size(); 18 | 19 | boolean isEmpty(); 20 | 21 | boolean containsKey(K key); 22 | 23 | Collection get(K key); 24 | 25 | Set keySet(); 26 | 27 | boolean put(K key, V value); 28 | 29 | void putAll(K key, Iterable values); 30 | 31 | void putAll(MultiMap map); 32 | 33 | Collection remove(K key); 34 | 35 | boolean remove(K key, V value); 36 | 37 | Map> asMap(); 38 | 39 | String getString(K key); 40 | 41 | String getString(K key, String defaultValue); 42 | 43 | Integer getInteger(K key, int defaultValue); 44 | 45 | Boolean getBoolean(K key, boolean defaultValue); 46 | } 47 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/mime/MimePart.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.mime; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Map; 6 | 7 | public class MimePart implements MimeMultipart { 8 | 9 | Map headers; 10 | 11 | ByteBuf body; 12 | 13 | int length; 14 | 15 | MimePart(Map headers, ByteBuf body) { 16 | this.headers = headers; 17 | this.body = body; 18 | this.length = body.readableBytes(); 19 | } 20 | 21 | @Override 22 | public Map headers() { 23 | return headers; 24 | } 25 | 26 | @Override 27 | public ByteBuf body() { 28 | return body; 29 | } 30 | 31 | @Override 32 | public int length() { 33 | return length; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | String b = body != null ? body.toString(StandardCharsets.UTF_8) : ""; 39 | return "headers=" + headers + " length=" + length + " body=" + b; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /netty-http-bouncycastle/src/test/java/org/xbib/netty/http/bouncycastle/SelfSignedCertificateTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.bouncycastle; 2 | 3 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.nio.charset.StandardCharsets; 8 | import java.security.SecureRandom; 9 | import java.security.Security; 10 | import java.util.logging.Logger; 11 | 12 | class SelfSignedCertificateTest { 13 | 14 | private static final Logger logger = Logger.getLogger("test"); 15 | 16 | @Test 17 | void testSelfSignedCertificate() throws Exception { 18 | Security.addProvider(new BouncyCastleProvider()); 19 | SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate(); 20 | selfSignedCertificate.generate("localhost", new SecureRandom(), 2048); 21 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 22 | selfSignedCertificate.exportPEM(outputStream); 23 | logger.info(new String(outputStream.toByteArray(), StandardCharsets.US_ASCII)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/http2/XbibTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.http2; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | import org.junit.jupiter.api.Test; 7 | import org.xbib.netty.http.client.Client; 8 | import org.xbib.netty.http.client.api.Request; 9 | 10 | public class XbibTest { 11 | 12 | private static final Logger logger = Logger.getLogger(XbibTest.class.getName()); 13 | 14 | @Test 15 | void testXbib() throws Exception { 16 | try (Client client = Client.builder() 17 | .enableDebug() 18 | .build()) { 19 | Request request = Request.get() 20 | .url("https://xbib.org/") 21 | .setVersion("HTTP/2.0") 22 | .setResponseListener(resp -> logger.log(Level.INFO, "got HTTP/2 response: " + 23 | resp.getHeaders() + resp.getBodyAsString(StandardCharsets.UTF_8))) 24 | .build(); 25 | client.execute(request).get().close(); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/ThreadLeakTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test; 2 | 3 | import org.junit.jupiter.api.AfterAll; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.TestInstance; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.xbib.netty.http.client.Client; 8 | 9 | import java.io.IOException; 10 | import java.util.Set; 11 | import java.util.logging.Level; 12 | import java.util.logging.Logger; 13 | 14 | @ExtendWith(NettyHttpTestExtension.class) 15 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 16 | class ThreadLeakTest { 17 | 18 | private static final Logger logger = Logger.getLogger(ThreadLeakTest.class.getName()); 19 | 20 | @Test 21 | void testForLeaks() throws IOException { 22 | Client client = new Client(); 23 | client.shutdownGracefully(); 24 | } 25 | 26 | @AfterAll 27 | void checkThreads() { 28 | Set threadSet = Thread.getAllStackTraces().keySet(); 29 | logger.log(Level.INFO, "threads = " + threadSet.size() ); 30 | threadSet.forEach( thread -> logger.log(Level.INFO, thread.toString())); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/handler/IdleTimeoutHandler.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.handler; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.timeout.IdleStateEvent; 6 | import io.netty.handler.timeout.IdleStateHandler; 7 | 8 | import java.text.MessageFormat; 9 | import java.util.logging.Level; 10 | import java.util.logging.Logger; 11 | 12 | /** 13 | * Idle timeout handler. 14 | */ 15 | @ChannelHandler.Sharable 16 | public class IdleTimeoutHandler extends IdleStateHandler { 17 | 18 | private final Logger logger = Logger.getLogger(IdleTimeoutHandler.class.getName()); 19 | 20 | public IdleTimeoutHandler(int idleTimeoutMillis) { 21 | super(idleTimeoutMillis, idleTimeoutMillis, idleTimeoutMillis); 22 | } 23 | 24 | @Override 25 | protected final void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) { 26 | if (!evt.isFirst()) { 27 | return; 28 | } 29 | logger.log(Level.WARNING, () -> MessageFormat.format("{0} closing an idle connection", ctx.channel())); 30 | ctx.close(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | import org.xbib.netty.http.client.Http1; 2 | import org.xbib.netty.http.client.Http2; 3 | 4 | module org.xbib.netty.http.client { 5 | uses org.xbib.netty.http.client.api.ClientProtocolProvider; 6 | uses org.xbib.netty.http.common.TransportProvider; 7 | exports org.xbib.netty.http.client; 8 | exports org.xbib.netty.http.client.cookie; 9 | exports org.xbib.netty.http.client.handler.http; 10 | exports org.xbib.netty.http.client.handler.http2; 11 | exports org.xbib.netty.http.client.pool; 12 | exports org.xbib.netty.http.client.retry; 13 | exports org.xbib.netty.http.client.transport; 14 | requires transitive org.xbib.netty.http.client.api; 15 | requires transitive org.xbib.netty.http.common; 16 | requires org.xbib.net; 17 | requires io.netty.handler.proxy; 18 | requires java.logging; 19 | requires io.netty.transport; 20 | requires io.netty.buffer; 21 | requires io.netty.codec.http; 22 | requires io.netty.codec.http2; 23 | requires io.netty.handler; 24 | requires io.netty.common; 25 | provides org.xbib.netty.http.client.api.ClientProtocolProvider with Http1, Http2; 26 | } 27 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/ws2/Http2WebSocketChannelFutureListener.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.ws2; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.util.concurrent.GenericFutureListener; 6 | import org.xbib.netty.http.common.ws.Http2WebSocketEvent; 7 | 8 | /** 9 | * ChannelFuture listener that gracefully closes websocket by sending empty DATA frame with 10 | * END_STREAM flag set. 11 | */ 12 | public final class Http2WebSocketChannelFutureListener implements GenericFutureListener { 13 | 14 | public static final Http2WebSocketChannelFutureListener CLOSE = new Http2WebSocketChannelFutureListener(); 15 | 16 | private Http2WebSocketChannelFutureListener() {} 17 | 18 | @Override 19 | public void operationComplete(ChannelFuture future) { 20 | Channel channel = future.channel(); 21 | Throwable cause = future.cause(); 22 | if (cause != null) { 23 | Http2WebSocketEvent.fireFrameWriteError(channel, cause); 24 | } 25 | channel.pipeline().fireUserEventTriggered(Http2WebSocketEvent.Http2WebSocketLocalCloseEvent.INSTANCE); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/CookieEncoder.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.cookie; 2 | 3 | /** 4 | * Parent of Client and Server side cookie encoders 5 | */ 6 | public abstract class CookieEncoder { 7 | 8 | protected final boolean strict; 9 | 10 | protected CookieEncoder(boolean strict) { 11 | this.strict = strict; 12 | } 13 | 14 | protected void validateCookie(String name, String value) { 15 | if (strict) { 16 | int pos; 17 | if ((pos = CookieUtil.firstInvalidCookieNameOctet(name)) >= 0) { 18 | throw new IllegalArgumentException("Cookie name contains an invalid char: " + (name.charAt(pos))); 19 | } 20 | CharSequence unwrappedValue = CookieUtil.unwrapValue(value); 21 | if (unwrappedValue == null) { 22 | throw new IllegalArgumentException("Cookie value wrapping quotes are not balanced: " + value); 23 | } 24 | if ((pos = CookieUtil.firstInvalidCookieValueOctet(unwrappedValue)) >= 0) { 25 | throw new IllegalArgumentException("Cookie value contains an invalid char: " + (value.charAt(pos))); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/http2/GoogleTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.http2; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.xbib.netty.http.client.Client; 5 | import org.xbib.netty.http.client.api.Request; 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.logging.Level; 8 | import java.util.logging.Logger; 9 | 10 | public class GoogleTest { 11 | 12 | private static final Logger logger = Logger.getLogger(GoogleTest.class.getName()); 13 | 14 | @Test 15 | void testSequentialRequests() throws Exception { 16 | Client client = Client.builder() 17 | .build(); 18 | try { 19 | // TODO decompression of frames 20 | Request request2 = Request.get().url("https://google.com") 21 | .setVersion("HTTP/2.0") 22 | .setResponseListener(resp -> logger.log(Level.INFO, "got HTTP/2 response: " + 23 | resp.getHeaders() + resp.getBodyAsString(StandardCharsets.UTF_8))) 24 | .build(); 25 | client.execute(request2).get(); 26 | } finally { 27 | client.shutdownGracefully(); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /netty-http-epoll/src/main/java/org/xbib/netty/http/epoll/EpollTransportProvider.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.epoll; 2 | 3 | import io.netty.channel.EventLoopGroup; 4 | import io.netty.channel.epoll.Epoll; 5 | import io.netty.channel.epoll.EpollEventLoopGroup; 6 | import io.netty.channel.epoll.EpollServerSocketChannel; 7 | import io.netty.channel.epoll.EpollSocketChannel; 8 | import io.netty.channel.socket.ServerSocketChannel; 9 | import io.netty.channel.socket.SocketChannel; 10 | import org.xbib.netty.http.common.TransportProvider; 11 | import java.util.concurrent.ThreadFactory; 12 | 13 | public class EpollTransportProvider implements TransportProvider { 14 | 15 | @Override 16 | public EventLoopGroup createEventLoopGroup(int nThreads, ThreadFactory threadFactory) { 17 | return Epoll.isAvailable() ? new EpollEventLoopGroup(nThreads, threadFactory) : null; 18 | } 19 | 20 | @Override 21 | public Class createSocketChannelClass() { 22 | return Epoll.isAvailable() ? EpollSocketChannel.class : null; 23 | } 24 | 25 | @Override 26 | public Class createServerSocketChannelClass() { 27 | return Epoll.isAvailable() ? EpollServerSocketChannel.class : null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/util/LimitedConcurrentHashMap.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.util; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import java.util.concurrent.Semaphore; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | @SuppressWarnings("serial") 8 | public class LimitedConcurrentHashMap extends ConcurrentHashMap { 9 | 10 | private final Semaphore semaphore; 11 | 12 | public LimitedConcurrentHashMap(int limit) { 13 | super(16, 0.75f); 14 | this.semaphore = new Semaphore(limit); 15 | } 16 | 17 | @Override 18 | public V put(K key, V value) { 19 | try { 20 | if (semaphore.tryAcquire(1L, TimeUnit.SECONDS)) { 21 | return super.put(key, value); 22 | } 23 | } catch (InterruptedException e) { 24 | throw new IllegalArgumentException("size limit exceeded"); 25 | } 26 | throw new IllegalArgumentException("size limit exceeded"); 27 | } 28 | 29 | @Override 30 | public V remove(Object key) { 31 | V v; 32 | try { 33 | v = super.remove(key); 34 | } finally { 35 | semaphore.release(); 36 | } 37 | return v; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /netty-http-kqueue/src/main/java/org/xbib/netty/http/kqueue/KqueueTransportProvider.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.kqueue; 2 | 3 | import io.netty.channel.EventLoopGroup; 4 | import io.netty.channel.kqueue.KQueue; 5 | import io.netty.channel.kqueue.KQueueEventLoopGroup; 6 | import io.netty.channel.kqueue.KQueueServerSocketChannel; 7 | import io.netty.channel.kqueue.KQueueSocketChannel; 8 | import io.netty.channel.socket.ServerSocketChannel; 9 | import io.netty.channel.socket.SocketChannel; 10 | import org.xbib.netty.http.common.TransportProvider; 11 | import java.util.concurrent.ThreadFactory; 12 | 13 | public class KqueueTransportProvider implements TransportProvider { 14 | 15 | @Override 16 | public EventLoopGroup createEventLoopGroup(int nThreads, ThreadFactory threadFactory) { 17 | return KQueue.isAvailable() ? new KQueueEventLoopGroup(nThreads, threadFactory) : null; 18 | } 19 | 20 | @Override 21 | public Class createSocketChannelClass() { 22 | return KQueue.isAvailable() ? KQueueSocketChannel.class : null; 23 | } 24 | 25 | @Override 26 | public Class createServerSocketChannelClass() { 27 | return KQueue.isAvailable() ? KQueueServerSocketChannel.class : null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/HttpResponseHandler.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.handler.http; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import io.netty.handler.codec.http.FullHttpResponse; 7 | import org.xbib.netty.http.client.api.ClientTransport; 8 | 9 | @ChannelHandler.Sharable 10 | public class HttpResponseHandler extends SimpleChannelInboundHandler { 11 | 12 | @Override 13 | public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse fullHttpResponse) throws Exception { 14 | ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get(); 15 | if (transport != null) { 16 | transport.responseReceived(ctx.channel(), null, fullHttpResponse); 17 | } 18 | } 19 | 20 | @Override 21 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 22 | ctx.fireExceptionCaught(cause); 23 | ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get(); 24 | if (transport != null) { 25 | transport.fail(ctx.channel(), cause); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/util/ParameterMap.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.util; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public interface ParameterMap extends Iterable> { 7 | 8 | String get(String name); 9 | 10 | List getAll(String name); 11 | 12 | List> entries(); 13 | 14 | boolean contains(String name); 15 | 16 | default boolean contains(String name, String value, boolean caseInsensitive) { 17 | return getAll(name).stream() 18 | .anyMatch(val -> caseInsensitive ? val.equalsIgnoreCase(value) : val.equals(value)); 19 | } 20 | 21 | boolean isEmpty(); 22 | 23 | List names(); 24 | 25 | ParameterMap add(String name, String value); 26 | 27 | ParameterMap add(String name, Iterable values); 28 | 29 | ParameterMap addAll(ParameterMap map); 30 | 31 | ParameterMap addAll(Map map); 32 | 33 | ParameterMap set(String name, String value); 34 | 35 | ParameterMap set(String name, Iterable values); 36 | 37 | ParameterMap setAll(ParameterMap map); 38 | 39 | ParameterMap remove(String name); 40 | 41 | ParameterMap clear(); 42 | 43 | int size(); 44 | } 45 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/ws2/PathAcceptor.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.ws2; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException; 6 | import io.netty.handler.codec.http2.Http2Headers; 7 | import io.netty.util.concurrent.Future; 8 | 9 | import java.util.List; 10 | 11 | public class PathAcceptor implements Http2WebSocketAcceptor { 12 | 13 | private final String path; 14 | 15 | private final ChannelHandler webSocketHandler; 16 | 17 | public PathAcceptor(String path, ChannelHandler webSocketHandler) { 18 | this.path = path; 19 | this.webSocketHandler = webSocketHandler; 20 | } 21 | 22 | @Override 23 | public Future accept(ChannelHandlerContext ctx, String path, List subprotocols, 24 | Http2Headers request, Http2Headers response) { 25 | if (subprotocols.isEmpty() && path.equals(this.path)) { 26 | return ctx.executor().newSucceededFuture(webSocketHandler); 27 | } 28 | return ctx.executor().newFailedFuture(new WebSocketHandshakeException(String.format("Path not found: %s , subprotocols: %s", path, subprotocols))); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | import org.xbib.netty.http.server.api.ServerCertificateProvider; 2 | import org.xbib.netty.http.server.protocol.http1.Http1; 3 | import org.xbib.netty.http.server.protocol.http2.Http2; 4 | 5 | module org.xbib.netty.http.server { 6 | uses ServerCertificateProvider; 7 | uses org.xbib.netty.http.server.api.ServerProtocolProvider; 8 | uses org.xbib.netty.http.common.TransportProvider; 9 | exports org.xbib.netty.http.server; 10 | exports org.xbib.netty.http.server.cookie; 11 | exports org.xbib.netty.http.server.endpoint; 12 | exports org.xbib.netty.http.server.endpoint.service; 13 | exports org.xbib.netty.http.server.handler; 14 | exports org.xbib.netty.http.server.protocol.http1; 15 | exports org.xbib.netty.http.server.protocol.http2; 16 | exports org.xbib.netty.http.server.util; 17 | requires transitive org.xbib.netty.http.server.api; 18 | requires transitive org.xbib.netty.http.common; 19 | requires org.xbib.net; 20 | requires org.xbib.net.path; 21 | requires java.logging; 22 | requires io.netty.buffer; 23 | requires io.netty.common; 24 | requires io.netty.handler; 25 | requires io.netty.transport; 26 | requires io.netty.codec.http; 27 | requires io.netty.codec.http2; 28 | provides org.xbib.netty.http.server.api.ServerProtocolProvider with Http1, Http2; 29 | } 30 | -------------------------------------------------------------------------------- /netty-http-server-rest/src/main/java/org/xbib/netty/http/server/rest/RestName.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.rest; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | 5 | import java.util.Optional; 6 | 7 | /** 8 | * Server name. 9 | */ 10 | public final class RestName { 11 | 12 | /** 13 | * The default value for {@code Server} header. 14 | */ 15 | private static final String SERVER_NAME = String.format("RestServer/%s (Java/%s/%s) (Netty/%s)", 16 | httpServerVersion(), javaVendor(), javaVersion(), nettyVersion()); 17 | 18 | private RestName() { 19 | } 20 | 21 | public static String getServerName() { 22 | return SERVER_NAME; 23 | } 24 | 25 | private static String httpServerVersion() { 26 | return Optional.ofNullable(Rest.class.getPackage().getImplementationVersion()) 27 | .orElse("unknown"); 28 | } 29 | 30 | private static String javaVendor() { 31 | return Optional.ofNullable(System.getProperty("java.vendor")) 32 | .orElse("unknown"); 33 | } 34 | 35 | private static String javaVersion() { 36 | return Optional.ofNullable(System.getProperty("java.version")) 37 | .orElse("unknown"); 38 | } 39 | 40 | private static String nettyVersion() { 41 | return Optional.ofNullable(Bootstrap.class.getPackage().getImplementationVersion()) 42 | .orElse("unknown"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/ServerName.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | 5 | import java.util.Optional; 6 | 7 | /** 8 | * Server name. 9 | */ 10 | public final class ServerName { 11 | 12 | /** 13 | * The default value for {@code Server} header. 14 | */ 15 | private static final String SERVER_NAME = String.format("NettyHttpServer/%s (Java/%s/%s) (Netty/%s)", 16 | httpServerVersion(), javaVendor(), javaVersion(), nettyVersion()); 17 | 18 | private ServerName() { 19 | } 20 | 21 | public static String getServerName() { 22 | return SERVER_NAME; 23 | } 24 | 25 | private static String httpServerVersion() { 26 | return Optional.ofNullable(Server.class.getPackage().getImplementationVersion()) 27 | .orElse("unknown"); 28 | } 29 | 30 | private static String javaVendor() { 31 | return Optional.ofNullable(System.getProperty("java.vendor")) 32 | .orElse("unknown"); 33 | } 34 | 35 | private static String javaVersion() { 36 | return Optional.ofNullable(System.getProperty("java.version")) 37 | .orElse("unknown"); 38 | } 39 | 40 | private static String nettyVersion() { 41 | return Optional.ofNullable(Bootstrap.class.getPackage().getImplementationVersion()) 42 | .orElse("unknown"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/handler/TrafficLoggingHandler.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.handler; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelPromise; 7 | import io.netty.handler.logging.LogLevel; 8 | import io.netty.handler.logging.LoggingHandler; 9 | 10 | /** 11 | * A Netty handler that logs the I/O traffic of a connection. 12 | */ 13 | @ChannelHandler.Sharable 14 | public class TrafficLoggingHandler extends LoggingHandler { 15 | 16 | public TrafficLoggingHandler(LogLevel level) { 17 | super("server", level); 18 | } 19 | 20 | @Override 21 | public void channelRegistered(ChannelHandlerContext ctx) { 22 | ctx.fireChannelRegistered(); 23 | } 24 | 25 | @Override 26 | public void channelUnregistered(ChannelHandlerContext ctx) { 27 | ctx.fireChannelUnregistered(); 28 | } 29 | 30 | @Override 31 | public void flush(ChannelHandlerContext ctx) { 32 | ctx.flush(); 33 | } 34 | 35 | @Override 36 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 37 | if (msg instanceof ByteBuf && !((ByteBuf) msg).isReadable()) { 38 | ctx.write(msg, promise); 39 | } else { 40 | super.write(ctx, msg, promise); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/UserAgent.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.api; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | 5 | import java.util.Optional; 6 | 7 | /** 8 | * HTTP client user agent. 9 | */ 10 | public final class UserAgent { 11 | 12 | /** 13 | * The default value for {@code User-Agent}. 14 | */ 15 | private static final String USER_AGENT = String.format("NettyHttpClient/%s (Java/%s/%s) (Netty/%s)", 16 | httpClientVersion(), javaVendor(), javaVersion(), nettyVersion()); 17 | 18 | private UserAgent() { 19 | } 20 | 21 | public static String getUserAgent() { 22 | return USER_AGENT; 23 | } 24 | 25 | private static String httpClientVersion() { 26 | return Optional.ofNullable(UserAgent.class.getPackage().getImplementationVersion()) 27 | .orElse("unknown"); 28 | } 29 | 30 | private static String javaVendor() { 31 | return Optional.ofNullable(System.getProperty("java.vendor")) 32 | .orElse("unknown"); 33 | } 34 | 35 | private static String javaVersion() { 36 | return Optional.ofNullable(System.getProperty("java.version")) 37 | .orElse("unknown"); 38 | } 39 | 40 | private static String nettyVersion() { 41 | return Optional.ofNullable(Bootstrap.class.getPackage().getImplementationVersion()) 42 | .orElse("unknown"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/TrafficLoggingHandler.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.handler.http; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelPromise; 7 | import io.netty.handler.logging.LogLevel; 8 | import io.netty.handler.logging.LoggingHandler; 9 | 10 | /** 11 | * A Netty handler that logs the I/O traffic of a connection. 12 | */ 13 | @ChannelHandler.Sharable 14 | public class TrafficLoggingHandler extends LoggingHandler { 15 | 16 | public TrafficLoggingHandler(LogLevel level) { 17 | super("client", level); 18 | } 19 | 20 | @Override 21 | public void channelRegistered(ChannelHandlerContext ctx) { 22 | ctx.fireChannelRegistered(); 23 | } 24 | 25 | @Override 26 | public void channelUnregistered(ChannelHandlerContext ctx) { 27 | ctx.fireChannelUnregistered(); 28 | } 29 | 30 | @Override 31 | public void flush(ChannelHandlerContext ctx) { 32 | ctx.flush(); 33 | } 34 | 35 | @Override 36 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 37 | if (msg instanceof ByteBuf && !((ByteBuf) msg).isReadable()) { 38 | ctx.write(msg, promise); 39 | } else { 40 | super.write(ctx, msg, promise); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /netty-http-client-rest/src/test/java/org/xbib/netty/http/client/rest/NettyHttpTestExtension.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.rest; 2 | 3 | import org.junit.jupiter.api.extension.BeforeAllCallback; 4 | import org.junit.jupiter.api.extension.ExtensionContext; 5 | import java.util.logging.ConsoleHandler; 6 | import java.util.logging.Handler; 7 | import java.util.logging.Level; 8 | import java.util.logging.LogManager; 9 | import java.util.logging.Logger; 10 | import java.util.logging.SimpleFormatter; 11 | 12 | public class NettyHttpTestExtension implements BeforeAllCallback { 13 | 14 | @Override 15 | public void beforeAll(ExtensionContext context) { 16 | System.setProperty("io.netty.noUnsafe", Boolean.toString(true)); 17 | System.setProperty("io.netty.leakDetection.level", "ADVANCED"); 18 | Level level = Level.INFO; 19 | System.setProperty("java.util.logging.SimpleFormatter.format", 20 | "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n"); 21 | LogManager.getLogManager().reset(); 22 | Logger rootLogger = LogManager.getLogManager().getLogger(""); 23 | Handler handler = new ConsoleHandler(); 24 | handler.setFormatter(new SimpleFormatter()); 25 | rootLogger.addHandler(handler); 26 | rootLogger.setLevel(level); 27 | for (Handler h : rootLogger.getHandlers()) { 28 | handler.setFormatter(new SimpleFormatter()); 29 | h.setLevel(level); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /netty-http-common/src/test/java/org/xbib/netty/http/common/test/security/CryptUtilTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.test.security; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.xbib.netty.http.common.security.CryptUtil; 5 | 6 | import java.nio.charset.StandardCharsets; 7 | import java.security.InvalidKeyException; 8 | import java.security.NoSuchAlgorithmException; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | class CryptUtilTest { 13 | 14 | @Test 15 | void testRfc2307() throws NoSuchAlgorithmException { 16 | assertEquals("{md5}ixqZU8RhEpaoJ6v4xHgE1w==", 17 | CryptUtil.md5("Hello")); 18 | assertEquals("{sha}9/+ei3uy4Jtwk1pdeF4MxdnQq/A=", 19 | CryptUtil.sha("Hello")); 20 | assertEquals("{sha256}GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=", 21 | CryptUtil.sha256("Hello")); 22 | assertEquals("{sha512}NhX4DJ0pPtdAJof5SyLVjlKbjMeRb4+sf933+9WvTPd309eVp6AKFr9+fz+5Vh7puq5IDan+ehh2nnGIawPzFQ==", 23 | CryptUtil.sha512("Hello")); 24 | } 25 | 26 | @Test 27 | void testHmac() throws InvalidKeyException, NoSuchAlgorithmException { 28 | assertEquals("Wgxn2SLeDKU+MGJQ5oWMH20sSUM=", 29 | CryptUtil.hmacSHA1(StandardCharsets.ISO_8859_1, "hello", "world")); 30 | assertEquals("PPp27xSTfBwOpRn4/AV6gPzQSnQg+Oi80KdWfCcuAHs=", 31 | CryptUtil.hmacSHA256(StandardCharsets.ISO_8859_1, "hello", "world")); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/cookie/CookieDecoder.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.cookie; 2 | 3 | import java.nio.CharBuffer; 4 | 5 | /** 6 | * Parent of Client and Server side cookie decoders. 7 | */ 8 | public abstract class CookieDecoder { 9 | 10 | protected final boolean strict; 11 | 12 | protected CookieDecoder(boolean strict) { 13 | this.strict = strict; 14 | } 15 | 16 | protected DefaultCookie initCookie(String header, int nameBegin, int nameEnd, int valueBegin, int valueEnd) { 17 | if (nameBegin == -1 || nameBegin == nameEnd) { 18 | return null; 19 | } 20 | if (valueBegin == -1) { 21 | return null; 22 | } 23 | CharSequence wrappedValue = CharBuffer.wrap(header, valueBegin, valueEnd); 24 | CharSequence unwrappedValue = CookieUtil.unwrapValue(wrappedValue); 25 | if (unwrappedValue == null) { 26 | return null; 27 | } 28 | String name = header.substring(nameBegin, nameEnd); 29 | if (strict && CookieUtil.firstInvalidCookieNameOctet(name) >= 0) { 30 | return null; 31 | } 32 | final boolean wrap = unwrappedValue.length() != valueEnd - valueBegin; 33 | if (strict && CookieUtil.firstInvalidCookieValueOctet(unwrappedValue) >= 0) { 34 | return null; 35 | } 36 | DefaultCookie cookie = new DefaultCookie(name, unwrappedValue.toString()); 37 | cookie.setWrap(wrap); 38 | return cookie; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/ws/Http2WebSocketMessages.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.ws; 2 | 3 | public interface Http2WebSocketMessages { 4 | String HANDSHAKE_UNEXPECTED_RESULT = 5 | "websocket handshake error: unexpected result - status=200, end_of_stream=true"; 6 | String HANDSHAKE_UNSUPPORTED_VERSION = 7 | "websocket handshake error: unsupported version; supported versions - "; 8 | String HANDSHAKE_BAD_REQUEST = 9 | "websocket handshake error: bad request"; 10 | String HANDSHAKE_PATH_NOT_FOUND = 11 | "websocket handshake error: path not found - "; 12 | String HANDSHAKE_PATH_NOT_FOUND_SUBPROTOCOLS = 13 | ", subprotocols - "; 14 | String HANDSHAKE_UNEXPECTED_SUBPROTOCOL = 15 | "websocket handshake error: unexpected subprotocol - "; 16 | String HANDSHAKE_GENERIC_ERROR = 17 | "websocket handshake error: "; 18 | String HANDSHAKE_UNSUPPORTED_ACCEPTOR_TYPE = 19 | "websocket handshake error: async acceptors are not supported"; 20 | String HANDSHAKE_UNSUPPORTED_BOOTSTRAP = 21 | "websocket handshake error: bootstrapping websockets with http2 is not supported by server"; 22 | String HANDSHAKE_INVALID_REQUEST_HEADERS = 23 | "websocket handshake error: invalid request headers"; 24 | String HANDSHAKE_INVALID_RESPONSE_HEADERS = 25 | "websocket handshake error: invalid response headers"; 26 | String WRITE_ERROR = "websocket frame write error"; 27 | } 28 | -------------------------------------------------------------------------------- /netty-http-server/src/test/java/org/xbib/netty/http/server/test/BindExceptionTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.test; 2 | 3 | import io.netty.channel.ChannelFuture; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.xbib.netty.http.common.HttpAddress; 7 | import org.xbib.netty.http.server.Server; 8 | import org.xbib.netty.http.server.HttpServerDomain; 9 | 10 | import java.io.IOException; 11 | import java.net.BindException; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertNotNull; 14 | import static org.junit.jupiter.api.Assertions.fail; 15 | 16 | class BindExceptionTest { 17 | 18 | @Test 19 | void testDoubleServer() throws IOException { 20 | HttpServerDomain domain = HttpServerDomain.builder(HttpAddress.http1("localhost", 8008)) 21 | .singleEndpoint("/", (request, response) -> response.write("Hello World")) 22 | .build(); 23 | Server server1 = Server.builder(domain).build(); 24 | Server server2 = Server.builder(domain).build(); 25 | try { 26 | Assertions.assertThrows(BindException.class, () ->{ 27 | ChannelFuture channelFuture1 = server1.accept(); 28 | assertNotNull(channelFuture1); 29 | ChannelFuture channelFuture2 = server2.accept(); 30 | // should crash with BindException 31 | fail(); 32 | }); 33 | } finally { 34 | server1.shutdownGracefully(); 35 | server2.shutdownGracefully(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/http2push/Http2PushTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.http2push; 2 | 3 | import io.netty.handler.codec.http.HttpMethod; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.xbib.netty.http.client.Client; 8 | import org.xbib.netty.http.client.api.Request; 9 | import org.xbib.netty.http.client.test.NettyHttpTestExtension; 10 | 11 | import java.io.IOException; 12 | import java.util.logging.Level; 13 | import java.util.logging.Logger; 14 | 15 | @Disabled // /http2-push.io "connection refused" 16 | @ExtendWith(NettyHttpTestExtension.class) 17 | class Http2PushTest { 18 | 19 | private static final Logger logger = Logger.getLogger(Http2PushTest.class.getName()); 20 | 21 | @Test 22 | void testHttp2PushIO() throws IOException { 23 | String url = "https://http2-push.io"; 24 | Client client = Client.builder() 25 | .addServerNameForIdentification("http2-push.io") 26 | .build(); 27 | try { 28 | Request request = Request.builder(HttpMethod.GET) 29 | .url(url).setVersion("HTTP/2.0") 30 | .setResponseListener(resp -> logger.log(Level.INFO, 31 | "got response: " + resp.getHeaders() + " status=" + resp.getStatus())) 32 | .build(); 33 | client.execute(request).get(); 34 | 35 | } finally { 36 | client.shutdownGracefully(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /netty-http-bouncycastle/src/main/java/org/xbib/netty/http/bouncycastle/BouncyCastleSelfSignedCertificateProvider.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.bouncycastle; 2 | 3 | import org.bouncycastle.operator.OperatorCreationException; 4 | import org.xbib.netty.http.server.api.ServerCertificateProvider; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.UncheckedIOException; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.security.NoSuchProviderException; 10 | import java.security.SecureRandom; 11 | 12 | public class BouncyCastleSelfSignedCertificateProvider implements ServerCertificateProvider { 13 | 14 | private final SelfSignedCertificate selfSignedCertificate; 15 | 16 | public BouncyCastleSelfSignedCertificateProvider() { 17 | this.selfSignedCertificate = new SelfSignedCertificate(); 18 | } 19 | 20 | @Override 21 | public void prepare(String fqdn) { 22 | try { 23 | selfSignedCertificate.generate(fqdn, new SecureRandom(), 2048); 24 | } catch (IOException | NoSuchProviderException | NoSuchAlgorithmException | OperatorCreationException e) { 25 | throw new UncheckedIOException(new IOException(e)); 26 | } 27 | } 28 | 29 | @Override 30 | public InputStream getPrivateKey() { 31 | return selfSignedCertificate.privateKey(); 32 | } 33 | 34 | @Override 35 | public InputStream getCertificateChain() { 36 | return selfSignedCertificate.certificate(); 37 | } 38 | 39 | @Override 40 | public String getKeyPassword() { 41 | return null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/NettyHttpTestExtension.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test; 2 | 3 | import org.junit.jupiter.api.extension.BeforeAllCallback; 4 | import org.junit.jupiter.api.extension.ExtensionContext; 5 | 6 | import java.util.logging.ConsoleHandler; 7 | import java.util.logging.Handler; 8 | import java.util.logging.Level; 9 | import java.util.logging.LogManager; 10 | import java.util.logging.Logger; 11 | import java.util.logging.SimpleFormatter; 12 | 13 | public class NettyHttpTestExtension implements BeforeAllCallback { 14 | 15 | @Override 16 | public void beforeAll(ExtensionContext context) { 17 | //if (Security.getProvider("BC") == null) { 18 | // Security.addProvider(new BouncyCastleProvider()); 19 | //} 20 | System.setProperty("io.netty.noUnsafe", Boolean.toString(true)); 21 | // System.setProperty("io.netty.leakDetection.level", "paranoid"); 22 | Level level = Level.INFO; 23 | System.setProperty("java.util.logging.SimpleFormatter.format", 24 | "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n"); 25 | LogManager.getLogManager().reset(); 26 | Logger rootLogger = LogManager.getLogManager().getLogger(""); 27 | Handler handler = new ConsoleHandler(); 28 | handler.setFormatter(new SimpleFormatter()); 29 | rootLogger.addHandler(handler); 30 | rootLogger.setLevel(level); 31 | for (Handler h : rootLogger.getHandlers()) { 32 | handler.setFormatter(new SimpleFormatter()); 33 | h.setLevel(level); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/HttpEndpointDescriptor.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.endpoint; 2 | 3 | import org.xbib.netty.http.common.HttpMethod; 4 | import org.xbib.netty.http.server.api.EndpointDescriptor; 5 | 6 | public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable { 7 | 8 | private final String path; 9 | 10 | private final HttpMethod method; 11 | 12 | private final String contentType; 13 | 14 | public HttpEndpointDescriptor(String path, HttpMethod method, String contentType) { 15 | this.path = path; 16 | this.method = method; 17 | this.contentType = contentType; 18 | } 19 | 20 | @Override 21 | public String getSortKey() { 22 | return path; 23 | } 24 | 25 | public String getPath() { 26 | return path; 27 | } 28 | 29 | public HttpMethod getMethod() { 30 | return method; 31 | } 32 | 33 | public String getContentType() { 34 | return contentType; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "[HttpEndpointDescriptor:path=" + path + ",method=" + method + ",contentType=" + contentType + "]"; 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return toString().hashCode(); 45 | } 46 | 47 | @Override 48 | public boolean equals(Object o) { 49 | return o instanceof HttpEndpointDescriptor && toString().equals(o.toString()); 50 | } 51 | 52 | @Override 53 | public int compareTo(HttpEndpointDescriptor o) { 54 | return toString().compareTo(o.toString()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/MethodService.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.endpoint.service; 2 | 3 | import org.xbib.netty.http.server.api.Filter; 4 | import org.xbib.netty.http.server.api.FilterConfig; 5 | import org.xbib.netty.http.server.api.ServerRequest; 6 | import org.xbib.netty.http.server.api.ServerResponse; 7 | 8 | import java.io.IOException; 9 | import java.lang.reflect.Method; 10 | 11 | /** 12 | * The {@code MethodHandler} invokes a handler method on a specified object. 13 | * The method must have the same signature and contract as 14 | * {@link Filter#handle}, but can have an arbitrary name. 15 | */ 16 | public class MethodService implements Filter { 17 | 18 | private final Method m; 19 | 20 | private final Object obj; 21 | 22 | public MethodService(Method m, Object obj) throws IllegalArgumentException { 23 | this.m = m; 24 | this.obj = obj; 25 | Class[] params = m.getParameterTypes(); 26 | if (params.length != 2 || 27 | !ServerRequest.class.isAssignableFrom(params[0]) || 28 | !ServerResponse.class.isAssignableFrom(params[1]) || 29 | !int.class.isAssignableFrom(m.getReturnType())) { 30 | throw new IllegalArgumentException("invalid method signature: " + m); 31 | } 32 | } 33 | 34 | public void initialize(FilterConfig filterConfig) { 35 | } 36 | 37 | @Override 38 | public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { 39 | try { 40 | m.invoke(obj, serverRequest, serverResponse); 41 | } catch (Exception e) { 42 | throw new IOException(e); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/http1/FileDescriptorLeakTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.http1; 2 | 3 | import com.sun.management.UnixOperatingSystemMXBean; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.api.Test; 6 | import org.xbib.netty.http.client.Client; 7 | import org.xbib.netty.http.client.api.Request; 8 | import java.lang.management.ManagementFactory; 9 | import java.lang.management.OperatingSystemMXBean; 10 | import java.util.logging.Level; 11 | import java.util.logging.Logger; 12 | 13 | @Disabled 14 | class FileDescriptorLeakTest { 15 | 16 | private static final Logger logger = Logger.getLogger(FileDescriptorLeakTest.class.getName()); 17 | 18 | @Test 19 | void testFileLeak() throws Exception { 20 | OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); 21 | for (int i = 0; i< 10; i++) { 22 | if (os instanceof UnixOperatingSystemMXBean) { 23 | logger.info("before: number of open file descriptor : " + ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount()); 24 | } 25 | try (Client client = Client.builder().setThreadCount(1).build()) { 26 | Request request = Request.get().url("http://xbib.org") 27 | .setResponseListener(resp -> { 28 | logger.log(Level.INFO, "status = " + resp.getStatus()); 29 | }) 30 | .build(); 31 | client.execute(request); 32 | } 33 | if (os instanceof UnixOperatingSystemMXBean){ 34 | logger.info("after: number of open file descriptor : " + ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount()); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /netty-http-server/src/test/java/org/xbib/netty/http/server/test/NettyHttpTestExtension.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.test; 2 | 3 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 4 | import org.junit.jupiter.api.extension.BeforeAllCallback; 5 | import org.junit.jupiter.api.extension.ExtensionContext; 6 | 7 | import java.security.Security; 8 | import java.util.logging.ConsoleHandler; 9 | import java.util.logging.Handler; 10 | import java.util.logging.Level; 11 | import java.util.logging.LogManager; 12 | import java.util.logging.Logger; 13 | import java.util.logging.SimpleFormatter; 14 | 15 | public class NettyHttpTestExtension implements BeforeAllCallback { 16 | 17 | @Override 18 | public void beforeAll(ExtensionContext context) { 19 | if (Security.getProvider("BC") == null) { 20 | // for insecure trust manager 21 | Security.addProvider(new BouncyCastleProvider()); 22 | } 23 | System.setProperty("io.netty.noUnsafe", Boolean.toString(true)); 24 | //System.setProperty("io.netty.leakDetection.level", "paranoid"); 25 | Level level = Level.INFO; 26 | //Level level = Level.ALL; 27 | System.setProperty("java.util.logging.SimpleFormatter.format", 28 | "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n"); 29 | LogManager.getLogManager().reset(); 30 | Logger rootLogger = LogManager.getLogManager().getLogger(""); 31 | Handler handler = new ConsoleHandler(); 32 | handler.setFormatter(new SimpleFormatter()); 33 | rootLogger.addHandler(handler); 34 | rootLogger.setLevel(level); 35 | for (Handler h : rootLogger.getHandlers()) { 36 | handler.setFormatter(new SimpleFormatter()); 37 | h.setLevel(level); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/util/HtmlUtils.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.util; 2 | 3 | public class HtmlUtils { 4 | 5 | /** 6 | * Returns an HTML-escaped version of the given string for safe display 7 | * within a web page. The characters '&', '>' and '<' must always 8 | * be escaped, and single and double quotes must be escaped within 9 | * attribute values; this method escapes them always. This method can 10 | * be used for generating both HTML and XHTML valid content. 11 | * 12 | * @param s the string to escape 13 | * @return the escaped string 14 | * @see The W3C FAQ 15 | */ 16 | public static String escapeHTML(String s) { 17 | int len = s.length(); 18 | StringBuilder es = new StringBuilder(len + 30); 19 | int start = 0; 20 | for (int i = 0; i < len; i++) { 21 | String ref = null; 22 | switch (s.charAt(i)) { 23 | case '&': 24 | ref = "&"; 25 | break; 26 | case '>': 27 | ref = ">"; 28 | break; 29 | case '<': 30 | ref = "<"; 31 | break; 32 | case '"': 33 | ref = """; 34 | break; 35 | case '\'': 36 | ref = "'"; 37 | break; 38 | default: 39 | break; 40 | } 41 | if (ref != null) { 42 | es.append(s, start, i).append(ref); 43 | start = i + 1; 44 | } 45 | } 46 | return start == 0 ? s : es.append(s.substring(start)).toString(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/util/ExceptionFormatter.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.util; 2 | 3 | import java.io.PrintWriter; 4 | import java.io.StringWriter; 5 | 6 | /** 7 | * Format exception messages and stack traces. 8 | */ 9 | public final class ExceptionFormatter { 10 | 11 | private ExceptionFormatter() { 12 | } 13 | 14 | /** 15 | * Format exception with stack trace. 16 | * 17 | * @param t the thrown object 18 | * @return the formatted exception 19 | */ 20 | public static String format(Throwable t) { 21 | StringBuilder sb = new StringBuilder(); 22 | append(sb, t, 0, true); 23 | return sb.toString(); 24 | } 25 | 26 | /** 27 | * Append Exception to string builder. 28 | */ 29 | private static void append(StringBuilder sb, Throwable t, int level, boolean details) { 30 | if (((t != null) && (t.getMessage() != null)) && (!t.getMessage().isEmpty())) { 31 | if (details && (level > 0)) { 32 | sb.append("\n\nCaused by\n"); 33 | } 34 | sb.append(t.getMessage()); 35 | } 36 | if (details) { 37 | if (t != null) { 38 | if ((t.getMessage() != null) && (t.getMessage().isEmpty())) { 39 | sb.append("\n\nCaused by "); 40 | } else { 41 | sb.append("\n\n"); 42 | } 43 | } 44 | StringWriter sw = new StringWriter(); 45 | if (t != null) { 46 | t.printStackTrace(new PrintWriter(sw)); 47 | } 48 | sb.append(sw.toString()); 49 | } 50 | if (t != null) { 51 | if (t.getCause() != null) { 52 | append(sb, t.getCause(), level + 1, details); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/BackOff.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.api; 2 | 3 | /** 4 | * Back-off policy when retrying an operation. 5 | */ 6 | public interface BackOff { 7 | 8 | /** 9 | * Indicates that no more retries should be made for use in {@link #nextBackOffMillis()}. */ 10 | long STOP = -1L; 11 | 12 | /** 13 | * Reset to initial state. 14 | */ 15 | void reset(); 16 | 17 | /** 18 | * Gets the number of milliseconds to wait before retrying the operation or {@link #STOP} to 19 | * indicate that no retries should be made. 20 | * 21 | * @return milliseconds before operation retry 22 | * 23 | *

24 | * Example usage: 25 | *

26 | * 27 | *
28 |      long backOffMillis = backoff.nextBackOffMillis();
29 |      if (backOffMillis == Backoff.STOP) {
30 |      // do not retry operation
31 |      } else {
32 |      // sleep for backOffMillis milliseconds and retry operation
33 |      }
34 |      * 
35 | */ 36 | long nextBackOffMillis(); 37 | 38 | /** 39 | * Fixed back-off policy whose back-off time is always zero, meaning that the operation is retried 40 | * immediately without waiting. 41 | */ 42 | BackOff ZERO_BACKOFF = new BackOff() { 43 | 44 | public void reset() { 45 | } 46 | 47 | public long nextBackOffMillis() { 48 | return 0; 49 | } 50 | }; 51 | 52 | /** 53 | * Fixed back-off policy that always returns {@code #STOP} for {@link #nextBackOffMillis()}, 54 | * meaning that the operation should not be retried. 55 | */ 56 | BackOff STOP_BACKOFF = new BackOff() { 57 | 58 | public void reset() { 59 | } 60 | 61 | public long nextBackOffMillis() { 62 | return STOP; 63 | } 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/security/SecurityUtil.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.security; 2 | 3 | import io.netty.handler.codec.http2.Http2SecurityUtil; 4 | import io.netty.handler.ssl.CipherSuiteFilter; 5 | import io.netty.handler.ssl.OpenSsl; 6 | import io.netty.handler.ssl.SslProvider; 7 | import io.netty.handler.ssl.SupportedCipherSuiteFilter; 8 | 9 | import javax.net.ssl.SSLSocketFactory; 10 | import javax.net.ssl.TrustManagerFactory; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | public class SecurityUtil { 15 | 16 | private static TrustManagerFactory TRUST_MANAGER_FACTORY; 17 | 18 | static { 19 | try { 20 | TRUST_MANAGER_FACTORY = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 21 | } catch (Exception e) { 22 | TRUST_MANAGER_FACTORY = null; 23 | } 24 | } 25 | 26 | private SecurityUtil() { 27 | } 28 | 29 | public interface Defaults { 30 | 31 | List OPENSSL_CIPHERS = Http2SecurityUtil.CIPHERS; 32 | 33 | List JDK_CIPHERS = 34 | Arrays.asList(((SSLSocketFactory) SSLSocketFactory.getDefault()).getDefaultCipherSuites()); 35 | 36 | 37 | TrustManagerFactory DEFAULT_TRUST_MANAGER_FACTORY = TRUST_MANAGER_FACTORY; 38 | /** 39 | * Default SSL provider. 40 | */ 41 | SslProvider DEFAULT_SSL_PROVIDER = OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK; 42 | 43 | /** 44 | * Default ciphers. 45 | */ 46 | Iterable DEFAULT_CIPHERS = OpenSsl.isAvailable() ? OPENSSL_CIPHERS : JDK_CIPHERS; 47 | 48 | /** 49 | * Default cipher suite filter. 50 | */ 51 | CipherSuiteFilter DEFAULT_CIPHER_SUITE_FILTER = SupportedCipherSuiteFilter.INSTANCE; 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerResponse.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufOutputStream; 5 | import io.netty.handler.stream.ChunkedInput; 6 | import org.xbib.netty.http.common.cookie.Cookie; 7 | import java.io.Flushable; 8 | import java.io.IOException; 9 | import java.nio.CharBuffer; 10 | import java.nio.charset.Charset; 11 | 12 | /** 13 | * HTTP server response. 14 | */ 15 | public interface ServerResponse extends Flushable { 16 | 17 | Builder getBuilder(); 18 | 19 | Integer getStreamId(); 20 | 21 | Integer getSequenceId(); 22 | 23 | Long getResponseId(); 24 | 25 | ByteBufOutputStream newOutputStream(); 26 | 27 | void flush() throws IOException; 28 | 29 | void write(String content); 30 | 31 | void write(CharBuffer charBuffer, Charset charset); 32 | 33 | void write(byte[] bytes); 34 | 35 | void write(ByteBufOutputStream byteBufOutputStream); 36 | 37 | void write(ByteBuf byteBuf); 38 | 39 | void write(ChunkedInput chunkedInput); 40 | 41 | interface Builder { 42 | 43 | Builder setStatus(int statusCode); 44 | 45 | Builder setContentType(CharSequence contentType); 46 | 47 | Builder setCharset(Charset charset); 48 | 49 | Builder setHeader(CharSequence name, String value); 50 | 51 | Builder setTrailingHeader(CharSequence name, String value); 52 | 53 | Builder addCookie(Cookie cookie); 54 | 55 | Builder shouldClose(boolean shouldClose); 56 | 57 | Builder shouldAddServerName(boolean shouldAddServerName); 58 | 59 | Builder setSequenceId(Integer sequenceId); 60 | 61 | Builder setStreamId(Integer streamId); 62 | 63 | Builder setResponseId(Long responseId); 64 | 65 | ServerResponse build(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/ws2/PathSubprotocolAcceptor.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.ws2; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.http2.Http2Headers; 6 | import io.netty.util.concurrent.Future; 7 | 8 | import java.util.List; 9 | 10 | public class PathSubprotocolAcceptor implements Http2WebSocketAcceptor { 11 | 12 | private final ChannelHandler webSocketHandler; 13 | 14 | private final String path; 15 | 16 | private final String subprotocol; 17 | 18 | private final boolean acceptSubprotocol; 19 | 20 | public PathSubprotocolAcceptor(String path, String subprotocol, ChannelHandler webSocketHandler) { 21 | this(path, subprotocol, webSocketHandler, true); 22 | } 23 | 24 | public PathSubprotocolAcceptor(String path, String subprotocol, ChannelHandler webSocketHandler, boolean acceptSubprotocol) { 25 | this.path = path; 26 | this.subprotocol = subprotocol; 27 | this.webSocketHandler = webSocketHandler; 28 | this.acceptSubprotocol = acceptSubprotocol; 29 | } 30 | 31 | @Override 32 | public Future accept(ChannelHandlerContext ctx, 33 | String path, List subprotocols, Http2Headers request, Http2Headers response) { 34 | String subprotocol = this.subprotocol; 35 | if (path.equals(this.path) && subprotocols.contains(subprotocol)) { 36 | if (acceptSubprotocol) { 37 | Subprotocol.accept(subprotocol, response); 38 | } 39 | return ctx.executor().newSucceededFuture(webSocketHandler); 40 | } 41 | return ctx.executor().newFailedFuture(new Http2WebSocketPathNotFoundException( 42 | String.format("Path not found: %s , subprotocols: %s", path, subprotocols))); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /netty-http-server/src/test/java/org/xbib/netty/http/server/test/ThreadLeakTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.test; 2 | 3 | import io.netty.buffer.UnpooledByteBufAllocator; 4 | import org.junit.jupiter.api.AfterAll; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.TestInstance; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.xbib.netty.http.common.HttpAddress; 9 | import org.xbib.netty.http.server.Server; 10 | import org.xbib.netty.http.server.HttpServerDomain; 11 | 12 | import java.io.IOException; 13 | import java.util.Set; 14 | import java.util.logging.Level; 15 | import java.util.logging.Logger; 16 | 17 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) 18 | @ExtendWith(NettyHttpTestExtension.class) 19 | class ThreadLeakTest { 20 | 21 | private static final Logger logger = Logger.getLogger(ThreadLeakTest.class.getName()); 22 | 23 | @Test 24 | void testForLeaks() throws IOException { 25 | HttpServerDomain domain = HttpServerDomain.builder(HttpAddress.http1("localhost", 8008)) 26 | .singleEndpoint("/", (request, response) -> 27 | response.write("Hello World")) 28 | .build(); 29 | Server server = Server.builder(domain) 30 | .setByteBufAllocator(UnpooledByteBufAllocator.DEFAULT) 31 | .build(); 32 | try { 33 | server.accept(); 34 | } finally { 35 | server.shutdownGracefully(); 36 | } 37 | } 38 | 39 | @AfterAll 40 | void checkThreads() throws Exception { 41 | Thread.sleep(1000L); 42 | System.gc(); 43 | Thread.sleep(3000L); 44 | Set threadSet = Thread.getAllStackTraces().keySet(); 45 | logger.log(Level.INFO, "threads = " + threadSet.size() ); 46 | threadSet.forEach( thread -> logger.log(Level.INFO, thread.toString())); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http2/Http2ResponseHandler.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.handler.http2; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import io.netty.handler.codec.http.FullHttpResponse; 7 | import io.netty.handler.codec.http2.HttpConversionUtil; 8 | import org.xbib.netty.http.client.api.ClientTransport; 9 | 10 | @ChannelHandler.Sharable 11 | public class Http2ResponseHandler extends SimpleChannelInboundHandler { 12 | 13 | @Override 14 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse httpResponse) throws Exception { 15 | Integer streamId = httpResponse.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); 16 | ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get(); 17 | if (transport != null) { 18 | transport.responseReceived(ctx.channel(), streamId, httpResponse); 19 | } 20 | // do not close ctx here 21 | } 22 | 23 | @Override 24 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 25 | ctx.fireChannelInactive(); 26 | ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get(); 27 | if (transport != null) { 28 | transport.inactive(ctx.channel()); 29 | } 30 | } 31 | 32 | @Override 33 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 34 | ctx.fireExceptionCaught(cause); 35 | ClientTransport transport = ctx.channel().attr(ClientTransport.TRANSPORT_ATTRIBUTE_KEY).get(); 36 | if (transport != null) { 37 | transport.fail(ctx.channel(), cause); 38 | } 39 | // do not close ctx here 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/ws/Preconditions.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.ws; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandler; 5 | 6 | public final class Preconditions { 7 | 8 | public static T requireNonNull(T t, String message) { 9 | if (t == null) { 10 | throw new IllegalArgumentException(message + " must be non null"); 11 | } 12 | return t; 13 | } 14 | 15 | public static String requireNonEmpty(String string, String message) { 16 | if (string == null || string.isEmpty()) { 17 | throw new IllegalArgumentException(message + " must be non empty"); 18 | } 19 | return string; 20 | } 21 | 22 | public static T requireHandler(Channel channel, Class handler) { 23 | T h = channel.pipeline().get(handler); 24 | if (h == null) { 25 | throw new IllegalArgumentException( 26 | handler.getSimpleName() + " is absent in the channel pipeline"); 27 | } 28 | return h; 29 | } 30 | 31 | public static long requirePositive(long value, String message) { 32 | if (value <= 0) { 33 | throw new IllegalArgumentException(message + " must be positive: " + value); 34 | } 35 | return value; 36 | } 37 | 38 | public static int requireNonNegative(int value, String message) { 39 | if (value < 0) { 40 | throw new IllegalArgumentException(message + " must be non-negative: " + value); 41 | } 42 | return value; 43 | } 44 | 45 | public static short requireRange(int value, int from, int to, String message) { 46 | if (value >= from && value <= to) { 47 | return (short) value; 48 | } 49 | throw new IllegalArgumentException( 50 | String.format("%s must belong to range [%d, %d]: ", message, from, to)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/conscrypt/ConscryptTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.conscrypt; 2 | 3 | import org.conscrypt.Conscrypt; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.xbib.netty.http.client.Client; 7 | import org.xbib.netty.http.client.api.Request; 8 | import org.xbib.netty.http.client.test.NettyHttpTestExtension; 9 | 10 | import java.io.IOException; 11 | import java.nio.charset.StandardCharsets; 12 | import java.security.Provider; 13 | import java.util.logging.Level; 14 | import java.util.logging.Logger; 15 | 16 | @ExtendWith(NettyHttpTestExtension.class) 17 | class ConscryptTest { 18 | 19 | private static final Logger logger = Logger.getLogger(ConscryptTest.class.getName()); 20 | 21 | @Test 22 | void testConscrypt() throws IOException { 23 | 24 | Provider provider = Conscrypt.newProviderBuilder() 25 | .provideTrustManager(true) 26 | .build(); 27 | 28 | Client client = Client.builder() 29 | .setJdkSslProvider() 30 | .setSslContextProvider(provider) 31 | .setTransportLayerSecurityProtocols("TLSv1.2") // disable TLSv1.3 for Conscrypt 32 | .build(); 33 | logger.log(Level.INFO, client.getClientConfig().toString()); 34 | try { 35 | Request request = Request.get() 36 | .url("https://google.com") 37 | .setVersion("HTTP/1.1") 38 | .setResponseListener(resp -> { 39 | logger.log(Level.INFO, "status = " + resp.getStatus() 40 | + " response body = " + resp.getBodyAsString(StandardCharsets.UTF_8)); 41 | }) 42 | .build(); 43 | client.execute(request).get(); 44 | } finally { 45 | client.shutdownGracefully(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpPipelinedRequest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.http1; 2 | 3 | import io.netty.channel.ChannelPromise; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | import io.netty.handler.codec.http.LastHttpContent; 6 | import io.netty.util.ReferenceCounted; 7 | 8 | public class HttpPipelinedRequest implements ReferenceCounted { 9 | 10 | private final LastHttpContent request; 11 | 12 | private final int sequenceId; 13 | 14 | public HttpPipelinedRequest(LastHttpContent request, int sequenceId) { 15 | this.request = request; 16 | this.sequenceId = sequenceId; 17 | } 18 | 19 | public HttpPipelinedResponse createHttpResponse(FullHttpResponse response, ChannelPromise promise) { 20 | return new HttpPipelinedResponse(response, promise, sequenceId); 21 | } 22 | 23 | public LastHttpContent getRequest() { 24 | return request; 25 | } 26 | 27 | public int getSequenceId() { 28 | return sequenceId; 29 | } 30 | 31 | @Override 32 | public int refCnt() { 33 | return request.refCnt(); 34 | } 35 | 36 | @Override 37 | public ReferenceCounted retain() { 38 | request.retain(); 39 | return this; 40 | } 41 | 42 | @Override 43 | public ReferenceCounted retain(int increment) { 44 | request.retain(increment); 45 | return this; 46 | } 47 | 48 | @Override 49 | public ReferenceCounted touch() { 50 | request.touch(); 51 | return this; 52 | } 53 | 54 | @Override 55 | public ReferenceCounted touch(Object hint) { 56 | request.touch(hint); 57 | return this; 58 | } 59 | 60 | @Override 61 | public boolean release() { 62 | return request.release(); 63 | } 64 | 65 | @Override 66 | public boolean release(int decrement) { 67 | return request.release(decrement); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /netty-http-common/src/test/java/org/xbib/netty/http/common/test/HttpParametersTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.xbib.netty.http.common.HttpParameters; 5 | import java.nio.charset.MalformedInputException; 6 | import java.nio.charset.UnmappableCharacterException; 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | class HttpParametersTest { 10 | 11 | @Test 12 | void testSimpleParameter() throws MalformedInputException, UnmappableCharacterException { 13 | HttpParameters httpParameters = new HttpParameters(); 14 | httpParameters.addRaw("a", "b"); 15 | String query = httpParameters.getAsQueryString(false); 16 | assertEquals("a=b", query); 17 | } 18 | 19 | @Test 20 | void testSimpleParameters() throws MalformedInputException, UnmappableCharacterException { 21 | HttpParameters httpParameters = new HttpParameters(); 22 | httpParameters.addRaw("a", "b"); 23 | httpParameters.addRaw("c", "d"); 24 | String query = httpParameters.getAsQueryString(false); 25 | assertEquals("a=b&c=d", query); 26 | } 27 | 28 | @Test 29 | void testMultiParameters() throws MalformedInputException, UnmappableCharacterException { 30 | HttpParameters httpParameters = new HttpParameters(); 31 | httpParameters.addRaw("a", "b"); 32 | httpParameters.addRaw("a", "c"); 33 | httpParameters.addRaw("a", "d"); 34 | String query = httpParameters.getAsQueryString(false); 35 | assertEquals("a=b&a=c&a=d", query); 36 | } 37 | 38 | @Test 39 | void testUtf8() throws MalformedInputException, UnmappableCharacterException { 40 | HttpParameters httpParameters = new HttpParameters("text/plain; charset=utf-8"); 41 | httpParameters.addRaw("Hello", "Jörg"); 42 | String query = httpParameters.getAsQueryString(false); 43 | assertEquals("Hello=Jörg", query); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/ws/Http2WebSocket.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.ws; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.http2.Http2Exception; 6 | import io.netty.handler.codec.http2.Http2Flags; 7 | import io.netty.handler.codec.http2.Http2FrameAdapter; 8 | import io.netty.handler.codec.http2.Http2FrameListener; 9 | 10 | public interface Http2WebSocket extends Http2FrameListener { 11 | 12 | @Override 13 | void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData); 14 | 15 | void trySetWritable(); 16 | 17 | void fireExceptionCaught(Throwable t); 18 | 19 | void streamClosed(); 20 | 21 | void closeForcibly(); 22 | 23 | Http2WebSocket CLOSED = new Http2WebSocketClosedChannel(); 24 | 25 | class Http2WebSocketClosedChannel extends Http2FrameAdapter implements Http2WebSocket { 26 | 27 | @Override 28 | public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) {} 29 | 30 | @Override 31 | public void streamClosed() {} 32 | 33 | @Override 34 | public void trySetWritable() {} 35 | 36 | @Override 37 | public void fireExceptionCaught(Throwable t) {} 38 | 39 | @Override 40 | public void closeForcibly() {} 41 | 42 | @Override 43 | public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) 44 | throws Http2Exception { 45 | int processed = super.onDataRead(ctx, streamId, data, padding, endOfStream); 46 | data.release(); 47 | return processed; 48 | } 49 | 50 | @Override 51 | public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags, ByteBuf payload) { 52 | payload.release(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/ws/Http2WebSocketProtocol.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.ws; 2 | 3 | import io.netty.handler.codec.http2.Http2Headers; 4 | import io.netty.util.AsciiString; 5 | 6 | public final class Http2WebSocketProtocol { 7 | 8 | public static final char SETTINGS_ENABLE_CONNECT_PROTOCOL = 8; 9 | 10 | public static final AsciiString HEADER_METHOD_CONNECT = AsciiString.of("CONNECT"); 11 | 12 | public static final AsciiString HEADER_PROTOCOL_NAME = AsciiString.of(":protocol"); 13 | 14 | public static final AsciiString HEADER_PROTOCOL_VALUE = AsciiString.of("websocket"); 15 | 16 | public static final AsciiString SCHEME_HTTP = AsciiString.of("http"); 17 | 18 | public static final AsciiString SCHEME_HTTPS = AsciiString.of("https"); 19 | 20 | public static final AsciiString HEADER_WEBSOCKET_VERSION_NAME = AsciiString.of("sec-websocket-version"); 21 | 22 | public static final AsciiString HEADER_WEBSOCKET_VERSION_VALUE = AsciiString.of("13"); 23 | 24 | public static final AsciiString HEADER_WEBSOCKET_SUBPROTOCOL_NAME = AsciiString.of("sec-websocket-protocol"); 25 | 26 | public static final AsciiString HEADER_WEBSOCKET_EXTENSIONS_NAME = AsciiString.of("sec-websocket-extensions"); 27 | 28 | public static final AsciiString HEADER_PROTOCOL_NAME_HANDSHAKED = AsciiString.of("x-protocol"); 29 | 30 | public static final AsciiString HEADER_METHOD_CONNECT_HANDSHAKED = AsciiString.of("POST"); 31 | 32 | public static Http2Headers extendedConnect(Http2Headers headers) { 33 | return headers.method(Http2WebSocketProtocol.HEADER_METHOD_CONNECT) 34 | .set(Http2WebSocketProtocol.HEADER_PROTOCOL_NAME, Http2WebSocketProtocol.HEADER_PROTOCOL_VALUE); 35 | } 36 | 37 | public static boolean isExtendedConnect(Http2Headers headers) { 38 | return HEADER_METHOD_CONNECT.equals(headers.method()) 39 | && HEADER_PROTOCOL_VALUE.equals(headers.get(HEADER_PROTOCOL_NAME)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /netty-http-client-api/src/main/java/org/xbib/netty/http/client/api/ClientTransport.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.api; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | import io.netty.handler.codec.http2.Http2Headers; 6 | import io.netty.handler.codec.http2.Http2Settings; 7 | import io.netty.util.AttributeKey; 8 | import org.xbib.netty.http.common.HttpAddress; 9 | import org.xbib.netty.http.common.HttpResponse; 10 | import org.xbib.netty.http.common.Transport; 11 | import org.xbib.netty.http.common.cookie.CookieBox; 12 | import javax.net.ssl.SSLSession; 13 | import java.io.IOException; 14 | import java.util.concurrent.CompletableFuture; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.function.Function; 17 | 18 | public interface ClientTransport extends Transport { 19 | 20 | AttributeKey TRANSPORT_ATTRIBUTE_KEY = AttributeKey.valueOf("transport"); 21 | 22 | HttpAddress getHttpAddress(); 23 | 24 | ClientTransport execute(Request request) throws IOException; 25 | 26 | CompletableFuture execute(Request request, Function supplier) throws IOException; 27 | 28 | void waitForSettings(); 29 | 30 | void settingsReceived(Http2Settings http2Settings) throws IOException; 31 | 32 | void responseReceived(Channel channel, Integer streamId, FullHttpResponse fullHttpResponse) throws IOException; 33 | 34 | void pushPromiseReceived(Channel channel, Integer streamId, Integer promisedStreamId, Http2Headers headers); 35 | 36 | void fail(Channel channel, Throwable throwable); 37 | 38 | void inactive(Channel channel); 39 | 40 | void setCookieBox(CookieBox cookieBox); 41 | 42 | CookieBox getCookieBox(); 43 | 44 | ClientTransport get(); 45 | 46 | ClientTransport get(long value, TimeUnit timeUnit); 47 | 48 | void cancel(); 49 | 50 | boolean isFailed(); 51 | 52 | Throwable getFailure(); 53 | 54 | SSLSession getSession(); 55 | 56 | void close() throws IOException; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/cookie/ClientCookieEncoderTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.cookie; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.xbib.netty.http.client.cookie.ClientCookieEncoder; 5 | import org.xbib.netty.http.common.cookie.Cookie; 6 | import org.xbib.netty.http.common.cookie.DefaultCookie; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertThrows; 10 | 11 | class ClientCookieEncoderTest { 12 | 13 | @Test 14 | void testEncodingMultipleClientCookies() { 15 | String c1 = "myCookie=myValue"; 16 | String c2 = "myCookie2=myValue2"; 17 | String c3 = "myCookie3=myValue3"; 18 | Cookie cookie1 = new DefaultCookie("myCookie", "myValue"); 19 | cookie1.setDomain(".adomainsomewhere"); 20 | cookie1.setMaxAge(50); 21 | cookie1.setPath("/apathsomewhere"); 22 | cookie1.setSecure(true); 23 | Cookie cookie2 = new DefaultCookie("myCookie2", "myValue2"); 24 | cookie2.setDomain(".anotherdomainsomewhere"); 25 | cookie2.setPath("/anotherpathsomewhere"); 26 | cookie2.setSecure(false); 27 | Cookie cookie3 = new DefaultCookie("myCookie3", "myValue3"); 28 | String encodedCookie = ClientCookieEncoder.STRICT.encode(cookie1, cookie2, cookie3); 29 | // Cookies should be sorted into decreasing order of path length, as per RFC6265. 30 | // When no path is provided, we assume maximum path length (so cookie3 comes first). 31 | assertEquals(c3 + "; " + c2 + "; " + c1, encodedCookie); 32 | } 33 | 34 | @Test 35 | void testWrappedCookieValue() { 36 | ClientCookieEncoder.STRICT.encode(new DefaultCookie("myCookie", "\"foo\"")); 37 | } 38 | 39 | @Test 40 | void testRejectCookieValueWithSemicolon() { 41 | assertThrows(IllegalArgumentException.class, () -> 42 | ClientCookieEncoder.STRICT.encode(new DefaultCookie("myCookie", "foo;bar"))); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/ws2/Http2WebSocketAcceptor.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.ws2; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.http2.Http2Headers; 6 | import io.netty.util.concurrent.Future; 7 | import org.xbib.netty.http.common.ws.Http2WebSocketProtocol; 8 | import java.util.List; 9 | import java.util.Objects; 10 | 11 | /** 12 | * Accepts valid websocket-over-http2 request, optionally modifies request and response headers. 13 | */ 14 | public interface Http2WebSocketAcceptor { 15 | 16 | /** 17 | * @param ctx ChannelHandlerContext of connection channel. Intended for creating acceptor result 18 | * with context.executor().newFailedFuture(Throwable), 19 | * context.executor().newSucceededFuture(ChannelHandler) 20 | * @param path websocket path 21 | * @param subprotocols requested websocket subprotocols. Accepted subprotocol must be set on 22 | * response headers, e.g. with {@link Subprotocol#accept(String, Http2Headers)} 23 | * @param request request headers 24 | * @param response response headers 25 | * @return Succeeded future for accepted request, failed for rejected request. It is an error to 26 | * return non completed future 27 | */ 28 | Future accept(ChannelHandlerContext ctx, String path, List subprotocols, 29 | Http2Headers request, Http2Headers response); 30 | 31 | final class Subprotocol { 32 | private Subprotocol() {} 33 | 34 | public static void accept(String subprotocol, Http2Headers response) { 35 | Objects.requireNonNull(subprotocol, "subprotocol"); 36 | Objects.requireNonNull(response, "response"); 37 | if (subprotocol.isEmpty()) { 38 | return; 39 | } 40 | response.set(Http2WebSocketProtocol.HEADER_WEBSOCKET_SUBPROTOCOL_NAME, subprotocol); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/handler/ws1/Http1WebSocketClientHandler.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.handler.ws1; 2 | 3 | import io.netty.channel.ChannelFutureListener; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; 7 | import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; 8 | import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler; 9 | 10 | import java.io.IOException; 11 | 12 | public class Http1WebSocketClientHandler extends ChannelInboundHandlerAdapter { 13 | 14 | final WebSocketClientHandshaker handshaker; 15 | 16 | public Http1WebSocketClientHandler(WebSocketClientHandshaker handshaker) { 17 | this.handshaker = handshaker; 18 | } 19 | 20 | @Override 21 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 22 | ctx.fireChannelActive(); 23 | } 24 | 25 | @Override 26 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 27 | ctx.fireChannelInactive(); 28 | } 29 | 30 | @Override 31 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 32 | if (msg instanceof CloseWebSocketFrame) { 33 | handshaker.close(ctx.channel(), (CloseWebSocketFrame) msg) 34 | .addListener(ChannelFutureListener.CLOSE); 35 | } else { 36 | ctx.fireChannelRead(msg); 37 | } 38 | } 39 | 40 | @Override 41 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 42 | if (evt == WebSocketClientProtocolHandler.ClientHandshakeStateEvent.HANDSHAKE_COMPLETE) { 43 | String actualProtocol = handshaker.actualSubprotocol(); 44 | if (actualProtocol.equals("")) { 45 | } 46 | else { 47 | throw new IOException("Invalid Websocket Protocol"); 48 | } 49 | } else { 50 | ctx.fireUserEventTriggered(evt); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/security/CertificateUtils.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.security; 2 | 3 | import java.io.InputStream; 4 | import java.security.cert.CertificateException; 5 | import java.security.cert.CertificateFactory; 6 | import java.security.cert.CertificateParsingException; 7 | import java.security.cert.X509Certificate; 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | public class CertificateUtils { 12 | 13 | @SuppressWarnings("unchecked") 14 | public static Collection toCertificate(InputStream keyCertChainInputStream) 15 | throws CertificateException { 16 | return (Collection) CertificateFactory.getInstance("X509") 17 | .generateCertificates(keyCertChainInputStream); 18 | } 19 | 20 | public static void processSubjectAlternativeNames(Collection certificates, 21 | SubjectAlternativeNamesProcessor processor) throws CertificateParsingException { 22 | if (certificates == null) { 23 | return; 24 | } 25 | for (X509Certificate certificate : certificates) { 26 | processor.setServerName(new DistinguishedNameParser(certificate.getSubjectX500Principal()) 27 | .findMostSpecific("CN")); 28 | Collection> altNames = certificate.getSubjectAlternativeNames(); 29 | if (altNames != null) { 30 | for (List altName : altNames) { 31 | Integer type = (Integer) altName.get(0); 32 | if (type == 2) { // Type DNS 33 | String string = altName.get(1).toString(); 34 | processor.setSubjectAlternativeName(string); 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | public interface SubjectAlternativeNamesProcessor { 42 | 43 | void setServerName(String serverName); 44 | 45 | void setSubjectAlternativeName(String subjectAlternativeName); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/DefaultHttpResponse.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufInputStream; 5 | import io.netty.handler.codec.http.FullHttpResponse; 6 | 7 | import org.xbib.netty.http.common.cookie.CookieBox; 8 | import java.io.InputStream; 9 | import java.nio.charset.Charset; 10 | 11 | public class DefaultHttpResponse implements HttpResponse { 12 | 13 | private final HttpAddress httpAddress; 14 | 15 | private final FullHttpResponse fullHttpResponse; 16 | 17 | private final HttpStatus httpStatus; 18 | 19 | private final HttpHeaders httpHeaders; 20 | 21 | private final CookieBox cookieBox; 22 | 23 | public DefaultHttpResponse(HttpAddress httpAddress, 24 | FullHttpResponse fullHttpResponse, 25 | CookieBox cookieBox) { 26 | this.httpAddress = httpAddress; 27 | this.fullHttpResponse = fullHttpResponse.retain(); 28 | this.httpStatus = new HttpStatus(this.fullHttpResponse.status()); 29 | this.httpHeaders = new DefaultHttpHeaders(this.fullHttpResponse.headers()); 30 | this.cookieBox = cookieBox; 31 | } 32 | 33 | @Override 34 | public HttpAddress getAddress() { 35 | return httpAddress; 36 | } 37 | 38 | @Override 39 | public HttpStatus getStatus() { 40 | return httpStatus; 41 | } 42 | 43 | @Override 44 | public HttpHeaders getHeaders() { 45 | return httpHeaders; 46 | } 47 | 48 | @Override 49 | public CookieBox getCookies() { 50 | return cookieBox; 51 | } 52 | 53 | @Override 54 | public ByteBuf getBody() { 55 | return fullHttpResponse.content(); 56 | } 57 | 58 | @Override 59 | public InputStream getBodyAsStream() { 60 | return new ByteBufInputStream(getBody()); 61 | } 62 | 63 | @Override 64 | public String getBodyAsString(Charset charset) { 65 | return getBody().toString(charset); 66 | } 67 | 68 | @Override 69 | public void release() { 70 | this.fullHttpResponse.release(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/HttpPipelinedResponse.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.http1; 2 | 3 | import io.netty.channel.ChannelPromise; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | import io.netty.handler.codec.http.HttpResponse; 6 | import io.netty.util.ReferenceCounted; 7 | 8 | public class HttpPipelinedResponse implements ReferenceCounted, Comparable { 9 | 10 | private final FullHttpResponse response; 11 | 12 | private final ChannelPromise promise; 13 | 14 | private final int sequenceId; 15 | 16 | public HttpPipelinedResponse(FullHttpResponse response, ChannelPromise promise, int sequenceId) { 17 | this.response = response; 18 | this.promise = promise; 19 | this.sequenceId = sequenceId; 20 | } 21 | 22 | public int getSequenceId() { 23 | return sequenceId; 24 | } 25 | 26 | public HttpResponse getResponse() { 27 | return response; 28 | } 29 | 30 | public ChannelPromise getPromise() { 31 | return promise; 32 | } 33 | 34 | @Override 35 | public int compareTo(HttpPipelinedResponse other) { 36 | return Integer.compare(this.sequenceId, other.sequenceId); 37 | } 38 | 39 | @Override 40 | public int refCnt() { 41 | return response.refCnt(); 42 | } 43 | 44 | @Override 45 | public ReferenceCounted retain() { 46 | response.retain(); 47 | return this; 48 | } 49 | 50 | @Override 51 | public ReferenceCounted retain(int increment) { 52 | response.retain(increment); 53 | return this; 54 | } 55 | 56 | @Override 57 | public ReferenceCounted touch() { 58 | response.touch(); 59 | return this; 60 | } 61 | 62 | @Override 63 | public ReferenceCounted touch(Object hint) { 64 | response.touch(hint); 65 | return this; 66 | } 67 | 68 | @Override 69 | public boolean release() { 70 | return response.release(); 71 | } 72 | 73 | @Override 74 | public boolean release(int decrement) { 75 | return response.release(decrement); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /netty-http-client/src/main/java/org/xbib/netty/http/client/transport/Flow.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.transport; 2 | 3 | import java.util.Set; 4 | import java.util.SortedMap; 5 | import java.util.concurrent.CompletableFuture; 6 | import java.util.concurrent.ConcurrentSkipListMap; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | public class Flow { 10 | 11 | private final AtomicInteger counter; 12 | 13 | private final SortedMap> map; 14 | 15 | public Flow() { 16 | this.counter = new AtomicInteger(3); 17 | this.map = new ConcurrentSkipListMap<>(); 18 | } 19 | 20 | CompletableFuture get(Integer key) { 21 | return map.get(key); 22 | } 23 | 24 | Set keys() { 25 | return map.keySet(); 26 | } 27 | 28 | Integer firstKey() { 29 | return map.isEmpty() ? null : map.firstKey(); 30 | } 31 | 32 | Integer lastKey() { 33 | return map.isEmpty() ? null : map.lastKey(); 34 | } 35 | 36 | void put(Integer key, CompletableFuture promise) { 37 | map.put(key, promise); 38 | } 39 | 40 | void remove(Integer key) { 41 | if (key != null) { 42 | map.remove(key); 43 | } 44 | } 45 | 46 | Integer nextStreamId() { 47 | int streamId = counter.getAndAdd(2); 48 | if (streamId == Integer.MIN_VALUE) { 49 | // reset if overflow, Java wraps atomic integers to Integer.MIN_VALUE 50 | // should we send a GOAWAY? 51 | counter.set(3); 52 | streamId = 3; 53 | } 54 | map.put(streamId, new CompletableFuture<>()); 55 | return streamId; 56 | } 57 | 58 | void fail(Throwable throwable) { 59 | for (CompletableFuture promise : map.values()) { 60 | promise.completeExceptionally(throwable); 61 | } 62 | } 63 | 64 | public void close() { 65 | map.clear(); 66 | } 67 | 68 | public boolean isClosed() { 69 | return map.isEmpty(); 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return "[next=" + counter + ", " + map + "]"; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /gradle/publishing/publication.gradle: -------------------------------------------------------------------------------- 1 | import java.time.Duration 2 | 3 | apply plugin: "de.marcphilipp.nexus-publish" 4 | 5 | publishing { 6 | publications { 7 | mavenJava(MavenPublication) { 8 | from components.java 9 | pom { 10 | name = project.name 11 | description = rootProject.ext.description 12 | url = rootProject.ext.url 13 | inceptionYear = rootProject.ext.inceptionYear 14 | packaging = 'jar' 15 | organization { 16 | name = 'xbib' 17 | url = 'https://xbib.org' 18 | } 19 | developers { 20 | developer { 21 | id = 'jprante' 22 | name = 'Jörg Prante' 23 | email = 'joergprante@gmail.com' 24 | url = 'https://github.com/jprante' 25 | } 26 | } 27 | scm { 28 | url = rootProject.ext.scmUrl 29 | connection = rootProject.ext.scmConnection 30 | developerConnection = rootProject.ext.scmDeveloperConnection 31 | } 32 | issueManagement { 33 | system = rootProject.ext.issueManagementSystem 34 | url = rootProject.ext.issueManagementUrl 35 | } 36 | licenses { 37 | license { 38 | name = rootProject.ext.licenseName 39 | url = rootProject.ext.licenseUrl 40 | distribution = 'repo' 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | if (project.hasProperty("signing.keyId")) { 49 | apply plugin: 'signing' 50 | signing { 51 | sign publishing.publications.mavenJava 52 | } 53 | } 54 | 55 | nexusPublishing { 56 | repositories { 57 | sonatype { 58 | username = project.property('ossrhUsername') 59 | password = project.property('ossrhPassword') 60 | packageGroup = "org.xbib" 61 | } 62 | } 63 | clientTimeout = Duration.ofSeconds(600) 64 | } 65 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/ws2/Http2WebSocketChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.ws2; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 8 | import io.netty.handler.codec.http.websocketx.WebSocketDecoderConfig; 9 | import io.netty.handler.codec.http2.Http2FrameCodec; 10 | import io.netty.handler.codec.http2.Http2FrameCodecBuilder; 11 | import io.netty.handler.ssl.SslContext; 12 | import io.netty.handler.ssl.SslHandler; 13 | 14 | public class Http2WebSocketChannelInitializer extends ChannelInitializer { 15 | private final SslContext sslContext; 16 | 17 | Http2WebSocketChannelInitializer(SslContext sslContext) { 18 | this.sslContext = sslContext; 19 | } 20 | 21 | @Override 22 | protected void initChannel(SocketChannel ch) { 23 | SslHandler sslHandler = sslContext.newHandler(ch.alloc()); 24 | Http2FrameCodec http2frameCodec = Http2WebSocketServerBuilder 25 | .configureHttp2Server(Http2FrameCodecBuilder.forServer()) 26 | .build(); 27 | ServerWebSocketHandler serverWebSocketHandler = new ServerWebSocketHandler(); 28 | Http2WebSocketServerHandler http2webSocketHandler = 29 | Http2WebSocketServerBuilder.create() 30 | .decoderConfig(WebSocketDecoderConfig.newBuilder().allowExtensions(true).build()) 31 | .compression(true) 32 | .acceptor(new PathAcceptor("/test", serverWebSocketHandler)) 33 | .build(); 34 | ch.pipeline().addLast(sslHandler, http2frameCodec, http2webSocketHandler); 35 | } 36 | 37 | @Sharable 38 | private static class ServerWebSocketHandler extends SimpleChannelInboundHandler { 39 | 40 | @Override 41 | protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame webSocketFrame) { 42 | // echo 43 | ctx.writeAndFlush(webSocketFrame.retain()); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/webtide/WebtideTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.webtide; 2 | 3 | import io.netty.handler.codec.http.HttpMethod; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.xbib.netty.http.client.Client; 7 | import org.xbib.netty.http.client.api.Request; 8 | import org.xbib.netty.http.client.test.NettyHttpTestExtension; 9 | 10 | import java.io.IOException; 11 | import java.util.logging.Level; 12 | import java.util.logging.Logger; 13 | 14 | @ExtendWith(NettyHttpTestExtension.class) 15 | class WebtideTest { 16 | 17 | private static final Logger logger = Logger.getLogger(WebtideTest.class.getName()); 18 | 19 | @Test 20 | void testWebtide() throws Exception { 21 | Client client = Client.builder() 22 | .build(); 23 | try { 24 | Request request = Request.get().url("https://webtide.com").setVersion("HTTP/2.0") 25 | .setResponseListener(msg -> logger.log(Level.INFO, "got response: " + msg)) 26 | .build(); 27 | client.execute(request).get(); 28 | } finally { 29 | client.shutdownGracefully(); 30 | } 31 | } 32 | 33 | @Test 34 | void testWebtideTwoRequestsOnSameConnection() throws IOException { 35 | Client client = new Client(); 36 | try { 37 | Request request1 = Request.builder(HttpMethod.GET) 38 | .url("https://webtide.com").setVersion("HTTP/2.0") 39 | .setResponseListener(resp -> logger.log(Level.INFO, "got response: " + 40 | resp.getHeaders() + " status=" + resp.getStatus())) 41 | .build(); 42 | Request request2 = Request.builder(HttpMethod.GET) 43 | .url("https://webtide.com/why-choose-jetty/").setVersion("HTTP/2.0") 44 | .setResponseListener(resp -> logger.log(Level.INFO, "got response: " + 45 | resp.getHeaders() + " status=" +resp.getStatus())) 46 | .build(); 47 | client.execute(request1).execute(request2); 48 | } finally { 49 | client.shutdownGracefully(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/BaseTransport.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.http.HttpHeaderNames; 5 | import io.netty.handler.codec.http.HttpHeaders; 6 | import io.netty.handler.codec.http.HttpVersion; 7 | import org.xbib.netty.http.server.api.ServerTransport; 8 | 9 | import java.util.concurrent.atomic.AtomicLong; 10 | import java.util.logging.Level; 11 | import java.util.logging.Logger; 12 | 13 | public abstract class BaseTransport implements ServerTransport { 14 | 15 | private static final Logger logger = Logger.getLogger(BaseTransport.class.getName()); 16 | 17 | protected final Server server; 18 | 19 | protected BaseTransport(Server server) { 20 | this.server = server; 21 | } 22 | 23 | @Override 24 | public void exceptionReceived(ChannelHandlerContext ctx, Throwable throwable) { 25 | logger.log(Level.WARNING, throwable.getMessage(), throwable); 26 | } 27 | 28 | /** 29 | * Accepts a request, performing various validation checks 30 | * and required special header handling, possibly returning an 31 | * appropriate response. 32 | * 33 | * @param httpVersion the server HTTP version 34 | * @param reqHeaders the request headers 35 | * @return whether further processing should be performed 36 | */ 37 | protected static AcceptState acceptRequest(HttpVersion httpVersion, HttpHeaders reqHeaders) { 38 | if (httpVersion.majorVersion() == 1 || httpVersion.majorVersion() == 2) { 39 | if (!reqHeaders.contains(HttpHeaderNames.HOST)) { 40 | // RFC2616#14.23: missing Host header gets 400 41 | return AcceptState.MISSING_HOST_HEADER; 42 | } 43 | // return a continue response before reading body 44 | String expect = reqHeaders.get(HttpHeaderNames.EXPECT); 45 | if (expect != null) { 46 | if (!"100-continue".equalsIgnoreCase(expect)) { 47 | // RFC2616#14.20: if unknown expect, send 417 48 | return AcceptState.EXPECTATION_FAILED; 49 | } 50 | } 51 | return AcceptState.OK; 52 | } else { 53 | return AcceptState.UNSUPPORTED_HTTP_VERSION; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/akamai/AkamaiTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.akamai; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.xbib.netty.http.client.Client; 6 | import org.xbib.netty.http.client.api.Request; 7 | import org.xbib.netty.http.client.test.NettyHttpTestExtension; 8 | 9 | import java.io.IOException; 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.logging.Level; 12 | import java.util.logging.Logger; 13 | 14 | @ExtendWith(NettyHttpTestExtension.class) 15 | public class AkamaiTest { 16 | 17 | private static Logger logger = Logger.getLogger(AkamaiTest.class.getName()); 18 | 19 | /** 20 | * Problems with akamai: 21 | * 22 | * 2018-03-07 16:02:52.385 FEIN [client] io.netty.handler.codec.http2.Http2FrameLogger logRstStream 23 | * [id: 0x57cc65bb, L:/10.1.1.94:52834 - R:http2.akamai.com/104.94.191.203:443] INBOUND RST_STREAM: streamId=2 errorCode=8 24 | * 2018-03-07 16:02:52.385 FEIN [client] io.netty.handler.codec.http2.Http2FrameLogger logGoAway 25 | * [id: 0x57cc65bb, L:/10.1.1.94:52834 - R:http2.akamai.com/104.94.191.203:443] OUTBOUND GO_AWAY: lastStreamId=2 errorCode=0 length=0 bytes= 26 | * 27 | * demo/h2_demo_frame.html sends no content, only a push promise, and does not continue 28 | * 29 | * @throws IOException if test fails 30 | */ 31 | @Test 32 | void testAkamai() throws IOException { 33 | Client client = Client.builder() 34 | .addServerNameForIdentification("http2.akamai.com") 35 | .build(); 36 | try { 37 | Request request = Request.get() 38 | .url("https://http2.akamai.com/demo/h2_demo_frame.html") 39 | //.url("https://http2.akamai.com/") 40 | .setVersion("HTTP/2.0") 41 | .setResponseListener(resp -> { 42 | logger.log(Level.INFO, "status = " + resp.getStatus().getCode() + 43 | resp.getHeaders() + " " + resp.getBodyAsString(StandardCharsets.UTF_8)); 44 | }) 45 | .build(); 46 | client.execute(request).get(); 47 | } finally { 48 | client.shutdownGracefully(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/retry/MockBackOff.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.retry; 2 | 3 | import org.xbib.netty.http.client.api.BackOff; 4 | 5 | /** 6 | * Mock for {@link BackOff} that always returns a fixed number. 7 | * 8 | *

9 | * Implementation is not thread-safe. 10 | *

11 | * 12 | */ 13 | public class MockBackOff implements BackOff { 14 | 15 | /** Fixed back-off milliseconds. */ 16 | private long backOffMillis; 17 | 18 | /** Maximum number of tries before returning {@link #STOP}. */ 19 | private int maxTries = 10; 20 | 21 | /** Number of tries so far. */ 22 | private int numTries; 23 | 24 | @Override 25 | public void reset() { 26 | numTries = 0; 27 | } 28 | 29 | @Override 30 | public long nextBackOffMillis() { 31 | if (numTries >= maxTries || backOffMillis == STOP) { 32 | return STOP; 33 | } 34 | numTries++; 35 | return backOffMillis; 36 | } 37 | 38 | /** 39 | * Sets the fixed back-off milliseconds (defaults to {@code 0}). 40 | * 41 | *

42 | * Overriding is only supported for the purpose of calling the super implementation and changing 43 | * the return type, but nothing else. 44 | *

45 | */ 46 | public MockBackOff setBackOffMillis(long backOffMillis) { 47 | //Preconditions.checkArgument(backOffMillis == STOP || backOffMillis >= 0); 48 | this.backOffMillis = backOffMillis; 49 | return this; 50 | } 51 | 52 | /** 53 | * Sets the maximum number of tries before returning {@link #STOP} (defaults to {@code 10}). 54 | * 55 | *

56 | * Overriding is only supported for the purpose of calling the super implementation and changing 57 | * the return type, but nothing else. 58 | *

59 | */ 60 | public MockBackOff setMaxTries(int maxTries) { 61 | //Preconditions.checkArgument(maxTries >= 0); 62 | this.maxTries = maxTries; 63 | return this; 64 | } 65 | 66 | /** Returns the maximum number of tries before returning {@link #STOP}. */ 67 | public final int getMaxTries() { 68 | return numTries; 69 | } 70 | 71 | /** Returns the number of tries so far. */ 72 | public final int getNumberOfTries() { 73 | return numTries; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/handler/ExtendedSNIHandler.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.handler; 2 | 3 | import io.netty.buffer.ByteBufAllocator; 4 | import io.netty.handler.ssl.SniHandler; 5 | import io.netty.handler.ssl.SslContext; 6 | import io.netty.handler.ssl.SslHandler; 7 | import io.netty.util.Mapping; 8 | import org.xbib.netty.http.common.HttpAddress; 9 | import org.xbib.netty.http.server.api.ServerConfig; 10 | import java.net.InetSocketAddress; 11 | import java.util.Arrays; 12 | import java.util.logging.Level; 13 | import java.util.logging.Logger; 14 | import javax.net.ssl.SSLEngine; 15 | import javax.net.ssl.SSLParameters; 16 | 17 | public class ExtendedSNIHandler extends SniHandler { 18 | 19 | private static final Logger logger = Logger.getLogger(ExtendedSNIHandler.class.getName()); 20 | 21 | private final ServerConfig serverConfig; 22 | 23 | private final HttpAddress httpAddress; 24 | 25 | public ExtendedSNIHandler(Mapping mapping, 26 | ServerConfig serverConfig, HttpAddress httpAddress) { 27 | super(mapping); 28 | this.serverConfig = serverConfig; 29 | this.httpAddress = httpAddress; 30 | } 31 | 32 | @Override 33 | protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocator) { 34 | return newSslHandler(context, serverConfig, allocator, httpAddress); 35 | } 36 | 37 | private static SslHandler newSslHandler(SslContext sslContext, 38 | ServerConfig serverConfig, 39 | ByteBufAllocator allocator, 40 | HttpAddress httpAddress) { 41 | InetSocketAddress peer = httpAddress.getInetSocketAddress(); 42 | SslHandler sslHandler = sslContext.newHandler(allocator, peer.getHostName(), peer.getPort()); 43 | SSLEngine engine = sslHandler.engine(); 44 | SSLParameters params = engine.getSSLParameters(); 45 | params.setEndpointIdentificationAlgorithm("HTTPS"); 46 | engine.setSSLParameters(params); 47 | logger.log(Level.FINE, () -> "set enabled TLS protocols in SSL engine: " + Arrays.asList(serverConfig.getProtocols())); 48 | engine.setEnabledProtocols(serverConfig.getProtocols()); 49 | return sslHandler; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/cookie/CookieSetterHttpBinTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.cookie; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertTrue; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.xbib.netty.http.client.Client; 7 | import org.xbib.netty.http.client.api.Request; 8 | import org.xbib.netty.http.client.test.NettyHttpTestExtension; 9 | import org.xbib.netty.http.common.cookie.Cookie; 10 | 11 | import java.io.IOException; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.concurrent.atomic.AtomicBoolean; 14 | import java.util.logging.Level; 15 | import java.util.logging.Logger; 16 | 17 | @ExtendWith(NettyHttpTestExtension.class) 18 | class CookieSetterHttpBinTest { 19 | 20 | private static final Logger logger = Logger.getLogger(CookieSetterHttpBinTest.class.getName()); 21 | 22 | /** 23 | * Test httpbin.org "Set-Cookie:" header after redirection of URL. 24 | * 25 | * The reponse body should be 26 | *
27 |      *   {
28 |      *     "cookies": {
29 |      *       "name": "value"
30 |      *     }
31 |      *   }
32 |      * 
33 | * @throws IOException if test fails 34 | */ 35 | @Test 36 | void testHttpBinCookies() throws IOException { 37 | Client client = new Client(); 38 | AtomicBoolean success = new AtomicBoolean(); 39 | try { 40 | Request request = Request.get() 41 | .url("http://httpbin.org/cookies/set?name=value") 42 | .setResponseListener(resp -> { 43 | logger.log(Level.INFO, "status = " + resp.getStatus() + 44 | " response body = " + resp.getBodyAsString(StandardCharsets.UTF_8)); 45 | for (Cookie cookie : resp.getCookies().keySet()) { 46 | logger.log(Level.INFO, "got cookie: " + cookie.toString()); 47 | if ("name".equals(cookie.name()) && ("value".equals(cookie.value()))) { 48 | success.set(true); 49 | } 50 | } 51 | }) 52 | .build(); 53 | client.execute(request).get(); 54 | } finally { 55 | client.shutdownGracefully(); 56 | } 57 | assertTrue(success.get()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/ServerRequest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.api; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufInputStream; 5 | import io.netty.handler.codec.http.HttpHeaders; 6 | import io.netty.handler.codec.http.HttpMethod; 7 | import org.xbib.net.URL; 8 | import org.xbib.netty.http.common.HttpParameters; 9 | import javax.net.ssl.SSLSession; 10 | import java.net.InetSocketAddress; 11 | import java.nio.charset.Charset; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | public interface ServerRequest { 16 | 17 | Builder getBuilder(); 18 | 19 | InetSocketAddress getLocalAddress(); 20 | 21 | InetSocketAddress getRemoteAddress(); 22 | 23 | URL getURL(); 24 | 25 | List getContext(); 26 | 27 | Map getPathParameters(); 28 | 29 | String getRequestURI(); 30 | 31 | HttpMethod getMethod(); 32 | 33 | HttpHeaders getHeaders(); 34 | 35 | String getHeader(String name); 36 | 37 | HttpParameters getParameters(); 38 | 39 | String getContextPath(); 40 | 41 | String getEffectiveRequestPath(); 42 | 43 | Integer getSequenceId(); 44 | 45 | Integer getStreamId(); 46 | 47 | Long getRequestId(); 48 | 49 | ByteBuf getContent(); 50 | 51 | String getContent(Charset charset); 52 | 53 | ByteBufInputStream getInputStream(); 54 | 55 | SSLSession getSession(); 56 | 57 | URL getBaseURL(); 58 | 59 | URL getContextURL(); 60 | 61 | Domain>> getDomain(); 62 | 63 | EndpointResolver> getEndpointResolver(); 64 | 65 | Endpoint getEndpoint(); 66 | 67 | interface Builder { 68 | 69 | String getRequestURI(); 70 | 71 | HttpMethod getMethod(); 72 | 73 | HttpHeaders getHeaders(); 74 | 75 | String getEffectiveRequestPath(); 76 | 77 | Builder setBaseURL(URL baseURL); 78 | 79 | Builder setDomain(Domain>> domain); 80 | 81 | Builder setEndpointResolver(EndpointResolver> endpointResolver); 82 | 83 | Builder setEndpoint(Endpoint endpoint); 84 | 85 | Builder setContext(List context); 86 | 87 | Builder addPathParameter(String key, String value); 88 | 89 | ServerRequest build(); 90 | 91 | void release(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/CompletableFutureTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.xbib.netty.http.client.Client; 5 | import org.xbib.netty.http.client.api.Request; 6 | import org.xbib.netty.http.common.HttpResponse; 7 | 8 | import java.io.IOException; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.function.Function; 12 | import java.util.logging.Level; 13 | import java.util.logging.Logger; 14 | 15 | class CompletableFutureTest { 16 | 17 | private static final Logger logger = Logger.getLogger(CompletableFutureTest.class.getName()); 18 | 19 | /** 20 | * Get some weird content from one URL and post it to another URL, by composing completable futures. 21 | */ 22 | @Test 23 | void testComposeCompletableFutures() throws IOException { 24 | Client client = Client.builder().build(); 25 | try { 26 | final Function stringFunction = response -> 27 | response.getBodyAsString(StandardCharsets.UTF_8); 28 | Request request = Request.get() 29 | .url("http://repo.maven.apache.org/maven2/org/xbib/netty-http-client/maven-metadata.xml.sha1") 30 | .build(); 31 | CompletableFuture completableFuture = client.execute(request, stringFunction) 32 | .exceptionally(Throwable::getMessage) 33 | .thenCompose(content -> { 34 | logger.log(Level.INFO, content); 35 | // POST is not allowed, will give a 405. We don't care 36 | try { 37 | return client.execute(Request.post() 38 | .url("http://google.com/") 39 | .addParameter("query", content) 40 | .build(), stringFunction); 41 | } catch (IOException e) { 42 | logger.log(Level.WARNING, e.getMessage(), e); 43 | return null; 44 | } 45 | }); 46 | String result = completableFuture.join(); 47 | logger.log(Level.INFO, "completablefuture result = " + result); 48 | } finally { 49 | client.shutdownGracefully(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/BasicAuthTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.test.http1; 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus; 4 | import io.netty.handler.codec.http.HttpVersion; 5 | import org.junit.jupiter.api.Test; 6 | import org.xbib.netty.http.client.Client; 7 | import org.xbib.netty.http.client.api.Request; 8 | import org.xbib.netty.http.client.api.ResponseListener; 9 | import org.xbib.netty.http.common.HttpAddress; 10 | import org.xbib.netty.http.common.HttpResponse; 11 | import org.xbib.netty.http.server.HttpServerDomain; 12 | import org.xbib.netty.http.server.Server; 13 | 14 | import java.nio.charset.StandardCharsets; 15 | import java.util.logging.Level; 16 | import java.util.logging.Logger; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | 20 | public class BasicAuthTest { 21 | 22 | private static final Logger logger = Logger.getLogger(PostTest.class.getName()); 23 | 24 | @Test 25 | void testBasicAuth() throws Exception { 26 | HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); 27 | HttpServerDomain domain = HttpServerDomain.builder(httpAddress) 28 | .singleEndpoint("/**", (request, response) -> { 29 | String authorization = request.getHeader("Authorization"); 30 | response.getBuilder().setStatus(HttpResponseStatus.OK.code()) 31 | .setContentType("text/plain").build().write(authorization); 32 | }) 33 | .build(); 34 | Server server = Server.builder(domain) 35 | .build(); 36 | Client client = Client.builder() 37 | .build(); 38 | try { 39 | server.accept(); 40 | ResponseListener responseListener = (resp) -> 41 | assertEquals("Basic aGVsbG86d29ybGQ=", resp.getBodyAsString(StandardCharsets.UTF_8)); 42 | Request postRequest = Request.get() 43 | .setVersion(HttpVersion.HTTP_1_1) 44 | .url(server.getServerConfig().getAddress().base()) 45 | .addBasicAuthorization("hello", "world") 46 | .setResponseListener(responseListener) 47 | .build(); 48 | client.execute(postRequest).get(); 49 | } finally { 50 | server.shutdownGracefully(); 51 | client.shutdownGracefully(); 52 | logger.log(Level.INFO, "server and client shut down"); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /netty-http-server/src/test/java/org/xbib/netty/http/server/test/ws1/EchoTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.test.ws1; 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus; 4 | import io.netty.handler.codec.http.HttpVersion; 5 | import org.junit.jupiter.api.Test; 6 | import org.xbib.netty.http.client.Client; 7 | import org.xbib.netty.http.client.api.Request; 8 | import org.xbib.netty.http.client.api.ResponseListener; 9 | import org.xbib.netty.http.common.HttpAddress; 10 | import org.xbib.netty.http.common.HttpResponse; 11 | import org.xbib.netty.http.server.HttpServerDomain; 12 | import org.xbib.netty.http.server.Server; 13 | 14 | import java.nio.charset.StandardCharsets; 15 | import java.util.logging.Level; 16 | import java.util.logging.Logger; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | 20 | public class EchoTest { 21 | 22 | private static final Logger logger = Logger.getLogger(EchoTest.class.getName()); 23 | 24 | @Test 25 | void testBasicAuth() throws Exception { 26 | HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); 27 | HttpServerDomain domain = HttpServerDomain.builder(httpAddress) 28 | .singleEndpoint("/**", (request, response) -> { 29 | String authorization = request.getHeader("Authorization"); 30 | response.getBuilder().setStatus(HttpResponseStatus.OK.code()) 31 | .setContentType("text/plain").build().write(authorization); 32 | }) 33 | .build(); 34 | Server server = Server.builder(domain) 35 | .build(); 36 | Client client = Client.builder() 37 | .build(); 38 | try { 39 | server.accept(); 40 | ResponseListener responseListener = (resp) -> 41 | assertEquals("Basic aGVsbG86d29ybGQ=", resp.getBodyAsString(StandardCharsets.UTF_8)); 42 | Request postRequest = Request.get() 43 | .setVersion(HttpVersion.HTTP_1_1) 44 | .url(server.getServerConfig().getAddress().base()) 45 | .addBasicAuthorization("hello", "world") 46 | .setResponseListener(responseListener) 47 | .build(); 48 | client.execute(postRequest).get(); 49 | } finally { 50 | server.shutdownGracefully(); 51 | client.shutdownGracefully(); 52 | logger.log(Level.INFO, "server and client shut down"); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /netty-http-common/src/main/java/org/xbib/netty/http/common/util/DateTimeUtil.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.common.util; 2 | 3 | import java.time.Instant; 4 | import java.time.ZoneId; 5 | import java.time.ZoneOffset; 6 | import java.time.ZonedDateTime; 7 | import java.time.format.DateTimeFormatter; 8 | import java.time.format.DateTimeParseException; 9 | import java.util.Locale; 10 | 11 | public class DateTimeUtil { 12 | 13 | private static final ZoneId ZONE_UTC = ZoneId.of("UTC"); 14 | 15 | private static final Locale ROOT_LOCALE = Locale.ROOT; 16 | 17 | private static final String RFC1036_PATTERN = "EEE, dd-MMM-yyyy HH:mm:ss zzz"; 18 | 19 | private static final String ASCIITIME_PATTERN = "EEE MMM d HH:mm:ss yyyyy"; 20 | 21 | private DateTimeUtil() { 22 | } 23 | 24 | public static String formatRfc1123(Instant instant) { 25 | return DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.ofInstant(instant, ZoneOffset.UTC)); 26 | } 27 | 28 | public static String formatRfc1123(long millis) { 29 | return formatRfc1123(Instant.ofEpochMilli(millis)); 30 | } 31 | 32 | // RFC 2616 allows RFC 1123, RFC 1036, ASCII time 33 | private static final DateTimeFormatter[] dateTimeFormatters = { 34 | DateTimeFormatter.RFC_1123_DATE_TIME.withLocale(ROOT_LOCALE).withZone(ZONE_UTC), 35 | DateTimeFormatter.ofPattern(RFC1036_PATTERN).withLocale(ROOT_LOCALE).withZone(ZONE_UTC), 36 | DateTimeFormatter.ofPattern(ASCIITIME_PATTERN).withLocale(ROOT_LOCALE).withZone(ZONE_UTC) 37 | }; 38 | 39 | public static Instant parseDate(String date, int start, int end) { 40 | int length = end - start; 41 | if (length == 0) { 42 | return null; 43 | } else if (length < 0) { 44 | throw new IllegalArgumentException("Can't have end < start"); 45 | } else if (length > 64) { 46 | throw new IllegalArgumentException("Can't parse more than 64 chars," + 47 | "looks like a user error or a malformed header"); 48 | } 49 | return parseDate(date.substring(start, end)); 50 | } 51 | 52 | public static Instant parseDate(String input) { 53 | if (input == null) { 54 | return null; 55 | } 56 | int semicolonIndex = input.indexOf(';'); 57 | String trimmedDate = semicolonIndex >= 0 ? input.substring(0, semicolonIndex) : input; 58 | for (DateTimeFormatter formatter : dateTimeFormatters) { 59 | try { 60 | return Instant.from(formatter.parse(trimmedDate)); 61 | } catch (DateTimeParseException e) { 62 | // 63 | } 64 | } 65 | return null; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | plugins { 3 | id('com.gradle.plugin-publish') version('0.18.0') 4 | id('de.marcphilipp.nexus-publish') version('0.4.0') 5 | id('io.codearte.nexus-staging') version('0.21.1') 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | versionCatalogs { 11 | libs { 12 | version('gradle', '7.5.1') 13 | version('groovy', '3.0.10') 14 | version('spock', '2.0-groovy-3.0') 15 | version('junit', '5.9.1') 16 | version('netty', '4.1.89.Final') 17 | version('netty-tcnative', '2.0.59.Final') 18 | library('groovy-core', 'org.codehaus.groovy', 'groovy').versionRef('groovy') 19 | library('spock-core', 'org.spockframework', 'spock-core').versionRef('spock') 20 | library('spock-junit4', 'org.spockframework', 'spock-junit4').versionRef('spock') 21 | library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit') 22 | library('junit-jupiter-params', 'org.junit.jupiter', 'junit-jupiter-params').versionRef('junit') 23 | library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit') 24 | library('hamcrest', 'org.hamcrest', 'hamcrest-library').version('2.2') 25 | library('netty-http2', 'io.netty', 'netty-codec-http2').versionRef('netty') 26 | library('netty-handler-proxy', 'io.netty', 'netty-handler-proxy').versionRef('netty') 27 | library('netty-epoll', 'io.netty', 'netty-transport-native-epoll').versionRef('netty') 28 | library('netty-kqueue', 'io.netty', 'netty-transport-native-kqueue').versionRef('netty') 29 | library('netty-boringssl', 'io.netty', 'netty-tcnative-boringssl-static').versionRef('netty-tcnative') 30 | library('net', 'org.xbib', 'net').version('3.0.3') 31 | library('net-path', 'org.xbib', 'net-path').version('3.0.3') 32 | library('bouncycastle', 'org.bouncycastle', 'bcpkix-jdk18on').version('1.72') 33 | library('conscrypt', 'org.conscrypt', 'conscrypt-openjdk-uber').version('2.5.2') 34 | library('jackson', 'com.fasterxml.jackson.core', 'jackson-databind').version('2.12.7') 35 | library('guice', 'org.xbib', 'guice').version('5.0.1.0') 36 | library('javassist', 'org.javassist', 'javassist').version('3.29.1-GA') 37 | } 38 | } 39 | } 40 | 41 | include 'netty-http-common' 42 | include 'netty-http-epoll' 43 | include 'netty-http-kqueue' 44 | include 'netty-http-bouncycastle' 45 | include 'netty-http-client-api' 46 | include 'netty-http-client' 47 | include 'netty-http-client-rest' 48 | include 'netty-http-server-api' 49 | include 'netty-http-server' 50 | include 'netty-http-server-rest' 51 | -------------------------------------------------------------------------------- /netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainServerTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.test; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.xbib.netty.http.client.Client; 8 | import org.xbib.netty.http.client.api.Request; 9 | import org.xbib.netty.http.common.HttpAddress; 10 | import org.xbib.netty.http.server.Server; 11 | import org.xbib.netty.http.server.HttpServerDomain; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.logging.Level; 14 | import java.util.logging.Logger; 15 | 16 | @Disabled 17 | @ExtendWith(NettyHttpTestExtension.class) 18 | class MultiDomainServerTest { 19 | 20 | private static final Logger logger = Logger.getLogger(MultiDomainServerTest.class.getName()); 21 | 22 | @Test 23 | void testServer() throws Exception { 24 | HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); 25 | HttpServerDomain fl = HttpServerDomain.builder(httpAddress, "fl.hbz-nrw.de") 26 | .singleEndpoint("/", (request, response) -> 27 | response.write( "Hello fl.hbz-nrw.de")) 28 | .build(); 29 | HttpServerDomain zfl2 = HttpServerDomain.builder(fl) 30 | .setServerName("zfl2.hbz-nrw.de") 31 | .singleEndpoint("/", (request, response) -> 32 | response.write("Hello zfl2.hbz-nrw.de")) 33 | .build(); 34 | Server server = Server.builder(fl) 35 | .addDomain(zfl2) 36 | .build(); 37 | Client client = Client.builder() 38 | .build(); 39 | try { 40 | server.accept(); 41 | Request request = Request.get() 42 | .url("http://fl.hbz-nrw.de:8008") 43 | .setResponseListener(resp -> { 44 | String response = resp.getBodyAsString(StandardCharsets.UTF_8); 45 | logger.log(Level.INFO, "fl: got response: " + response + " status=" + resp.getStatus()); 46 | assertEquals("Hello fl.hbz-nrw.de", response); 47 | }) 48 | .build(); 49 | client.execute(request).get(); 50 | request = Request.get() 51 | .url("http://zfl2.hbz-nrw.de:8008") 52 | .setResponseListener(resp -> { 53 | String response = resp.getBodyAsString(StandardCharsets.UTF_8); 54 | logger.log(Level.INFO, "zfl2: got response: " + response + " status=" + resp.getStatus()); 55 | assertEquals("Hello zfl2.hbz-nrw.de", response); 56 | }) 57 | .build(); 58 | client.execute(request).get(); 59 | } finally { 60 | client.shutdownGracefully(); 61 | server.shutdownGracefully(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /netty-http-server/src/test/java/org/xbib/netty/http/server/test/http2/StreamTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.test.http2; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.xbib.netty.http.client.Client; 7 | import org.xbib.netty.http.client.api.Request; 8 | import org.xbib.netty.http.common.HttpAddress; 9 | import org.xbib.netty.http.server.HttpServerDomain; 10 | import org.xbib.netty.http.server.Server; 11 | import org.xbib.netty.http.server.test.NettyHttpTestExtension; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | import io.netty.buffer.ByteBufInputStream; 15 | import io.netty.buffer.ByteBufOutputStream; 16 | import io.netty.handler.codec.http.HttpResponseStatus; 17 | 18 | @ExtendWith(NettyHttpTestExtension.class) 19 | class StreamTest { 20 | 21 | @Test 22 | void testServerBodyInputStreamHttp2() throws Exception { 23 | HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); 24 | HttpServerDomain domain = HttpServerDomain.builder(httpAddress) 25 | .singleEndpoint("/", (request, response) -> { 26 | ByteBufInputStream inputStream = request.getInputStream(); 27 | String content = inputStream.readLine(); 28 | assertEquals("my body parameter", content); 29 | ByteBufOutputStream outputStream = response.newOutputStream(); 30 | outputStream.writeBytes("Hello World"); 31 | response.getBuilder().setStatus(HttpResponseStatus.OK.code()) 32 | .setContentType("text/plain") 33 | .build() 34 | .write(outputStream); 35 | }) 36 | .build(); 37 | Server server = Server.builder(domain) 38 | .build(); 39 | Client client = Client.builder() 40 | .build(); 41 | int max = 1; 42 | final AtomicInteger count = new AtomicInteger(0); 43 | try { 44 | server.accept(); 45 | Request request = Request.get().setVersion("HTTP/2.0") 46 | .url(server.getServerConfig().getAddress().base().resolve("/")) 47 | .content("my body parameter", "text/plain") 48 | .setResponseListener(resp -> { 49 | if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { 50 | assertEquals("Hello World", resp.getBodyAsString(StandardCharsets.UTF_8)); 51 | count.incrementAndGet(); 52 | } 53 | }) 54 | .build(); 55 | for (int i = 0; i < max; i++) { 56 | client.execute(request).get(); 57 | } 58 | } finally { 59 | server.shutdownGracefully(); 60 | client.shutdownGracefully(); 61 | } 62 | assertEquals(max, count.get()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /netty-http-server/src/test/java/org/xbib/netty/http/server/test/http1/StreamTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.test.http1; 2 | 3 | import io.netty.buffer.ByteBufInputStream; 4 | import io.netty.buffer.ByteBufOutputStream; 5 | import io.netty.handler.codec.http.HttpResponseStatus; 6 | import io.netty.handler.codec.http.HttpVersion; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.xbib.netty.http.client.Client; 10 | import org.xbib.netty.http.client.api.Request; 11 | import org.xbib.netty.http.common.HttpAddress; 12 | import org.xbib.netty.http.server.HttpServerDomain; 13 | import org.xbib.netty.http.server.Server; 14 | import org.xbib.netty.http.server.test.NettyHttpTestExtension; 15 | 16 | import java.nio.charset.StandardCharsets; 17 | import java.util.concurrent.atomic.AtomicInteger; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | @ExtendWith(NettyHttpTestExtension.class) 22 | class StreamTest { 23 | 24 | @Test 25 | void testServerBodyInputStreamHttp1() throws Exception { 26 | HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); 27 | HttpServerDomain domain = HttpServerDomain.builder(httpAddress) 28 | .singleEndpoint("/", (request, response) -> { 29 | ByteBufInputStream inputStream = request.getInputStream(); 30 | String content = inputStream.readLine(); 31 | assertEquals("my body parameter", content); 32 | ByteBufOutputStream outputStream = response.newOutputStream(); 33 | outputStream.writeBytes("Hello World"); 34 | response.getBuilder().setStatus(HttpResponseStatus.OK.code()).setContentType("text/plain").build() 35 | .write(outputStream); 36 | }) 37 | .build(); 38 | Server server = Server.builder(domain) 39 | .build(); 40 | Client client = Client.builder() 41 | .build(); 42 | int max = 1; 43 | final AtomicInteger count = new AtomicInteger(0); 44 | try { 45 | server.accept(); 46 | Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) 47 | .url(server.getServerConfig().getAddress().base().resolve("/")) 48 | .content("my body parameter", "text/plain") 49 | .setResponseListener(resp -> { 50 | if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) { 51 | assertEquals("Hello World", resp.getBodyAsString(StandardCharsets.UTF_8)); 52 | count.incrementAndGet(); 53 | } 54 | }) 55 | .build(); 56 | for (int i = 0; i < max; i++) { 57 | client.execute(request).get(); 58 | } 59 | } finally { 60 | server.shutdownGracefully(); 61 | client.shutdownGracefully(); 62 | } 63 | assertEquals(max, count.get()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /netty-http-client-rest/src/main/java/org/xbib/netty/http/client/rest/RestClient.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.rest; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.handler.codec.http.HttpMethod; 5 | import org.xbib.net.URL; 6 | import org.xbib.netty.http.client.Client; 7 | import org.xbib.netty.http.common.HttpAddress; 8 | import org.xbib.netty.http.client.api.Request; 9 | import org.xbib.netty.http.common.HttpResponse; 10 | 11 | import java.io.IOException; 12 | import java.nio.charset.Charset; 13 | import java.nio.charset.StandardCharsets; 14 | 15 | public class RestClient { 16 | 17 | private HttpResponse response; 18 | 19 | private ByteBuf byteBuf; 20 | 21 | private RestClient() { 22 | } 23 | 24 | private void setResponse(HttpResponse response) { 25 | this.response = response; 26 | this.byteBuf = response != null ? response.getBody().retain() : null; 27 | } 28 | 29 | public HttpResponse getResponse() { 30 | return response; 31 | } 32 | 33 | public String asString() { 34 | return asString(StandardCharsets.UTF_8); 35 | } 36 | 37 | private String asString(Charset charset) { 38 | return byteBuf != null && byteBuf.isReadable() ? byteBuf.toString(charset) : null; 39 | } 40 | 41 | public static RestClient get(String urlString) throws IOException { 42 | return method(urlString, HttpMethod.GET); 43 | } 44 | 45 | public static RestClient delete(String urlString) throws IOException { 46 | return method(urlString, HttpMethod.DELETE); 47 | } 48 | 49 | public static RestClient post(String urlString, String body) throws IOException { 50 | return method(urlString, body, StandardCharsets.UTF_8, HttpMethod.POST); 51 | } 52 | 53 | public static RestClient put(String urlString, String body) throws IOException { 54 | return method(urlString, body, StandardCharsets.UTF_8, HttpMethod.PUT); 55 | } 56 | 57 | private static RestClient method(String urlString, HttpMethod httpMethod) throws IOException { 58 | return method(urlString, null, null, httpMethod); 59 | } 60 | 61 | private static RestClient method(String urlString, 62 | String body, Charset charset, 63 | HttpMethod httpMethod) throws IOException { 64 | URL url = URL.create(urlString); 65 | RestClient restClient = new RestClient(); 66 | try (Client client = Client.builder() 67 | .setThreadCount(2) // for redirect 68 | .build()) { 69 | Request.Builder requestBuilder = Request.builder(httpMethod).url(url); 70 | if (body != null) { 71 | ByteBuf byteBuf = client.getByteBufAllocator().buffer(); 72 | byteBuf.writeCharSequence(body, charset); 73 | requestBuilder.content(byteBuf); 74 | } 75 | client.newTransport(HttpAddress.http1(url)) 76 | .execute(requestBuilder.setResponseListener(restClient::setResponse).build()) 77 | .close(); 78 | } catch (Exception e) { 79 | throw new IOException(e); 80 | } 81 | return restClient; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/util/MimeTypeUtils.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.util; 2 | 3 | import java.net.URLConnection; 4 | import java.util.Collections; 5 | import java.util.HashMap; 6 | import java.util.Locale; 7 | import java.util.Map; 8 | 9 | import static java.util.Objects.requireNonNull; 10 | 11 | public class MimeTypeUtils { 12 | 13 | /** 14 | * A map from extension to MIME types, which is queried before 15 | * {@link URLConnection#guessContentTypeFromName(String)}, so that 16 | * important extensions are always mapped to the right MIME types. 17 | */ 18 | private static final Map EXTENSION_TO_MEDIA_TYPE; 19 | 20 | static { 21 | Map map = new HashMap<>(); 22 | // Text files 23 | add(map, "text/css", "css"); 24 | add(map, "text/html", "html", "htm"); 25 | add(map, "text/plain", "txt"); 26 | 27 | // Image files 28 | add(map, "image/gif", "gif"); 29 | add(map, "image/jpeg", "jpeg", "jpg"); 30 | add(map, "image/png", "png"); 31 | add(map, "image/svg+xml", "svg", "svgz"); 32 | add(map, "image/x-icon", "ico"); 33 | 34 | // Font files 35 | add(map, "application/x-font-ttf", "ttc", "ttf"); 36 | add(map, "application/font-woff", "woff"); 37 | add(map, "application/font-woff2", "woff2"); 38 | add(map, "application/vnd.ms-fontobject", "eot"); 39 | add(map, "font/opentype", "otf"); 40 | 41 | // JavaScript, XML, etc 42 | add(map, "application/javascript", "js", "map"); 43 | add(map, "application/json", "json"); 44 | add(map, "application/pdf", "pdf"); 45 | add(map, "application/xhtml+xml", "xhtml", "xhtm"); 46 | add(map, "application/xml", "xml", "xsd"); 47 | add(map, "application/xml-dtd", "dtd"); 48 | 49 | EXTENSION_TO_MEDIA_TYPE = Collections.unmodifiableMap(map); 50 | } 51 | 52 | private static void add(Map extensionToMediaType, 53 | String mediaType, String... extensions) { 54 | for (String s : extensions) { 55 | extensionToMediaType.put(s, mediaType); 56 | } 57 | } 58 | 59 | public static String guessFromPath(String path, boolean preCompressed) { 60 | requireNonNull(path, "path"); 61 | String s = path; 62 | // If the path is for a precompressed file, it will have an additional extension indicating the 63 | // encoding, which we don't want to use when determining content type. 64 | if (preCompressed) { 65 | s = s.substring(0, s.lastIndexOf('.')); 66 | } 67 | int dotIdx = s.lastIndexOf('.'); 68 | int slashIdx = s.lastIndexOf('/'); 69 | if (dotIdx < 0 || slashIdx > dotIdx) { 70 | // No extension 71 | return null; 72 | } 73 | String extension = s.substring(dotIdx + 1).toLowerCase(Locale.ROOT); 74 | String mediaType = EXTENSION_TO_MEDIA_TYPE.get(extension); 75 | if (mediaType != null) { 76 | return mediaType; 77 | } 78 | String guessedContentType = URLConnection.guessContentTypeFromName(path); 79 | return guessedContentType != null ? guessedContentType : "application/octet-stream"; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /netty-http-client/src/test/java/org/xbib/netty/http/client/test/pool/PooledClientTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.client.test.pool; 2 | 3 | import io.netty.handler.codec.http.HttpVersion; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.xbib.net.URL; 7 | import org.xbib.netty.http.client.Client; 8 | import org.xbib.netty.http.client.api.ResponseListener; 9 | import org.xbib.netty.http.client.test.NettyHttpTestExtension; 10 | import org.xbib.netty.http.common.HttpAddress; 11 | import org.xbib.netty.http.client.api.Request; 12 | import org.xbib.netty.http.common.HttpResponse; 13 | 14 | import java.io.IOException; 15 | import java.nio.charset.StandardCharsets; 16 | import java.util.concurrent.ExecutorService; 17 | import java.util.concurrent.Executors; 18 | import java.util.concurrent.TimeUnit; 19 | import java.util.concurrent.atomic.AtomicInteger; 20 | import java.util.logging.Level; 21 | import java.util.logging.Logger; 22 | 23 | @ExtendWith(NettyHttpTestExtension.class) 24 | class PooledClientTest { 25 | 26 | private static final Logger logger = Logger.getLogger(PooledClientTest.class.getName()); 27 | 28 | @Test 29 | void testPooledClientWithSingleNode() throws IOException { 30 | int loop = 10; 31 | int threads = Runtime.getRuntime().availableProcessors(); 32 | URL url = URL.from("https://fl-test.hbz-nrw.de/"); 33 | HttpAddress httpAddress = HttpAddress.of(url, HttpVersion.valueOf("HTTP/2.0")); 34 | Client client = Client.builder() 35 | .addPoolNode(httpAddress) 36 | .setPoolNodeConnectionLimit(threads) 37 | .build(); 38 | AtomicInteger count = new AtomicInteger(); 39 | ResponseListener responseListener = resp -> { 40 | String response = resp.getBodyAsString(StandardCharsets.UTF_8); 41 | count.getAndIncrement(); 42 | }; 43 | try { 44 | ExecutorService executorService = Executors.newFixedThreadPool(threads); 45 | for (int n = 0; n < threads; n++) { 46 | executorService.submit(() -> { 47 | try { 48 | logger.log(Level.INFO, "starting " + Thread.currentThread()); 49 | for (int i = 0; i < loop; i++) { 50 | Request request = Request.get().setVersion(httpAddress.getVersion()) 51 | .url(url.toString()) 52 | .setResponseListener(responseListener) 53 | .build(); 54 | client.newTransport().execute(request).get(); 55 | } 56 | logger.log(Level.INFO, "done " + Thread.currentThread()); 57 | } catch (Throwable e) { 58 | logger.log(Level.WARNING, e.getMessage(), e); 59 | } 60 | }); 61 | } 62 | executorService.shutdown(); 63 | executorService.awaitTermination(60, TimeUnit.SECONDS); 64 | } catch (Throwable e) { 65 | logger.log(Level.WARNING, e.getMessage(), e); 66 | } finally { 67 | client.shutdownGracefully(); 68 | } 69 | logger.log(Level.INFO, "count = " + count.get()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /netty-http-server/src/test/java/org/xbib/netty/http/server/test/MultiDomainSecureServerTest.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.test; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.xbib.netty.http.client.Client; 8 | import org.xbib.netty.http.client.api.Request; 9 | import org.xbib.netty.http.common.HttpAddress; 10 | import org.xbib.netty.http.server.HttpServerDomain; 11 | import org.xbib.netty.http.server.Server; 12 | import java.io.InputStream; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.logging.Level; 15 | import java.util.logging.Logger; 16 | 17 | @Disabled 18 | @ExtendWith(NettyHttpTestExtension.class) 19 | class MultiDomainSecureServerTest { 20 | 21 | private static final Logger logger = Logger.getLogger(MultiDomainSecureServerTest.class.getName()); 22 | 23 | @Test 24 | void testSecureServer() throws Exception { 25 | InputStream certInputStream = getClass().getResourceAsStream("/fl-20210906.crt"); 26 | if (certInputStream == null) { 27 | return; 28 | } 29 | InputStream keyInputStream = getClass().getResourceAsStream("/fl-20210906.pkcs8"); 30 | if (keyInputStream == null) { 31 | return; 32 | } 33 | HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8443); 34 | HttpServerDomain fl = HttpServerDomain.builder(httpAddress, "fl.hbz-nrw.de") 35 | .setKeyCertChain(certInputStream) 36 | .setKey(keyInputStream, null) 37 | .singleEndpoint("/", (request, response) -> 38 | response.write("Hello fl.hbz-nrw.de")) 39 | .build(); 40 | HttpServerDomain zfl2 = HttpServerDomain.builder(fl) 41 | .setServerName("zfl2.hbz-nrw.de") 42 | .singleEndpoint("/", (request, response) -> 43 | response.write( "Hello zfl2.hbz-nrw.de")) 44 | .build(); 45 | Server server = Server.builder(fl) 46 | .addDomain(zfl2) 47 | .setTransportLayerSecurityProtocols("TLSv1.3") 48 | .build(); 49 | Client client = Client.builder() 50 | .build(); 51 | try { 52 | server.accept(); 53 | Request request = Request.get() 54 | .setVersion("HTTP/2.0") 55 | .url("https://fl.hbz-nrw.de:8443") 56 | .setResponseListener(resp -> { 57 | String response = resp.getBodyAsString(StandardCharsets.UTF_8); 58 | logger.log(Level.INFO, "fl: got response: " + response + " status=" + resp.getStatus()); 59 | assertEquals("Hello fl.hbz-nrw.de", response); 60 | }) 61 | .build(); 62 | client.execute(request).get(); 63 | request = Request.get() 64 | .setVersion("HTTP/2.0") 65 | .url("https://zfl2.hbz-nrw.de:8443") 66 | .setResponseListener(resp -> { 67 | String response = resp.getBodyAsString(StandardCharsets.UTF_8); 68 | logger.log(Level.INFO, "zfl2: got response: " + response + " status=" + resp.getStatus()); 69 | assertEquals("Hello zfl2.hbz-nrw.de", response); 70 | }) 71 | .build(); 72 | client.execute(request).get(); 73 | } finally { 74 | client.shutdownGracefully(); 75 | server.shutdownGracefully(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/http1/Http1Transport.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.http1; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http.HttpHeaderNames; 6 | import io.netty.handler.codec.http.HttpResponseStatus; 7 | import io.netty.handler.codec.http2.Http2Settings; 8 | import io.netty.handler.ssl.SslHandler; 9 | import org.xbib.netty.http.server.Server; 10 | import org.xbib.netty.http.server.api.ServerResponse; 11 | import org.xbib.netty.http.server.AcceptState; 12 | import org.xbib.netty.http.server.BaseTransport; 13 | import org.xbib.netty.http.server.HttpServerRequest; 14 | import java.io.IOException; 15 | import java.net.InetSocketAddress; 16 | 17 | public class Http1Transport extends BaseTransport { 18 | 19 | public Http1Transport(Server server) { 20 | super(server); 21 | } 22 | 23 | @Override 24 | public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException { 25 | AcceptState acceptState = acceptRequest(server.getServerConfig().getAddress().getVersion(), 26 | fullHttpRequest.headers()); 27 | ServerResponse.Builder serverResponseBuilder = HttpServerResponse.builder(ctx) 28 | .setResponseId(server.getResponseCounter().incrementAndGet()); 29 | switch (acceptState) { 30 | case OK: { 31 | HttpServerRequest.Builder serverRequestBuilder = HttpServerRequest.builder() 32 | .setLocalAddress((InetSocketAddress) ctx.channel().localAddress()) 33 | .setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress()) 34 | .setHttpRequest(fullHttpRequest.retainedDuplicate()) 35 | .setSequenceId(sequenceId) 36 | .setRequestId(server.getRequestCounter().incrementAndGet()); 37 | SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class); 38 | if (sslHandler != null) { 39 | serverRequestBuilder.setSession(sslHandler.engine().getSession()); 40 | } 41 | boolean shouldClose = "close".equalsIgnoreCase(fullHttpRequest.headers().get(HttpHeaderNames.CONNECTION)); 42 | serverResponseBuilder.shouldClose(shouldClose); 43 | server.handle(serverRequestBuilder, serverResponseBuilder); 44 | break; 45 | } 46 | case MISSING_HOST_HEADER: { 47 | HttpServerResponse.builder(ctx) 48 | .setStatus(HttpResponseStatus.BAD_REQUEST.code()) 49 | .setContentType("text/plain") 50 | .build() 51 | .write("missing 'Host' header"); 52 | } 53 | case EXPECTATION_FAILED: { 54 | HttpServerResponse.builder(ctx) 55 | .setStatus(HttpResponseStatus.EXPECTATION_FAILED.code()) 56 | .build() 57 | .flush(); 58 | break; 59 | } 60 | case UNSUPPORTED_HTTP_VERSION: { 61 | HttpServerResponse.builder(ctx) 62 | .setStatus(HttpResponseStatus.BAD_REQUEST.code()) 63 | .setContentType("text/plain") 64 | .build() 65 | .write("unsupported HTTP version"); 66 | break; 67 | } 68 | } 69 | } 70 | 71 | @Override 72 | public void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) { 73 | // there are no settings in HTTP 1 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/protocol/ws2/Http2WebSocketServerHandler.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.protocol.ws2; 2 | 3 | import io.netty.channel.*; 4 | import io.netty.handler.codec.http.websocketx.WebSocketDecoderConfig; 5 | import io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker; 6 | import io.netty.handler.codec.http2.Http2Exception; 7 | import io.netty.handler.codec.http2.Http2Headers; 8 | import org.xbib.netty.http.common.ws.Http2WebSocketChannelHandler; 9 | import org.xbib.netty.http.common.ws.Http2WebSocketProtocol; 10 | import org.xbib.netty.http.common.ws.Http2WebSocketValidator; 11 | 12 | /** 13 | * Provides server-side support for websocket-over-http2. Creates sub channel for http2 stream of 14 | * successfully handshaked websocket. Subchannel is compatible with http1 websocket handlers. 15 | */ 16 | public final class Http2WebSocketServerHandler extends Http2WebSocketChannelHandler { 17 | 18 | private final PerMessageDeflateServerExtensionHandshaker compressionHandshaker; 19 | 20 | private final Http2WebSocketAcceptor http2WebSocketAcceptor; 21 | 22 | private Http2WebSocketServerHandshaker handshaker; 23 | 24 | Http2WebSocketServerHandler(WebSocketDecoderConfig webSocketDecoderConfig, boolean isEncoderMaskPayload, 25 | long closedWebSocketRemoveTimeoutMillis, 26 | PerMessageDeflateServerExtensionHandshaker compressionHandshaker, 27 | Http2WebSocketAcceptor http2WebSocketAcceptor, 28 | boolean isSingleWebSocketPerConnection) { 29 | super(webSocketDecoderConfig, isEncoderMaskPayload, closedWebSocketRemoveTimeoutMillis, isSingleWebSocketPerConnection); 30 | this.compressionHandshaker = compressionHandshaker; 31 | this.http2WebSocketAcceptor = http2WebSocketAcceptor; 32 | } 33 | 34 | @Override 35 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 36 | super.handlerAdded(ctx); 37 | this.handshaker = new Http2WebSocketServerHandshaker(webSocketsParent, 38 | config, isEncoderMaskPayload, http2WebSocketAcceptor, compressionHandshaker); 39 | } 40 | 41 | @Override 42 | public void onHeadersRead(ChannelHandlerContext ctx, final int streamId, Http2Headers headers, 43 | int padding, boolean endOfStream) throws Http2Exception { 44 | boolean proceed = handshakeWebSocket(streamId, headers, endOfStream); 45 | if (proceed) { 46 | next().onHeadersRead(ctx, streamId, headers, padding, endOfStream); 47 | } 48 | } 49 | 50 | @Override 51 | public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, 52 | short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { 53 | boolean proceed = handshakeWebSocket(streamId, headers, endOfStream); 54 | if (proceed) { 55 | next().onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream); 56 | } 57 | } 58 | 59 | private boolean handshakeWebSocket(int streamId, Http2Headers headers, boolean endOfStream) { 60 | if (Http2WebSocketProtocol.isExtendedConnect(headers)) { 61 | if (!Http2WebSocketValidator.WebSocket.isValid(headers, endOfStream)) { 62 | handshaker.reject(streamId, headers, endOfStream); 63 | } else { 64 | handshaker.handshake(streamId, headers, endOfStream); 65 | } 66 | return false; 67 | } 68 | if (!Http2WebSocketValidator.Http.isValid(headers, endOfStream)) { 69 | handshaker.reject(streamId, headers, endOfStream); 70 | return false; 71 | } 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/FileService.java: -------------------------------------------------------------------------------- 1 | package org.xbib.netty.http.server.endpoint.service; 2 | 3 | import org.xbib.netty.http.server.api.Resource; 4 | import org.xbib.netty.http.server.api.ServerRequest; 5 | import org.xbib.netty.http.server.api.ServerResponse; 6 | 7 | import java.io.IOException; 8 | import java.net.URL; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.time.Instant; 12 | 13 | public class FileService extends ResourceService { 14 | 15 | private final Path prefix; 16 | 17 | private final String indexFileName; 18 | 19 | public FileService(Path prefix) { 20 | this(prefix, "index.html"); 21 | } 22 | 23 | public FileService(Path prefix, String indexFileName) { 24 | this.prefix = prefix; 25 | this.indexFileName = indexFileName; 26 | } 27 | 28 | @Override 29 | protected Resource createResource(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { 30 | return new ChunkedFileResource(serverRequest); 31 | } 32 | 33 | @Override 34 | protected boolean isETagResponseEnabled() { 35 | return true; 36 | } 37 | 38 | @Override 39 | protected boolean isCacheResponseEnabled() { 40 | return true; 41 | } 42 | 43 | @Override 44 | protected boolean isRangeResponseEnabled() { 45 | return true; 46 | } 47 | 48 | @Override 49 | protected int getMaxAgeSeconds() { 50 | return 24 * 3600; 51 | } 52 | 53 | class ChunkedFileResource implements Resource { 54 | 55 | private final String resourcePath; 56 | 57 | private final URL url; 58 | 59 | private final boolean isDirectory; 60 | 61 | private final Instant lastModified; 62 | 63 | private final long length; 64 | 65 | ChunkedFileResource(ServerRequest serverRequest) throws IOException { 66 | String effectivePath = serverRequest.getEffectiveRequestPath(); 67 | this.resourcePath = effectivePath.startsWith("/") ? effectivePath.substring(1) : effectivePath; 68 | Path path = prefix.resolve(resourcePath); 69 | this.url = path.toUri().toURL(); 70 | boolean isExists = Files.exists(path); 71 | this.isDirectory = Files.isDirectory(path); 72 | if (isExists) { 73 | this.lastModified = Files.getLastModifiedTime(path).toInstant(); 74 | this.length = Files.size(path); 75 | } else { 76 | this.lastModified = Instant.now(); 77 | this.length = 0; 78 | } 79 | } 80 | 81 | @Override 82 | public String getResourcePath() { 83 | return resourcePath; 84 | } 85 | 86 | @Override 87 | public URL getURL() { 88 | return url; 89 | } 90 | 91 | @Override 92 | public boolean isDirectory() { 93 | return isDirectory; 94 | } 95 | 96 | @Override 97 | public String indexFileName() { 98 | return indexFileName; 99 | } 100 | 101 | @Override 102 | public Instant getLastModified() { 103 | return lastModified; 104 | } 105 | 106 | @Override 107 | public long getLength() { 108 | return length; 109 | } 110 | 111 | @Override 112 | public String toString() { 113 | return "[FileResource:resourcePath=" + resourcePath + 114 | ",url=" + url + 115 | ",lastmodified=" + lastModified + 116 | ",length=" + length + 117 | ",isDirectory=" + isDirectory() + "]"; 118 | } 119 | } 120 | } 121 | --------------------------------------------------------------------------------