├── gradle.properties ├── settings.gradle ├── src ├── integration-test │ ├── resources │ │ ├── application.yml │ │ ├── application-errors.yml │ │ ├── application-nodefs.yml │ │ ├── application-onmessage.yml │ │ ├── application-ondisconnect.yml │ │ ├── application-filters.yml │ │ ├── application-onconnect.yml │ │ ├── application-duplicatedefs.yml │ │ └── application-multiple.yml │ └── java │ │ └── org │ │ └── kgusarov │ │ └── integration │ │ └── spring │ │ └── netty │ │ ├── etc │ │ ├── HandlerCallStack.java │ │ ├── HandlerMethodCalls.java │ │ ├── ProcessingCounter.java │ │ ├── ExceptionHandler.java │ │ └── ClientHandler.java │ │ ├── errors │ │ ├── nonhandler │ │ │ ├── handlers │ │ │ │ └── WrongHandler.java │ │ │ ├── NonHandler1IntegrationTest.java │ │ │ └── NonHandler1Application.java │ │ ├── nodefs │ │ │ ├── NoDefsIntergrationTest.java │ │ │ └── NoDefsApplication.java │ │ ├── mulhandler1 │ │ │ ├── MulHandler1IntegrationTest.java │ │ │ ├── MulHandler1Application.java │ │ │ └── handlers │ │ │ │ └── WrongController.java │ │ ├── mulhandler2 │ │ │ ├── MulHandler2IntegrationTest.java │ │ │ ├── MulHandler2Application.java │ │ │ └── handlers │ │ │ │ └── WrongController.java │ │ ├── mulhandler3 │ │ │ ├── MulHandler3IntegrationTest.java │ │ │ ├── MulHandler3Application.java │ │ │ └── handlers │ │ │ │ └── WrongController.java │ │ ├── nonsharable1 │ │ │ ├── NonSharable1IntegrationTest.java │ │ │ ├── NonSharable1Application.java │ │ │ └── handlers │ │ │ │ └── Decoder.java │ │ ├── nonsharable2 │ │ │ ├── NonSharable2IntegrationTest.java │ │ │ ├── NonSharable2Application.java │ │ │ └── handlers │ │ │ │ └── Decoder.java │ │ ├── duplicatedefs │ │ │ ├── DuplicateDefsIntegrationTest.java │ │ │ └── DuplicateDefsApplication.java │ │ ├── noconnectparamresolver │ │ │ ├── handlers │ │ │ │ └── WrongController.java │ │ │ ├── NoConnectParamResolverIntegrationTest.java │ │ │ └── NoConnectParamResolverApplication.java │ │ ├── nomessageparamresolver │ │ │ ├── handlers │ │ │ │ └── WrongController.java │ │ │ ├── NoMessageParamResolverIntegrationTest.java │ │ │ └── NoMessageParamResolverApplication.java │ │ ├── nodisconnectparamresolver │ │ │ ├── handlers │ │ │ │ └── WrongController.java │ │ │ ├── NoDisconnectParamResolverIntegrationTest.java │ │ │ └── NoDisconnectParamResolverApplication.java │ │ └── duplicatemessagebodies │ │ │ ├── DuplicateMessageBodiesIntegrationTest.java │ │ │ ├── handlers │ │ │ └── WrongController.java │ │ │ └── DuplicateMessageBodiesApplication.java │ │ ├── multiple │ │ ├── handlers │ │ │ ├── ExceptionHandlerFilter1.java │ │ │ ├── ExceptionHandlerFilter2.java │ │ │ ├── Server1Handler.java │ │ │ └── Server2Handler.java │ │ ├── MultipleServersApplication.java │ │ └── MultipleServersIntegrationTest.java │ │ ├── onconnect │ │ ├── handlers │ │ │ ├── ExceptionHandlerFilter.java │ │ │ └── TransactionalOnConnectController.java │ │ ├── OnConnectApplication.java │ │ ├── OnConnectController.java │ │ └── OnConnectIntegrationTest.java │ │ ├── onmessage │ │ ├── handlers │ │ │ ├── ExceptionHandlerFilter.java │ │ │ ├── TransactionalOnMessageController.java │ │ │ ├── Decoder.java │ │ │ ├── Encoder.java │ │ │ └── OnMessageController.java │ │ ├── OnMessageApplication.java │ │ └── OnMessageIntegrationTest.java │ │ ├── empty │ │ ├── EmptyApplication.java │ │ └── EmptyServersIntegrationTest.java │ │ ├── nettyfilters │ │ ├── handlers │ │ │ ├── ExceptionHandlerFilter.java │ │ │ ├── LongResponder.java │ │ │ ├── LongInverter.java │ │ │ ├── LongEncoder.java │ │ │ ├── LongDecoder.java │ │ │ └── AroundResponderFilter.java │ │ ├── NettyFiltersApplication.java │ │ └── NettyFiltersIntegrationTest.java │ │ ├── ondisconnect │ │ ├── handlers │ │ │ ├── ExceptionHandlerFilter.java │ │ │ ├── TransactionalOnDisconnectController.java │ │ │ └── OnDisconnectController.java │ │ ├── OnDisconnectApplication.java │ │ └── OnDisconnectIntegrationTest.java │ │ ├── customresolvers │ │ ├── handlers │ │ │ ├── ExceptionHandlerFilter.java │ │ │ └── CustomResolversController.java │ │ ├── resolvers │ │ │ ├── RNG.java │ │ │ ├── RandomLongOnConnectResolver.java │ │ │ ├── RandomLongOnDisconnectResolver.java │ │ │ └── RandomLongOnMessageResolver.java │ │ ├── CustomResolversApplication.java │ │ └── CustomResolversIntegrationTest.java │ │ ├── onmessagenohandler │ │ ├── OnMessageNoHandlerApplication.java │ │ ├── handlers │ │ │ ├── OnMessageNoHandlerController.java │ │ │ └── ExceptionHandlerFilter.java │ │ └── OnMessageNoHandlerIntegrationTest.java │ │ └── ServerClient.java ├── main │ └── java │ │ └── org │ │ └── kgusarov │ │ └── integration │ │ └── spring │ │ └── netty │ │ ├── configuration │ │ ├── NettyServers.java │ │ ├── EnableNettyServers.java │ │ ├── SpringNettyConfigurationProperties.java │ │ └── TcpServerProperties.java │ │ ├── support │ │ ├── invoke │ │ │ ├── assembler │ │ │ │ ├── MethodPrefixAssembler.java │ │ │ │ ├── LabelAssembler.java │ │ │ │ ├── ConstructorAssembler.java │ │ │ │ ├── LocalVariableAssembler.java │ │ │ │ ├── MethodHandleFieldAssembler.java │ │ │ │ ├── Descriptors.java │ │ │ │ └── InvokerMethodAssembler.java │ │ │ ├── GeneratedClassLoader.java │ │ │ ├── InvokerMethods.java │ │ │ ├── MethodHandleCreator.java │ │ │ ├── OnDisconnectMethodInvoker.java │ │ │ ├── OnConnectMethodInvoker.java │ │ │ ├── AbstractMethodInvoker.java │ │ │ └── OnMessageMethodInvoker.java │ │ ├── resolvers │ │ │ ├── NettyCallbackParameterResolver.java │ │ │ ├── NettyOnDisconnectParameterResolver.java │ │ │ ├── NettyOnConnectParameterResolver.java │ │ │ ├── NettyOnMessageParameterResolver.java │ │ │ └── impl │ │ │ │ ├── ChannelFutureOnDisconnectResolver.java │ │ │ │ ├── MessageBodyOnMessageResolver.java │ │ │ │ ├── ChannelOnConnectResolver.java │ │ │ │ ├── ChannelOnDisconnectResolver.java │ │ │ │ ├── ChannelHandlerContextOnConnectResolver.java │ │ │ │ ├── ChannelOnMessageResolver.java │ │ │ │ └── ChannelHandlerContextOnMessageResolver.java │ │ ├── SpringChannelFutureListener.java │ │ └── SpringChannelHandler.java │ │ ├── annotations │ │ ├── NettyMessageBody.java │ │ ├── NettyController.java │ │ ├── NettyOnMessage.java │ │ ├── NettyOnConnect.java │ │ ├── NettyOnDisconnect.java │ │ └── NettyFilter.java │ │ ├── TcpServerLifeCycle.java │ │ ├── handlers │ │ └── FlashPolicyHandler.java │ │ └── ChannelOptions.java └── test │ └── java │ └── org │ └── kgusarov │ └── integration │ └── spring │ └── netty │ ├── support │ └── invoke │ │ ├── Victim.java │ │ ├── MethodHandleCreatorTest.java │ │ └── GeneratedClassLoaderTest.java │ ├── ChannelOptionsTest.java │ ├── configuration │ ├── FilterBeanComparatorTest.java │ ├── OnMessageMethodComparatorTest.java │ ├── OnConnectMethodComparatorTest.java │ └── OnDisconnectMethodComparatorTest.java │ └── handlers │ └── FlashPolicyHandlerTest.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── gradle.yml │ └── codeql-analysis.yml ├── .travis.yml ├── LICENSE ├── gradlew.bat ├── CODE_OF_CONDUCT.md └── gradlew /gradle.properties: -------------------------------------------------------------------------------- 1 | version=2.1.1-SNAPSHOT 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'spring-boot-netty' 2 | -------------------------------------------------------------------------------- /src/integration-test/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: INFO 3 | level.org.kgusarov: TRACE 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgusarov/spring-boot-netty/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/integration-test/resources/application-errors.yml: -------------------------------------------------------------------------------- 1 | netty: 2 | servers: 3 | - name: server1 4 | host: localhost 5 | port: 40006 6 | options: 7 | soReuseAddr: true -------------------------------------------------------------------------------- /src/integration-test/resources/application-nodefs.yml: -------------------------------------------------------------------------------- 1 | netty: 2 | servers: 3 | - name: server1 4 | host: localhost 5 | port: 40005 6 | options: 7 | soReuseAddr: true -------------------------------------------------------------------------------- /src/integration-test/resources/application-onmessage.yml: -------------------------------------------------------------------------------- 1 | netty: 2 | servers: 3 | - name: server1 4 | host: localhost 5 | port: 0 6 | bossThreads: 4 7 | workerThreads: 4 8 | -------------------------------------------------------------------------------- /src/integration-test/resources/application-ondisconnect.yml: -------------------------------------------------------------------------------- 1 | netty: 2 | servers: 3 | - name: server1 4 | host: localhost 5 | port: 40000 6 | bossThreads: 4 7 | workerThreads: 4 8 | options: 9 | soReuseAddr: true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | 11 | .idea/ 12 | *.iml 13 | build/ 14 | out/ 15 | .gradle/ 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jun 17 21:40:01 MSK 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip 7 | -------------------------------------------------------------------------------- /src/integration-test/resources/application-filters.yml: -------------------------------------------------------------------------------- 1 | netty: 2 | servers: 3 | - name: server1 4 | host: localhost 5 | port: 40000 6 | bossThreads: 4 7 | workerThreads: 4 8 | options: 9 | soReuseAddr: true 10 | childOptions: 11 | tcpNodelay: true 12 | -------------------------------------------------------------------------------- /src/integration-test/resources/application-onconnect.yml: -------------------------------------------------------------------------------- 1 | netty: 2 | servers: 3 | - name: server1 4 | host: localhost 5 | port: 40000 6 | bossThreads: 4 7 | workerThreads: 4 8 | options: 9 | soReuseAddr: true 10 | childOptions: 11 | tcpNodelay: true 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to spring-boot-netty 2 | Thanks for taking time to contribute to my project! It is very important for me to see, 3 | that I am not only one interested in such a development! 4 | 5 | I do not enforce any specific requirements for contributors, just create pull request, and we will 6 | discuss it :wink: 7 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/etc/HandlerCallStack.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.etc; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.ArrayList; 6 | 7 | @Component 8 | public class HandlerCallStack extends ArrayList> { 9 | } 10 | -------------------------------------------------------------------------------- /src/integration-test/resources/application-duplicatedefs.yml: -------------------------------------------------------------------------------- 1 | netty: 2 | servers: 3 | - name: server1 4 | host: localhost 5 | port: 40000 6 | options: 7 | soReuseAddr: true 8 | 9 | - name: server1 10 | host: localhost 11 | port: 40001 12 | options: 13 | soReuseAddr: true 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nonhandler/handlers/WrongHandler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nonhandler.handlers; 2 | 3 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 4 | 5 | @NettyFilter(serverName = "server1", priority = 3) 6 | public class WrongHandler { 7 | } 8 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/etc/HandlerMethodCalls.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.etc; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.ArrayList; 7 | 8 | @Component 9 | public class HandlerMethodCalls extends ArrayList { 10 | } 11 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Pull template for: _describe feature_ 2 | **Motivation** 3 | Give short information on what and why you want to add/remove/change. 4 | 5 | **Description** 6 | Short description of changes made: 7 | 1. Added this 8 | 2. Changed that 9 | 3. It works 10 | 11 | **Additional context** 12 | Add any other context or screenshots about the pull request here. 13 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/etc/ProcessingCounter.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.etc; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.concurrent.Phaser; 6 | 7 | @Component 8 | public class ProcessingCounter extends Phaser { 9 | public ProcessingCounter(final int count) { 10 | super(count); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/configuration/NettyServers.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.configuration; 2 | 3 | import org.kgusarov.integration.spring.netty.TcpServer; 4 | 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * Bean holding all the servers 9 | */ 10 | public final class NettyServers extends ArrayList { 11 | // No additional fields or methods 12 | } 13 | -------------------------------------------------------------------------------- /src/integration-test/resources/application-multiple.yml: -------------------------------------------------------------------------------- 1 | netty: 2 | servers: 3 | - name: server1 4 | host: localhost 5 | port: 40000 6 | options: 7 | soReuseAddr: true 8 | childOptions: 9 | tcpNodelay: true 10 | 11 | - name: server2 12 | host: localhost 13 | port: 40001 14 | options: 15 | soReuseAddr: true 16 | childOptions: 17 | tcpNodelay: true 18 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/assembler/MethodPrefixAssembler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke.assembler; 2 | 3 | import org.objectweb.asm.MethodVisitor; 4 | 5 | /** 6 | * Internal API: code generation support - specific method prefix 7 | */ 8 | @FunctionalInterface 9 | public interface MethodPrefixAssembler { 10 | void assemble(String invokerInternalName, MethodVisitor m, int firstVarIdx); 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nodefs/NoDefsIntergrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nodefs; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.BeanCreationException; 5 | 6 | public class NoDefsIntergrationTest { 7 | @Test(expected = BeanCreationException.class) 8 | public void testThereShouldBeAnException() { 9 | NoDefsApplication.main("--spring.profiles.active=nodefs"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/configuration/EnableNettyServers.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.configuration; 2 | 3 | import org.springframework.context.annotation.Import; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * Annotation to enable Netty TCP servers. 9 | */ 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Documented 13 | @Inherited 14 | @Import(SpringNettyConfiguration.class) 15 | public @interface EnableNettyServers { 16 | } 17 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/mulhandler1/MulHandler1IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.mulhandler1; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.BeanCreationException; 5 | 6 | public class MulHandler1IntegrationTest { 7 | @Test(expected = BeanCreationException.class) 8 | public void testThereShouldBeAnException() { 9 | MulHandler1Application.main("--spring.profiles.active=errors"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/mulhandler2/MulHandler2IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.mulhandler2; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.BeanCreationException; 5 | 6 | public class MulHandler2IntegrationTest { 7 | @Test(expected = BeanCreationException.class) 8 | public void testThereShouldBeAnException() { 9 | MulHandler2Application.main("--spring.profiles.active=errors"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/mulhandler3/MulHandler3IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.mulhandler3; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.BeanCreationException; 5 | 6 | public class MulHandler3IntegrationTest { 7 | @Test(expected = BeanCreationException.class) 8 | public void testThereShouldBeAnException() { 9 | MulHandler3Application.main("--spring.profiles.active=errors"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nonhandler/NonHandler1IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nonhandler; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.BeanCreationException; 5 | 6 | public class NonHandler1IntegrationTest { 7 | @Test(expected = BeanCreationException.class) 8 | public void testThereShouldBeAnException() { 9 | NonHandler1Application.main("--spring.profiles.active=errors"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nonsharable1/NonSharable1IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nonsharable1; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.BeanCreationException; 5 | 6 | public class NonSharable1IntegrationTest { 7 | @Test(expected = BeanCreationException.class) 8 | public void testThereShouldBeAnException() { 9 | NonSharable1Application.main("--spring.profiles.active=errors"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nonsharable2/NonSharable2IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nonsharable2; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.BeanCreationException; 5 | 6 | public class NonSharable2IntegrationTest { 7 | @Test(expected = BeanCreationException.class) 8 | public void testThereShouldBeAnException() { 9 | NonSharable2Application.main("--spring.profiles.active=errors"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/duplicatedefs/DuplicateDefsIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.duplicatedefs; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.BeanCreationException; 5 | 6 | public class DuplicateDefsIntegrationTest { 7 | @Test(expected = BeanCreationException.class) 8 | public void testThereShouldBeAnException() { 9 | DuplicateDefsApplication.main("--spring.profiles.active=duplicatedefs"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/noconnectparamresolver/handlers/WrongController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.noconnectparamresolver.handlers; 2 | 3 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyOnConnect; 5 | 6 | @NettyController 7 | public class WrongController { 8 | @NettyOnConnect(serverName = "server1") 9 | public void onConnect(final boolean provideMe) { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nomessageparamresolver/handlers/WrongController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nomessageparamresolver.handlers; 2 | 3 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyOnMessage; 5 | 6 | @NettyController 7 | public class WrongController { 8 | @NettyOnMessage(serverName = "server1") 9 | public void onDisconnect(final boolean provideMe) { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nodisconnectparamresolver/handlers/WrongController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nodisconnectparamresolver.handlers; 2 | 3 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyOnDisconnect; 5 | 6 | @NettyController 7 | public class WrongController { 8 | @NettyOnDisconnect(serverName = "server1") 9 | public void onDisconnect(final boolean provideMe) { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/annotations/NettyMessageBody.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * This annotation can be used to mark a method parameter that will hold received 10 | * message body 11 | */ 12 | @Target(ElementType.PARAMETER) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface NettyMessageBody { 15 | } 16 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/duplicatemessagebodies/DuplicateMessageBodiesIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.duplicatemessagebodies; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.BeanCreationException; 5 | 6 | public class DuplicateMessageBodiesIntegrationTest { 7 | @Test(expected = BeanCreationException.class) 8 | public void testThereShouldBeAnException() { 9 | DuplicateMessageBodiesApplication.main("--spring.profiles.active=errors"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nomessageparamresolver/NoMessageParamResolverIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nomessageparamresolver; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.BeanCreationException; 5 | 6 | public class NoMessageParamResolverIntegrationTest { 7 | @Test(expected = BeanCreationException.class) 8 | public void testThereShouldBeAnException() { 9 | NoMessageParamResolverApplication.main("--spring.profiles.active=errors"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/multiple/handlers/ExceptionHandlerFilter1.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.multiple.handlers; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 5 | import org.kgusarov.integration.spring.netty.etc.ExceptionHandler; 6 | 7 | @ChannelHandler.Sharable 8 | @NettyFilter(serverName = "server1", priority = Integer.MIN_VALUE) 9 | public class ExceptionHandlerFilter1 extends ExceptionHandler { 10 | // No additional fields... 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/multiple/handlers/ExceptionHandlerFilter2.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.multiple.handlers; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 5 | import org.kgusarov.integration.spring.netty.etc.ExceptionHandler; 6 | 7 | @ChannelHandler.Sharable 8 | @NettyFilter(serverName = "server2", priority = Integer.MIN_VALUE) 9 | public class ExceptionHandlerFilter2 extends ExceptionHandler { 10 | // No additional fields... 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onconnect/handlers/ExceptionHandlerFilter.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onconnect.handlers; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 5 | import org.kgusarov.integration.spring.netty.etc.ExceptionHandler; 6 | 7 | @ChannelHandler.Sharable 8 | @NettyFilter(serverName = "server1", priority = Integer.MIN_VALUE) 9 | public class ExceptionHandlerFilter extends ExceptionHandler { 10 | // No additional fields... 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onmessage/handlers/ExceptionHandlerFilter.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onmessage.handlers; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 5 | import org.kgusarov.integration.spring.netty.etc.ExceptionHandler; 6 | 7 | @ChannelHandler.Sharable 8 | @NettyFilter(serverName = "server1", priority = Integer.MIN_VALUE) 9 | public class ExceptionHandlerFilter extends ExceptionHandler { 10 | // No additional fields... 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/empty/EmptyApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.empty; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class EmptyApplication { 10 | public static void main(final String[] args) { 11 | SpringApplication.run(EmptyApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/nettyfilters/handlers/ExceptionHandlerFilter.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.nettyfilters.handlers; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 5 | import org.kgusarov.integration.spring.netty.etc.ExceptionHandler; 6 | 7 | @ChannelHandler.Sharable 8 | @NettyFilter(serverName = "server1", priority = Integer.MIN_VALUE) 9 | public class ExceptionHandlerFilter extends ExceptionHandler { 10 | // No additional fields... 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/ondisconnect/handlers/ExceptionHandlerFilter.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.ondisconnect.handlers; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 5 | import org.kgusarov.integration.spring.netty.etc.ExceptionHandler; 6 | 7 | @ChannelHandler.Sharable 8 | @NettyFilter(serverName = "server1", priority = Integer.MIN_VALUE) 9 | public class ExceptionHandlerFilter extends ExceptionHandler { 10 | // No additional fields... 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/customresolvers/handlers/ExceptionHandlerFilter.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.customresolvers.handlers; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 5 | import org.kgusarov.integration.spring.netty.etc.ExceptionHandler; 6 | 7 | @ChannelHandler.Sharable 8 | @NettyFilter(serverName = "server1", priority = Integer.MIN_VALUE) 9 | public class ExceptionHandlerFilter extends ExceptionHandler { 10 | // No additional fields... 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nodisconnectparamresolver/NoDisconnectParamResolverIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nodisconnectparamresolver; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.BeanCreationException; 5 | 6 | public class NoDisconnectParamResolverIntegrationTest { 7 | @Test(expected = BeanCreationException.class) 8 | public void testThereShouldBeAnException() { 9 | NoDisconnectParamResolverApplication.main("--spring.profiles.active=errors"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nodefs/NoDefsApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nodefs; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class NoDefsApplication { 10 | public static void main(final String... args) { 11 | SpringApplication.run(NoDefsApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/multiple/MultipleServersApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.multiple; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class MultipleServersApplication { 10 | public static void main(final String[] args) { 11 | SpringApplication.run(MultipleServersApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/nettyfilters/NettyFiltersApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.nettyfilters; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class NettyFiltersApplication { 10 | public static void main(final String[] args) { 11 | SpringApplication.run(NettyFiltersApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/mulhandler1/MulHandler1Application.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.mulhandler1; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class MulHandler1Application { 10 | public static void main(final String... args) { 11 | SpringApplication.run(MulHandler1Application.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/mulhandler1/handlers/WrongController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.mulhandler1.handlers; 2 | 3 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyOnConnect; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyOnMessage; 6 | 7 | @NettyController 8 | public class WrongController { 9 | @NettyOnMessage(serverName = "server1") 10 | @NettyOnConnect(serverName = "server1") 11 | public void onConnect() { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/mulhandler2/MulHandler2Application.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.mulhandler2; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class MulHandler2Application { 10 | public static void main(final String... args) { 11 | SpringApplication.run(MulHandler2Application.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/mulhandler3/MulHandler3Application.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.mulhandler3; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class MulHandler3Application { 10 | public static void main(final String... args) { 11 | SpringApplication.run(MulHandler3Application.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nonhandler/NonHandler1Application.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nonhandler; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class NonHandler1Application { 10 | public static void main(final String... args) { 11 | SpringApplication.run(NonHandler1Application.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/mulhandler2/handlers/WrongController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.mulhandler2.handlers; 2 | 3 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyOnConnect; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyOnDisconnect; 6 | 7 | @NettyController 8 | public class WrongController { 9 | @NettyOnConnect(serverName = "server1") 10 | @NettyOnDisconnect(serverName = "server1") 11 | public void onConnect() { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/mulhandler3/handlers/WrongController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.mulhandler3.handlers; 2 | 3 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyOnDisconnect; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyOnMessage; 6 | 7 | @NettyController 8 | public class WrongController { 9 | @NettyOnMessage(serverName = "server1") 10 | @NettyOnDisconnect(serverName = "server1") 11 | public void onConnect() { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nonsharable1/NonSharable1Application.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nonsharable1; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class NonSharable1Application { 10 | public static void main(final String... args) { 11 | SpringApplication.run(NonSharable1Application.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nonsharable2/NonSharable2Application.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nonsharable2; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class NonSharable2Application { 10 | public static void main(final String... args) { 11 | SpringApplication.run(NonSharable2Application.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/duplicatedefs/DuplicateDefsApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.duplicatedefs; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class DuplicateDefsApplication { 10 | public static void main(final String... args) { 11 | SpringApplication.run(DuplicateDefsApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/annotations/NettyController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.annotations; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * Class that is marked with this annotation is considered to be a controller that is able to handle 9 | * various events received from Netty server. One might think of it as a concept similart 10 | * to Controller from spring-webmvc. 11 | */ 12 | @Component 13 | @Inherited 14 | @Target(ElementType.TYPE) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface NettyController { 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/duplicatemessagebodies/handlers/WrongController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.duplicatemessagebodies.handlers; 2 | 3 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyMessageBody; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyOnMessage; 6 | 7 | @NettyController 8 | public class WrongController { 9 | @NettyOnMessage(serverName = "server1") 10 | public void onConnect(@NettyMessageBody final int body1, @NettyMessageBody final int body2) { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/configuration/SpringNettyConfigurationProperties.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.configuration; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | import java.util.List; 6 | 7 | @ConfigurationProperties(prefix = "netty") 8 | public class SpringNettyConfigurationProperties { 9 | private List servers; 10 | 11 | public List getServers() { 12 | return servers; 13 | } 14 | 15 | public void setServers(final List servers) { 16 | this.servers = servers; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/noconnectparamresolver/NoConnectParamResolverIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.noconnectparamresolver; 2 | 3 | import org.junit.Test; 4 | import org.kgusarov.integration.spring.netty.errors.duplicatedefs.DuplicateDefsApplication; 5 | import org.springframework.beans.factory.BeanCreationException; 6 | 7 | public class NoConnectParamResolverIntegrationTest { 8 | @Test(expected = BeanCreationException.class) 9 | public void testThereShouldBeAnException() { 10 | NoConnectParamResolverApplication.main("--spring.profiles.active=errors"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Write some code 13 | 2. Launch it 14 | 3. See error 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Logs** 23 | If applicable, add logs to help explain your problem. 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/duplicatemessagebodies/DuplicateMessageBodiesApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.duplicatemessagebodies; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class DuplicateMessageBodiesApplication { 10 | public static void main(final String... args) { 11 | SpringApplication.run(DuplicateMessageBodiesApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/noconnectparamresolver/NoConnectParamResolverApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.noconnectparamresolver; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class NoConnectParamResolverApplication { 10 | public static void main(final String... args) { 11 | SpringApplication.run(NoConnectParamResolverApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nomessageparamresolver/NoMessageParamResolverApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nomessageparamresolver; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class NoMessageParamResolverApplication { 10 | public static void main(final String... args) { 11 | SpringApplication.run(NoMessageParamResolverApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/etc/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.etc; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | 7 | @ChannelHandler.Sharable 8 | public class ExceptionHandler extends ChannelInboundHandlerAdapter { 9 | @Override 10 | @SuppressWarnings("CallToPrintStackTrace") 11 | public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception { 12 | cause.printStackTrace(); 13 | super.exceptionCaught(ctx, cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nodisconnectparamresolver/NoDisconnectParamResolverApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nodisconnectparamresolver; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableNettyServers 8 | @SpringBootApplication 9 | public class NoDisconnectParamResolverApplication { 10 | public static void main(final String... args) { 11 | SpringApplication.run(NoDisconnectParamResolverApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/multiple/handlers/Server1Handler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.multiple.handlers; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 7 | 8 | @ChannelHandler.Sharable 9 | @NettyFilter(serverName = "server1") 10 | public class Server1Handler extends ChannelInboundHandlerAdapter { 11 | @Override 12 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) { 13 | ctx.writeAndFlush(msg); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/assembler/LabelAssembler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke.assembler; 2 | 3 | import org.objectweb.asm.Label; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Internal API: code generation support - labels 9 | */ 10 | final class LabelAssembler { 11 | private static final AtomicInteger LABEL_COUNTER = new AtomicInteger(0); 12 | 13 | private LabelAssembler() { 14 | } 15 | 16 | static Label createLabel() { 17 | final Label result = new Label(); 18 | result.info = LABEL_COUNTER.getAndIncrement(); 19 | return result; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nonsharable1/handlers/Decoder.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nonsharable1.handlers; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ReplayingDecoder; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 7 | 8 | import java.util.List; 9 | 10 | @NettyFilter(serverName = "server1") 11 | public class Decoder extends ReplayingDecoder { 12 | @Override 13 | protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List out) { 14 | out.add(null); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/resolvers/NettyCallbackParameterResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.resolvers; 2 | 3 | import org.springframework.core.MethodParameter; 4 | 5 | /** 6 | * Marker interface for parameter resolvers 7 | */ 8 | public interface NettyCallbackParameterResolver { 9 | /** 10 | * If this resolver can be used with appropriate method parameter 11 | * 12 | * @param methodParameter Method parameter to check 13 | * @return {@code true} if this resolver can extract value for given method parameter 14 | */ 15 | boolean canResolve(MethodParameter methodParameter); 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | - openjdk9 5 | - openjdk10 6 | - openjdk11 7 | - openjdk12 8 | - openjdk13 9 | - oraclejdk13 10 | 11 | branches: 12 | only: 13 | - master 14 | 15 | notifications: 16 | email: 17 | - konstantins.gusarovs@gmail.com 18 | 19 | before_cache: 20 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 21 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 22 | cache: 23 | directories: 24 | - $HOME/.gradle/caches/ 25 | - $HOME/.gradle/wrapper/ 26 | 27 | before_script: 28 | - chmod +x gradlew 29 | script: 30 | - ./gradlew clean check --info 31 | - ./gradlew jacocoTestReport 32 | after_success: 33 | - bash <(curl -s https://codecov.io/bash) 34 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 1.8 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.8 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Build with Gradle 26 | run: ./gradlew build 27 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/customresolvers/resolvers/RNG.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.customresolvers.resolvers; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.List; 7 | import java.util.Random; 8 | 9 | @Component 10 | public class RNG extends Random { 11 | private final List generatedNumbers = Lists.newArrayList(); 12 | 13 | @Override 14 | public long nextLong() { 15 | final long result = super.nextLong(); 16 | generatedNumbers.add(result); 17 | return result; 18 | } 19 | 20 | public List getGeneratedNumbers() { 21 | return generatedNumbers; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/resolvers/NettyOnDisconnectParameterResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.resolvers; 2 | 3 | import io.netty.channel.ChannelFuture; 4 | 5 | /** 6 | * Classes that implement this interface are used to resolve the arguments 7 | * for the {@link org.kgusarov.integration.spring.netty.annotations.NettyOnDisconnect} 8 | * handler methods 9 | */ 10 | public interface NettyOnDisconnectParameterResolver extends NettyCallbackParameterResolver { 11 | /** 12 | * Resolve the value of appropriate method parameter 13 | * 14 | * @param future Netty channel close future 15 | * @return Resolved value for method parameter 16 | */ 17 | Object resolve(ChannelFuture future); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/resolvers/NettyOnConnectParameterResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.resolvers; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | 5 | /** 6 | * Classes that implement this interface are used to resolve the arguments 7 | * for the {@link org.kgusarov.integration.spring.netty.annotations.NettyOnConnect} 8 | * handler methods 9 | */ 10 | public interface NettyOnConnectParameterResolver extends NettyCallbackParameterResolver { 11 | /** 12 | * Resolve the value of appropriate method parameter 13 | * 14 | * @param ctx Context provided by Netty callback 15 | * @return Resolved value for method parameter 16 | */ 17 | Object resolve(ChannelHandlerContext ctx); 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/org/kgusarov/integration/spring/netty/support/invoke/Victim.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | class Victim { 7 | private static final Logger LOGGER = LoggerFactory.getLogger(Victim.class); 8 | 9 | private String concat(final int a, final int b) { 10 | LOGGER.info("concat(a,b) invoked"); 11 | return a + "-" + b; 12 | } 13 | 14 | String concat(final int a, final int b, final String c) { 15 | LOGGER.info("concat(a,b,c) invoked"); 16 | return a + "-" + b + '-' + c; 17 | } 18 | 19 | void noArg() { 20 | LOGGER.info("noArg() invoked"); 21 | } 22 | 23 | public int proxied() { 24 | LOGGER.info("proxied() invoked"); 25 | return 1; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onconnect/OnConnectApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onconnect; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @EnableNettyServers 10 | @SpringBootApplication 11 | public class OnConnectApplication { 12 | public static void main(final String[] args) { 13 | SpringApplication.run(OnConnectApplication.class, args); 14 | } 15 | 16 | @Bean 17 | public ProcessingCounter counter() { 18 | return new ProcessingCounter(3); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onmessage/OnMessageApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onmessage; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @EnableNettyServers 10 | @SpringBootApplication 11 | public class OnMessageApplication { 12 | public static void main(final String[] args) { 13 | SpringApplication.run(OnMessageApplication.class, args); 14 | } 15 | 16 | @Bean 17 | public ProcessingCounter counter() { 18 | return new ProcessingCounter(5); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/ondisconnect/OnDisconnectApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.ondisconnect; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @EnableNettyServers 10 | @SpringBootApplication 11 | public class OnDisconnectApplication { 12 | public static void main(final String[] args) { 13 | SpringApplication.run(OnDisconnectApplication.class, args); 14 | } 15 | 16 | @Bean 17 | public ProcessingCounter counter() { 18 | return new ProcessingCounter(3); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/customresolvers/CustomResolversApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.customresolvers; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @EnableNettyServers 10 | @SpringBootApplication 11 | public class CustomResolversApplication { 12 | public static void main(final String[] args) { 13 | SpringApplication.run(CustomResolversApplication.class, args); 14 | } 15 | 16 | @Bean 17 | public ProcessingCounter counter() { 18 | return new ProcessingCounter(3); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/errors/nonsharable2/handlers/Decoder.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.errors.nonsharable2.handlers; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ReplayingDecoder; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 7 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 8 | import org.springframework.context.annotation.Scope; 9 | 10 | import java.util.List; 11 | 12 | @NettyFilter(serverName = "server1") 13 | @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) 14 | public class Decoder extends ReplayingDecoder { 15 | @Override 16 | protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List out) { 17 | out.add(null); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onmessagenohandler/OnMessageNoHandlerApplication.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onmessagenohandler; 2 | 3 | import org.kgusarov.integration.spring.netty.configuration.EnableNettyServers; 4 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @EnableNettyServers 10 | @SpringBootApplication 11 | public class OnMessageNoHandlerApplication { 12 | public static void main(final String[] args) { 13 | SpringApplication.run(OnMessageNoHandlerApplication.class, args); 14 | } 15 | 16 | @Bean 17 | public ProcessingCounter counter() { 18 | return new ProcessingCounter(1); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/resolvers/NettyOnMessageParameterResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.resolvers; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | 5 | /** 6 | * Classes that implement this interface are used to resolve the arguments 7 | * for the {@link org.kgusarov.integration.spring.netty.annotations.NettyOnMessage} 8 | * handler methods 9 | */ 10 | public interface NettyOnMessageParameterResolver extends NettyCallbackParameterResolver { 11 | /** 12 | * Resolve the value of appropriate method parameter 13 | * 14 | * @param ctx Context provided by Netty callback 15 | * @param msg Message received from upstream 16 | * @return Resolved value for method parameter 17 | */ 18 | Object resolve(ChannelHandlerContext ctx, Object msg); 19 | } 20 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/nettyfilters/handlers/LongResponder.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.nettyfilters.handlers; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 7 | import org.kgusarov.integration.spring.netty.etc.HandlerCallStack; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | @ChannelHandler.Sharable 11 | @NettyFilter(serverName = "server1", priority = 10) 12 | public class LongResponder extends ChannelInboundHandlerAdapter { 13 | @Autowired 14 | private HandlerCallStack handlerCallStack; 15 | 16 | @Override 17 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) { 18 | handlerCallStack.add(getClass()); 19 | ctx.writeAndFlush(msg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/resolvers/impl/ChannelFutureOnDisconnectResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.resolvers.impl; 2 | 3 | import io.netty.channel.ChannelFuture; 4 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnDisconnectParameterResolver; 5 | import org.springframework.core.MethodParameter; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Internal API: resolver for {@link ChannelFuture} 10 | */ 11 | @Component 12 | public class ChannelFutureOnDisconnectResolver implements NettyOnDisconnectParameterResolver { 13 | @Override 14 | public boolean canResolve(final MethodParameter methodParameter) { 15 | final Class parameterType = methodParameter.getParameterType(); 16 | return ChannelFuture.class.isAssignableFrom(parameterType); 17 | } 18 | 19 | @Override 20 | public Object resolve(final ChannelFuture future) { 21 | return future; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/nettyfilters/handlers/LongInverter.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.nettyfilters.handlers; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 7 | import org.kgusarov.integration.spring.netty.etc.HandlerCallStack; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | @ChannelHandler.Sharable 11 | @NettyFilter(serverName = "server1", priority = 3) 12 | public class LongInverter extends ChannelInboundHandlerAdapter { 13 | @Autowired 14 | private HandlerCallStack handlerCallStack; 15 | 16 | @Override 17 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) { 18 | handlerCallStack.add(getClass()); 19 | 20 | final Long l = (Long) msg; 21 | ctx.fireChannelRead(-l); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/resolvers/impl/MessageBodyOnMessageResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.resolvers.impl; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyMessageBody; 5 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnMessageParameterResolver; 6 | import org.springframework.core.MethodParameter; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * Internal API: resolver for message body 11 | */ 12 | @Component 13 | public class MessageBodyOnMessageResolver implements NettyOnMessageParameterResolver { 14 | @Override 15 | public boolean canResolve(final MethodParameter methodParameter) { 16 | return methodParameter.hasParameterAnnotation(NettyMessageBody.class); 17 | } 18 | 19 | @Override 20 | public Object resolve(final ChannelHandlerContext ctx, final Object msg) { 21 | return msg; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/resolvers/impl/ChannelOnConnectResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.resolvers.impl; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnConnectParameterResolver; 6 | import org.springframework.core.MethodParameter; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * Internal API: resolver for {@link Channel} 11 | */ 12 | @Component 13 | public class ChannelOnConnectResolver implements NettyOnConnectParameterResolver { 14 | @Override 15 | public boolean canResolve(final MethodParameter methodParameter) { 16 | final Class parameterType = methodParameter.getParameterType(); 17 | return Channel.class.isAssignableFrom(parameterType); 18 | } 19 | 20 | @Override 21 | public Object resolve(final ChannelHandlerContext ctx) { 22 | return ctx.channel(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/resolvers/impl/ChannelOnDisconnectResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.resolvers.impl; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnDisconnectParameterResolver; 6 | import org.springframework.core.MethodParameter; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * Internal API: resolver for {@link Channel} 11 | */ 12 | @Component 13 | public class ChannelOnDisconnectResolver implements NettyOnDisconnectParameterResolver { 14 | @Override 15 | public boolean canResolve(final MethodParameter methodParameter) { 16 | final Class parameterType = methodParameter.getParameterType(); 17 | return Channel.class.isAssignableFrom(parameterType); 18 | } 19 | 20 | @Override 21 | public Object resolve(final ChannelFuture future) { 22 | return future.channel(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/resolvers/impl/ChannelHandlerContextOnConnectResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.resolvers.impl; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnConnectParameterResolver; 5 | import org.springframework.core.MethodParameter; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Internal API: resolver for {@link ChannelHandlerContext} 10 | */ 11 | @Component 12 | public class ChannelHandlerContextOnConnectResolver implements NettyOnConnectParameterResolver { 13 | @Override 14 | public boolean canResolve(final MethodParameter methodParameter) { 15 | final Class parameterType = methodParameter.getParameterType(); 16 | return ChannelHandlerContext.class.isAssignableFrom(parameterType); 17 | } 18 | 19 | @Override 20 | public Object resolve(final ChannelHandlerContext ctx) { 21 | return ctx; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/resolvers/impl/ChannelOnMessageResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.resolvers.impl; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnMessageParameterResolver; 6 | import org.springframework.core.MethodParameter; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * Internal API: resolver for {@link Channel} 11 | */ 12 | @Component 13 | public class ChannelOnMessageResolver implements NettyOnMessageParameterResolver { 14 | @Override 15 | public boolean canResolve(final MethodParameter methodParameter) { 16 | final Class parameterType = methodParameter.getParameterType(); 17 | return Channel.class.isAssignableFrom(parameterType); 18 | } 19 | 20 | @Override 21 | public Object resolve(final ChannelHandlerContext ctx, final Object msg) { 22 | return ctx.channel(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/resolvers/impl/ChannelHandlerContextOnMessageResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.resolvers.impl; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnMessageParameterResolver; 5 | import org.springframework.core.MethodParameter; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Internal API: resolver for {@link ChannelHandlerContext} 10 | */ 11 | @Component 12 | public class ChannelHandlerContextOnMessageResolver implements NettyOnMessageParameterResolver { 13 | @Override 14 | public boolean canResolve(final MethodParameter methodParameter) { 15 | final Class parameterType = methodParameter.getParameterType(); 16 | return ChannelHandlerContext.class.isAssignableFrom(parameterType); 17 | } 18 | 19 | @Override 20 | public Object resolve(final ChannelHandlerContext ctx, final Object msg) { 21 | return ctx; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/multiple/handlers/Server2Handler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.multiple.handlers; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufUtil; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import io.netty.util.CharsetUtil; 9 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 10 | 11 | import static io.netty.buffer.Unpooled.copiedBuffer; 12 | 13 | @ChannelHandler.Sharable 14 | @NettyFilter(serverName = "server2") 15 | public class Server2Handler extends ChannelInboundHandlerAdapter { 16 | @Override 17 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) { 18 | final ByteBuf byteBuf = (ByteBuf) msg; 19 | final String hexDump = ByteBufUtil.hexDump(byteBuf); 20 | final ByteBuf response = copiedBuffer(hexDump, CharsetUtil.UTF_8); 21 | 22 | ctx.writeAndFlush(response); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/annotations/NettyOnMessage.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * This annotation can be used to mark a method that will become a handler for the given 10 | * TCP event with data. 11 | */ 12 | @Target(ElementType.METHOD) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface NettyOnMessage { 15 | /** 16 | * Get logical name of the server annotated events handler should be attached to 17 | * 18 | * @return Associated server's name 19 | */ 20 | String serverName(); 21 | 22 | /** 23 | * Get the priority of the given handler. Priority is used to determine in what order 24 | * events handlers will be called when appropriate message arrives 25 | * 26 | * @return Priority for the given handler 27 | */ 28 | int priority() default 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/etc/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.etc; 2 | 3 | import com.google.common.util.concurrent.SettableFuture; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | 9 | @ChannelHandler.Sharable 10 | public class ClientHandler extends ChannelInboundHandlerAdapter { 11 | private final SettableFuture[] responseHolders; 12 | private int currentResponse; 13 | 14 | @SafeVarargs 15 | public ClientHandler(final SettableFuture... responseHolders) { 16 | this.responseHolders = responseHolders; 17 | } 18 | 19 | @Override 20 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) { 21 | final ByteBuf buf = (ByteBuf) msg; 22 | 23 | while (buf.isReadable(8)) { 24 | final long i = buf.readLong(); 25 | responseHolders[currentResponse++].set(i); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/annotations/NettyOnConnect.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * This annotation can be used to mark a method that will become a connection event 10 | * handler for the given TCP server. 11 | */ 12 | @Target(ElementType.METHOD) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface NettyOnConnect { 15 | /** 16 | * Get logical name of the server annotated event handler should be attached to 17 | * 18 | * @return Associated server's name 19 | */ 20 | String serverName(); 21 | 22 | /** 23 | * Get the priority of the given handler. Priority is used to determine in what order 24 | * event handlers will be called when client connects to server with appropriate name 25 | * 26 | * @return Priority for the given handler 27 | */ 28 | int priority() default 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/nettyfilters/handlers/LongEncoder.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.nettyfilters.handlers; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 7 | import org.kgusarov.integration.spring.netty.etc.HandlerCallStack; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 10 | import org.springframework.context.annotation.Scope; 11 | 12 | @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 13 | @NettyFilter(serverName = "server1", priority = 5) 14 | public class LongEncoder extends MessageToByteEncoder { 15 | @Autowired 16 | private HandlerCallStack handlerCallStack; 17 | 18 | @Override 19 | protected void encode(final ChannelHandlerContext ctx, final Long msg, final ByteBuf out) { 20 | handlerCallStack.add(getClass()); 21 | out.writeLong(msg); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/annotations/NettyOnDisconnect.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * This annotation can be used to mark a method that will become a disconnect event 10 | * handler for the given TCP server. 11 | */ 12 | @Target(ElementType.METHOD) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface NettyOnDisconnect { 15 | /** 16 | * Get logical name of the server annotated event handler should be attached to 17 | * 18 | * @return Associated server's name 19 | */ 20 | String serverName(); 21 | 22 | /** 23 | * Get the priority of the given handler. Priority is used to determine in what order 24 | * event handlers will be called when client disconnects from server with appropriate name 25 | * 26 | * @return Priority for the given handler 27 | */ 28 | int priority() default 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/nettyfilters/handlers/LongDecoder.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.nettyfilters.handlers; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ReplayingDecoder; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 7 | import org.kgusarov.integration.spring.netty.etc.HandlerCallStack; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 10 | import org.springframework.context.annotation.Scope; 11 | 12 | import java.util.List; 13 | 14 | @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 15 | @NettyFilter(serverName = "server1", priority = 1) 16 | public class LongDecoder extends ReplayingDecoder { 17 | @Autowired 18 | private HandlerCallStack handlerCallStack; 19 | 20 | @Override 21 | protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List out) { 22 | handlerCallStack.add(getClass()); 23 | out.add(in.readLong()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2020 Konstantin Gusarov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/GeneratedClassLoader.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke; 2 | 3 | import java.lang.invoke.MethodHandle; 4 | 5 | /** 6 | * Internal API: fast invocation support 7 | */ 8 | final class GeneratedClassLoader { 9 | private static final MethodHandle DEFINE_CLASS_HANDLE; 10 | 11 | static { 12 | try { 13 | DEFINE_CLASS_HANDLE = MethodHandleCreator.create(ClassLoader.class, "defineClass", 14 | String.class, byte[].class, int.class, int.class); 15 | } catch (final IllegalAccessException | NoSuchMethodException e) { 16 | throw new IllegalStateException(e); 17 | } 18 | } 19 | 20 | @SuppressWarnings("unchecked") 21 | Class load(final byte[] bytecode, final String fqcn) { 22 | final ClassLoader classLoader = getClass().getClassLoader(); 23 | try { 24 | return (Class) DEFINE_CLASS_HANDLE.invokeExact(classLoader, fqcn, bytecode, 0, bytecode.length); 25 | } catch (final Throwable t) { 26 | throw new IllegalStateException(t); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/customresolvers/resolvers/RandomLongOnConnectResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.customresolvers.resolvers; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnConnectParameterResolver; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.core.MethodParameter; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class RandomLongOnConnectResolver implements NettyOnConnectParameterResolver { 11 | private final RNG rng; 12 | 13 | @Autowired 14 | public RandomLongOnConnectResolver(final RNG rng) { 15 | this.rng = rng; 16 | } 17 | 18 | @Override 19 | public Object resolve(final ChannelHandlerContext ctx) { 20 | return rng.nextLong(); 21 | } 22 | 23 | @Override 24 | public boolean canResolve(final MethodParameter methodParameter) { 25 | final Class parameterType = methodParameter.getParameterType(); 26 | return long.class.isAssignableFrom(parameterType) || Long.class.isAssignableFrom(parameterType); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/customresolvers/resolvers/RandomLongOnDisconnectResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.customresolvers.resolvers; 2 | 3 | import io.netty.channel.ChannelFuture; 4 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnDisconnectParameterResolver; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.core.MethodParameter; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class RandomLongOnDisconnectResolver implements NettyOnDisconnectParameterResolver { 11 | private final RNG rng; 12 | 13 | @Autowired 14 | public RandomLongOnDisconnectResolver(final RNG rng) { 15 | this.rng = rng; 16 | } 17 | 18 | @Override 19 | public boolean canResolve(final MethodParameter methodParameter) { 20 | final Class parameterType = methodParameter.getParameterType(); 21 | return long.class.isAssignableFrom(parameterType) || Long.class.isAssignableFrom(parameterType); 22 | } 23 | 24 | @Override 25 | public Object resolve(final ChannelFuture future) { 26 | return rng.nextLong(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/customresolvers/resolvers/RandomLongOnMessageResolver.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.customresolvers.resolvers; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnMessageParameterResolver; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.core.MethodParameter; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class RandomLongOnMessageResolver implements NettyOnMessageParameterResolver { 11 | private final RNG rng; 12 | 13 | @Autowired 14 | public RandomLongOnMessageResolver(final RNG rng) { 15 | this.rng = rng; 16 | } 17 | 18 | @Override 19 | public boolean canResolve(final MethodParameter methodParameter) { 20 | final Class parameterType = methodParameter.getParameterType(); 21 | return long.class.isAssignableFrom(parameterType) || Long.class.isAssignableFrom(parameterType); 22 | } 23 | 24 | @Override 25 | public Object resolve(final ChannelHandlerContext ctx, final Object msg) { 26 | return rng.nextLong(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/empty/EmptyServersIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.empty; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.kgusarov.integration.spring.netty.configuration.NettyServers; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootContextLoader; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.annotation.DirtiesContext; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | 13 | import static org.hamcrest.Matchers.hasSize; 14 | import static org.junit.Assert.assertThat; 15 | 16 | @SpringBootTest 17 | @ContextConfiguration(classes = EmptyApplication.class, loader = SpringBootContextLoader.class) 18 | @RunWith(SpringJUnit4ClassRunner.class) 19 | public class EmptyServersIntegrationTest { 20 | @Autowired 21 | private NettyServers servers; 22 | 23 | @Test 24 | @DirtiesContext 25 | public void testNoServerDefinitionsWillResultInEmptyServerList() { 26 | assertThat(servers, hasSize(0)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/SpringChannelFutureListener.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support; 2 | 3 | import io.netty.channel.ChannelFuture; 4 | import io.netty.channel.ChannelFutureListener; 5 | import org.kgusarov.integration.spring.netty.support.invoke.OnDisconnectMethodInvoker; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Handler that is part of internal API and is used to invoke appropriate 11 | * {@link org.kgusarov.integration.spring.netty.annotations.NettyController} 12 | * {@link org.kgusarov.integration.spring.netty.annotations.NettyOnDisconnect} 13 | * annotated methods 14 | */ 15 | public class SpringChannelFutureListener implements ChannelFutureListener { 16 | private final List onDisconnectCallbacks; 17 | 18 | public SpringChannelFutureListener(final List onDisconnectCallbacks) { 19 | this.onDisconnectCallbacks = onDisconnectCallbacks; 20 | } 21 | 22 | @Override 23 | @SuppressWarnings("CodeBlock2Expr") 24 | public void operationComplete(final ChannelFuture channelFuture) throws Exception { 25 | onDisconnectCallbacks.forEach(cb -> { 26 | cb.channelClosed(channelFuture); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/nettyfilters/handlers/AroundResponderFilter.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.nettyfilters.handlers; 2 | 3 | import io.netty.channel.ChannelDuplexHandler; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelPromise; 7 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 8 | import org.kgusarov.integration.spring.netty.etc.HandlerCallStack; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | 11 | @ChannelHandler.Sharable 12 | @NettyFilter(serverName = "server1", priority = 9) 13 | public class AroundResponderFilter extends ChannelDuplexHandler { 14 | @Autowired 15 | private HandlerCallStack handlerCallStack; 16 | 17 | @Override 18 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { 19 | handlerCallStack.add(getClass()); 20 | super.channelRead(ctx, msg); 21 | } 22 | 23 | @Override 24 | public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise) throws Exception { 25 | handlerCallStack.add(getClass()); 26 | super.write(ctx, msg, promise); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onmessagenohandler/handlers/OnMessageNoHandlerController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onmessagenohandler.handlers; 2 | 3 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyMessageBody; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyOnMessage; 6 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | @NettyController 12 | class OnMessageNoHandlerController { 13 | private static final Method ON_MESSAGE; 14 | 15 | static { 16 | try { 17 | ON_MESSAGE = OnMessageNoHandlerController.class.getDeclaredMethod("onMessage", long.class); 18 | } catch (final NoSuchMethodException ignored) { 19 | throw new IllegalStateException(); 20 | } 21 | } 22 | 23 | @Autowired 24 | private HandlerMethodCalls calls; 25 | 26 | @SuppressWarnings("unused") 27 | @NettyOnMessage(serverName = "server1") 28 | private void onMessage(@NettyMessageBody final long body) { 29 | calls.add(ON_MESSAGE); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/InvokerMethods.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelHandlerContext; 6 | 7 | import java.lang.reflect.Method; 8 | 9 | /** 10 | * Internal API: code generation support - invoker methods 11 | */ 12 | final class InvokerMethods { 13 | static final Method ONC_INVOKE_HANDLER; 14 | static final Method OND_INVOKE_HANDLER; 15 | static final Method ONM_INVOKE_HANDLER; 16 | 17 | static { 18 | try { 19 | ONC_INVOKE_HANDLER = OnConnectMethodInvoker.Invoker.class.getDeclaredMethod("invokeHandler", 20 | Channel.class, ChannelHandlerContext.class); 21 | 22 | OND_INVOKE_HANDLER = OnDisconnectMethodInvoker.Invoker.class.getDeclaredMethod("invokeHandler", 23 | Channel.class, ChannelFuture.class); 24 | 25 | ONM_INVOKE_HANDLER = OnMessageMethodInvoker.Invoker.class.getDeclaredMethod("invokeHandler", 26 | Channel.class, ChannelHandlerContext.class, Object.class); 27 | } catch (final NoSuchMethodException e) { 28 | throw new IllegalStateException(e); 29 | } 30 | } 31 | 32 | private InvokerMethods() { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onconnect/handlers/TransactionalOnConnectController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onconnect.handlers; 2 | 3 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyOnConnect; 5 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 6 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import java.lang.reflect.Method; 11 | 12 | @NettyController 13 | public class TransactionalOnConnectController { 14 | public static final Method ON_CONNECT; 15 | 16 | static { 17 | try { 18 | ON_CONNECT = TransactionalOnConnectController.class.getDeclaredMethod("onConnect"); 19 | } catch (final NoSuchMethodException ignored) { 20 | throw new IllegalStateException(); 21 | } 22 | } 23 | 24 | @Autowired 25 | private HandlerMethodCalls calls; 26 | 27 | @Autowired 28 | private ProcessingCounter counter; 29 | 30 | @Transactional 31 | @NettyOnConnect(serverName = "server1", priority = 3) 32 | public void onConnect() { 33 | calls.add(ON_CONNECT); 34 | counter.arrive(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/customresolvers/handlers/CustomResolversController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.customresolvers.handlers; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyOnConnect; 7 | import org.kgusarov.integration.spring.netty.annotations.NettyOnDisconnect; 8 | import org.kgusarov.integration.spring.netty.annotations.NettyOnMessage; 9 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | 12 | @NettyController 13 | public class CustomResolversController { 14 | @Autowired 15 | private ProcessingCounter counter; 16 | 17 | @NettyOnConnect(serverName = "server1") 18 | public ByteBuf onConnect(final long rnd) { 19 | counter.arrive(); 20 | return Unpooled.copyLong(rnd); 21 | } 22 | 23 | @NettyOnMessage(serverName = "server1") 24 | public ByteBuf onMessage(final Long rnd) { 25 | counter.arrive(); 26 | return Unpooled.copyLong(rnd); 27 | } 28 | 29 | @SuppressWarnings("unused") 30 | @NettyOnDisconnect(serverName = "server1") 31 | public void onDisconnect(final long rnd) { 32 | counter.arrive(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/ondisconnect/handlers/TransactionalOnDisconnectController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.ondisconnect.handlers; 2 | 3 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyOnDisconnect; 5 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 6 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import java.lang.reflect.Method; 11 | 12 | @NettyController 13 | public class TransactionalOnDisconnectController { 14 | public static final Method ON_DISCONNECT; 15 | 16 | static { 17 | try { 18 | ON_DISCONNECT = TransactionalOnDisconnectController.class.getDeclaredMethod("onDisconnect"); 19 | } catch (final NoSuchMethodException ignored) { 20 | throw new IllegalStateException(); 21 | } 22 | } 23 | 24 | @Autowired 25 | private HandlerMethodCalls calls; 26 | 27 | @Autowired 28 | private ProcessingCounter counter; 29 | 30 | @Transactional 31 | @NettyOnDisconnect(serverName = "server1", priority = 3) 32 | public void onDisconnect() { 33 | calls.add(ON_DISCONNECT); 34 | counter.arrive(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/assembler/ConstructorAssembler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke.assembler; 2 | 3 | import org.objectweb.asm.ClassWriter; 4 | import org.objectweb.asm.Label; 5 | import org.objectweb.asm.MethodVisitor; 6 | import org.objectweb.asm.Type; 7 | 8 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.LabelAssembler.createLabel; 9 | import static org.objectweb.asm.Opcodes.*; 10 | 11 | /** 12 | * Internal API: code generation support - constructor 13 | */ 14 | public final class ConstructorAssembler { 15 | private ConstructorAssembler() { 16 | } 17 | 18 | public static void assembleConstructor(final Type invokerType, final String parentName, final ClassWriter cw) { 19 | final MethodVisitor ctor = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); 20 | final Label cs = createLabel(); 21 | final Label ce = createLabel(); 22 | 23 | ctor.visitCode(); 24 | ctor.visitLabel(cs); 25 | ctor.visitVarInsn(ALOAD, 0); 26 | ctor.visitMethodInsn(INVOKESPECIAL, parentName, "", "()V", false); 27 | ctor.visitInsn(RETURN); 28 | ctor.visitLabel(ce); 29 | final String cn = invokerType.getDescriptor(); 30 | ctor.visitLocalVariable("this", cn, null, cs, ce, 0); 31 | ctor.visitEnd(); 32 | 33 | ctor.visitMaxs(0, 0); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/annotations/NettyFilter.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.annotations; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * This annotation can be used to mark so called "Filters" - handlers that will be 9 | * invoked before any {@link NettyController} 10 | * instances will start message processing or after it will be finished. So basically, handlers will 11 | * work in a way similar to servlet filters. This may include encoders/decoders and another 12 | * stuff that can be used to preprocess the message before handling it or post-process it afterwards. 13 | * 14 | * Class should implement {@code io.netty.channel.ChannelHandler} 15 | * interface. This is enforced during appropriate bean construction 16 | */ 17 | @Component 18 | @Inherited 19 | @Target(ElementType.TYPE) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | public @interface NettyFilter { 22 | /** 23 | * Get logical name of the server annotated event handler should be attached to 24 | * 25 | * @return Associated server's name 26 | */ 27 | String serverName(); 28 | 29 | /** 30 | * Get the priority of the given handler. Priority is used to determine in what order 31 | * event handlers will be called 32 | * 33 | * @return Priority for the given handler 34 | */ 35 | int priority() default 0; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/TcpServerLifeCycle.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty; 2 | 3 | import com.google.common.util.concurrent.Futures; 4 | import com.google.common.util.concurrent.ListenableFuture; 5 | import org.kgusarov.integration.spring.netty.configuration.NettyServers; 6 | import org.springframework.beans.factory.BeanInitializationException; 7 | 8 | import javax.annotation.PostConstruct; 9 | import javax.annotation.PreDestroy; 10 | import java.util.List; 11 | import java.util.concurrent.ExecutionException; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * Component that performs initialization of all the netty servers 16 | */ 17 | public class TcpServerLifeCycle { 18 | private final NettyServers nettyServers; 19 | 20 | public TcpServerLifeCycle(final NettyServers nettyServers) { 21 | this.nettyServers = nettyServers; 22 | } 23 | 24 | /** 25 | * Start all servers 26 | */ 27 | @PostConstruct 28 | public void start() { 29 | try { 30 | final List> startFutures = nettyServers.stream() 31 | .map(TcpServer::start) 32 | .collect(Collectors.toList()); 33 | 34 | Futures.allAsList(startFutures).get(); 35 | } catch (final InterruptedException | ExecutionException e) { 36 | throw new BeanInitializationException("Failed to start NETTY servers", e); 37 | } 38 | } 39 | 40 | /** 41 | * Stop all servers 42 | */ 43 | @PreDestroy 44 | public void stop() { 45 | nettyServers.forEach(TcpServer::stop); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onmessage/handlers/TransactionalOnMessageController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onmessage.handlers; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyOnMessage; 7 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 8 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import java.lang.reflect.Method; 13 | 14 | @NettyController 15 | public class TransactionalOnMessageController { 16 | public static final Method TRANSACTIONAL_ON_MESSAGE; 17 | 18 | static { 19 | try { 20 | TRANSACTIONAL_ON_MESSAGE = TransactionalOnMessageController.class.getDeclaredMethod("onMessage", 21 | ChannelHandlerContext.class, Channel.class); 22 | } catch (final NoSuchMethodException ignored) { 23 | throw new IllegalStateException(); 24 | } 25 | } 26 | 27 | @Autowired 28 | private HandlerMethodCalls calls; 29 | 30 | @Autowired 31 | private ProcessingCounter counter; 32 | 33 | @Transactional 34 | @NettyOnMessage(serverName = "server1", priority = 5) 35 | public void onMessage(final ChannelHandlerContext ctx, final Channel channel) { 36 | calls.add(TRANSACTIONAL_ON_MESSAGE); 37 | counter.arrive(); 38 | 39 | ctx.writeAndFlush(889L); 40 | channel.writeAndFlush(26576374L); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onmessage/handlers/Decoder.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onmessage.handlers; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ReplayingDecoder; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 7 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 10 | import org.springframework.context.annotation.Scope; 11 | 12 | import java.lang.reflect.Method; 13 | import java.nio.charset.Charset; 14 | import java.util.List; 15 | 16 | @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 17 | @NettyFilter(serverName = "server1", priority = 1) 18 | public class Decoder extends ReplayingDecoder { 19 | public static final Method DECODE; 20 | 21 | static { 22 | try { 23 | DECODE = Decoder.class.getDeclaredMethod("decode", ChannelHandlerContext.class, 24 | ByteBuf.class, List.class); 25 | } catch (final NoSuchMethodException ignored) { 26 | throw new IllegalStateException(); 27 | } 28 | } 29 | 30 | @Autowired 31 | private HandlerMethodCalls calls; 32 | 33 | @Override 34 | protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List out) { 35 | final byte b = in.readByte(); 36 | if (b == 0) { 37 | out.add(in.readLong()); 38 | } else { 39 | final int size = in.readInt(); 40 | final byte[] bytes = new byte[size]; 41 | 42 | in.readBytes(bytes, 0, size); 43 | out.add(new String(bytes, Charset.forName("UTF-8"))); 44 | } 45 | 46 | if (calls != null) { 47 | calls.add(DECODE); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onmessage/handlers/Encoder.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onmessage.handlers; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 7 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 10 | import org.springframework.context.annotation.Scope; 11 | 12 | import java.lang.reflect.Method; 13 | import java.nio.charset.Charset; 14 | 15 | @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 16 | @NettyFilter(serverName = "server1", priority = 2) 17 | public class Encoder extends MessageToByteEncoder { 18 | public static final Method ENCODE; 19 | 20 | static { 21 | try { 22 | ENCODE = Encoder.class.getDeclaredMethod("encode", ChannelHandlerContext.class, 23 | Object.class, ByteBuf.class); 24 | } catch (final NoSuchMethodException ignored) { 25 | throw new IllegalStateException(); 26 | } 27 | } 28 | 29 | @Autowired 30 | private HandlerMethodCalls calls; 31 | 32 | @Override 33 | protected void encode(final ChannelHandlerContext ctx, final Object msg, final ByteBuf out) { 34 | if (calls != null) { 35 | calls.add(ENCODE); 36 | } 37 | 38 | if (msg instanceof Long) { 39 | out.writeByte(0); 40 | out.writeLong((Long) msg); 41 | } else { 42 | final String s = (String) msg; 43 | final byte[] bytes = s.getBytes(Charset.forName("UTF-8")); 44 | 45 | out.writeByte(1); 46 | out.writeInt(bytes.length); 47 | out.writeBytes(bytes); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/MethodHandleCreator.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke; 2 | 3 | import java.lang.invoke.MethodHandle; 4 | import java.lang.invoke.MethodHandles; 5 | import java.lang.invoke.MethodType; 6 | import java.lang.reflect.Method; 7 | import java.util.Arrays; 8 | 9 | /** 10 | * Internal API: fast invocation support 11 | */ 12 | public final class MethodHandleCreator { 13 | private MethodHandleCreator() { 14 | } 15 | 16 | static MethodHandle createUniversal(final String className, final String methodName, final Class ...params) 17 | throws IllegalAccessException, NoSuchMethodException, ClassNotFoundException { 18 | 19 | final MethodHandle h = create(className, methodName, params); 20 | final MethodType mt = h.type(); 21 | final Class[] parameterArray = mt.parameterArray(); 22 | final Class[] adaptedParameterArray = new Class[parameterArray.length]; 23 | 24 | Arrays.fill(adaptedParameterArray, Object.class); 25 | 26 | final MethodType adaptedMt = MethodType.methodType(Object.class, adaptedParameterArray); 27 | return h.asType(adaptedMt); 28 | } 29 | 30 | static MethodHandle create(final String className, final String methodName, final Class ...params) 31 | throws IllegalAccessException, NoSuchMethodException, ClassNotFoundException { 32 | 33 | final Class clazz = Class.forName(className); 34 | return create(clazz, methodName, params); 35 | } 36 | 37 | static MethodHandle create(final Class clazz, final String methodName, final Class ...params) 38 | throws IllegalAccessException, NoSuchMethodException { 39 | 40 | final MethodHandles.Lookup caller = MethodHandles.lookup(); 41 | final Method method = clazz.getDeclaredMethod(methodName, params); 42 | method.setAccessible(true); 43 | 44 | return caller.unreflect(method); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/kgusarov/integration/spring/netty/ChannelOptionsTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.buffer.PooledByteBufAllocator; 5 | import io.netty.channel.AdaptiveRecvByteBufAllocator; 6 | import io.netty.channel.ChannelOption; 7 | import io.netty.channel.WriteBufferWaterMark; 8 | import org.junit.Test; 9 | 10 | import java.util.Map; 11 | 12 | import static java.net.InetAddress.getLoopbackAddress; 13 | import static java.net.NetworkInterface.getNetworkInterfaces; 14 | 15 | public class ChannelOptionsTest { 16 | @Test 17 | @SuppressWarnings({"rawtypes", "unchecked"}) 18 | public void testOptionsHaveCorrectTypes() throws Exception { 19 | final ServerBootstrap bootstrap = new ServerBootstrap(); 20 | final ChannelOptions options = new ChannelOptions(); 21 | 22 | options.setAllocator(new PooledByteBufAllocator()); 23 | options.setRecvBufAllocator(new AdaptiveRecvByteBufAllocator()); 24 | options.setConnectTimeout(1); 25 | options.setWriteSpinCount(1); 26 | options.setWriteBufferWaterMark(new WriteBufferWaterMark(8192, 32768)); 27 | options.setAllowHalfClosure(true); 28 | options.setAutoRead(true); 29 | options.setSoBroadcast(true); 30 | options.setSoKeepAlive(true); 31 | options.setSoReuseAddr(true); 32 | options.setSoSndBuf(8192); 33 | options.setSoRcvBuf(8192); 34 | options.setSoLinger(0); 35 | options.setSoBacklog(0); 36 | options.setSoTimeout(0); 37 | options.setIpTos(0); 38 | options.setIpMulticastAddr(getLoopbackAddress()); 39 | options.setIpMulticastIf(getNetworkInterfaces().nextElement()); 40 | options.setIpMulticastTtl(300); 41 | options.setIpMulticastLoopDisabled(true); 42 | options.setTcpNodelay(true); 43 | 44 | final Map channelOptionMap = options.get(); 45 | channelOptionMap.forEach((key, value) -> { 46 | bootstrap.option(key, value); 47 | bootstrap.childOption(key, value); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onconnect/OnConnectController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onconnect; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 8 | import org.kgusarov.integration.spring.netty.annotations.NettyOnConnect; 9 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 10 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | 13 | import java.lang.reflect.Method; 14 | 15 | @NettyController 16 | class OnConnectController { 17 | static final Method ON_CONNECT1; 18 | static final Method ON_CONNECT2; 19 | 20 | static { 21 | try { 22 | ON_CONNECT1 = OnConnectController.class.getDeclaredMethod("onConnect1", 23 | ChannelHandlerContext.class, Channel.class); 24 | ON_CONNECT2 = OnConnectController.class.getDeclaredMethod("onConnect2", ChannelHandlerContext.class); 25 | } catch (final NoSuchMethodException ignored) { 26 | throw new IllegalStateException(); 27 | } 28 | } 29 | 30 | @Autowired 31 | private HandlerMethodCalls calls; 32 | 33 | @Autowired 34 | private ProcessingCounter counter; 35 | 36 | @NettyOnConnect(serverName = "server1", priority = 1) 37 | private void onConnect1(final ChannelHandlerContext ctx, final Channel channel) { 38 | calls.add(ON_CONNECT1); 39 | counter.arrive(); 40 | 41 | final ByteBuf r1 = Unpooled.copyLong(92L); 42 | final ByteBuf r2 = Unpooled.copyLong(87L); 43 | 44 | ctx.writeAndFlush(r1); 45 | channel.writeAndFlush(r2); 46 | } 47 | 48 | @SuppressWarnings("WeakerAccess") 49 | @NettyOnConnect(serverName = "server1", priority = 2) 50 | ByteBuf onConnect2(final ChannelHandlerContext ignored) { 51 | calls.add(ON_CONNECT2); 52 | counter.arrive(); 53 | return Unpooled.copyLong(106L); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onmessagenohandler/handlers/ExceptionHandlerFilter.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onmessagenohandler.handlers; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandler; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 7 | import org.kgusarov.integration.spring.netty.etc.ExceptionHandler; 8 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | 11 | @ChannelHandler.Sharable 12 | @NettyFilter(serverName = "server1", priority = Integer.MIN_VALUE) 13 | public class ExceptionHandlerFilter extends ExceptionHandler implements ChannelInboundHandler { 14 | @Autowired 15 | private ProcessingCounter counter; 16 | 17 | @Override 18 | public void channelRegistered(final ChannelHandlerContext ctx) throws Exception { 19 | ctx.fireChannelRegistered(); 20 | } 21 | 22 | @Override 23 | public void channelUnregistered(final ChannelHandlerContext ctx) throws Exception { 24 | ctx.fireChannelUnregistered(); 25 | } 26 | 27 | @Override 28 | public void channelActive(final ChannelHandlerContext ctx) throws Exception { 29 | ctx.fireChannelActive(); 30 | } 31 | 32 | @Override 33 | public void channelInactive(final ChannelHandlerContext ctx) throws Exception { 34 | ctx.fireChannelInactive(); 35 | } 36 | 37 | @Override 38 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { 39 | ctx.fireChannelRead(msg); 40 | } 41 | 42 | @Override 43 | public void channelReadComplete(final ChannelHandlerContext ctx) throws Exception { 44 | counter.arrive(); 45 | ctx.fireChannelReadComplete(); 46 | } 47 | 48 | @Override 49 | public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception { 50 | ctx.fireUserEventTriggered(evt); 51 | } 52 | 53 | @Override 54 | public void channelWritabilityChanged(final ChannelHandlerContext ctx) throws Exception { 55 | ctx.fireChannelWritabilityChanged(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/ondisconnect/handlers/OnDisconnectController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.ondisconnect.handlers; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelFuture; 7 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 8 | import org.kgusarov.integration.spring.netty.annotations.NettyOnDisconnect; 9 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 10 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | 13 | import java.lang.reflect.Method; 14 | 15 | @NettyController 16 | public class OnDisconnectController { 17 | public static final Method ON_DISCONNECT1; 18 | public static final Method ON_DISCONNECT2; 19 | 20 | static { 21 | try { 22 | ON_DISCONNECT1 = OnDisconnectController.class.getDeclaredMethod("onDisconnect1", 23 | ChannelFuture.class, Channel.class); 24 | ON_DISCONNECT2 = OnDisconnectController.class.getDeclaredMethod("onDisconnect2"); 25 | } catch (final NoSuchMethodException ignored) { 26 | throw new IllegalStateException(); 27 | } 28 | } 29 | 30 | @Autowired 31 | private HandlerMethodCalls calls; 32 | 33 | @Autowired 34 | private ProcessingCounter counter; 35 | 36 | @NettyOnDisconnect(serverName = "server1", priority = 1) 37 | private void onDisconnect1(final ChannelFuture future, final Channel channel) { 38 | calls.add(ON_DISCONNECT1); 39 | counter.arrive(); 40 | 41 | final ByteBuf r1 = Unpooled.copyLong(92L); 42 | final ByteBuf r2 = Unpooled.copyLong(87L); 43 | 44 | try { 45 | future.channel().writeAndFlush(r1); 46 | channel.writeAndFlush(r2); 47 | } catch (final Exception ignored) { 48 | } 49 | } 50 | 51 | @SuppressWarnings("WeakerAccess") 52 | @NettyOnDisconnect(serverName = "server1", priority = 2) 53 | ByteBuf onDisconnect2() { 54 | calls.add(ON_DISCONNECT2); 55 | counter.arrive(); 56 | return Unpooled.copyLong(106L); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/configuration/TcpServerProperties.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.configuration; 2 | 3 | import org.kgusarov.integration.spring.netty.ChannelOptions; 4 | import org.springframework.boot.context.properties.NestedConfigurationProperty; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | import javax.validation.constraints.NotNull; 8 | 9 | /** 10 | * Configuration properties for a single TCP server instance 11 | */ 12 | public class TcpServerProperties { 13 | private @NotBlank String name; 14 | private @NotBlank String host; 15 | private @NotNull Integer port; 16 | 17 | private Integer bossThreads; 18 | private Integer workerThreads; 19 | 20 | @NestedConfigurationProperty 21 | private ChannelOptions options; 22 | 23 | @NestedConfigurationProperty 24 | private ChannelOptions childOptions; 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(final String name) { 31 | this.name = name; 32 | } 33 | 34 | public Integer getBossThreads() { 35 | return bossThreads; 36 | } 37 | 38 | public void setBossThreads(final Integer bossThreads) { 39 | this.bossThreads = bossThreads; 40 | } 41 | 42 | public Integer getWorkerThreads() { 43 | return workerThreads; 44 | } 45 | 46 | public void setWorkerThreads(final Integer workerThreads) { 47 | this.workerThreads = workerThreads; 48 | } 49 | 50 | public String getHost() { 51 | return host; 52 | } 53 | 54 | public void setHost(final String host) { 55 | this.host = host; 56 | } 57 | 58 | public Integer getPort() { 59 | return port; 60 | } 61 | 62 | public void setPort(final Integer port) { 63 | this.port = port; 64 | } 65 | 66 | public ChannelOptions getOptions() { 67 | return options; 68 | } 69 | 70 | public void setOptions(final ChannelOptions options) { 71 | this.options = options; 72 | } 73 | 74 | public ChannelOptions getChildOptions() { 75 | return childOptions; 76 | } 77 | 78 | public void setChildOptions(final ChannelOptions childOptions) { 79 | this.childOptions = childOptions; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/handlers/FlashPolicyHandler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.handlers; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.*; 6 | import io.netty.util.CharsetUtil; 7 | 8 | import static io.netty.buffer.Unpooled.copiedBuffer; 9 | 10 | /** 11 | * Simple handler implementation for sending cross-domain connection policy 12 | * (Adobe Flash format) to the clients. This handler always permits connection 13 | * from any host to any port as well as disconnects the client after the policy 14 | * has been sent. 15 | */ 16 | @ChannelHandler.Sharable 17 | public final class FlashPolicyHandler extends ChannelInboundHandlerAdapter { 18 | @VisibleForTesting 19 | static final String POLICY_FILE_REQUEST = ""; 20 | 21 | @VisibleForTesting 22 | static final String POLICY_FILE_RESPONSE = "" 23 | + "" 24 | + " " 25 | + " " 26 | + " " 27 | + ""; 28 | 29 | private final ByteBuf requestBuffer = copiedBuffer(POLICY_FILE_REQUEST, CharsetUtil.UTF_8); 30 | private final ByteBuf responseBuffer = copiedBuffer(POLICY_FILE_RESPONSE, CharsetUtil.UTF_8); 31 | 32 | @Override 33 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { 34 | if (msg instanceof ByteBuf) { 35 | final ByteBuf message = (ByteBuf) msg; 36 | final int readableBytes = requestBuffer.readableBytes(); 37 | final ByteBuf data = message.slice(0, readableBytes); 38 | 39 | if (data.equals(requestBuffer)) { 40 | message.release(); 41 | final ChannelFuture f = ctx.writeAndFlush(copiedBuffer(responseBuffer)); 42 | f.addListener(ChannelFutureListener.CLOSE); 43 | return; 44 | } 45 | 46 | ctx.pipeline().remove(this); 47 | } 48 | 49 | ctx.fireChannelRead(msg); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/assembler/LocalVariableAssembler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke.assembler; 2 | 3 | import org.objectweb.asm.Label; 4 | import org.objectweb.asm.MethodVisitor; 5 | import org.objectweb.asm.Type; 6 | 7 | import java.lang.reflect.Parameter; 8 | 9 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.Descriptors.OBJ_DESCRIPTOR; 10 | 11 | /** 12 | * Internal API: code generation support - local variables 13 | */ 14 | final class LocalVariableAssembler { 15 | private LocalVariableAssembler() { 16 | } 17 | 18 | static int getResultIdx(final Parameter[] invokeParameters) { 19 | return invokeParameters.length + 1; 20 | } 21 | 22 | static int getFirstVarIdx(final Parameter[] invokeParameters, final boolean sendResult) { 23 | final int resultIdx = getResultIdx(invokeParameters); 24 | return sendResult ? resultIdx + 1 : resultIdx; 25 | } 26 | 27 | static void assembleLocalVariables(final MethodVisitor m, final Label ms, final Label me, 28 | final String invokerDescriptor, final Parameter[] invokeParameters, 29 | final Parameter[] targetMethodParameters, final boolean sendResult) { 30 | 31 | final int resultIdx = getResultIdx(invokeParameters); 32 | final int firstVarIdx = getFirstVarIdx(invokeParameters, sendResult); 33 | m.visitLocalVariable("this", invokerDescriptor, null, ms, me, 0); 34 | 35 | for (int i = 0; i < invokeParameters.length; i++) { 36 | final Parameter parameter = invokeParameters[i]; 37 | final Class parameterType = parameter.getType(); 38 | final String descriptor = Type.getDescriptor(parameterType); 39 | 40 | //noinspection StringConcatenationMissingWhitespace 41 | m.visitLocalVariable("arg" + i, descriptor, null, ms, me, i + 1); 42 | } 43 | 44 | if (sendResult) { 45 | m.visitLocalVariable("result", OBJ_DESCRIPTOR, null, ms, me, resultIdx); 46 | } 47 | 48 | for (int i = 0; i < targetMethodParameters.length; i++) { 49 | //noinspection StringConcatenationMissingWhitespace 50 | m.visitLocalVariable("o" + i, OBJ_DESCRIPTOR, null, ms, me, i + firstVarIdx); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/OnDisconnectMethodInvoker.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyOnDisconnect; 6 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnDisconnectParameterResolver; 7 | 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Parameter; 10 | import java.util.List; 11 | 12 | import static org.kgusarov.integration.spring.netty.support.invoke.InvokerMethods.OND_INVOKE_HANDLER; 13 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.Descriptors.*; 14 | import static org.objectweb.asm.Opcodes.*; 15 | 16 | /** 17 | * Internal API: invocation support for {@link NettyOnDisconnect} 18 | */ 19 | public final class OnDisconnectMethodInvoker extends AbstractMethodInvoker { 20 | @SuppressWarnings("AbstractClassNeverImplemented") 21 | abstract static class Invoker extends InvokerBase { 22 | NettyOnDisconnectParameterResolver[] resolvers; 23 | 24 | abstract void invokeHandler(final Channel channel, final ChannelFuture channelFuture); 25 | } 26 | 27 | private final Invoker invoker; 28 | 29 | public OnDisconnectMethodInvoker(final Object bean, final Method method, 30 | final List parameterResolvers) { 31 | 32 | invoker = buildInvoker(Invoker.class, method, OND_INVOKE_HANDLER, false, (invokerInternalName, m, firstVarIdx) -> { 33 | final Parameter[] parameters = method.getParameters(); 34 | for (int i = 0; i < parameters.length; i++) { 35 | m.visitIntInsn(ALOAD, 0); 36 | m.visitFieldInsn(GETFIELD, invokerInternalName, "resolvers", OND_RESA_DESCRIPTOR); 37 | m.visitIntInsn(BIPUSH, i); 38 | m.visitInsn(AALOAD); 39 | m.visitIntInsn(ALOAD, 2); 40 | m.visitMethodInsn(INVOKEINTERFACE, OND_RES_INTERNAL_NAME, "resolve", OND_RESOLVE_DESCRIPTOR, true); 41 | m.visitIntInsn(ASTORE, firstVarIdx + i); 42 | } 43 | }); 44 | 45 | invoker.bean = bean; 46 | invoker.resolvers = parameterResolvers.toArray(new NettyOnDisconnectParameterResolver[0]); 47 | } 48 | 49 | public void channelClosed(final ChannelFuture channelFuture) { 50 | final Channel channel = channelFuture.channel(); 51 | invoker.invokeHandler(channel, channelFuture); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/OnConnectMethodInvoker.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyOnConnect; 6 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnConnectParameterResolver; 7 | 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Parameter; 10 | import java.util.List; 11 | 12 | import static org.kgusarov.integration.spring.netty.support.invoke.InvokerMethods.ONC_INVOKE_HANDLER; 13 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.Descriptors.*; 14 | import static org.objectweb.asm.Opcodes.*; 15 | 16 | /** 17 | * Internal API: invocation support for {@link NettyOnConnect} 18 | */ 19 | public final class OnConnectMethodInvoker extends AbstractMethodInvoker { 20 | @SuppressWarnings("AbstractClassNeverImplemented") 21 | abstract static class Invoker extends InvokerBase { 22 | NettyOnConnectParameterResolver[] resolvers; 23 | 24 | abstract void invokeHandler(final Channel channel, final ChannelHandlerContext ctx); 25 | } 26 | 27 | private final Invoker invoker; 28 | 29 | public OnConnectMethodInvoker(final Object bean, final Method method, 30 | final List parameterResolvers, 31 | final boolean sendResult) { 32 | 33 | invoker = buildInvoker(Invoker.class, method, ONC_INVOKE_HANDLER, sendResult, (invokerInternalName, m, firstVarIdx) -> { 34 | final Parameter[] parameters = method.getParameters(); 35 | for (int i = 0; i < parameters.length; i++) { 36 | m.visitIntInsn(ALOAD, 0); 37 | m.visitFieldInsn(GETFIELD, invokerInternalName, "resolvers", ONC_RESA_DESCRIPTOR); 38 | m.visitIntInsn(BIPUSH, i); 39 | m.visitInsn(AALOAD); 40 | m.visitIntInsn(ALOAD, 2); 41 | m.visitMethodInsn(INVOKEINTERFACE, ONC_RES_INTERNAL_NAME, "resolve", ONC_RESOLVE_DESCRIPTOR, true); 42 | m.visitIntInsn(ASTORE, firstVarIdx + i); 43 | } 44 | }); 45 | 46 | invoker.bean = bean; 47 | invoker.resolvers = parameterResolvers.toArray(new NettyOnConnectParameterResolver[0]); 48 | } 49 | 50 | public void channelActive(final ChannelHandlerContext ctx) { 51 | final Channel channel = ctx.channel(); 52 | invoker.invokeHandler(channel, ctx); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/org/kgusarov/integration/spring/netty/support/invoke/MethodHandleCreatorTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke; 2 | 3 | import org.junit.Test; 4 | import org.springframework.cglib.proxy.Enhancer; 5 | import org.springframework.cglib.proxy.MethodInterceptor; 6 | 7 | import java.lang.invoke.MethodHandle; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | @SuppressWarnings({"CodeBlock2Expr", "ProhibitedExceptionDeclared"}) 12 | public class MethodHandleCreatorTest { 13 | private static final Victim VICTIM = new Victim(); 14 | 15 | @Test 16 | public void nonExistentMethod() { 17 | assertThrows(NoSuchMethodException.class, () -> { 18 | MethodHandleCreator.create(Victim.class, "non-existent"); 19 | }); 20 | } 21 | 22 | @Test 23 | public void noArgUniversal() throws Throwable { 24 | final MethodHandle methodHandle = MethodHandleCreator.createUniversal(Victim.class.getName(), "noArg"); 25 | final Object result = methodHandle.invokeExact((Object) VICTIM); 26 | assertNull(result); 27 | } 28 | 29 | @Test 30 | public void noArgMethod() throws NoSuchMethodException, IllegalAccessException { 31 | final MethodHandle methodHandle = MethodHandleCreator.create(Victim.class, "noArg"); 32 | assertDoesNotThrow(() -> { 33 | methodHandle.invokeExact(VICTIM); 34 | }); 35 | } 36 | 37 | @Test 38 | public void privateMethod() throws Throwable { 39 | final MethodHandle methodHandle = MethodHandleCreator.create(Victim.class, "concat", int.class, int.class); 40 | assertEquals("1-2", (String) methodHandle.invokeExact(VICTIM, 1, 2)); 41 | } 42 | 43 | @Test 44 | public void packagePrivateMethod() throws Throwable { 45 | final MethodHandle methodHandle = MethodHandleCreator.create(Victim.class, "concat", int.class, int.class, String.class); 46 | assertEquals("1-2-str", (String) methodHandle.invokeExact(VICTIM, 1, 2, "str")); 47 | } 48 | 49 | @Test 50 | public void proxiedMethod() throws Throwable { 51 | final MethodHandle methodHandle = MethodHandleCreator.create(Victim.class.getName(), "proxied"); 52 | final Enhancer enhancer = new Enhancer(); 53 | enhancer.setSuperclass(Victim.class); 54 | enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { 55 | final int result = (int) proxy.invokeSuper(obj, args); 56 | return result + 1; 57 | }); 58 | final Victim proxy = (Victim) enhancer.create(); 59 | 60 | assertEquals(2, (int) methodHandle.invokeExact(proxy)); 61 | } 62 | } -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onmessagenohandler/OnMessageNoHandlerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onmessagenohandler; 2 | 3 | import io.netty.buffer.Unpooled; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.kgusarov.integration.spring.netty.ServerClient; 7 | import org.kgusarov.integration.spring.netty.configuration.NettyServers; 8 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 9 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootContextLoader; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.test.annotation.DirtiesContext; 14 | import org.springframework.test.context.ActiveProfiles; 15 | import org.springframework.test.context.ContextConfiguration; 16 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 17 | 18 | import java.util.concurrent.ExecutionException; 19 | import java.util.concurrent.TimeUnit; 20 | import java.util.concurrent.TimeoutException; 21 | 22 | import static org.hamcrest.Matchers.hasSize; 23 | import static org.hamcrest.Matchers.not; 24 | import static org.junit.Assert.assertThat; 25 | import static org.junit.Assert.assertTrue; 26 | 27 | @ActiveProfiles("onmessage") 28 | @SpringBootTest 29 | @ContextConfiguration(classes = { 30 | OnMessageNoHandlerApplication.class, 31 | HandlerMethodCalls.class}, loader = SpringBootContextLoader.class) 32 | @RunWith(SpringJUnit4ClassRunner.class) 33 | public class OnMessageNoHandlerIntegrationTest { 34 | @Autowired 35 | private NettyServers servers; 36 | 37 | @Autowired 38 | private HandlerMethodCalls calls; 39 | 40 | @Autowired 41 | private ProcessingCounter counter; 42 | 43 | @Test 44 | @DirtiesContext 45 | public void testServersShouldBePresent() { 46 | assertThat(servers, not(hasSize(0))); 47 | } 48 | 49 | @Test 50 | @DirtiesContext 51 | public void testNandlersNotCalled() throws Exception { 52 | runTestOnce(0); 53 | runTestOnce(1); 54 | } 55 | 56 | private void runTestOnce(final int phase) throws InterruptedException, TimeoutException, ExecutionException { 57 | calls.clear(); 58 | 59 | final int serverPort = servers.get(0).getBoundToPort(); 60 | final ServerClient client = new ServerClient(serverPort, "localhost"); 61 | 62 | client.connect(); 63 | client.writeAndFlush(Unpooled.copyLong(100500L)).syncUninterruptibly(); 64 | counter.awaitAdvanceInterruptibly(phase, 30, TimeUnit.SECONDS); 65 | client.disconnect(); 66 | 67 | assertTrue(calls.isEmpty()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onmessage/handlers/OnMessageController.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onmessage.handlers; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyController; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyMessageBody; 7 | import org.kgusarov.integration.spring.netty.annotations.NettyOnMessage; 8 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 9 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | 12 | import java.lang.reflect.Method; 13 | 14 | @NettyController 15 | @SuppressWarnings("WeakerAccess") 16 | public class OnMessageController { 17 | public static final Method ON_MESSAGE1; 18 | public static final Method ON_MESSAGE2; 19 | public static final Method ON_MESSAGE3; 20 | public static final Method ON_STRING_MESSAGE; 21 | 22 | static { 23 | try { 24 | ON_MESSAGE1 = OnMessageController.class.getDeclaredMethod("onMessage1", 25 | ChannelHandlerContext.class, Channel.class, Long.class); 26 | ON_MESSAGE2 = OnMessageController.class.getDeclaredMethod("onMessage2", long.class); 27 | ON_MESSAGE3 = OnMessageController.class.getDeclaredMethod("onMessage3"); 28 | ON_STRING_MESSAGE = OnMessageController.class.getDeclaredMethod("onStringMessage", String.class); 29 | } catch (final NoSuchMethodException ignored) { 30 | throw new IllegalStateException(); 31 | } 32 | } 33 | 34 | @Autowired 35 | private HandlerMethodCalls calls; 36 | 37 | @Autowired 38 | private ProcessingCounter counter; 39 | 40 | @NettyOnMessage(serverName = "server1", priority = 1) 41 | public String onStringMessage(@NettyMessageBody final String msg) { 42 | calls.add(ON_STRING_MESSAGE); 43 | counter.arrive(); 44 | 45 | return msg; 46 | } 47 | 48 | @NettyOnMessage(serverName = "server1", priority = 4) 49 | private void onMessage3() { 50 | calls.add(ON_MESSAGE3); 51 | counter.arrive(); 52 | } 53 | 54 | @NettyOnMessage(serverName = "server1", priority = 3) 55 | long onMessage2(@NettyMessageBody final long msg) { 56 | calls.add(ON_MESSAGE2); 57 | counter.arrive(); 58 | 59 | return msg; 60 | } 61 | 62 | @NettyOnMessage(serverName = "server1", priority = 2) 63 | void onMessage1(final ChannelHandlerContext ctx, final Channel channel, @NettyMessageBody final Long msg) { 64 | calls.add(ON_MESSAGE1); 65 | counter.arrive(); 66 | 67 | ctx.writeAndFlush(msg + 1); 68 | channel.writeAndFlush(msg + 2); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 21 * * 5' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['java'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/ServerClient.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty; 2 | 3 | import com.google.common.util.concurrent.SettableFuture; 4 | import io.netty.bootstrap.Bootstrap; 5 | import io.netty.channel.*; 6 | import io.netty.channel.nio.NioEventLoopGroup; 7 | import io.netty.channel.socket.SocketChannel; 8 | import io.netty.channel.socket.nio.NioSocketChannel; 9 | import org.kgusarov.integration.spring.netty.etc.ExceptionHandler; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.Arrays; 14 | import java.util.concurrent.Future; 15 | import java.util.stream.Stream; 16 | 17 | public class ServerClient { 18 | private static final Logger LOGGER = LoggerFactory.getLogger(ServerClient.class); 19 | 20 | private final String host; 21 | private final ChannelHandler[] handlers; 22 | private final int port; 23 | private Channel channel; 24 | 25 | public ServerClient(final int port, final String host, final ChannelHandler... handlers) { 26 | this.port = port; 27 | this.host = host; 28 | this.handlers = Stream.concat( 29 | Stream.of(new ExceptionHandler()), 30 | Arrays.stream(handlers) 31 | ).toArray(ChannelHandler[]::new); 32 | } 33 | 34 | public ChannelFuture writeAndFlush(final Object msg) { 35 | return channel.writeAndFlush(msg); 36 | } 37 | 38 | public void disconnect() { 39 | channel.close().syncUninterruptibly(); 40 | } 41 | 42 | public Future connect() { 43 | final EventLoopGroup group = new NioEventLoopGroup(); 44 | final SettableFuture result = SettableFuture.create(); 45 | 46 | try { 47 | final Bootstrap b = new Bootstrap(); 48 | b.group(group) 49 | .channel(NioSocketChannel.class) 50 | .handler(new ChannelInitializer() { 51 | @Override 52 | protected void initChannel(final SocketChannel ch) { 53 | final ChannelPipeline p = ch.pipeline(); 54 | Arrays.stream(handlers).forEach(p::addLast); 55 | } 56 | }); 57 | 58 | // Make the connection attempt. 59 | final ChannelFuture f = b.connect(host, port).syncUninterruptibly(); 60 | channel = f.channel(); 61 | 62 | new Thread(() -> { 63 | result.set(this); 64 | 65 | // Wait until the connection is closed. 66 | channel.closeFuture().syncUninterruptibly(); 67 | group.shutdownGracefully(); 68 | }).start(); 69 | } catch (final Exception e) { 70 | LOGGER.error("Exception while connecting", e); 71 | result.setException(e); 72 | } 73 | 74 | return result; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/org/kgusarov/integration/spring/netty/configuration/FilterBeanComparatorTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.configuration; 2 | 3 | import io.netty.channel.ChannelDuplexHandler; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import io.netty.channel.ChannelOutboundHandlerAdapter; 7 | import org.junit.Test; 8 | import org.kgusarov.integration.spring.netty.annotations.NettyFilter; 9 | 10 | import java.util.SortedSet; 11 | import java.util.TreeSet; 12 | 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | import static org.hamcrest.Matchers.contains; 15 | 16 | public class FilterBeanComparatorTest { 17 | private static final Object FIRST_BEAN = new FirstHandler(); 18 | private static final Object SECOND_BEAN = new SecondHandler(); 19 | private static final Object THIRD_BEAN = new ThirdHandler(); 20 | private static final Object NON_ANNOTATED_BEAN = new NonAnnotatedHandler(); 21 | private static final Object WRONG_ANNOTATION_BEAN = new WrongAnnotationHandler(); 22 | 23 | @Test 24 | public void methodSorting() { 25 | final SortedSet handlers = new TreeSet<>(SpringNettyConfiguration.FILTER_BEAN_COMPARATOR); 26 | handlers.add(THIRD_BEAN); 27 | handlers.add(SECOND_BEAN); 28 | handlers.add(FIRST_BEAN); 29 | 30 | assertThat(handlers, contains(FIRST_BEAN, SECOND_BEAN, THIRD_BEAN)); 31 | } 32 | 33 | @Test(expected = IllegalStateException.class) 34 | @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") 35 | public void nonAnnotatedMethodSorting() { 36 | final SortedSet methods = new TreeSet<>(SpringNettyConfiguration.FILTER_BEAN_COMPARATOR); 37 | methods.add(NON_ANNOTATED_BEAN); 38 | } 39 | 40 | @Test(expected = IllegalStateException.class) 41 | @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") 42 | public void wrongAnnotationMethodSorting() { 43 | final SortedSet methods = new TreeSet<>(SpringNettyConfiguration.FILTER_BEAN_COMPARATOR); 44 | methods.add(WRONG_ANNOTATION_BEAN); 45 | } 46 | 47 | @NettyFilter(serverName = "whatever", priority = 1) 48 | private static final class FirstHandler extends ChannelDuplexHandler { 49 | // Nothing here 50 | } 51 | 52 | @NettyFilter(serverName = "whatever", priority = 2) 53 | private static final class SecondHandler extends ChannelInboundHandlerAdapter { 54 | // Nothing here 55 | } 56 | 57 | @NettyFilter(serverName = "whatever", priority = 3) 58 | private static final class ThirdHandler extends ChannelOutboundHandlerAdapter { 59 | // Nothing here 60 | } 61 | 62 | private static final class NonAnnotatedHandler extends ChannelDuplexHandler { 63 | // Nothing here 64 | } 65 | 66 | @ChannelHandler.Sharable 67 | private static final class WrongAnnotationHandler extends ChannelDuplexHandler { 68 | // Nothing here 69 | } 70 | } -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/ondisconnect/OnDisconnectIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.ondisconnect; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.kgusarov.integration.spring.netty.ServerClient; 6 | import org.kgusarov.integration.spring.netty.configuration.NettyServers; 7 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 8 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 9 | import org.kgusarov.integration.spring.netty.ondisconnect.handlers.OnDisconnectController; 10 | import org.kgusarov.integration.spring.netty.ondisconnect.handlers.TransactionalOnDisconnectController; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootContextLoader; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.test.annotation.DirtiesContext; 15 | import org.springframework.test.context.ActiveProfiles; 16 | import org.springframework.test.context.ContextConfiguration; 17 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 18 | 19 | import java.util.concurrent.ExecutionException; 20 | import java.util.concurrent.TimeUnit; 21 | import java.util.concurrent.TimeoutException; 22 | 23 | import static org.hamcrest.Matchers.*; 24 | import static org.junit.Assert.assertThat; 25 | 26 | @ActiveProfiles("ondisconnect") 27 | @SpringBootTest 28 | @ContextConfiguration(classes = { 29 | OnDisconnectApplication.class, 30 | HandlerMethodCalls.class 31 | }, loader = SpringBootContextLoader.class) 32 | @RunWith(SpringJUnit4ClassRunner.class) 33 | public class OnDisconnectIntegrationTest { 34 | @Autowired 35 | private NettyServers servers; 36 | 37 | @Autowired 38 | private HandlerMethodCalls calls; 39 | 40 | @Autowired 41 | private ProcessingCounter counter; 42 | 43 | @Test 44 | @DirtiesContext 45 | public void testServersShouldBePresent() { 46 | assertThat(servers, not(hasSize(0))); 47 | } 48 | 49 | @Test 50 | @DirtiesContext 51 | @SuppressWarnings("unchecked") 52 | public void testHandlersAddedInCorrectOrder() throws Exception { 53 | final ServerClient client = new ServerClient(40000, "localhost"); 54 | 55 | doDisconnectCycle(client, 0); 56 | doDisconnectCycle(client, 1); 57 | 58 | assertThat(calls, contains( 59 | is(OnDisconnectController.ON_DISCONNECT1), 60 | is(OnDisconnectController.ON_DISCONNECT2), 61 | is(TransactionalOnDisconnectController.ON_DISCONNECT), 62 | is(OnDisconnectController.ON_DISCONNECT1), 63 | is(OnDisconnectController.ON_DISCONNECT2), 64 | is(TransactionalOnDisconnectController.ON_DISCONNECT) 65 | )); 66 | } 67 | 68 | private void doDisconnectCycle(final ServerClient client, final int phase) throws InterruptedException, ExecutionException, TimeoutException { 69 | client.connect().get().disconnect(); 70 | counter.awaitAdvanceInterruptibly(phase, 30, TimeUnit.SECONDS); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/org/kgusarov/integration/spring/netty/configuration/OnMessageMethodComparatorTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.configuration; 2 | 3 | import org.junit.Test; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyOnConnect; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyOnMessage; 6 | 7 | import java.lang.reflect.Method; 8 | import java.util.SortedSet; 9 | import java.util.TreeSet; 10 | 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | import static org.hamcrest.Matchers.contains; 13 | 14 | public class OnMessageMethodComparatorTest { 15 | private static final Method FIRST_METHOD; 16 | private static final Method SECOND_METHOD; 17 | private static final Method THIRD_METHOD; 18 | private static final Method NON_ANNOTATED_METHOD; 19 | private static final Method WRONG_ANNOTATION_METHOD; 20 | 21 | static { 22 | try { 23 | FIRST_METHOD = OnMessageMethodComparatorTest.class.getDeclaredMethod("firstMethod"); 24 | SECOND_METHOD = OnMessageMethodComparatorTest.class.getDeclaredMethod("secondMethod"); 25 | THIRD_METHOD = OnMessageMethodComparatorTest.class.getDeclaredMethod("thirdMethod"); 26 | NON_ANNOTATED_METHOD = OnMessageMethodComparatorTest.class.getDeclaredMethod("nonAnnotatedMethod"); 27 | WRONG_ANNOTATION_METHOD = OnMessageMethodComparatorTest.class.getDeclaredMethod("wrongAnnotationMethod"); 28 | } catch (final NoSuchMethodException ignored) { 29 | throw new IllegalStateException(); 30 | } 31 | } 32 | 33 | @Test 34 | public void methodSorting() { 35 | final SortedSet methods = new TreeSet<>(SpringNettyConfiguration.ON_MESSAGE_METHOD_COMPARATOR); 36 | methods.add(THIRD_METHOD); 37 | methods.add(SECOND_METHOD); 38 | methods.add(FIRST_METHOD); 39 | 40 | assertThat(methods, contains(FIRST_METHOD, SECOND_METHOD, THIRD_METHOD)); 41 | } 42 | 43 | @Test(expected = IllegalStateException.class) 44 | @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") 45 | public void nonAnnotatedMethodSorting() { 46 | final SortedSet methods = new TreeSet<>(SpringNettyConfiguration.ON_MESSAGE_METHOD_COMPARATOR); 47 | methods.add(NON_ANNOTATED_METHOD); 48 | } 49 | 50 | @Test(expected = IllegalStateException.class) 51 | @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") 52 | public void wrongAnnotationMethodSorting() { 53 | final SortedSet methods = new TreeSet<>(SpringNettyConfiguration.ON_MESSAGE_METHOD_COMPARATOR); 54 | methods.add(WRONG_ANNOTATION_METHOD); 55 | } 56 | 57 | @NettyOnMessage(serverName = "whatever", priority = 1) 58 | private void firstMethod() { 59 | } 60 | 61 | @NettyOnMessage(serverName = "whatever", priority = 2) 62 | private void secondMethod() { 63 | } 64 | 65 | @NettyOnMessage(serverName = "whatever", priority = 3) 66 | private void thirdMethod() { 67 | } 68 | 69 | private void nonAnnotatedMethod() { 70 | } 71 | 72 | @NettyOnConnect(serverName = "whatever") 73 | private void wrongAnnotationMethod() { 74 | } 75 | } -------------------------------------------------------------------------------- /src/test/java/org/kgusarov/integration/spring/netty/configuration/OnConnectMethodComparatorTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.configuration; 2 | 3 | import org.junit.Test; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyOnConnect; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyOnDisconnect; 6 | 7 | import java.lang.reflect.Method; 8 | import java.util.SortedSet; 9 | import java.util.TreeSet; 10 | 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | import static org.hamcrest.Matchers.contains; 13 | 14 | public class OnConnectMethodComparatorTest { 15 | private static final Method FIRST_METHOD; 16 | private static final Method SECOND_METHOD; 17 | private static final Method THIRD_METHOD; 18 | private static final Method NON_ANNOTATED_METHOD; 19 | private static final Method WRONG_ANNOTATION_METHOD; 20 | 21 | static { 22 | try { 23 | FIRST_METHOD = OnConnectMethodComparatorTest.class.getDeclaredMethod("firstMethod"); 24 | SECOND_METHOD = OnConnectMethodComparatorTest.class.getDeclaredMethod("secondMethod"); 25 | THIRD_METHOD = OnConnectMethodComparatorTest.class.getDeclaredMethod("thirdMethod"); 26 | NON_ANNOTATED_METHOD = OnConnectMethodComparatorTest.class.getDeclaredMethod("nonAnnotatedMethod"); 27 | WRONG_ANNOTATION_METHOD = OnConnectMethodComparatorTest.class.getDeclaredMethod("wrongAnnotationMethod"); 28 | } catch (final NoSuchMethodException ignored) { 29 | throw new IllegalStateException(); 30 | } 31 | } 32 | 33 | @Test 34 | public void methodSorting() { 35 | final SortedSet methods = new TreeSet<>(SpringNettyConfiguration.ON_CONNECT_METHOD_COMPARATOR); 36 | methods.add(THIRD_METHOD); 37 | methods.add(SECOND_METHOD); 38 | methods.add(FIRST_METHOD); 39 | 40 | assertThat(methods, contains(FIRST_METHOD, SECOND_METHOD, THIRD_METHOD)); 41 | } 42 | 43 | @Test(expected = IllegalStateException.class) 44 | @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") 45 | public void nonAnnotatedMethodSorting() { 46 | final SortedSet methods = new TreeSet<>(SpringNettyConfiguration.ON_CONNECT_METHOD_COMPARATOR); 47 | methods.add(NON_ANNOTATED_METHOD); 48 | } 49 | 50 | @Test(expected = IllegalStateException.class) 51 | @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") 52 | public void wrongAnnotationMethodSorting() { 53 | final SortedSet methods = new TreeSet<>(SpringNettyConfiguration.ON_CONNECT_METHOD_COMPARATOR); 54 | methods.add(WRONG_ANNOTATION_METHOD); 55 | } 56 | 57 | @NettyOnConnect(serverName = "whatever", priority = 1) 58 | private void firstMethod() { 59 | } 60 | 61 | @NettyOnConnect(serverName = "whatever", priority = 2) 62 | private void secondMethod() { 63 | } 64 | 65 | @NettyOnConnect(serverName = "whatever", priority = 3) 66 | private void thirdMethod() { 67 | } 68 | 69 | private void nonAnnotatedMethod() { 70 | } 71 | 72 | @NettyOnDisconnect(serverName = "whatever") 73 | private void wrongAnnotationMethod() { 74 | } 75 | } -------------------------------------------------------------------------------- /src/test/java/org/kgusarov/integration/spring/netty/configuration/OnDisconnectMethodComparatorTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.configuration; 2 | 3 | import org.junit.Test; 4 | import org.kgusarov.integration.spring.netty.annotations.NettyOnConnect; 5 | import org.kgusarov.integration.spring.netty.annotations.NettyOnDisconnect; 6 | 7 | import java.lang.reflect.Method; 8 | import java.util.SortedSet; 9 | import java.util.TreeSet; 10 | 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | import static org.hamcrest.Matchers.contains; 13 | 14 | public class OnDisconnectMethodComparatorTest { 15 | private static final Method FIRST_METHOD; 16 | private static final Method SECOND_METHOD; 17 | private static final Method THIRD_METHOD; 18 | private static final Method NON_ANNOTATED_METHOD; 19 | private static final Method WRONG_ANNOTATION_METHOD; 20 | 21 | static { 22 | try { 23 | FIRST_METHOD = OnDisconnectMethodComparatorTest.class.getDeclaredMethod("firstMethod"); 24 | SECOND_METHOD = OnDisconnectMethodComparatorTest.class.getDeclaredMethod("secondMethod"); 25 | THIRD_METHOD = OnDisconnectMethodComparatorTest.class.getDeclaredMethod("thirdMethod"); 26 | NON_ANNOTATED_METHOD = OnDisconnectMethodComparatorTest.class.getDeclaredMethod("nonAnnotatedMethod"); 27 | WRONG_ANNOTATION_METHOD = OnDisconnectMethodComparatorTest.class.getDeclaredMethod("wrongAnnotationMethod"); 28 | } catch (final NoSuchMethodException ignored) { 29 | throw new IllegalStateException(); 30 | } 31 | } 32 | 33 | @Test 34 | public void methodSorting() { 35 | final SortedSet methods = new TreeSet<>(SpringNettyConfiguration.ON_DISCONNECT_METHOD_COMPARATOR); 36 | methods.add(THIRD_METHOD); 37 | methods.add(SECOND_METHOD); 38 | methods.add(FIRST_METHOD); 39 | 40 | assertThat(methods, contains(FIRST_METHOD, SECOND_METHOD, THIRD_METHOD)); 41 | } 42 | 43 | @Test(expected = IllegalStateException.class) 44 | @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") 45 | public void nonAnnotatedMethodSorting() { 46 | final SortedSet methods = new TreeSet<>(SpringNettyConfiguration.ON_DISCONNECT_METHOD_COMPARATOR); 47 | methods.add(NON_ANNOTATED_METHOD); 48 | } 49 | 50 | @Test(expected = IllegalStateException.class) 51 | @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") 52 | public void wrongAnnotationMethodSorting() { 53 | final SortedSet methods = new TreeSet<>(SpringNettyConfiguration.ON_DISCONNECT_METHOD_COMPARATOR); 54 | methods.add(WRONG_ANNOTATION_METHOD); 55 | } 56 | 57 | @NettyOnDisconnect(serverName = "whatever", priority = 1) 58 | private void firstMethod() { 59 | } 60 | 61 | @NettyOnDisconnect(serverName = "whatever", priority = 2) 62 | private void secondMethod() { 63 | } 64 | 65 | @NettyOnDisconnect(serverName = "whatever", priority = 3) 66 | private void thirdMethod() { 67 | } 68 | 69 | private void nonAnnotatedMethod() { 70 | } 71 | 72 | @NettyOnConnect(serverName = "whatever") 73 | private void wrongAnnotationMethod() { 74 | } 75 | } -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/AbstractMethodInvoker.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke; 2 | 3 | import org.kgusarov.integration.spring.netty.support.invoke.assembler.MethodPrefixAssembler; 4 | import org.objectweb.asm.ClassWriter; 5 | import org.objectweb.asm.Type; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | import java.util.concurrent.atomic.AtomicLong; 10 | 11 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.ConstructorAssembler.assembleConstructor; 12 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.InvokerMethodAssembler.assembleInvokerMethod; 13 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.MethodHandleFieldAssembler.assembleMethodHandleField; 14 | import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; 15 | import static org.objectweb.asm.Opcodes.*; 16 | 17 | /** 18 | * Internal API: invocation support for {@link org.kgusarov.integration.spring.netty.annotations.NettyController} 19 | * methods 20 | */ 21 | abstract class AbstractMethodInvoker { 22 | @SuppressWarnings("AbstractClassNeverImplemented") 23 | abstract static class InvokerBase { 24 | Object bean; 25 | } 26 | 27 | private static final String[] EMPTY_INTERFACES = new String[0]; 28 | private static final GeneratedClassLoader CLASS_LOADER = new GeneratedClassLoader(); 29 | private static final AtomicLong COUNTER = new AtomicLong(1); 30 | 31 | final T buildInvoker(final Class parent, final Method targetMethod, final Method invokerMethod, 32 | final boolean sendResult, final MethodPrefixAssembler prefixAssembler) { 33 | 34 | final String packageName = parent.getPackage().getName(); 35 | final String invokerClassName = packageName + ".DynamicInvoker$$GeneratedClass$$" + COUNTER.getAndIncrement(); 36 | 37 | final String invokerInternalName = invokerClassName.replace('.', '/'); 38 | final String parentInternalName = Type.getInternalName(parent); 39 | 40 | final ClassWriter cw = new ClassWriter(COMPUTE_MAXS); 41 | cw.visit(V1_8, ACC_SUPER | ACC_PUBLIC, invokerInternalName, null, parentInternalName, EMPTY_INTERFACES); 42 | 43 | final Type invokerType = Type.getObjectType(invokerInternalName); 44 | final String invokerDescriptor = invokerType.getDescriptor(); 45 | 46 | assembleConstructor(invokerType, parentInternalName, cw); 47 | assembleMethodHandleField(cw, targetMethod, invokerInternalName); 48 | assembleInvokerMethod(cw, invokerDescriptor, invokerInternalName, invokerMethod, targetMethod, sendResult, prefixAssembler); 49 | 50 | cw.visitEnd(); 51 | 52 | final byte[] bytecode = cw.toByteArray(); 53 | final Class cl = CLASS_LOADER.load(bytecode, invokerClassName); 54 | 55 | try { 56 | return cl.getDeclaredConstructor().newInstance(); 57 | } catch (final IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { 58 | throw new IllegalStateException(e); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at konstantins.gusarovs@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/assembler/MethodHandleFieldAssembler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke.assembler; 2 | 3 | import com.google.common.primitives.Primitives; 4 | import org.objectweb.asm.ClassWriter; 5 | import org.objectweb.asm.Label; 6 | import org.objectweb.asm.MethodVisitor; 7 | import org.objectweb.asm.Type; 8 | 9 | import java.lang.invoke.MethodHandle; 10 | import java.lang.reflect.Method; 11 | import java.lang.reflect.Parameter; 12 | 13 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.Descriptors.*; 14 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.Descriptors.MH_DESCRIPTOR; 15 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.LabelAssembler.createLabel; 16 | import static org.objectweb.asm.Opcodes.*; 17 | 18 | /** 19 | * Internal API: code generation support - method handle private static final field 20 | */ 21 | public final class MethodHandleFieldAssembler { 22 | private MethodHandleFieldAssembler() { 23 | } 24 | 25 | public static void assembleMethodHandleField(final ClassWriter cw, final Method targetMethod, final String invokerName) { 26 | final String desc = Type.getDescriptor(MethodHandle.class); 27 | cw.visitField(ACC_PRIVATE | ACC_FINAL + ACC_STATIC, "HANDLE", desc, null, null); 28 | cw.visitEnd(); 29 | 30 | final MethodVisitor ctor = cw.visitMethod(ACC_STATIC, "", "()V", null, null); 31 | final Label cs = createLabel(); 32 | final Label ce = createLabel(); 33 | 34 | ctor.visitCode(); 35 | ctor.visitLabel(cs); 36 | 37 | final String clName = targetMethod.getDeclaringClass().getCanonicalName(); 38 | final String targetMethodName = targetMethod.getName(); 39 | ctor.visitLdcInsn(clName); 40 | ctor.visitLdcInsn(targetMethodName); 41 | 42 | final Parameter[] parameters = targetMethod.getParameters(); 43 | ctor.visitLdcInsn(parameters.length); 44 | 45 | final Type classType = Type.getType(Class.class); 46 | final String descriptor = classType.getInternalName(); 47 | ctor.visitTypeInsn(ANEWARRAY, descriptor); 48 | 49 | for (int i = 0; i < parameters.length; i++) { 50 | ctor.visitInsn(DUP); 51 | ctor.visitLdcInsn(i); 52 | 53 | final Class paramClass = parameters[i].getType(); 54 | if (paramClass.isPrimitive()) { 55 | final Class wrapper = Primitives.wrap(paramClass); 56 | final String holderName = Type.getType(wrapper).getInternalName(); 57 | 58 | ctor.visitFieldInsn(GETSTATIC, holderName, "TYPE", CL_DESCRIPTOR); 59 | } else { 60 | final Type parameterType = Type.getType(paramClass); 61 | ctor.visitLdcInsn(parameterType); 62 | } 63 | 64 | ctor.visitInsn(AASTORE); 65 | } 66 | 67 | ctor.visitMethodInsn(INVOKESTATIC, MHC_INTERNAL_NAME, "createUniversal", 68 | '(' + STR_DESCRIPTOR + STR_DESCRIPTOR + CLA_DESCRIPTOR + ')' + MH_DESCRIPTOR, false); 69 | ctor.visitFieldInsn(PUTSTATIC, invokerName, "HANDLE", MH_DESCRIPTOR); 70 | ctor.visitInsn(RETURN); 71 | 72 | ctor.visitLabel(ce); 73 | ctor.visitEnd(); 74 | 75 | ctor.visitMaxs(0, 0); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/nettyfilters/NettyFiltersIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.nettyfilters; 2 | 3 | import com.google.common.util.concurrent.SettableFuture; 4 | import io.netty.buffer.ByteBuf; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.kgusarov.integration.spring.netty.ServerClient; 8 | import org.kgusarov.integration.spring.netty.configuration.NettyServers; 9 | import org.kgusarov.integration.spring.netty.etc.ClientHandler; 10 | import org.kgusarov.integration.spring.netty.etc.HandlerCallStack; 11 | import org.kgusarov.integration.spring.netty.nettyfilters.handlers.*; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootContextLoader; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.test.annotation.DirtiesContext; 16 | import org.springframework.test.context.ActiveProfiles; 17 | import org.springframework.test.context.ContextConfiguration; 18 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 19 | 20 | import static io.netty.buffer.Unpooled.copyLong; 21 | import static org.hamcrest.Matchers.*; 22 | import static org.junit.Assert.assertEquals; 23 | import static org.junit.Assert.assertThat; 24 | 25 | @ActiveProfiles("filters") 26 | @SpringBootTest 27 | @ContextConfiguration(classes = { 28 | NettyFiltersApplication.class, 29 | HandlerCallStack.class 30 | }, loader = SpringBootContextLoader.class) 31 | @RunWith(SpringJUnit4ClassRunner.class) 32 | public class NettyFiltersIntegrationTest { 33 | 34 | @Autowired 35 | private NettyServers servers; 36 | 37 | @Autowired 38 | private HandlerCallStack handlerCallStack; 39 | 40 | @Test 41 | @DirtiesContext 42 | public void testServersShouldBePresent() { 43 | assertThat(servers, not(hasSize(0))); 44 | } 45 | 46 | @Test 47 | @DirtiesContext 48 | public void testHandlersAddedInCorrectOrder() throws Exception { 49 | final SettableFuture responseHolder1 = SettableFuture.create(); 50 | final SettableFuture responseHolder2 = SettableFuture.create(); 51 | final ServerClient client = new ServerClient(40000, "localhost", 52 | new ClientHandler(responseHolder1, responseHolder2)); 53 | 54 | client.connect().get(); 55 | 56 | final ByteBuf msg1 = copyLong(1L); 57 | final ByteBuf msg2 = copyLong(2L); 58 | client.writeAndFlush(msg1).syncUninterruptibly(); 59 | client.writeAndFlush(msg2).syncUninterruptibly(); 60 | 61 | final long actual1 = responseHolder1.get(); 62 | final long actual2 = responseHolder2.get(); 63 | assertEquals(-1L, actual1); 64 | assertEquals(-2L, actual2); 65 | 66 | client.disconnect(); 67 | 68 | //noinspection unchecked 69 | assertThat(handlerCallStack, contains( 70 | LongDecoder.class, 71 | LongInverter.class, 72 | AroundResponderFilter.class, 73 | LongResponder.class, 74 | AroundResponderFilter.class, 75 | LongEncoder.class, 76 | 77 | LongDecoder.class, 78 | LongInverter.class, 79 | AroundResponderFilter.class, 80 | LongResponder.class, 81 | AroundResponderFilter.class, 82 | LongEncoder.class 83 | )); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/org/kgusarov/integration/spring/netty/support/invoke/GeneratedClassLoaderTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke; 2 | 3 | import org.junit.Test; 4 | import org.junit.jupiter.api.Assertions; 5 | 6 | import java.lang.reflect.Field; 7 | import java.util.Base64; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | @SuppressWarnings({"CodeBlock2Expr", "StringConcatenationMissingWhitespace"}) 12 | public class GeneratedClassLoaderTest { 13 | private final GeneratedClassLoader cl = new GeneratedClassLoader(); 14 | 15 | @Test 16 | public void invalidClass() { 17 | Assertions.assertThrows(IllegalStateException.class, () -> { 18 | cl.load(new byte[] {1, 2, 3}, "org.kgusarov.Test"); 19 | }); 20 | } 21 | 22 | @Test 23 | public void success() throws NoSuchFieldException { 24 | final byte[] bytes = Base64.getDecoder().decode( 25 | "yv66vgAAADQALwEAXW9yZy9rZ3VzYXJvdi9pbnRlZ3JhdGlvbi9zcHJpbmcvbmV0dHkvc3VwcG9ydC9pbnZva2UvRHlu" + 26 | "YW1pY0ludm9rZXIkJEdlbmVyYXRlZENsYXNzJCQxMDAwMDAwNQcAAQEAU29yZy9rZ3VzYXJvdi9pbnRlZ3JhdGlvb" + 27 | "i9zcHJpbmcvbmV0dHkvc3VwcG9ydC9pbnZva2UvT25Db25uZWN0TWV0aG9kSW52b2tlciRJbnZva2VyBwADAQAGPG" + 28 | "luaXQ+AQADKClWDAAFAAYKAAQABwEABHRoaXMBAF9Mb3JnL2tndXNhcm92L2ludGVncmF0aW9uL3NwcmluZy9uZXR" + 29 | "0eS9zdXBwb3J0L2ludm9rZS9EeW5hbWljSW52b2tlciQkR2VuZXJhdGVkQ2xhc3MkJDEwMDAwMDA1OwEABkhBTkRM" + 30 | "RQEAH0xqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZTsBAAg8Y2xpbml0PgEAWW9yZy5rZ3VzYXJvdi5pbnRlZ" + 31 | "3JhdGlvbi5zcHJpbmcubmV0dHkub25jb25uZWN0LmhhbmRsZXJzLlRyYW5zYWN0aW9uYWxPbkNvbm5lY3RDb250cm" + 32 | "9sbGVyCAAOAQAJb25Db25uZWN0CAAQAwAAAAABAA9qYXZhL2xhbmcvQ2xhc3MHABMBAEhvcmcva2d1c2Fyb3YvaW5" + 33 | "0ZWdyYXRpb24vc3ByaW5nL25ldHR5L3N1cHBvcnQvaW52b2tlL01ldGhvZEhhbmRsZUNyZWF0b3IHABUBAA9jcmVh" + 34 | "dGVVbml2ZXJzYWwBAFcoTGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc" + 35 | "3M7KUxqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZTsMABcAGAoAFgAZDAALAAwJAAIAGwEADWludm9rZUhhbm" + 36 | "RsZXIBAEUoTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbDtMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckN" + 37 | "vbnRleHQ7KVYBAARiZWFuAQASTGphdmEvbGFuZy9PYmplY3Q7DAAfACAJAAIAIQEAHWphdmEvbGFuZy9pbnZva2Uv" + 38 | "TWV0aG9kSGFuZGxlBwAjAQALaW52b2tlRXhhY3QBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZ" + 39 | "WN0OwwAJQAmCgAkACcBAARhcmcwAQAaTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbDsBAARhcmcxAQAoTGlvL25ldH" + 40 | "R5L2NoYW5uZWwvQ2hhbm5lbEhhbmRsZXJDb250ZXh0OwEABENvZGUBABJMb2NhbFZhcmlhYmxlVGFibGUAIQACAAQ" + 41 | "AAAABABoACwAMAAAAAwABAAUABgABAC0AAAAjAAEAAQAAAAUqtwAIsQAAAAEALgAAAAwAAQAAAAUACQAKAAAACAAN" + 42 | "AAYAAQAtAAAAHAADAAAAAAAQEg8SERISvQAUuAAaswAcsQAAAAAAAQAdAB4AAQAtAAAAPwACAAMAAAANsgAcGQC0A" + 43 | "CK2AChXsQAAAAEALgAAACAAAwAAAA0ACQAKAAAAAAANACkAKgABAAAADQArACwAAgAA"); 44 | 45 | final Class clazz = cl.load(bytes, 46 | "org.kgusarov.integration.spring.netty.support.invoke.DynamicInvoker$$GeneratedClass$$10000005"); 47 | assertNotNull(clazz); 48 | 49 | final Field handle = clazz.getDeclaredField("HANDLE"); 50 | assertNotNull(handle); 51 | } 52 | } -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/multiple/MultipleServersIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.multiple; 2 | 3 | import com.google.common.util.concurrent.SettableFuture; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufUtil; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import io.netty.util.CharsetUtil; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.kgusarov.integration.spring.netty.ServerClient; 12 | import org.kgusarov.integration.spring.netty.configuration.NettyServers; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootContextLoader; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.test.annotation.DirtiesContext; 17 | import org.springframework.test.context.ActiveProfiles; 18 | import org.springframework.test.context.ContextConfiguration; 19 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 20 | 21 | import java.util.concurrent.ExecutionException; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.concurrent.TimeoutException; 24 | 25 | import static io.netty.buffer.Unpooled.copiedBuffer; 26 | import static org.hamcrest.Matchers.hasSize; 27 | import static org.junit.Assert.assertEquals; 28 | import static org.junit.Assert.assertThat; 29 | 30 | @ActiveProfiles("multiple") 31 | @SpringBootTest 32 | @ContextConfiguration(classes = MultipleServersApplication.class, loader = SpringBootContextLoader.class) 33 | @RunWith(SpringJUnit4ClassRunner.class) 34 | public class MultipleServersIntegrationTest { 35 | @Autowired 36 | private NettyServers servers; 37 | 38 | @Test 39 | @DirtiesContext 40 | public void testServersShouldBePresent() { 41 | assertThat(servers, hasSize(2)); 42 | } 43 | 44 | @Test 45 | @DirtiesContext 46 | public void testMultipleServersWork() throws Exception { 47 | final String s1 = connectAndGetResponse(40000); 48 | final String s2 = connectAndGetResponse(40001); 49 | 50 | assertEquals("Hello, world!", s1); 51 | assertEquals("48656c6c6f2c20776f726c6421", s2); 52 | } 53 | 54 | private String connectAndGetResponse(final int port) throws InterruptedException, TimeoutException, ExecutionException { 55 | final SettableFuture strFuture = SettableFuture.create(); 56 | final ServerClient client = new ServerClient(port, "localhost", new ClientHandler(strFuture)); 57 | final ByteBuf request = copiedBuffer("Hello, world!", CharsetUtil.UTF_8); 58 | 59 | client.connect().get().writeAndFlush(request).syncUninterruptibly(); 60 | final String response = strFuture.get(30, TimeUnit.SECONDS); 61 | client.disconnect(); 62 | 63 | return response; 64 | } 65 | 66 | private static class ClientHandler extends ChannelInboundHandlerAdapter { 67 | private final SettableFuture strFuture; 68 | 69 | private ClientHandler(final SettableFuture strFuture) { 70 | this.strFuture = strFuture; 71 | } 72 | 73 | @Override 74 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) { 75 | final ByteBuf byteBuf = (ByteBuf) msg; 76 | final byte[] bytes = ByteBufUtil.getBytes(byteBuf); 77 | final String str = new String(bytes, CharsetUtil.UTF_8); 78 | 79 | strFuture.set(str); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/org/kgusarov/integration/spring/netty/handlers/FlashPolicyHandlerTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.handlers; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelFutureListener; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelPipeline; 8 | import io.netty.util.CharsetUtil; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.mockito.Mock; 13 | import org.mockito.junit.MockitoJUnitRunner; 14 | 15 | import java.util.Arrays; 16 | 17 | import static io.netty.buffer.Unpooled.copiedBuffer; 18 | import static org.kgusarov.integration.spring.netty.handlers.FlashPolicyHandler.POLICY_FILE_REQUEST; 19 | import static org.kgusarov.integration.spring.netty.handlers.FlashPolicyHandler.POLICY_FILE_RESPONSE; 20 | import static org.mockito.Mockito.*; 21 | 22 | @RunWith(MockitoJUnitRunner.class) 23 | public class FlashPolicyHandlerTest { 24 | @Mock 25 | private ChannelHandlerContext ctx; 26 | 27 | @Mock 28 | private ChannelFuture channelFuture; 29 | 30 | @Mock 31 | private ChannelPipeline channelPipeline; 32 | 33 | private final FlashPolicyHandler handler = new FlashPolicyHandler(); 34 | 35 | @Before 36 | public void setUp() { 37 | when(channelFuture.addListener(ChannelFutureListener.CLOSE)).thenReturn(channelFuture); 38 | when(ctx.writeAndFlush(any(ByteBuf.class))).thenReturn(channelFuture); 39 | when(ctx.pipeline()).thenReturn(channelPipeline); 40 | when(ctx.fireChannelRead(any())).thenReturn(ctx); 41 | when(channelPipeline.remove(handler)).thenReturn(channelPipeline); 42 | } 43 | 44 | @Test 45 | public void testPolicyIsSentAndConnectionIsClosed() throws Exception { 46 | final ByteBuf request = copiedBuffer(POLICY_FILE_REQUEST, CharsetUtil.UTF_8); 47 | final ByteBuf expectedResponse = copiedBuffer(copiedBuffer(POLICY_FILE_RESPONSE, CharsetUtil.UTF_8)); 48 | handler.channelRead(ctx, request); 49 | 50 | verifyNoInteractions(channelPipeline); 51 | verify(ctx, times(1)).writeAndFlush( 52 | argThat(arg -> { 53 | final ByteBuf bb = (ByteBuf) arg; 54 | final byte[] bytesSent = bb.array(); 55 | final byte[] bytesExpected = expectedResponse.array(); 56 | 57 | return Arrays.equals(bytesExpected, bytesSent); 58 | }) 59 | ); 60 | verify(channelFuture, times(1)).addListener(ChannelFutureListener.CLOSE); 61 | verify(ctx, times(0)).fireChannelRead(any(ByteBuf.class)); 62 | } 63 | 64 | @Test 65 | public void testHandlerIsRemovedIfNonPolicyRequestComesIn() throws Exception { 66 | final ByteBuf request = copiedBuffer("just some random stuff", CharsetUtil.UTF_8); 67 | handler.channelRead(ctx, request); 68 | 69 | verify(channelPipeline, times(1)).remove(handler); 70 | verify(ctx, times(0)).writeAndFlush(any(ByteBuf.class)); 71 | verify(channelFuture, times(0)).addListener(ChannelFutureListener.CLOSE); 72 | verify(ctx, times(1)).fireChannelRead(any(ByteBuf.class)); 73 | } 74 | 75 | @Test 76 | public void testNonByteBufMessageIsIgnored() throws Exception { 77 | handler.channelRead(ctx, null); 78 | 79 | verifyNoInteractions(channelPipeline); 80 | verify(ctx, times(0)).writeAndFlush(any(ByteBuf.class)); 81 | verify(channelFuture, times(0)).addListener(ChannelFutureListener.CLOSE); 82 | verify(ctx, times(1)).fireChannelRead(null); 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/SpringChannelHandler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support; 2 | 3 | import com.google.common.collect.ArrayListMultimap; 4 | import com.google.common.collect.ImmutableListMultimap; 5 | import com.google.common.collect.ListMultimap; 6 | import com.google.common.collect.Multimap; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.ChannelInboundHandlerAdapter; 9 | import org.kgusarov.integration.spring.netty.support.invoke.OnConnectMethodInvoker; 10 | import org.kgusarov.integration.spring.netty.support.invoke.OnMessageMethodInvoker; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.util.List; 15 | import java.util.Objects; 16 | import java.util.Set; 17 | import java.util.stream.Collectors; 18 | 19 | /** 20 | * Handler that is part of internal API and is used to invoke appropriate 21 | * {@link org.kgusarov.integration.spring.netty.annotations.NettyController} 22 | * annotated methods 23 | */ 24 | @SuppressWarnings("CodeBlock2Expr") 25 | public class SpringChannelHandler extends ChannelInboundHandlerAdapter{ 26 | private static final Logger LOGGER = LoggerFactory.getLogger(SpringChannelHandler.class); 27 | 28 | private final List onConnectCallbacks; 29 | private final List onMessageCallbacks; 30 | 31 | private final ListMultimap, OnMessageMethodInvoker> typedCallbackMap; 32 | private final Set> typedCallbackClasses; 33 | 34 | public SpringChannelHandler(final List onConnectCallbacks, 35 | final List onMessageCallbacks) { 36 | 37 | this.onConnectCallbacks = onConnectCallbacks; 38 | this.onMessageCallbacks = onMessageCallbacks; 39 | 40 | final Multimap, OnMessageMethodInvoker> onMessageInvokers = ArrayListMultimap.create(); 41 | final Set> messageBodyTypes = onMessageCallbacks.stream() 42 | .map(OnMessageMethodInvoker::getMessageBodyType) 43 | .filter(Objects::nonNull) 44 | .collect(Collectors.toSet()); 45 | 46 | messageBodyTypes.forEach(mbt -> { 47 | onMessageCallbacks.forEach(invoker -> { 48 | final Class messageBodyType = invoker.getMessageBodyType(); 49 | if ((messageBodyType == null) || mbt.isAssignableFrom(messageBodyType)) { 50 | onMessageInvokers.put(mbt, invoker); 51 | } 52 | }); 53 | }); 54 | 55 | typedCallbackMap = ImmutableListMultimap.copyOf(onMessageInvokers); 56 | typedCallbackClasses = typedCallbackMap.keySet(); 57 | } 58 | 59 | @Override 60 | public void channelActive(final ChannelHandlerContext ctx) throws Exception { 61 | onConnectCallbacks.forEach(cb -> { 62 | cb.channelActive(ctx); 63 | }); 64 | 65 | super.channelActive(ctx); 66 | } 67 | 68 | @Override 69 | @SuppressWarnings("NestedMethodCall") 70 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { 71 | final Class messageClass = msg.getClass(); 72 | final List callbacks = typedCallbackClasses.stream() 73 | .filter(clazz -> clazz.isAssignableFrom(messageClass)) 74 | .findFirst() 75 | .map(typedCallbackMap::get) 76 | .orElse(onMessageCallbacks); 77 | 78 | final boolean processed = callbacks.stream() 79 | .map(cb -> cb.channelRead(ctx, msg)) 80 | .reduce(false, (a, b) -> a || b); 81 | 82 | if (!processed) { 83 | LOGGER.warn("Message " + msg + " was not processed by any handler"); 84 | } 85 | 86 | super.channelRead(ctx, msg); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/ChannelOptions.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty; 2 | 3 | import com.google.common.collect.Maps; 4 | import io.netty.buffer.ByteBufAllocator; 5 | import io.netty.channel.ChannelOption; 6 | import io.netty.channel.RecvByteBufAllocator; 7 | import io.netty.channel.WriteBufferWaterMark; 8 | 9 | import java.net.InetAddress; 10 | import java.net.NetworkInterface; 11 | import java.util.Map; 12 | import java.util.function.Supplier; 13 | 14 | /** 15 | * A typesafe configuration for {@code io.netty.channel.ChannelOption} options 16 | */ 17 | @SuppressWarnings({"rawtypes", "SameParameterValue", "WeakerAccess"}) 18 | public final class ChannelOptions implements Supplier> { 19 | private final Map options = Maps.newHashMap(); 20 | 21 | public void setAllocator(final ByteBufAllocator allocator) { 22 | options.put(ChannelOption.ALLOCATOR, allocator); 23 | } 24 | 25 | public void setRecvBufAllocator(final RecvByteBufAllocator allocator) { 26 | options.put(ChannelOption.RCVBUF_ALLOCATOR, allocator); 27 | } 28 | 29 | public void setConnectTimeout(final int milliseconds) { 30 | options.put(ChannelOption.CONNECT_TIMEOUT_MILLIS, milliseconds); 31 | } 32 | 33 | public void setWriteSpinCount(final int count) { 34 | options.put(ChannelOption.WRITE_SPIN_COUNT, count); 35 | } 36 | 37 | public void setWriteBufferWaterMark(final WriteBufferWaterMark mark) { 38 | options.put(ChannelOption.WRITE_BUFFER_WATER_MARK, mark); 39 | } 40 | 41 | public void setAllowHalfClosure(final boolean allow) { 42 | options.put(ChannelOption.ALLOW_HALF_CLOSURE, allow); 43 | } 44 | 45 | public void setAutoRead(final boolean autoRead) { 46 | options.put(ChannelOption.AUTO_READ, autoRead); 47 | } 48 | 49 | public void setSoBroadcast(final boolean broadcast) { 50 | options.put(ChannelOption.SO_BROADCAST, broadcast); 51 | } 52 | 53 | public void setSoKeepAlive(final boolean keepAlive) { 54 | options.put(ChannelOption.SO_KEEPALIVE, keepAlive); 55 | } 56 | 57 | public void setSoSndBuf(final int buf) { 58 | options.put(ChannelOption.SO_SNDBUF, buf); 59 | } 60 | 61 | public void setSoRcvBuf(final int buf) { 62 | options.put(ChannelOption.SO_RCVBUF, buf); 63 | } 64 | 65 | public void setSoReuseAddr(final boolean reuseAddr) { 66 | options.put(ChannelOption.SO_REUSEADDR, reuseAddr); 67 | } 68 | 69 | public void setSoLinger(final int linger) { 70 | options.put(ChannelOption.SO_LINGER, linger); 71 | } 72 | 73 | public void setSoBacklog(final int backlog) { 74 | options.put(ChannelOption.SO_BACKLOG, backlog); 75 | } 76 | 77 | public void setSoTimeout(final int timeout) { 78 | options.put(ChannelOption.SO_TIMEOUT, timeout); 79 | } 80 | 81 | public void setIpTos(final int tos) { 82 | options.put(ChannelOption.IP_TOS, tos); 83 | } 84 | 85 | public void setIpMulticastAddr(final InetAddress addr) { 86 | options.put(ChannelOption.IP_MULTICAST_ADDR, addr); 87 | } 88 | 89 | public void setIpMulticastIf(final NetworkInterface iface) { 90 | options.put(ChannelOption.IP_MULTICAST_IF, iface); 91 | } 92 | 93 | public void setIpMulticastTtl(final int ttl) { 94 | options.put(ChannelOption.IP_MULTICAST_TTL, ttl); 95 | } 96 | 97 | public void setIpMulticastLoopDisabled(final boolean loopDisabled) { 98 | options.put(ChannelOption.IP_MULTICAST_LOOP_DISABLED, loopDisabled); 99 | } 100 | 101 | public void setTcpNodelay(final boolean noDelay) { 102 | options.put(ChannelOption.TCP_NODELAY, noDelay); 103 | } 104 | 105 | @Override 106 | public Map get() { 107 | return options; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/assembler/Descriptors.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke.assembler; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelOutboundInvoker; 7 | import org.kgusarov.integration.spring.netty.support.invoke.MethodHandleCreator; 8 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnConnectParameterResolver; 9 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnDisconnectParameterResolver; 10 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnMessageParameterResolver; 11 | import org.objectweb.asm.Type; 12 | 13 | import java.lang.invoke.MethodHandle; 14 | import java.lang.reflect.Method; 15 | 16 | /** 17 | * Internal API: code generation support - descriptors and other string constants 18 | */ 19 | @SuppressWarnings("WeakerAccess") 20 | public final class Descriptors { 21 | public static final String MHC_INTERNAL_NAME = Type.getType(MethodHandleCreator.class).getInternalName(); 22 | public static final String CHANNEL_INTERNAL_NAME = Type.getType(Channel.class).getInternalName(); 23 | 24 | public static final String MH_DESCRIPTOR = Type.getDescriptor(MethodHandle.class); 25 | public static final String MH_INTERNAL_NAME = Type.getType(MethodHandle.class).getInternalName(); 26 | 27 | public static final String CL_DESCRIPTOR = Type.getDescriptor(Class.class); 28 | public static final String CLA_DESCRIPTOR = Type.getDescriptor(Class[].class); 29 | public static final String STR_DESCRIPTOR = Type.getDescriptor(String.class); 30 | public static final String OBJ_DESCRIPTOR = Type.getDescriptor(Object.class); 31 | 32 | public static final String ONC_RESA_DESCRIPTOR = Type.getDescriptor(NettyOnConnectParameterResolver[].class); 33 | public static final String ONC_RES_INTERNAL_NAME = Type.getType(NettyOnConnectParameterResolver.class).getInternalName(); 34 | public static final String ONC_RESOLVE_DESCRIPTOR; 35 | 36 | public static final String OND_RESA_DESCRIPTOR = Type.getDescriptor(NettyOnDisconnectParameterResolver[].class); 37 | public static final String OND_RES_INTERNAL_NAME = Type.getType(NettyOnDisconnectParameterResolver.class).getInternalName(); 38 | public static final String OND_RESOLVE_DESCRIPTOR; 39 | 40 | public static final String ONM_RESA_DESCRIPTOR = Type.getDescriptor(NettyOnMessageParameterResolver[].class); 41 | public static final String ONM_RES_INTERNAL_NAME = Type.getType(NettyOnMessageParameterResolver.class).getInternalName(); 42 | public static final String ONM_RESOLVE_DESCRIPTOR; 43 | 44 | public static final String CHANNEL_WRITE_AND_FLUSH_DESCRIPTOR; 45 | 46 | static { 47 | try { 48 | final Method oncResolve = NettyOnConnectParameterResolver.class 49 | .getDeclaredMethod("resolve", ChannelHandlerContext.class); 50 | ONC_RESOLVE_DESCRIPTOR = Type.getMethodDescriptor(oncResolve); 51 | 52 | final Method ondResolve = NettyOnDisconnectParameterResolver.class 53 | .getDeclaredMethod("resolve", ChannelFuture.class); 54 | OND_RESOLVE_DESCRIPTOR = Type.getMethodDescriptor(ondResolve); 55 | 56 | final Method onmResolve = NettyOnMessageParameterResolver.class 57 | .getDeclaredMethod("resolve", ChannelHandlerContext.class, Object.class); 58 | ONM_RESOLVE_DESCRIPTOR = Type.getMethodDescriptor(onmResolve); 59 | 60 | final Method writeAndFlush = ChannelOutboundInvoker.class 61 | .getDeclaredMethod("writeAndFlush", Object.class); 62 | CHANNEL_WRITE_AND_FLUSH_DESCRIPTOR = Type.getMethodDescriptor(writeAndFlush); 63 | } catch (final NoSuchMethodException e) { 64 | throw new IllegalStateException(e); 65 | } 66 | } 67 | 68 | private Descriptors() { 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/assembler/InvokerMethodAssembler.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke.assembler; 2 | 3 | import io.netty.channel.Channel; 4 | import org.objectweb.asm.ClassWriter; 5 | import org.objectweb.asm.Label; 6 | import org.objectweb.asm.MethodVisitor; 7 | import org.objectweb.asm.Type; 8 | 9 | import java.lang.reflect.Method; 10 | import java.lang.reflect.Parameter; 11 | import java.util.Arrays; 12 | import java.util.stream.IntStream; 13 | 14 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.Descriptors.*; 15 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.LabelAssembler.createLabel; 16 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.LocalVariableAssembler.*; 17 | import static org.objectweb.asm.Opcodes.*; 18 | 19 | /** 20 | * Internal API: code generation support - invoker method 21 | */ 22 | public final class InvokerMethodAssembler { 23 | private InvokerMethodAssembler() { 24 | } 25 | 26 | public static void assembleInvokerMethod(final ClassWriter cw, final String invokerDescriptor, final String invokerInternalName, 27 | final Method invokerMethod, final Method targetMethod, final boolean sendResult, 28 | final MethodPrefixAssembler prefixAssembler) { 29 | 30 | final String methodDescriptor = Type.getMethodDescriptor(invokerMethod); 31 | final String methodName = invokerMethod.getName(); 32 | 33 | final MethodVisitor m = cw.visitMethod(ACC_PUBLIC, methodName, methodDescriptor, null, null); 34 | final Label ms = createLabel(); 35 | final Label me = createLabel(); 36 | 37 | m.visitCode(); 38 | m.visitLabel(ms); 39 | 40 | final Parameter[] invokeParameters = invokerMethod.getParameters(); 41 | final int resultIdx = getResultIdx(invokeParameters); 42 | final int firstVarIdx = getFirstVarIdx(invokeParameters, sendResult); 43 | prefixAssembler.assemble(invokerInternalName, m, firstVarIdx); 44 | 45 | m.visitFieldInsn(GETSTATIC, invokerInternalName, "HANDLE", MH_DESCRIPTOR); 46 | m.visitIntInsn(ALOAD, 0); 47 | m.visitFieldInsn(GETFIELD, invokerInternalName, "bean", OBJ_DESCRIPTOR); 48 | 49 | final Parameter[] targetMethodParameters = targetMethod.getParameters(); 50 | for (int i = 0; i < targetMethodParameters.length; i++) { 51 | m.visitIntInsn(ALOAD, i + firstVarIdx); 52 | } 53 | 54 | final String[] paramDescriptors = new String[targetMethodParameters.length + 1]; 55 | Arrays.fill(paramDescriptors, OBJ_DESCRIPTOR); 56 | 57 | final String tmd = '(' + String.join("", paramDescriptors) + ')' + OBJ_DESCRIPTOR; 58 | m.visitMethodInsn(INVOKEVIRTUAL, MH_INTERNAL_NAME, "invokeExact", tmd, false); 59 | 60 | if (sendResult) { 61 | final int channelArgIdx = IntStream.range(0, invokeParameters.length) 62 | .filter(i -> { 63 | final Parameter parameter = invokeParameters[i]; 64 | final Class type = parameter.getType(); 65 | return Channel.class.isAssignableFrom(type); 66 | }) 67 | .findFirst() 68 | .orElse(-2) + 1; 69 | 70 | m.visitIntInsn(ASTORE, resultIdx); 71 | m.visitIntInsn(ALOAD, channelArgIdx); 72 | m.visitIntInsn(ALOAD, resultIdx); 73 | 74 | m.visitMethodInsn(INVOKEINTERFACE, CHANNEL_INTERNAL_NAME, "writeAndFlush", 75 | CHANNEL_WRITE_AND_FLUSH_DESCRIPTOR, true); 76 | } 77 | 78 | m.visitInsn(POP); 79 | m.visitInsn(RETURN); 80 | 81 | m.visitLabel(me); 82 | assembleLocalVariables(m, ms, me, invokerDescriptor, invokeParameters, targetMethodParameters, sendResult); 83 | 84 | m.visitEnd(); 85 | m.visitMaxs(0, 0); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onconnect/OnConnectIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onconnect; 2 | 3 | import com.google.common.util.concurrent.Futures; 4 | import com.google.common.util.concurrent.SettableFuture; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.kgusarov.integration.spring.netty.ServerClient; 8 | import org.kgusarov.integration.spring.netty.configuration.NettyServers; 9 | import org.kgusarov.integration.spring.netty.etc.ClientHandler; 10 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 11 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 12 | import org.kgusarov.integration.spring.netty.onconnect.handlers.TransactionalOnConnectController; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootContextLoader; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.test.annotation.DirtiesContext; 17 | import org.springframework.test.context.ActiveProfiles; 18 | import org.springframework.test.context.ContextConfiguration; 19 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 20 | 21 | import java.util.concurrent.ExecutionException; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.concurrent.TimeoutException; 24 | 25 | import static org.hamcrest.Matchers.*; 26 | import static org.junit.Assert.assertEquals; 27 | import static org.junit.Assert.assertThat; 28 | 29 | @ActiveProfiles("onconnect") 30 | @SpringBootTest 31 | @ContextConfiguration(classes = { 32 | OnConnectApplication.class, 33 | HandlerMethodCalls.class 34 | }, loader = SpringBootContextLoader.class) 35 | @RunWith(SpringJUnit4ClassRunner.class) 36 | public class OnConnectIntegrationTest { 37 | @Autowired 38 | private NettyServers servers; 39 | 40 | @Autowired 41 | private HandlerMethodCalls calls; 42 | 43 | @Autowired 44 | private ProcessingCounter counter; 45 | 46 | @Test 47 | @DirtiesContext 48 | public void testServersShouldBePresent() { 49 | assertThat(servers, not(hasSize(0))); 50 | } 51 | 52 | @Test 53 | @DirtiesContext 54 | public void testHandlersAddedInCorrectOrder() throws Exception { 55 | final SettableFuture r1 = SettableFuture.create(); 56 | final SettableFuture r2 = SettableFuture.create(); 57 | final SettableFuture r3 = SettableFuture.create(); 58 | final SettableFuture r4 = SettableFuture.create(); 59 | final SettableFuture r5 = SettableFuture.create(); 60 | final SettableFuture r6 = SettableFuture.create(); 61 | 62 | final ClientHandler ch = new ClientHandler(r1, r2, r3, r4, r5, r6); 63 | final ServerClient client = new ServerClient(40000, "localhost", ch); 64 | 65 | doConnectionCycle(r1, r2, r3, client, 0); 66 | doConnectionCycle(r4, r5, r6, client, 1); 67 | 68 | assertThat(calls, contains( 69 | is(OnConnectController.ON_CONNECT1), 70 | is(OnConnectController.ON_CONNECT2), 71 | is(TransactionalOnConnectController.ON_CONNECT), 72 | is(OnConnectController.ON_CONNECT1), 73 | is(OnConnectController.ON_CONNECT2), 74 | is(TransactionalOnConnectController.ON_CONNECT) 75 | )); 76 | } 77 | 78 | private void doConnectionCycle(final SettableFuture f1, final SettableFuture f2, 79 | final SettableFuture f3, final ServerClient client, final int phase) 80 | throws InterruptedException, ExecutionException, TimeoutException { 81 | 82 | client.connect().get(); 83 | counter.awaitAdvanceInterruptibly(phase, 30, TimeUnit.SECONDS); 84 | Futures.successfulAsList(f1, f2, f3).get(30, TimeUnit.SECONDS); 85 | 86 | assertEquals(92L, (long) f1.get()); 87 | assertEquals(87L, (long) f2.get()); 88 | assertEquals(106L, (long) f3.get()); 89 | 90 | client.disconnect(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/kgusarov/integration/spring/netty/support/invoke/OnMessageMethodInvoker.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.support.invoke; 2 | 3 | import com.google.common.primitives.Primitives; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import org.kgusarov.integration.spring.netty.annotations.NettyMessageBody; 7 | import org.kgusarov.integration.spring.netty.support.resolvers.NettyOnMessageParameterResolver; 8 | import org.springframework.core.MethodParameter; 9 | 10 | import java.lang.reflect.Method; 11 | import java.lang.reflect.Parameter; 12 | import java.util.List; 13 | import java.util.Optional; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.IntStream; 16 | 17 | import static org.kgusarov.integration.spring.netty.support.invoke.InvokerMethods.ONM_INVOKE_HANDLER; 18 | import static org.kgusarov.integration.spring.netty.support.invoke.assembler.Descriptors.*; 19 | import static org.objectweb.asm.Opcodes.*; 20 | 21 | /** 22 | * Internal API: invocation support for {@link org.kgusarov.integration.spring.netty.annotations.NettyOnMessage} 23 | */ 24 | public final class OnMessageMethodInvoker extends AbstractMethodInvoker { 25 | @SuppressWarnings("AbstractClassNeverImplemented") 26 | abstract static class Invoker extends InvokerBase { 27 | NettyOnMessageParameterResolver[] resolvers; 28 | 29 | abstract void invokeHandler(final Channel channel, final ChannelHandlerContext ctx, final Object msg); 30 | } 31 | 32 | private final Invoker invoker; 33 | private final Class messageBodyType; 34 | 35 | @SuppressWarnings("NestedMethodCall") 36 | public OnMessageMethodInvoker(final Object bean, final Method method, 37 | final List parameterResolvers, 38 | final boolean sendResult) { 39 | 40 | final int parameterCount = method.getParameterCount(); 41 | 42 | //noinspection NestedMethodCall 43 | final List messageBodyArgs = IntStream.range(0, parameterCount) 44 | .mapToObj(i -> new MethodParameter(method, i)) 45 | .filter(mp -> mp.hasParameterAnnotation(NettyMessageBody.class)) 46 | .collect(Collectors.toList()); 47 | 48 | if (messageBodyArgs.size() > 1) { 49 | throw new IllegalArgumentException(method + " has more than one NettyMessageBody annotated parameters"); 50 | } 51 | 52 | messageBodyType = Optional.ofNullable( 53 | messageBodyArgs.size() == 1 ? messageBodyArgs.get(0).getParameterType() : null 54 | ) 55 | .map(c -> c.isPrimitive() ? Primitives.wrap(c) : c) 56 | .orElse(null); 57 | 58 | invoker = buildInvoker(Invoker.class, method, ONM_INVOKE_HANDLER, sendResult, (invokerInternalName, m, firstVarIdx) -> { 59 | final Parameter[] parameters = method.getParameters(); 60 | for (int i = 0; i < parameters.length; i++) { 61 | m.visitIntInsn(ALOAD, 0); 62 | m.visitFieldInsn(GETFIELD, invokerInternalName, "resolvers", ONM_RESA_DESCRIPTOR); 63 | m.visitIntInsn(BIPUSH, i); 64 | m.visitInsn(AALOAD); 65 | m.visitIntInsn(ALOAD, 2); 66 | m.visitIntInsn(ALOAD, 3); 67 | m.visitMethodInsn(INVOKEINTERFACE, ONM_RES_INTERNAL_NAME, "resolve", ONM_RESOLVE_DESCRIPTOR, true); 68 | m.visitIntInsn(ASTORE, firstVarIdx + i); 69 | } 70 | }); 71 | 72 | invoker.bean = bean; 73 | invoker.resolvers = parameterResolvers.toArray(new NettyOnMessageParameterResolver[0]); 74 | } 75 | 76 | public Class getMessageBodyType() { 77 | return messageBodyType; 78 | } 79 | 80 | @SuppressWarnings("NestedMethodCall") 81 | public boolean channelRead(final ChannelHandlerContext ctx, final Object msg) { 82 | if ((messageBodyType != null) && !messageBodyType.isAssignableFrom(msg.getClass())) { 83 | return false; 84 | } 85 | 86 | final Channel channel = ctx.channel(); 87 | invoker.invokeHandler(channel, ctx, msg); 88 | return true; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/customresolvers/CustomResolversIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.customresolvers; 2 | 3 | import com.google.common.util.concurrent.Futures; 4 | import com.google.common.util.concurrent.SettableFuture; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.Unpooled; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.ChannelInboundHandlerAdapter; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.kgusarov.integration.spring.netty.ServerClient; 12 | import org.kgusarov.integration.spring.netty.configuration.NettyServers; 13 | import org.kgusarov.integration.spring.netty.customresolvers.resolvers.RNG; 14 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.context.SpringBootContextLoader; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | import org.springframework.test.annotation.DirtiesContext; 19 | import org.springframework.test.context.ActiveProfiles; 20 | import org.springframework.test.context.ContextConfiguration; 21 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 22 | 23 | import java.util.concurrent.ExecutionException; 24 | import java.util.concurrent.TimeUnit; 25 | import java.util.concurrent.TimeoutException; 26 | 27 | import static org.hamcrest.Matchers.hasSize; 28 | import static org.hamcrest.Matchers.not; 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.assertThat; 31 | 32 | @ActiveProfiles("onmessage") 33 | @SpringBootTest 34 | @ContextConfiguration(classes = CustomResolversApplication.class, loader = SpringBootContextLoader.class) 35 | @RunWith(SpringJUnit4ClassRunner.class) 36 | public class CustomResolversIntegrationTest { 37 | @Autowired 38 | private NettyServers servers; 39 | 40 | @Autowired 41 | private ProcessingCounter counter; 42 | 43 | @Autowired 44 | private RNG rng; 45 | 46 | @Test 47 | @DirtiesContext 48 | public void testServersShouldBePresent() { 49 | assertThat(servers, not(hasSize(0))); 50 | } 51 | 52 | @Test 53 | @DirtiesContext 54 | public void testMessageHandlersWork() throws Exception { 55 | runTestOnce(0); 56 | runTestOnce(1); 57 | } 58 | 59 | private void runTestOnce(final int phase) throws InterruptedException, TimeoutException, ExecutionException { 60 | final SettableFuture firstRN = SettableFuture.create(); 61 | final SettableFuture secondRN = SettableFuture.create(); 62 | 63 | final int serverPort = servers.get(0).getBoundToPort(); 64 | 65 | rng.getGeneratedNumbers().clear(); 66 | 67 | final ServerClient client = new ServerClient( 68 | serverPort, 69 | "localhost", 70 | new ClientHandler(firstRN, secondRN) 71 | ); 72 | 73 | client.connect(); 74 | client.writeAndFlush(Unpooled.copyBoolean(true)).syncUninterruptibly(); 75 | Futures.successfulAsList(firstRN, secondRN).get(30, TimeUnit.SECONDS); 76 | client.disconnect(); 77 | 78 | counter.awaitAdvanceInterruptibly(phase, 30, TimeUnit.SECONDS); 79 | final Long l1 = firstRN.get(30, TimeUnit.SECONDS); 80 | final Long l2 = secondRN.get(30, TimeUnit.SECONDS); 81 | 82 | assertEquals(3, rng.getGeneratedNumbers().size()); 83 | assertEquals(l1, rng.getGeneratedNumbers().get(0)); 84 | assertEquals(l2, rng.getGeneratedNumbers().get(1)); 85 | } 86 | 87 | private static final class ClientHandler extends ChannelInboundHandlerAdapter { 88 | private final SettableFuture[] futures; 89 | int idx; 90 | 91 | @SafeVarargs 92 | private ClientHandler(final SettableFuture ...futures) { 93 | this.futures = futures; 94 | } 95 | 96 | @Override 97 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) { 98 | final ByteBuf bb = (ByteBuf) msg; 99 | 100 | while (bb.isReadable(8)) { 101 | futures[idx++].set(bb.readLong()); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/integration-test/java/org/kgusarov/integration/spring/netty/onmessage/OnMessageIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.kgusarov.integration.spring.netty.onmessage; 2 | 3 | import com.google.common.util.concurrent.SettableFuture; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.kgusarov.integration.spring.netty.ServerClient; 9 | import org.kgusarov.integration.spring.netty.configuration.NettyServers; 10 | import org.kgusarov.integration.spring.netty.etc.HandlerMethodCalls; 11 | import org.kgusarov.integration.spring.netty.etc.ProcessingCounter; 12 | import org.kgusarov.integration.spring.netty.onmessage.handlers.Decoder; 13 | import org.kgusarov.integration.spring.netty.onmessage.handlers.Encoder; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.context.SpringBootContextLoader; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.test.annotation.DirtiesContext; 18 | import org.springframework.test.context.ActiveProfiles; 19 | import org.springframework.test.context.ContextConfiguration; 20 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 21 | 22 | import java.util.concurrent.ExecutionException; 23 | import java.util.concurrent.TimeUnit; 24 | import java.util.concurrent.TimeoutException; 25 | 26 | import static org.hamcrest.Matchers.*; 27 | import static org.junit.Assert.assertEquals; 28 | import static org.junit.Assert.assertThat; 29 | import static org.kgusarov.integration.spring.netty.onmessage.handlers.Decoder.DECODE; 30 | import static org.kgusarov.integration.spring.netty.onmessage.handlers.Encoder.ENCODE; 31 | import static org.kgusarov.integration.spring.netty.onmessage.handlers.OnMessageController.*; 32 | import static org.kgusarov.integration.spring.netty.onmessage.handlers.TransactionalOnMessageController.TRANSACTIONAL_ON_MESSAGE; 33 | 34 | @ActiveProfiles("onmessage") 35 | @SpringBootTest 36 | @ContextConfiguration(classes = { 37 | OnMessageApplication.class, 38 | HandlerMethodCalls.class}, loader = SpringBootContextLoader.class) 39 | @RunWith(SpringJUnit4ClassRunner.class) 40 | public class OnMessageIntegrationTest { 41 | @Autowired 42 | private NettyServers servers; 43 | 44 | @Autowired 45 | private HandlerMethodCalls calls; 46 | 47 | @Autowired 48 | private ProcessingCounter counter; 49 | 50 | @Test 51 | @DirtiesContext 52 | public void testServersShouldBePresent() { 53 | assertThat(servers, not(hasSize(0))); 54 | } 55 | 56 | @Test 57 | @DirtiesContext 58 | public void testMessageHandlersWork() throws Exception { 59 | runTestOnce(0); 60 | runTestOnce(1); 61 | } 62 | 63 | private void runTestOnce(final int phase) throws InterruptedException, TimeoutException, ExecutionException { 64 | calls.clear(); 65 | 66 | final SettableFuture msgPlus1Future = SettableFuture.create(); 67 | final SettableFuture msgPlus2Future = SettableFuture.create(); 68 | final SettableFuture msgFuture = SettableFuture.create(); 69 | final SettableFuture l889Future1 = SettableFuture.create(); 70 | final SettableFuture l26576374Future1 = SettableFuture.create(); 71 | final SettableFuture helloWorldFuture = SettableFuture.create(); 72 | final SettableFuture l889Future2 = SettableFuture.create(); 73 | final SettableFuture l26576374Future2 = SettableFuture.create(); 74 | 75 | final int serverPort = servers.get(0).getBoundToPort(); 76 | 77 | final ServerClient client = new ServerClient( 78 | serverPort, 79 | "localhost", 80 | new Decoder(), 81 | new Encoder(), 82 | new ClientHandler(msgPlus1Future, msgPlus2Future, msgFuture, l889Future1, l26576374Future1, 83 | helloWorldFuture, l889Future2, l26576374Future2) 84 | ); 85 | 86 | client.connect(); 87 | 88 | client.writeAndFlush(100500L).syncUninterruptibly(); 89 | client.writeAndFlush("Hello, world!").syncUninterruptibly(); 90 | 91 | counter.awaitAdvanceInterruptibly(phase, 30, TimeUnit.SECONDS); 92 | assertEquals(100501L, msgPlus1Future.get(30, TimeUnit.SECONDS)); 93 | assertEquals(100502L, msgPlus2Future.get(30, TimeUnit.SECONDS)); 94 | assertEquals(100500L, msgFuture.get(30, TimeUnit.SECONDS)); 95 | assertEquals(889L, l889Future1.get(30, TimeUnit.SECONDS)); 96 | assertEquals(26576374L, l26576374Future1.get(30, TimeUnit.SECONDS)); 97 | assertEquals("Hello, world!", helloWorldFuture.get(30, TimeUnit.SECONDS)); 98 | assertEquals(889L, l889Future2.get(30, TimeUnit.SECONDS)); 99 | assertEquals(26576374L, l26576374Future2.get(30, TimeUnit.SECONDS)); 100 | 101 | client.disconnect(); 102 | 103 | assertThat(calls, contains( 104 | DECODE, 105 | ON_MESSAGE1, 106 | ENCODE, 107 | ENCODE, 108 | ON_MESSAGE2, 109 | ENCODE, 110 | ON_MESSAGE3, 111 | TRANSACTIONAL_ON_MESSAGE, 112 | ENCODE, 113 | ENCODE, 114 | 115 | DECODE, 116 | ON_STRING_MESSAGE, 117 | ENCODE, 118 | ON_MESSAGE3, 119 | TRANSACTIONAL_ON_MESSAGE, 120 | ENCODE, 121 | ENCODE 122 | )); 123 | } 124 | 125 | private static final class ClientHandler extends ChannelInboundHandlerAdapter { 126 | private final SettableFuture[] futures; 127 | int idx; 128 | 129 | @SafeVarargs 130 | private ClientHandler(final SettableFuture ...futures) { 131 | this.futures = futures; 132 | } 133 | 134 | @Override 135 | public void channelRead(final ChannelHandlerContext ctx, final Object msg) { 136 | futures[idx++].set(msg); 137 | } 138 | } 139 | } 140 | --------------------------------------------------------------------------------