├── .gitignore ├── LICENSE ├── README.md ├── articles ├── bitchat-overview │ └── bitchat-overview.md ├── drawios │ └── bitchat-overview.xml └── resources │ └── bitchat-overview │ ├── client-connect.jpg │ ├── client-reconnect.jpg │ ├── cluster-arch.jpg │ ├── list-user.jpg │ ├── login.jpg │ ├── received-p2p-msg.jpg │ ├── register.jpg │ ├── send-p2p-msg.jpg │ ├── server-startup.jpg │ └── stand-alone-arch.jpg ├── bitchat-client ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── bitchat │ └── client │ ├── Client.java │ ├── ClientFactory.java │ ├── ClientHandler.java │ ├── ClientInitializer.java │ ├── ClientMode.java │ ├── GenericClient.java │ ├── HealthyChecker.java │ └── SimpleClientFactory.java ├── bitchat-core ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── bitchat │ │ │ ├── core │ │ │ ├── IdleStateChecker.java │ │ │ ├── LoadBalancer.java │ │ │ ├── ServerAttr.java │ │ │ ├── executor │ │ │ │ ├── AbstractExecutor.java │ │ │ │ └── Executor.java │ │ │ ├── id │ │ │ │ ├── IdFactory.java │ │ │ │ ├── MemoryIdFactory.java │ │ │ │ └── SnowflakeIdFactory.java │ │ │ └── init │ │ │ │ ├── InitAble.java │ │ │ │ ├── InitOrder.java │ │ │ │ └── Initializer.java │ │ │ ├── http │ │ │ ├── ContentType.java │ │ │ ├── HttpContext.java │ │ │ ├── RenderType.java │ │ │ ├── RequestMethod.java │ │ │ ├── controller │ │ │ │ ├── Controller.java │ │ │ │ ├── ControllerProxy.java │ │ │ │ ├── Mapping.java │ │ │ │ ├── Param.java │ │ │ │ └── ProxyInvocation.java │ │ │ ├── converter │ │ │ │ ├── AbstractConverter.java │ │ │ │ ├── Converter.java │ │ │ │ ├── PrimitiveConverter.java │ │ │ │ └── PrimitiveTypeUtil.java │ │ │ ├── exception │ │ │ │ ├── InvocationException.java │ │ │ │ └── ValidationException.java │ │ │ ├── maker │ │ │ │ ├── DefaultHtmlMaker.java │ │ │ │ ├── HtmlMaker.java │ │ │ │ ├── HtmlMakerEnum.java │ │ │ │ └── HtmlMakerFactory.java │ │ │ ├── router │ │ │ │ ├── BadClientSilencer.java │ │ │ │ ├── MethodlessRouter.java │ │ │ │ ├── OrderlessRouter.java │ │ │ │ ├── PathPattern.java │ │ │ │ ├── RouteResult.java │ │ │ │ └── Router.java │ │ │ ├── util │ │ │ │ ├── HtmlContentUtil.java │ │ │ │ ├── HttpRenderUtil.java │ │ │ │ └── HttpRequestUtil.java │ │ │ └── view │ │ │ │ ├── HtmlKeyHolder.java │ │ │ │ ├── Page404.java │ │ │ │ ├── Page500.java │ │ │ │ ├── PageError.java │ │ │ │ └── PageIndex.java │ │ │ ├── lang │ │ │ ├── BeanMapper.java │ │ │ ├── config │ │ │ │ ├── BaseConfig.java │ │ │ │ ├── ConfigFactory.java │ │ │ │ ├── SnowflakeConfig.java │ │ │ │ └── ThreadPoolConfig.java │ │ │ ├── constants │ │ │ │ ├── ResultCode.java │ │ │ │ └── ServiceName.java │ │ │ └── util │ │ │ │ └── GenericsUtil.java │ │ │ ├── packet │ │ │ ├── Command.java │ │ │ ├── DefaultPacket.java │ │ │ ├── Packet.java │ │ │ ├── PacketType.java │ │ │ ├── Payload.java │ │ │ ├── PendingPackets.java │ │ │ ├── Request.java │ │ │ ├── codec │ │ │ │ ├── PacketCodec.java │ │ │ │ ├── PacketDecoder.java │ │ │ │ └── PacketEncoder.java │ │ │ ├── ctx │ │ │ │ ├── CommandProcessorContext.java │ │ │ │ └── RequestProcessorContext.java │ │ │ ├── factory │ │ │ │ ├── CommandFactory.java │ │ │ │ ├── PacketFactory.java │ │ │ │ ├── PayloadFactory.java │ │ │ │ └── RequestFactory.java │ │ │ ├── interceptor │ │ │ │ ├── Interceptor.java │ │ │ │ ├── InterceptorBuilder.java │ │ │ │ ├── InterceptorHandler.java │ │ │ │ └── InterceptorProvider.java │ │ │ └── processor │ │ │ │ ├── AbstractCommandProcessor.java │ │ │ │ ├── AbstractRequestProcessor.java │ │ │ │ ├── CommandProcessor.java │ │ │ │ ├── Processor.java │ │ │ │ └── RequestProcessor.java │ │ │ ├── serialize │ │ │ ├── DefaultSerializerChooser.java │ │ │ ├── FastJsonSerializer.java │ │ │ ├── HessianSerializer.java │ │ │ ├── JdkSerializer.java │ │ │ ├── KryoSerializer.java │ │ │ ├── ProtoStuffSerializer.java │ │ │ ├── SerializeAlgorithm.java │ │ │ ├── Serializer.java │ │ │ └── SerializerChooser.java │ │ │ └── ws │ │ │ ├── Frame.java │ │ │ ├── FrameFactory.java │ │ │ ├── PendingFrames.java │ │ │ └── codec │ │ │ └── FrameCodec.java │ └── resources │ │ ├── config │ │ ├── base-config.properties │ │ ├── snowflake-config.properties │ │ └── thread-pool-config.properties │ │ └── logback.xml │ └── test │ └── java │ └── io │ └── bitchat │ ├── core │ └── IdTest.java │ ├── lang │ └── config │ │ └── ConfigTest.java │ └── serialize │ └── SerializeTest.java ├── bitchat-router ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── bitchat │ └── router │ ├── RouterServer.java │ └── RouterServerAttr.java ├── bitchat-server ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── bitchat │ └── server │ ├── AbstractServer.java │ ├── ClusterServer.java │ ├── HeartBeatProcessor.java │ ├── ProtocolDispatcher.java │ ├── Server.java │ ├── ServerAttrHolder.java │ ├── ServerBootstrap.java │ ├── ServerFactory.java │ ├── ServerInitializer.java │ ├── ServerMode.java │ ├── ServerShell.java │ ├── ServerSpeaker.java │ ├── SessionIdKeeper.java │ ├── SimpleServerFactory.java │ ├── StandaloneServer.java │ ├── channel │ ├── ChannelHelper.java │ ├── ChannelListener.java │ ├── ChannelManager.java │ ├── ChannelType.java │ ├── ChannelWrapper.java │ ├── DefaultChannelListener.java │ └── DefaultChannelManager.java │ ├── http │ ├── ControllerContext.java │ ├── HttpExecutor.java │ └── HttpHandler.java │ ├── packet │ ├── PacketExecutor.java │ └── PacketHandler.java │ ├── rest │ └── StatusController.java │ ├── session │ ├── AbstractSessionManager.java │ ├── DefaultSession.java │ ├── DefaultSessionManager.java │ ├── Session.java │ ├── SessionHelper.java │ └── SessionManager.java │ └── ws │ ├── FrameExecutor.java │ └── FrameHandler.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | 27 | # OS generated files # 28 | ###################### 29 | .DS_Store* 30 | ehthumbs.db 31 | Icon? 32 | Thumbs.db 33 | 34 | # Editor Files # 35 | ################ 36 | *~ 37 | *.swp 38 | 39 | # Gradle Files # 40 | ################ 41 | .gradle 42 | .m2 43 | 44 | # Build output directies 45 | target/ 46 | build/ 47 | 48 | ### STS ### 49 | .apt_generated 50 | .classpath 51 | .factorypath 52 | .project 53 | .settings 54 | .springBeans 55 | 56 | # IntelliJ specific files/directories 57 | out 58 | .idea 59 | *.ipr 60 | *.iws 61 | *.iml 62 | atlassian-ide-plugin.xml 63 | 64 | # Eclipse specific files/directories 65 | .classpath 66 | .project 67 | .settings 68 | .metadata 69 | 70 | # NetBeans specific files/directories 71 | .nbattrs 72 | nbproject/private/ 73 | build/ 74 | nbbuild/ 75 | dist/ 76 | nbdist/ -------------------------------------------------------------------------------- /articles/drawios/bitchat-overview.xml: -------------------------------------------------------------------------------- 1 | 7V1bk5pIFP41Pk6K7ubSPI5zSWqrdpPaydYmjwitUoPiImac/PptBFROg7ZIAzqmKgk00DZ9zvf1uQED8jBbf46cxfTP0GPBAGveekAeBxhTm/J/k4b3tMHUzLRhEvle2qTtGl783yxtRHnryvfYMmtLm+IwDGJ/UWx0w/mcuXGhzYmi8K142jgMvELDwpmwwjCShhfXCZhw2r++F0+z28LWrv0L8yfT/JeRaadHZk5+ctbxcup44dteE3kakIcoDON0a7Z+YEEyd8V5ea44uh1YxOaxzAWvMdUW9J59f/1MR8s/hrPHf+I7w0i7+eUEq+yOs9HG7/kUTKJwtRB/LRvALxbFbF0mC2cUMHi7XE1YOGNx9M7Py66yszFkGoJoNmFvu/kmRtY23ZtrbGWNTibjybbr3TTwjWwmTpgVYh6fFS7ORbIZ+PPXARlO4xn/iUfEN5dxFL6yhzAIo82p5PmZ8L/8iDiJB4XCvMm+FooTuDdDZROUt0UscGL/V1GjyyYt+4Vvoc/Ht5UPLsoHa/ono9jHMlxFLssu21dB2JNNjnUVO9GExUJXGzFu7/wMyVqCZBdRGIduGAgS5modFyUbsaX/O1Nrje8vkmFuBm4MB8Yjb3FWcbhMSSy5wAn8yTxREjZOukqw4nNuuc+a43CR6MvCcf355Huy83inn64nAgQroYZtq4g1WxewZhBRk5CuCGlIEMcAm0EyV+Nwozk7eZj/rcL8wF06x/f8BKQv1ruDfGuS/O8GfjJ3WV98aGl36UFB0pzg5h7zMqG9Tf2YvXChJEff+KpWVIKxHwR74PYcRsduGexNl7LRWKU4iVaEExGZE2klxKCrIk6sSJxLFvFZaUWcBqOeXiZOikfENFWKE6GiPJHetTz1GzzPkafdN3yW2TVNyHO2nNy5fPraweiYuswtlemIGrqhKZFpZzITLZZmZLbipHrtQlv3BXhUFZGmrpYfzj+IKBEyOhalXSJKMLnuipsr+dyyuXefBCL4rhs4y6XvFieXz1D0/iPxKLgvlO3+3D/2uM7cjXTvPd9b+/HeZXzv596R3UXJTn6NILB7+0F/Gp4usNTjO245pO7ccYuxJ66uXlywCQZKJOvowoUfRioqvFyuJ8773mmZf1ntmYPx5gZH1bCQCcZVDBnxjXQEjbrcuVF7A8xBwOTBzcsCDNRAgXZlEYOsclVuGjIU/Aw9ETGkDcSURUUuFjFP9+awjt98FDFYEjFWvxBjAA006yIGqjJSgxjhd7TDkIGLXz4utZApizydBZmrUH3aK9UXON6oq/pmMSVBz9X8ptMMebz8xuCNqLF5nWoMmRL20xSBwxXnCIFDp6IN/jYkMq6q89AGDL/n3t++WulU1CsCl/Dm0mOqgn8XGYA3KyQtnVAhJRJtN3iEVAUCLzJDdrJACahFQDrpWqBl4cAbQusKtHuE5gO45cjOp93uhKiqruRyk2anQ7Pz6oPcBrxlzc6V5baGrztZkhJZnuMRV0fmdz4vlnd6T5z64+H8zC44Hs/vWYwGOIs6jCpKO7fAS1aVAyNgwJhaJ42LtOHe6uJ6xO/9JbPZAQxq1Z1mFaUZnYmlpjPf85Lrh4EzYsHQcV8nGzLbJ6jNH3ksbCF9tud9p30iBJSm4kbU+Q5bn4DLYMKS43A8XjIl1ca48TDgltqsNnOY0qRnXSbpQQ4hpOekB3My2MZHxoWLN5g9haOW9ERP6gpIL4d0E6SHKSC9vArg3EXcLlIeaY3wJB4nuuK8hzRPUlmeRP3iSVANUTt3XZUjbtw41Iu8hzTj4Lh0rXxcSnkSS+Q+Osxd14Vgd6DBvQKNUHJUO11oNZz1rsAMOYgBcVim2QFmyhJjt4WmPma068QMJHRFKXYCcuYn1uGiNupwsarUIzep2wqhjse4PITqmSPTUJp63EYktpWg6FPnQVVVycePkU0Wn7fsOpucL73Xsayd/N4D2WUNSwfX+7WsCf6Tgesta7AETdGyhiFAjpiCiOBD56tZ18hVVcv3ADM9c5+gDhogmtU3zAghBPPgsHD+Co5tiAK3gBl8w0yjmOlXnK4xzGBaDDm0hpnD6wyB7mEb/hNpsYKhAk1aLTShg2hqGjPyIYd+5QCF1F3dHKBg5KmK0xngvVzH4nQWyArZdgugET3UiE38ZXzBKUDSZN2DRnPAnFvpUDQj7mBGRV3SjzRd5SBT2iVPcHXJtDtjou/EWNNp3b5F7h3sN10cYYFKomMWOCiOQHahgkwRMYrR2MsnxiZrI0wbAbGQZtQZFXnSaosmdeX24/OzaW78oIoK2AvhSVkDEvXswX7odNm1na6i4uO8iKHxt8cAnsze1VwZQBEqbduonBWNi4jFq2h+sSypN2k+qmJJPu+gbnbL7i0wZdNVZDJMaV0zU9r9YkoDEA8FBCcd0rWLLq1Qt9sUU8LadPtwGRmhtHi+1YKrrYtlZJfOlFVvB+wTU+oEEqXZGk8qLILCNcP4JzxyVcLKtv3woCImKf1cQr+IElb5UwSfXZF/Yw504oWuGuJKQMnYPvw4FoJBAfgVC+HJB3C+2Qa1lpVONQQ0qybQznkASB3QZGNcqF8xLmJDFif5rZxccUhgVwiaNw0hTYcJAPOw/6bj8kBepRVj0EP9K4KaxOt1VX56pV+vmjJQqQROp/+80KiqI8WfXTHEGp0L/uyKfKFhPt05krr6zIohEXv8OKgiwuuZQRfSXE/twx2phpUYHPsQsMqn34YLIG4NZtNvL+zLV2v4I1y9jaju/q399bX0a0ZADKWW4ZLrSSxpMBYCTrv4U4W9KL7SyRziDXQ9Zznd2qul8Owk+Zkahp1FpvLgLKiROPtLZ0gHHdVmBr67+2Zgevruw4vk6X8= -------------------------------------------------------------------------------- /articles/resources/bitchat-overview/client-connect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/bitchat/17e9fd5cc86140cb12d289138098d1e1cbb70448/articles/resources/bitchat-overview/client-connect.jpg -------------------------------------------------------------------------------- /articles/resources/bitchat-overview/client-reconnect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/bitchat/17e9fd5cc86140cb12d289138098d1e1cbb70448/articles/resources/bitchat-overview/client-reconnect.jpg -------------------------------------------------------------------------------- /articles/resources/bitchat-overview/cluster-arch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/bitchat/17e9fd5cc86140cb12d289138098d1e1cbb70448/articles/resources/bitchat-overview/cluster-arch.jpg -------------------------------------------------------------------------------- /articles/resources/bitchat-overview/list-user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/bitchat/17e9fd5cc86140cb12d289138098d1e1cbb70448/articles/resources/bitchat-overview/list-user.jpg -------------------------------------------------------------------------------- /articles/resources/bitchat-overview/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/bitchat/17e9fd5cc86140cb12d289138098d1e1cbb70448/articles/resources/bitchat-overview/login.jpg -------------------------------------------------------------------------------- /articles/resources/bitchat-overview/received-p2p-msg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/bitchat/17e9fd5cc86140cb12d289138098d1e1cbb70448/articles/resources/bitchat-overview/received-p2p-msg.jpg -------------------------------------------------------------------------------- /articles/resources/bitchat-overview/register.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/bitchat/17e9fd5cc86140cb12d289138098d1e1cbb70448/articles/resources/bitchat-overview/register.jpg -------------------------------------------------------------------------------- /articles/resources/bitchat-overview/send-p2p-msg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/bitchat/17e9fd5cc86140cb12d289138098d1e1cbb70448/articles/resources/bitchat-overview/send-p2p-msg.jpg -------------------------------------------------------------------------------- /articles/resources/bitchat-overview/server-startup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/bitchat/17e9fd5cc86140cb12d289138098d1e1cbb70448/articles/resources/bitchat-overview/server-startup.jpg -------------------------------------------------------------------------------- /articles/resources/bitchat-overview/stand-alone-arch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all4you/bitchat/17e9fd5cc86140cb12d289138098d1e1cbb70448/articles/resources/bitchat-overview/stand-alone-arch.jpg -------------------------------------------------------------------------------- /bitchat-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bitchat 7 | io.bitchat 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | bitchat-client 13 | 14 | 15 | 16 | io.bitchat 17 | bitchat-core 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /bitchat-client/src/main/java/io/bitchat/client/Client.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.client; 2 | 3 | import io.bitchat.packet.Packet; 4 | 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | /** 8 | *

9 | * A client which can communicate with Server 10 | *

11 | * 12 | * @author houyi 13 | */ 14 | public interface Client { 15 | 16 | /** 17 | * connect server 18 | */ 19 | void connect(); 20 | 21 | /** 22 | * send request to server 23 | * 24 | * @param request the request packet 25 | * @return the response future 26 | */ 27 | CompletableFuture sendRequest(Packet request); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /bitchat-client/src/main/java/io/bitchat/client/ClientFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.client; 2 | 3 | import io.bitchat.core.LoadBalancer; 4 | import io.bitchat.core.ServerAttr; 5 | 6 | /** 7 | * @author houyi 8 | */ 9 | public interface ClientFactory { 10 | 11 | /** 12 | * create a {@link Client} with {@link ServerAttr} 13 | * 14 | * @param serverAttr the serverAttr 15 | * @return a {@link Client} 16 | */ 17 | Client newClient(ServerAttr serverAttr); 18 | 19 | /** 20 | * create a {@link Client} by {@link LoadBalancer} 21 | * 22 | * @param loadBalancer the load balancer 23 | * @return a {@link Client} 24 | */ 25 | Client newBalancedClient(LoadBalancer loadBalancer); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /bitchat-client/src/main/java/io/bitchat/client/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.client; 2 | 3 | import io.bitchat.packet.PendingPackets; 4 | import io.bitchat.packet.*; 5 | import io.bitchat.packet.factory.PacketFactory; 6 | import io.bitchat.packet.ctx.CommandProcessorContext; 7 | import io.bitchat.packet.ctx.RequestProcessorContext; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.SimpleChannelInboundHandler; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.util.concurrent.CompletableFuture; 13 | 14 | /** 15 | * @author houyi 16 | */ 17 | @Slf4j 18 | public class ClientHandler extends SimpleChannelInboundHandler { 19 | 20 | private RequestProcessorContext requestHandler; 21 | private CommandProcessorContext commandHandler; 22 | 23 | public ClientHandler() { 24 | this.requestHandler = RequestProcessorContext.getInstance(); 25 | this.commandHandler = CommandProcessorContext.getInstance(); 26 | } 27 | 28 | /** 29 | * there are three kind of packet the client will receive 30 | * 1: a request packet 31 | * this kind of packet will be handled by client itself 32 | * see {@link RequestProcessorContext} 33 | *

34 | * 2: a response packet 35 | * this kind of packet will be handled or not due to biz 36 | *

37 | * 3: a command packet 38 | * this kind of packet is a one way packet sent by server 39 | * 40 | * @param ctx the context 41 | * @param packet the response 42 | */ 43 | @SuppressWarnings("unchecked") 44 | @Override 45 | public void channelRead0(ChannelHandlerContext ctx, Packet packet) { 46 | log.debug("ClientPacketDispatcher has received {}", packet); 47 | byte type = packet.getType(); 48 | if (type == PacketType.PACKET_TYPE_REQUEST) { 49 | onRequest(ctx, packet); 50 | } else if (type == PacketType.PACKET_TYPE_RESPONSE) { 51 | onResponse(packet); 52 | } else { 53 | onCommand(ctx, packet); 54 | } 55 | } 56 | 57 | @Override 58 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 59 | ctx.close(); 60 | log.error("ctx close,cause:", cause); 61 | } 62 | 63 | private void onRequest(ChannelHandlerContext ctx, Packet packet) { 64 | Payload payload = requestHandler.process(ctx, packet.getRequest()); 65 | Packet response = PacketFactory.newResponsePacket(payload, packet.getId()); 66 | writeResponse(ctx, response); 67 | } 68 | 69 | private void writeResponse(ChannelHandlerContext ctx, Packet response) { 70 | if (response != null) { 71 | ctx.channel().writeAndFlush(response); 72 | } 73 | } 74 | 75 | private void onResponse(Packet packet) { 76 | CompletableFuture pending = PendingPackets.remove(packet.getId()); 77 | if (pending != null) { 78 | pending.complete(packet); 79 | } 80 | } 81 | 82 | public void onCommand(ChannelHandlerContext ctx, Packet packet) { 83 | commandHandler.process(ctx, packet.getCommand()); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /bitchat-client/src/main/java/io/bitchat/client/ClientInitializer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.client; 2 | 3 | import io.bitchat.core.IdleStateChecker; 4 | import io.bitchat.lang.config.BaseConfig; 5 | import io.bitchat.lang.config.ConfigFactory; 6 | import io.bitchat.packet.codec.PacketCodec; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelPipeline; 9 | import io.netty.channel.socket.SocketChannel; 10 | 11 | 12 | /** 13 | * @author houyi 14 | */ 15 | public class ClientInitializer extends ChannelInitializer { 16 | 17 | private Client client; 18 | 19 | public ClientInitializer(Client client) { 20 | this.client = client; 21 | } 22 | 23 | @Override 24 | protected void initChannel(SocketChannel channel) throws Exception { 25 | ChannelPipeline pipeline = channel.pipeline(); 26 | BaseConfig baseConfig = ConfigFactory.getConfig(BaseConfig.class); 27 | pipeline.addLast(new IdleStateChecker(baseConfig.readerIdleTime())); 28 | pipeline.addLast(new PacketCodec()); 29 | pipeline.addLast(new HealthyChecker(client, baseConfig.pingInterval())); 30 | pipeline.addLast(new ClientHandler()); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /bitchat-client/src/main/java/io/bitchat/client/ClientMode.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.client; 2 | 3 | /** 4 | * @author houyi 5 | */ 6 | public interface ClientMode { 7 | 8 | /** 9 | * direct connect to server mode 10 | */ 11 | int DIRECT_CONNECT_SERVER = 1; 12 | 13 | /** 14 | * connect to router mode 15 | */ 16 | int CONNECT_ROUTER = 2; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /bitchat-client/src/main/java/io/bitchat/client/GenericClient.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.client; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import io.bitchat.core.LoadBalancer; 5 | import io.bitchat.packet.PendingPackets; 6 | import io.bitchat.core.ServerAttr; 7 | import io.bitchat.core.init.Initializer; 8 | import io.bitchat.lang.constants.ResultCode; 9 | import io.bitchat.packet.Packet; 10 | import io.bitchat.packet.factory.PacketFactory; 11 | import io.bitchat.packet.Payload; 12 | import io.bitchat.packet.factory.PayloadFactory; 13 | import io.netty.bootstrap.Bootstrap; 14 | import io.netty.channel.*; 15 | import io.netty.channel.nio.NioEventLoopGroup; 16 | import io.netty.channel.socket.SocketChannel; 17 | import io.netty.channel.socket.nio.NioSocketChannel; 18 | import io.netty.handler.logging.LogLevel; 19 | import io.netty.handler.logging.LoggingHandler; 20 | import io.netty.util.concurrent.Future; 21 | import io.netty.util.concurrent.GenericFutureListener; 22 | import lombok.extern.slf4j.Slf4j; 23 | 24 | import java.util.concurrent.CompletableFuture; 25 | 26 | /** 27 | * @author houyi 28 | */ 29 | @Slf4j 30 | public class GenericClient implements Client { 31 | 32 | private ServerAttr serverAttr; 33 | 34 | private volatile boolean connected = false; 35 | 36 | private Channel channel = null; 37 | 38 | public GenericClient(ServerAttr serverAttr) { 39 | this.serverAttr = serverAttr; 40 | Initializer.init(); 41 | } 42 | 43 | public GenericClient(LoadBalancer loadBalancer) { 44 | Assert.notNull(loadBalancer, "loadBalancer can not be null"); 45 | this.serverAttr = loadBalancer.nextServer(); 46 | Initializer.init(); 47 | } 48 | 49 | @Override 50 | public void connect() { 51 | Assert.notNull(serverAttr, "serverAttr can not be null"); 52 | EventLoopGroup group = new NioEventLoopGroup(); 53 | Bootstrap bootstrap = new Bootstrap(); 54 | bootstrap.group(group) 55 | .channel(NioSocketChannel.class) 56 | .handler(new LoggingHandler(LogLevel.INFO)) 57 | .handler(new ChannelInitializer() { 58 | @Override 59 | protected void initChannel(SocketChannel ch) throws Exception { 60 | ChannelPipeline pipeline = ch.pipeline(); 61 | pipeline.addLast(new ClientInitializer(GenericClient.this)); 62 | } 63 | }); 64 | 65 | ChannelFuture future = bootstrap.connect(serverAttr.getAddress(), serverAttr.getPort()); 66 | future.addListener(new GenericFutureListener>() { 67 | @Override 68 | public void operationComplete(Future f) throws Exception { 69 | channel = future.channel(); 70 | if (f.isSuccess()) { 71 | connected = true; 72 | log.info("[{}] Has connected to {} successfully", GenericClient.class.getSimpleName(), serverAttr); 73 | } else { 74 | log.warn("[{}] Connect to {} failed, cause={}", GenericClient.class.getSimpleName(), serverAttr, f.cause().getMessage()); 75 | // fire the channelInactive and make sure 76 | // the {@link HealthyChecker} will reconnect 77 | channel.pipeline().fireChannelInactive(); 78 | } 79 | } 80 | }); 81 | } 82 | 83 | @Override 84 | public CompletableFuture sendRequest(Packet request) { 85 | // create a promise 86 | CompletableFuture promise = new CompletableFuture<>(); 87 | if (!connected) { 88 | String msg = "Not connected yet!"; 89 | log.debug(msg); 90 | Payload payload = PayloadFactory.newErrorPayload(ResultCode.BIZ_FAIL.getCode(), msg); 91 | promise.complete(PacketFactory.newResponsePacket(payload, request.getId())); 92 | return promise; 93 | } 94 | Long id = request.getId(); 95 | PendingPackets.add(id, promise); 96 | ChannelFuture future = channel.writeAndFlush(request); 97 | future.addListener(new GenericFutureListener>() { 98 | @Override 99 | public void operationComplete(Future f) throws Exception { 100 | if (!f.isSuccess()) { 101 | CompletableFuture pending = PendingPackets.remove(id); 102 | if (pending != null) { 103 | pending.completeExceptionally(f.cause()); 104 | } 105 | } 106 | } 107 | }); 108 | return promise; 109 | } 110 | 111 | 112 | } 113 | -------------------------------------------------------------------------------- /bitchat-client/src/main/java/io/bitchat/client/HealthyChecker.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.client; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import io.bitchat.packet.Packet; 5 | import io.bitchat.packet.factory.PacketFactory; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.ChannelInboundHandlerAdapter; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import java.util.concurrent.TimeUnit; 12 | 13 | /** 14 | *

15 | * This ChannelHandler will check 16 | * whether the Channel is healthy or not 17 | *

18 | * 19 | *

20 | * It will keep a heart beat with 21 | * the Server by send a Ping 22 | * and the Server will return a Pong 23 | *

24 | * 25 | *

26 | * If the Channel is InActive it will 27 | * try to reconnect to Server 28 | *

29 | * 30 | * @author houyi 31 | */ 32 | @Slf4j 33 | public class HealthyChecker extends ChannelInboundHandlerAdapter { 34 | 35 | private static final int DEFAULT_PING_INTERVAL = 5; 36 | 37 | private Client client; 38 | 39 | private int pingInterval; 40 | 41 | public HealthyChecker(Client client, int pingInterval) { 42 | Assert.notNull(client, "client can not be null"); 43 | this.client = client; 44 | this.pingInterval = pingInterval <= 0 ? DEFAULT_PING_INTERVAL : pingInterval; 45 | } 46 | 47 | @Override 48 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 49 | schedulePing(ctx); 50 | ctx.fireChannelActive(); 51 | } 52 | 53 | private void schedulePing(ChannelHandlerContext ctx) { 54 | ctx.executor().schedule(() -> { 55 | Channel channel = ctx.channel(); 56 | if (channel.isActive()) { 57 | Packet pingPacket = PacketFactory.newPingPacket(); 58 | channel.writeAndFlush(pingPacket); 59 | log.debug("[{}] Send a Ping={}", HealthyChecker.class.getSimpleName(), pingPacket); 60 | schedulePing(ctx); 61 | } 62 | }, pingInterval, TimeUnit.SECONDS); 63 | } 64 | 65 | @Override 66 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 67 | ctx.executor().schedule(() -> { 68 | log.info("[{}] Try to reconnecting...", HealthyChecker.class.getSimpleName()); 69 | client.connect(); 70 | }, 5, TimeUnit.SECONDS); 71 | ctx.fireChannelInactive(); 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /bitchat-client/src/main/java/io/bitchat/client/SimpleClientFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.client; 2 | 3 | import cn.hutool.core.lang.Singleton; 4 | import io.bitchat.core.LoadBalancer; 5 | import io.bitchat.core.ServerAttr; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | /** 9 | * @author houyi 10 | */ 11 | @Slf4j 12 | public class SimpleClientFactory implements ClientFactory { 13 | 14 | private SimpleClientFactory() { 15 | 16 | } 17 | 18 | public static ClientFactory getInstance() { 19 | return Singleton.get(SimpleClientFactory.class); 20 | } 21 | 22 | /** 23 | *

24 | * A Client which connect to server directly 25 | *

26 | * 27 | * @param serverAttr the serverAttr 28 | */ 29 | @Override 30 | public Client newClient(ServerAttr serverAttr) { 31 | return new GenericClient(serverAttr); 32 | } 33 | 34 | /** 35 | *

36 | * A Client which connect to server by 37 | * request the {@link io.bitchat.core.ServerAttr} 38 | * from a ${@link io.bitchat.core.LoadBalancer} 39 | *

40 | * 41 | * @param loadBalancer the load balancer 42 | */ 43 | @Override 44 | public Client newBalancedClient(LoadBalancer loadBalancer) { 45 | return new GenericClient(loadBalancer); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /bitchat-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bitchat 7 | io.bitchat 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | bitchat-core 13 | 14 | 15 | 16 | org.aeonbits.owner 17 | owner 18 | 19 | 20 | com.alibaba 21 | fastjson 22 | 23 | 24 | com.esotericsoftware 25 | kryo 26 | 27 | 28 | com.caucho 29 | hessian 30 | 31 | 32 | io.protostuff 33 | protostuff-core 34 | 35 | 36 | io.protostuff 37 | protostuff-runtime 38 | 39 | 40 | commons-beanutils 41 | commons-beanutils 42 | 43 | 44 | cglib 45 | cglib 46 | 47 | 48 | ma.glasnost.orika 49 | orika-core 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/core/IdleStateChecker.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.timeout.IdleStateEvent; 5 | import io.netty.handler.timeout.IdleStateHandler; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | *

12 | * A ChannelHandler which will check 13 | * the idle state of each Channel 14 | *

15 | * 16 | *

17 | * This ChannelHandler can be added to 18 | * Client or Server Channel Pipeline 19 | *

20 | * 21 | *

22 | * But one thing should be confirmed is 23 | * that this ChannelHandler can not be 24 | * {@link io.netty.channel.ChannelHandler.Sharable} 25 | *

26 | * 27 | * @author houyi 28 | */ 29 | @Slf4j 30 | public class IdleStateChecker extends IdleStateHandler { 31 | 32 | private static final int DEFAULT_READER_IDLE_TIME = 15; 33 | 34 | private int readerTime; 35 | 36 | public IdleStateChecker(int readerIdleTime) { 37 | super(readerIdleTime == 0 ? DEFAULT_READER_IDLE_TIME : readerIdleTime, 0, 0, TimeUnit.SECONDS); 38 | readerTime = readerIdleTime == 0 ? DEFAULT_READER_IDLE_TIME : readerIdleTime; 39 | } 40 | 41 | @Override 42 | protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) { 43 | log.warn("[{}] Hasn't read data after {} seconds, will close the channel:{}", IdleStateChecker.class.getSimpleName(), readerTime, ctx.channel()); 44 | ctx.channel().close(); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/core/LoadBalancer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core; 2 | 3 | 4 | /** 5 | *

6 | * A load balancer can return a healthy server 7 | * and make sure the servers are load balanced 8 | *

9 | * 10 | * @author houyi 11 | */ 12 | public interface LoadBalancer { 13 | 14 | /** 15 | * get a healthy server 16 | * 17 | * @return a healthy server 18 | */ 19 | ServerAttr nextServer(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/core/ServerAttr.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core; 2 | 3 | import io.bitchat.lang.util.GenericsUtil; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | /** 10 | *

11 | * A config which holds the address and port of a server 12 | *

13 | * 14 | * @author houyi 15 | */ 16 | @Data 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class ServerAttr { 21 | private String address; 22 | private int port; 23 | 24 | public static ServerAttr getLocalServer(int serverPort) { 25 | return ServerAttr.builder() 26 | .address(GenericsUtil.getLocalIpV4()) 27 | .port(serverPort) 28 | .build(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/core/executor/AbstractExecutor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core.executor; 2 | 3 | import io.bitchat.lang.config.ConfigFactory; 4 | import io.bitchat.lang.config.ThreadPoolConfig; 5 | import io.netty.util.concurrent.DefaultThreadFactory; 6 | import io.netty.util.concurrent.Future; 7 | import io.netty.util.concurrent.Promise; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.util.concurrent.ArrayBlockingQueue; 11 | import java.util.concurrent.ThreadPoolExecutor; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * @author houyi 16 | */ 17 | @Slf4j 18 | public abstract class AbstractExecutor implements Executor { 19 | 20 | private java.util.concurrent.Executor eventExecutor; 21 | 22 | public AbstractExecutor() { 23 | this(null); 24 | } 25 | 26 | public AbstractExecutor(java.util.concurrent.Executor eventExecutor) { 27 | this.eventExecutor = eventExecutor == null ? EventExecutorHolder.eventExecutor : eventExecutor; 28 | } 29 | 30 | @Override 31 | public T execute(Object... request) { 32 | return doExecute(request); 33 | } 34 | 35 | @Override 36 | public Future asyncExecute(Promise promise, Object... request) { 37 | if (promise == null) { 38 | throw new IllegalArgumentException("promise should not be null"); 39 | } 40 | // async execute 41 | eventExecutor.execute(new Runnable() { 42 | @Override 43 | public void run() { 44 | try { 45 | T response = doExecute(request); 46 | promise.setSuccess(response); 47 | } catch (Exception e) { 48 | promise.setFailure(e); 49 | } 50 | } 51 | }); 52 | // return the promise back 53 | return promise; 54 | } 55 | 56 | /** 57 | * do actual execute 58 | * 59 | * @param request the request 60 | * @return the response 61 | */ 62 | public abstract T doExecute(Object... request); 63 | 64 | private static final class EventExecutorHolder { 65 | private static ThreadPoolConfig config = ConfigFactory.getConfig(ThreadPoolConfig.class); 66 | private static java.util.concurrent.Executor eventExecutor = new ThreadPoolExecutor( 67 | config.corePoolSize(), 68 | config.maxPoolSize(), 69 | config.idealTime(), 70 | TimeUnit.SECONDS, 71 | new ArrayBlockingQueue<>(config.blockingQueueCap()), 72 | new DefaultThreadFactory("event-executor-pool", true)); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/core/executor/Executor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core.executor; 2 | 3 | import io.netty.util.concurrent.Future; 4 | import io.netty.util.concurrent.Promise; 5 | 6 | /** 7 | * @author houyi 8 | */ 9 | public interface Executor { 10 | 11 | /** 12 | * handle the request and return the response 13 | * 14 | * @param request the request 15 | * @return the response 16 | */ 17 | T execute(Object... request); 18 | 19 | /** 20 | * handle the request async and return a future directly 21 | * 22 | * @param promise a promise 23 | * @param request the request 24 | * @return the async result 25 | */ 26 | Future asyncExecute(Promise promise, Object... request); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/core/id/IdFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core.id; 2 | 3 | /** 4 | * @author houyi 5 | */ 6 | public interface IdFactory { 7 | 8 | /** 9 | * get next id 10 | * @return next id 11 | */ 12 | long nextId(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/core/id/MemoryIdFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core.id; 2 | 3 | import cn.hutool.core.lang.Singleton; 4 | 5 | import java.util.concurrent.atomic.AtomicLong; 6 | 7 | /** 8 | * @author houyi 9 | */ 10 | public class MemoryIdFactory implements IdFactory { 11 | 12 | private static AtomicLong id = new AtomicLong(1); 13 | 14 | private MemoryIdFactory() { 15 | 16 | } 17 | 18 | public static IdFactory getInstance() { 19 | return Singleton.get(MemoryIdFactory.class); 20 | } 21 | 22 | @Override 23 | public long nextId() { 24 | return id.getAndIncrement(); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/core/id/SnowflakeIdFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core.id; 2 | 3 | import cn.hutool.core.lang.Singleton; 4 | import cn.hutool.core.lang.Snowflake; 5 | import cn.hutool.core.util.IdUtil; 6 | import io.bitchat.lang.config.ConfigFactory; 7 | import io.bitchat.lang.config.SnowflakeConfig; 8 | 9 | /** 10 | * @author houyi 11 | */ 12 | public class SnowflakeIdFactory implements IdFactory { 13 | 14 | private Snowflake snowflake; 15 | 16 | private SnowflakeIdFactory() { 17 | this(null); 18 | } 19 | 20 | private SnowflakeIdFactory(Long workerId) { 21 | SnowflakeConfig config = ConfigFactory.getConfig(SnowflakeConfig.class); 22 | Long realWorkerId = workerId != null ? workerId : config.workerId(); 23 | this.snowflake = IdUtil.createSnowflake(realWorkerId, config.dataCenterId()); 24 | } 25 | 26 | public static IdFactory getInstance() { 27 | return Singleton.get(SnowflakeIdFactory.class); 28 | } 29 | 30 | public static IdFactory getInstance(Long workerId) { 31 | return Singleton.get(SnowflakeIdFactory.class, workerId); 32 | } 33 | 34 | 35 | @Override 36 | public long nextId() { 37 | return snowflake.nextId(); 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/core/init/InitAble.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core.init; 2 | 3 | /** 4 | *

5 | * A init Function 6 | * Any Class want to do some init work 7 | * just implements this interface will 8 | * be ok 9 | *

10 | * 11 | * @author houyi 12 | */ 13 | public interface InitAble { 14 | 15 | /** 16 | * do init work 17 | */ 18 | void init(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/core/init/InitOrder.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core.init; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * The Order InitAble will execute 7 | * order by asc 8 | * 9 | * @author houyi 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.TYPE}) 13 | @Documented 14 | public @interface InitOrder { 15 | 16 | /** 17 | * lowest precedence 18 | */ 19 | int LOWEST_PRECEDENCE = Integer.MAX_VALUE; 20 | 21 | /** 22 | * highest precedence 23 | */ 24 | int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; 25 | 26 | /** 27 | * The order value. Lowest precedence by default. 28 | * 29 | * @return the order value 30 | */ 31 | int value() default LOWEST_PRECEDENCE; 32 | } 33 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/core/init/Initializer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core.init; 2 | 3 | 4 | import cn.hutool.core.collection.CollectionUtil; 5 | import cn.hutool.core.lang.ClassScaner; 6 | import io.bitchat.lang.config.BaseConfig; 7 | import io.bitchat.lang.config.ConfigFactory; 8 | import lombok.Getter; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import java.lang.reflect.Constructor; 12 | import java.lang.reflect.Modifier; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Set; 16 | import java.util.concurrent.atomic.AtomicBoolean; 17 | 18 | /** 19 | *

20 | * A InitAble executor 21 | *

22 | * The init method of this class 23 | * should be called before Server 24 | * startup to init all the InitAble 25 | * classes 26 | *

27 | * 28 | * @author houyi 29 | */ 30 | @Slf4j 31 | public final class Initializer { 32 | 33 | private static AtomicBoolean initialized = new AtomicBoolean(false); 34 | 35 | private Initializer() { 36 | 37 | } 38 | 39 | /** 40 | * Scan all InitAbles 41 | * and do the init method 42 | */ 43 | public static void init() { 44 | if (!initialized.compareAndSet(false, true)) { 45 | return; 46 | } 47 | try { 48 | BaseConfig baseConfig = ConfigFactory.getConfig(BaseConfig.class); 49 | Set> classSet = ClassScaner.scanPackageBySuper(baseConfig.basePackage(), InitAble.class); 50 | if (CollectionUtil.isEmpty(classSet)) { 51 | log.info("[Initializer] No InitAble found"); 52 | return; 53 | } 54 | List initList = new ArrayList<>(); 55 | for (Class clazz : classSet) { 56 | if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()) || !InitAble.class.isAssignableFrom(clazz)) { 57 | continue; 58 | } 59 | try { 60 | Constructor constructor = clazz.getDeclaredConstructor(); 61 | constructor.setAccessible(true); 62 | InitAble initAble = (InitAble) constructor.newInstance(); 63 | log.info("[Initializer] Found InitAble=[{}]", initAble.getClass().getCanonicalName()); 64 | insertSorted(initList, initAble); 65 | } catch (Exception e) { 66 | log.warn("[Initializer] Prepare InitAble failed", e); 67 | } 68 | } 69 | for (OrderWrapper wrapper : initList) { 70 | // execute the init method of initAble 71 | wrapper.initAble.init(); 72 | log.info("[Initializer] Initialized [{}] with order={}", wrapper.initAble.getClass().getCanonicalName(), wrapper.order); 73 | } 74 | } catch (Exception e) { 75 | log.warn("[Initializer] Init failed", e); 76 | } catch (Error error) { 77 | log.warn("[Initializer] Init failed with fatal error", error); 78 | throw error; 79 | } 80 | } 81 | 82 | private static void insertSorted(List list, InitAble initAble) { 83 | int order = resolveOrder(initAble); 84 | int idx = 0; 85 | for (; idx < list.size(); idx++) { 86 | // put initAble before the first initAble who's order is larger than it 87 | if (list.get(idx).getOrder() > order) { 88 | break; 89 | } 90 | } 91 | list.add(idx, new OrderWrapper(order, initAble)); 92 | } 93 | 94 | private static int resolveOrder(InitAble initAble) { 95 | if (!initAble.getClass().isAnnotationPresent(InitOrder.class)) { 96 | return InitOrder.LOWEST_PRECEDENCE; 97 | } else { 98 | return initAble.getClass().getAnnotation(InitOrder.class).value(); 99 | } 100 | } 101 | 102 | 103 | @Getter 104 | private static class OrderWrapper { 105 | private final int order; 106 | private final InitAble initAble; 107 | 108 | OrderWrapper(int order, InitAble initAble) { 109 | this.order = order; 110 | this.initAble = initAble; 111 | } 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/ContentType.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http; 2 | 3 | public enum ContentType { 4 | 5 | APPLICATION_ATOM_XML("application/atom+xml"), 6 | APPLICATION_FORM_URLENCODED("application/x-www-form-urlencoded"), 7 | APPLICATION_JSON("application/json"), 8 | APPLICATION_OCTET_STREAM("application/octet-stream"), 9 | APPLICATION_SVG_XML("application/svg+xml"), 10 | APPLICATION_XHTML_XML("application/xhtml+xml"), 11 | APPLICATION_XML("application/xml") 12 | ; 13 | 14 | private String content; 15 | 16 | ContentType(String content){ 17 | this.content = content; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return content; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/HttpContext.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.http.HttpRequest; 6 | import io.netty.handler.codec.http.HttpResponse; 7 | import io.netty.handler.codec.http.cookie.Cookie; 8 | import io.netty.util.concurrent.FastThreadLocal; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | /** 15 | * @author houyi 16 | **/ 17 | @Slf4j 18 | public class HttpContext { 19 | 20 | /** 21 | * 使用FastThreadLocal替代JDK自带的ThreadLocal以提升并发性能 22 | */ 23 | private static final FastThreadLocal CONTEXT_HOLDER = new FastThreadLocal<>(); 24 | 25 | private HttpRequest request; 26 | 27 | private ChannelHandlerContext context; 28 | 29 | private HttpResponse response; 30 | 31 | private Set cookies; 32 | 33 | private HttpContext() { 34 | 35 | } 36 | 37 | public HttpContext setRequest(HttpRequest request) { 38 | this.request = request; 39 | return this; 40 | } 41 | 42 | public HttpContext setContext(ChannelHandlerContext context) { 43 | this.context = context; 44 | return this; 45 | } 46 | 47 | public HttpContext setResponse(HttpResponse response) { 48 | this.response = response; 49 | return this; 50 | } 51 | 52 | public HttpContext addCookie(Cookie cookie) { 53 | if (cookie != null) { 54 | if (CollectionUtil.isEmpty(cookies)) { 55 | cookies = new HashSet<>(); 56 | } 57 | cookies.add(cookie); 58 | } 59 | return this; 60 | } 61 | 62 | public HttpContext addCookies(Set cookieSet) { 63 | if (CollectionUtil.isNotEmpty(cookieSet)) { 64 | if (CollectionUtil.isEmpty(cookies)) { 65 | cookies = new HashSet<>(); 66 | } 67 | cookies.addAll(cookieSet); 68 | } 69 | return this; 70 | } 71 | 72 | public HttpRequest getRequest() { 73 | return request; 74 | } 75 | 76 | public ChannelHandlerContext getContext() { 77 | return context; 78 | } 79 | 80 | public HttpResponse getResponse() { 81 | return response; 82 | } 83 | 84 | public Set getCookies() { 85 | return cookies; 86 | } 87 | 88 | public static HttpContext currentContext() { 89 | HttpContext context = CONTEXT_HOLDER.get(); 90 | if (context == null) { 91 | context = new HttpContext(); 92 | CONTEXT_HOLDER.set(context); 93 | } 94 | return context; 95 | } 96 | 97 | public static void clear() { 98 | CONTEXT_HOLDER.remove(); 99 | } 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/RenderType.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http; 2 | 3 | /** 4 | * 返回的响应类型 5 | * 6 | * @author houyi 7 | */ 8 | public enum RenderType { 9 | 10 | /** 11 | * JSON 12 | */ 13 | JSON("application/json;charset=UTF-8"), 14 | /** 15 | * XML 16 | */ 17 | XML("text/xml;charset=UTF-8"), 18 | /** 19 | * TEXT 20 | */ 21 | TEXT("text/plain;charset=UTF-8"), 22 | /** 23 | * HTML 24 | */ 25 | HTML("text/html;charset=UTF-8"); 26 | 27 | private String contentType; 28 | 29 | RenderType(String contentType) { 30 | this.contentType = contentType; 31 | } 32 | 33 | public String getContentType() { 34 | return contentType; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/RequestMethod.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http; 2 | 3 | import io.netty.handler.codec.http.HttpMethod; 4 | 5 | /** 6 | * @author houyi 7 | **/ 8 | public enum RequestMethod { 9 | /** 10 | * GET 11 | */ 12 | GET(HttpMethod.GET), 13 | /** 14 | * HEAD 15 | */ 16 | HEAD(HttpMethod.HEAD), 17 | /** 18 | * POST 19 | */ 20 | POST(HttpMethod.POST), 21 | /** 22 | * PUT 23 | */ 24 | PUT(HttpMethod.PUT), 25 | /** 26 | * PATCH 27 | */ 28 | PATCH(HttpMethod.PATCH), 29 | /** 30 | * DELETE 31 | */ 32 | DELETE(HttpMethod.DELETE), 33 | /** 34 | * OPTIONS 35 | */ 36 | OPTIONS(HttpMethod.OPTIONS), 37 | /** 38 | * TRACE 39 | */ 40 | TRACE(HttpMethod.TRACE); 41 | 42 | HttpMethod httpMethod; 43 | 44 | RequestMethod(HttpMethod httpMethod) { 45 | this.httpMethod = httpMethod; 46 | } 47 | 48 | public static HttpMethod getHttpMethod(RequestMethod requestMethod) { 49 | for (RequestMethod method : values()) { 50 | if (requestMethod == method) { 51 | return method.httpMethod; 52 | } 53 | } 54 | return null; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/controller/Controller.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.controller; 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 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Controller { 11 | 12 | /** 13 | * 请求uri 14 | * 15 | * @return url 16 | */ 17 | String path() default ""; 18 | 19 | } -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/controller/ControllerProxy.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.controller; 2 | 3 | 4 | import io.bitchat.http.RenderType; 5 | import io.bitchat.http.RequestMethod; 6 | import lombok.Data; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * 路由请求代理,用以根据路由调用具体的controller类 12 | * 13 | * @author houyi 14 | */ 15 | @Data 16 | public class ControllerProxy { 17 | 18 | private RenderType renderType; 19 | 20 | private RequestMethod requestMethod; 21 | 22 | private Object controller; 23 | 24 | private Method method; 25 | 26 | private String methodName; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/controller/Mapping.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.controller; 2 | 3 | 4 | import io.bitchat.http.RenderType; 5 | import io.bitchat.http.RequestMethod; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Target(ElementType.METHOD) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface Mapping { 15 | 16 | /** 17 | * 请求方法类型 18 | * 19 | * @return 方法类型 20 | */ 21 | RequestMethod requestMethod() default RequestMethod.GET; 22 | 23 | /** 24 | * 请求的uri 25 | * 26 | * @return url 27 | */ 28 | String path() default ""; 29 | 30 | /** 31 | * 返回类型 32 | * 33 | * @return 返回类型 34 | */ 35 | RenderType renderType() default RenderType.JSON; 36 | 37 | } -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/controller/Param.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.controller; 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 | @Target({ElementType.FIELD, ElementType.PARAMETER}) 9 | @Retention(value = RetentionPolicy.RUNTIME) 10 | public @interface Param { 11 | 12 | /** 13 | * 将使用什么样的键值读取对象, 14 | * 对于field,就是他名字 15 | * 对于method的parameter,需要指明 16 | * 17 | * @return 参数的key 18 | */ 19 | String key() default ""; 20 | 21 | /** 22 | * 提供设置缺省值 23 | * 24 | * @return 提供设置缺省值 25 | */ 26 | String defaultValue() default ""; 27 | 28 | /** 29 | * 是否校验参数为空 30 | * 31 | * @return true:校验参数 false:不校验参数 32 | */ 33 | boolean notNull() default false; 34 | 35 | /** 36 | * 是否校验参数为空 37 | * 38 | * @return true:校验参数 false:不校验参数 39 | */ 40 | boolean notBlank() default false; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/converter/AbstractConverter.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.converter; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * 转换器的抽象实现类 10 | * 11 | * @author houyi 12 | */ 13 | public abstract class AbstractConverter implements Converter { 14 | 15 | protected final Logger logger = LoggerFactory.getLogger(this.getClass()); 16 | 17 | /** 18 | * 实现了TypeConverter中的相同方法 19 | */ 20 | @Override 21 | public Object convert(Object source, Class toType, Object... parmas) { 22 | 23 | /* 24 | * 如果对象本身已经是所指定的类型则不进行转换直接返回 25 | * 如果对象能够被复制,则返回复制后的对象 26 | */ 27 | if (source != null && toType.isInstance(source)) { 28 | if (source instanceof Cloneable) { 29 | if (source.getClass().isArray() && source.getClass().getComponentType() == String.class) { 30 | // 字符串数组虽然是Cloneable的子类,但并没有clone方法 31 | return source; 32 | } 33 | try { 34 | Method m = source.getClass().getDeclaredMethod("clone", new Class[0]); 35 | m.setAccessible(true); 36 | return m.invoke(source, new Object[0]); 37 | } catch (Exception e) { 38 | logger.debug("Can not clone object " + source, e); 39 | } 40 | } 41 | 42 | return source; 43 | } 44 | 45 | /* 46 | * 如果需要转换,且value为String类型并且长度为0,则按照null值进行处理 47 | */ 48 | if (source != null && source instanceof String && ((String) source).length() == 0) { 49 | source = null; 50 | } 51 | 52 | /* 53 | * 不对Annotation, Interface, 54 | * Enummeration类型进行转换。 55 | */ 56 | if (toType == null || (source == null && !toType.isPrimitive()) 57 | || toType.isInterface() || toType.isAnnotation() 58 | || toType.isEnum()) { 59 | return null; 60 | } 61 | 62 | return doConvertValue(source, toType); 63 | } 64 | 65 | /** 66 | * 需要被子类所实现的转换方法 67 | * 68 | * @param source 需要进行类型转换的对象 69 | * @param toType  需要被转换成的类型 70 | * @param params  转值时需要提供的可选参数 71 | * @return 转换后所生成的对象,如果不能够进行转换则返回null 72 | */ 73 | protected abstract Object doConvertValue(Object source, Class toType, Object... params); 74 | 75 | } 76 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/converter/Converter.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.converter; 2 | 3 | /** 4 | * 类型转换器所需要实现的总接口。TypeConverter中有唯一的一个方法,实现类请特别注意方法所需要返回的值。 5 | * @author houyi 6 | * @date 2017-10-20 7 | */ 8 | public interface Converter { 9 | 10 | /** 11 | * 类型转换 12 | * @param source 需要被转换的值 13 | * @param toType 需要被转换成的类型 14 | * @param params 转值时需要提供的可选参数 15 | * @return 经转换过的类型,如果实现类没有能力进行所指定的类型转换,应返回null 16 | */ 17 | Object convert(Object source, Class toType, Object... params); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/converter/PrimitiveTypeUtil.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.converter; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.BigInteger; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * 定义了基本类型的工具类, 10 | * 可以方便的判断一个Class对象是否属于基本类型或基本类型的数组。 11 | * 本工具类所包含的基本类型判断包括如下一些内容: 12 | *

13 | * String 14 | * boolean 15 | * byte 16 | * short 17 | * int 18 | * long 19 | * float 20 | * double 21 | * char 22 | * Boolean 23 | * Byte 24 | * Short 25 | * Integer 26 | * Long 27 | * Float 28 | * Double 29 | * Character 30 | * BigInteger 31 | * BigDecimal 32 | * 33 | * @author yunfeng.cheng 34 | */ 35 | public class PrimitiveTypeUtil { 36 | 37 | /** 38 | * 私有的构造函数防止用户进行实例化。 39 | */ 40 | private PrimitiveTypeUtil() { 41 | } 42 | 43 | /** 44 | * 基本类型 45 | **/ 46 | private static final Class[] PRI_TYPE = { 47 | String.class, 48 | boolean.class, 49 | byte.class, 50 | short.class, 51 | int.class, 52 | long.class, 53 | float.class, 54 | double.class, 55 | char.class, 56 | Boolean.class, 57 | Byte.class, 58 | Short.class, 59 | Integer.class, 60 | Long.class, 61 | Float.class, 62 | Double.class, 63 | Character.class, 64 | BigInteger.class, 65 | BigDecimal.class 66 | }; 67 | 68 | /** 69 | * 基本数组类型 70 | **/ 71 | private static final Class[] PRI_ARRAY_TYPE = { 72 | String[].class, 73 | boolean[].class, 74 | byte[].class, 75 | short[].class, 76 | int[].class, 77 | long[].class, 78 | float[].class, 79 | double[].class, 80 | char[].class, 81 | Boolean[].class, 82 | Byte[].class, 83 | Short[].class, 84 | Integer[].class, 85 | Long[].class, 86 | Float[].class, 87 | Double[].class, 88 | Character[].class, 89 | BigInteger[].class, 90 | BigDecimal[].class 91 | }; 92 | 93 | /** 94 | * 基本类型默认值 95 | */ 96 | private static final Map, Object> primitiveDefaults = new HashMap, Object>(9); 97 | 98 | static { 99 | primitiveDefaults.put(boolean.class, false); 100 | primitiveDefaults.put(byte.class, (byte) 0); 101 | primitiveDefaults.put(short.class, (short) 0); 102 | primitiveDefaults.put(char.class, (char) 0); 103 | primitiveDefaults.put(int.class, 0); 104 | primitiveDefaults.put(long.class, 0L); 105 | primitiveDefaults.put(float.class, 0.0f); 106 | primitiveDefaults.put(double.class, 0.0); 107 | } 108 | 109 | /** 110 | * 判断是否为基本类型 111 | * 112 | * @param cls 需要进行判断的Class对象 113 | * @return 是否为基本类型 114 | */ 115 | public static boolean isPriType(Class cls) { 116 | for (Class priType : PRI_TYPE) { 117 | if (cls == priType) { 118 | return true; 119 | } 120 | } 121 | return false; 122 | } 123 | 124 | /** 125 | * 判断是否为基本类型数组 126 | * 127 | * @param cls 需要进行判断的Class对象 128 | * @return 是否为基本类型数组 129 | */ 130 | public static boolean isPriArrayType(Class cls) { 131 | for (Class priType : PRI_ARRAY_TYPE) { 132 | if (cls == priType) { 133 | return true; 134 | } 135 | } 136 | return false; 137 | } 138 | 139 | /** 140 | * 获得基本类型的默认值 141 | * 142 | * @param type 基本类型的Class 143 | * @return 基本类型的默认值 144 | */ 145 | public static Object getPriDefaultValue(Class type) { 146 | return primitiveDefaults.get(type); 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/exception/InvocationException.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.exception; 2 | 3 | public class InvocationException extends Exception { 4 | private static final long serialVersionUID = 1L; 5 | 6 | public InvocationException(String message, Throwable cause) { 7 | super(message, cause); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/exception/ValidationException.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.exception; 2 | 3 | /** 4 | * ValidationException 5 | * 6 | * @author houyi 7 | */ 8 | public class ValidationException extends RuntimeException { 9 | private static final long serialVersionUID = 1L; 10 | 11 | public ValidationException(String s) { 12 | super(s); 13 | } 14 | 15 | public ValidationException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/maker/DefaultHtmlMaker.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.maker; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import io.bitchat.http.view.HtmlKeyHolder; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * 默认的HtmlMaker,只处理字符串 10 | * 11 | * @author houyi 12 | **/ 13 | public class DefaultHtmlMaker implements HtmlMaker { 14 | 15 | @Override 16 | public String make(String htmlTemplate, Map contentMap) { 17 | String html = htmlTemplate; 18 | if (CollectionUtil.isNotEmpty(contentMap)) { 19 | for (Map.Entry entry : contentMap.entrySet()) { 20 | String key = entry.getKey(); 21 | Object val = entry.getValue(); 22 | if (val instanceof String) { 23 | html = html.replaceAll(HtmlKeyHolder.START_ESCAPE + key + HtmlKeyHolder.END, val.toString()); 24 | } 25 | } 26 | } 27 | return html; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/maker/HtmlMaker.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.maker; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * html生成器 7 | * 8 | * @author houyi 9 | **/ 10 | public interface HtmlMaker { 11 | 12 | /** 13 | * 根据html模板生成html内容 14 | * 15 | * @param htmlTemplate html模板 16 | * @param contentMap 参数 17 | * @return html内容 18 | */ 19 | String make(String htmlTemplate, Map contentMap); 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/maker/HtmlMakerEnum.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.maker; 2 | 3 | /** 4 | * @author houyi 5 | **/ 6 | public enum HtmlMakerEnum { 7 | /** 8 | * 字符串 9 | */ 10 | STRING 11 | } 12 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/maker/HtmlMakerFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.maker; 2 | 3 | import cn.hutool.core.util.ReflectUtil; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.locks.Lock; 10 | import java.util.concurrent.locks.ReentrantLock; 11 | 12 | /** 13 | * @author houyi 14 | **/ 15 | public class HtmlMakerFactory { 16 | 17 | private volatile static HtmlMakerFactory factory; 18 | 19 | private Map htmlMakerMap; 20 | 21 | private final Lock lock; 22 | 23 | private HtmlMakerFactory() { 24 | htmlMakerMap = new ConcurrentHashMap<>(); 25 | lock = new ReentrantLock(); 26 | } 27 | 28 | /** 29 | * 获取工厂实例 30 | */ 31 | public static HtmlMakerFactory instance() { 32 | if (factory == null) { 33 | synchronized (HtmlMakerFactory.class) { 34 | if (factory == null) { 35 | factory = new HtmlMakerFactory(); 36 | } 37 | } 38 | } 39 | return factory; 40 | } 41 | 42 | /** 43 | * 创建HtmlMaker实例 44 | */ 45 | public HtmlMaker build(HtmlMakerEnum type, Class clazz) { 46 | if (type == null) { 47 | return null; 48 | } else { 49 | HtmlMaker htmlMaker = htmlMakerMap.get(type); 50 | if (htmlMaker == null) { 51 | lock.lock(); 52 | try { 53 | if (!htmlMakerMap.containsKey(type)) { 54 | htmlMaker = ReflectUtil.newInstance(clazz); 55 | htmlMakerMap.putIfAbsent(type, htmlMaker); 56 | } else { 57 | htmlMaker = htmlMakerMap.get(type); 58 | } 59 | } finally { 60 | lock.unlock(); 61 | } 62 | } 63 | return htmlMaker; 64 | } 65 | } 66 | 67 | public static void main(String[] args) { 68 | int loopTimes = 200; 69 | 70 | class Runner implements Runnable { 71 | 72 | private Logger logger = LoggerFactory.getLogger(Runner.class); 73 | 74 | @Override 75 | public void run() { 76 | HtmlMakerFactory factory = HtmlMakerFactory.instance(); 77 | logger.info("factory={},currentThread={}", (factory != null ? factory.getClass().getName() : "null"), Thread.currentThread().getName()); 78 | } 79 | } 80 | 81 | for (int i = 0; i < loopTimes; i++) { 82 | new Thread(new Runner()).start(); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/router/BadClientSilencer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.bitchat.http.router; 17 | 18 | import io.netty.channel.ChannelHandler.Sharable; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.SimpleChannelInboundHandler; 21 | import io.netty.handler.codec.http.LastHttpContent; 22 | import io.netty.util.internal.logging.InternalLogger; 23 | import io.netty.util.internal.logging.InternalLoggerFactory; 24 | 25 | /** 26 | * This utility handler should be put at the last position of the inbound pipeline to 27 | * catch all exceptions caused by bad client (closed connection, malformed request etc.) 28 | * and server processing, then close the connection. 29 | * 30 | * By default exceptions are logged to Netty internal LOGGER. You may need to override 31 | * {@link #onUnknownMessage(Object)}, {@link #onBadClient(Throwable)}, and 32 | * {@link #onBadServer(Throwable)} to log to more suitable places. 33 | */ 34 | @Sharable 35 | public class BadClientSilencer extends SimpleChannelInboundHandler { 36 | private static final InternalLogger log = InternalLoggerFactory.getInstance(BadClientSilencer.class); 37 | 38 | /** Logs to Netty internal LOGGER. Override this method to log to other places if you want. */ 39 | protected void onUnknownMessage(Object msg) { 40 | log.warn("Unknown msg: " + msg); 41 | } 42 | 43 | /** Logs to Netty internal LOGGER. Override this method to log to other places if you want. */ 44 | protected void onBadClient(Throwable e) { 45 | log.warn("Caught exception (maybe client is bad)", e); 46 | } 47 | 48 | /** Logs to Netty internal LOGGER. Override this method to log to other places if you want. */ 49 | protected void onBadServer(Throwable e) { 50 | log.warn("Caught exception (maybe server is bad)", e); 51 | } 52 | 53 | //---------------------------------------------------------------------------- 54 | 55 | @Override 56 | public void channelRead0(ChannelHandlerContext ctx, Object msg) { 57 | // This handler is the last inbound handler. 58 | // This means msg has not been handled by any previous handler. 59 | ctx.close(); 60 | 61 | if (msg != LastHttpContent.EMPTY_LAST_CONTENT) { 62 | onUnknownMessage(msg); 63 | } 64 | } 65 | 66 | @Override 67 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { 68 | ctx.close(); 69 | 70 | // To clarify where exceptions are from, imports are not used 71 | if (e instanceof java.io.IOException || // Connection reset by peer, Broken pipe 72 | e instanceof java.nio.channels.ClosedChannelException || 73 | e instanceof io.netty.handler.codec.DecoderException || 74 | e instanceof io.netty.handler.codec.CorruptedFrameException || // Bad WebSocket frame 75 | e instanceof IllegalArgumentException || // Use https://... to connect to HTTP server 76 | e instanceof javax.net.ssl.SSLException || // Use http://... to connect to HTTPS server 77 | e instanceof io.netty.handler.ssl.NotSslRecordException) { 78 | onBadClient(e); // Maybe client is bad 79 | } else { 80 | onBadServer(e); // Maybe server is bad 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/router/RouteResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.bitchat.http.router; 17 | 18 | import io.netty.handler.codec.http.HttpMethod; 19 | import io.netty.util.internal.ObjectUtil; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Collections; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | /** 27 | * Result of calling {@link Router#route(HttpMethod, String)}. 28 | */ 29 | public class RouteResult { 30 | private final String uri; 31 | private final String decodedPath; 32 | 33 | private final Map pathParams; 34 | private final Map> queryParams; 35 | 36 | private final T target; 37 | 38 | /** 39 | * The maps will be wrapped in Collections.unmodifiableMap. 40 | */ 41 | public RouteResult( 42 | String uri, String decodedPath, 43 | Map pathParams, Map> queryParams, 44 | T target 45 | ) { 46 | this.uri = ObjectUtil.checkNotNull(uri, "uri"); 47 | this.decodedPath = ObjectUtil.checkNotNull(decodedPath, "decodedPath"); 48 | this.pathParams = Collections.unmodifiableMap(ObjectUtil.checkNotNull(pathParams, "pathParams")); 49 | this.queryParams = Collections.unmodifiableMap(ObjectUtil.checkNotNull(queryParams, "queryParams")); 50 | this.target = ObjectUtil.checkNotNull(target, "target"); 51 | } 52 | 53 | /** 54 | * Returns the original request URI. 55 | */ 56 | public String uri() { 57 | return uri; 58 | } 59 | 60 | /** 61 | * Returns the decoded request path. 62 | */ 63 | public String decodedPath() { 64 | return decodedPath; 65 | } 66 | 67 | /** 68 | * Returns all params embedded in the request path. 69 | */ 70 | public Map pathParams() { 71 | return pathParams; 72 | } 73 | 74 | /** 75 | * Returns all params in the query part of the request URI. 76 | */ 77 | public Map> queryParams() { 78 | return queryParams; 79 | } 80 | 81 | public T target() { 82 | return target; 83 | } 84 | 85 | //---------------------------------------------------------------------------- 86 | // Utilities to get params. 87 | 88 | /** 89 | * Extracts the first matching param in {@code queryParams}. 90 | * 91 | * @return {@code null} if there's no match 92 | */ 93 | public String queryParam(String name) { 94 | List values = queryParams.get(name); 95 | return (values == null) ? null : values.get(0); 96 | } 97 | 98 | /** 99 | * Extracts the param in {@code pathParams} first, then falls back to the first matching 100 | * param in {@code queryParams}. 101 | * 102 | * @return {@code null} if there's no match 103 | */ 104 | public String param(String name) { 105 | String pathValue = pathParams.get(name); 106 | return (pathValue == null) ? queryParam(name) : pathValue; 107 | } 108 | 109 | /** 110 | * Extracts all params in {@code pathParams} and {@code queryParams} matching the name. 111 | * 112 | * @return Unmodifiable list; the list is empty if there's no match 113 | */ 114 | public List params(String name) { 115 | List values = queryParams.get(name); 116 | String value = pathParams.get(name); 117 | 118 | if (values == null) { 119 | return (value == null) ? Collections.emptyList() : Collections.singletonList(value); 120 | } 121 | 122 | if (value == null) { 123 | return Collections.unmodifiableList(values); 124 | } else { 125 | List aggregated = new ArrayList(values.size() + 1); 126 | aggregated.addAll(values); 127 | aggregated.add(value); 128 | return Collections.unmodifiableList(aggregated); 129 | } 130 | } 131 | 132 | @Override 133 | public String toString() { 134 | return "{target:"+target+",decodedPath:"+decodedPath+",queryParams:"+queryParams+"}"; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/util/HtmlContentUtil.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.util; 2 | 3 | import io.bitchat.http.maker.HtmlMaker; 4 | import io.bitchat.lang.config.BaseConfig; 5 | import io.bitchat.lang.config.ConfigFactory; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * @author houyi 12 | **/ 13 | @Slf4j 14 | public class HtmlContentUtil { 15 | 16 | private HtmlContentUtil() { 17 | 18 | } 19 | 20 | /** 21 | * 获取页面内容 22 | * 23 | * @param htmlMaker htmlMaker 24 | * @param htmlTemplate html模板 25 | * @param contentMap 参数 26 | * @return 页面内容 27 | */ 28 | public static String getPageContent(HtmlMaker htmlMaker, String htmlTemplate, Map contentMap) { 29 | try { 30 | return htmlMaker.make(htmlTemplate, contentMap); 31 | } catch (Exception e) { 32 | log.error("getPageContent Error,cause:", e); 33 | } 34 | return ConfigFactory.getConfig(BaseConfig.class).serverInternalError(); 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/util/HttpRenderUtil.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.util; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import io.bitchat.http.RenderType; 5 | import io.bitchat.http.maker.DefaultHtmlMaker; 6 | import io.bitchat.http.maker.HtmlMaker; 7 | import io.bitchat.http.maker.HtmlMakerEnum; 8 | import io.bitchat.http.maker.HtmlMakerFactory; 9 | import io.bitchat.http.view.Page404; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.buffer.Unpooled; 12 | import io.netty.handler.codec.http.*; 13 | import io.netty.util.CharsetUtil; 14 | 15 | /** 16 | * HttpRenderUtil 17 | * 18 | * @author houyi 19 | */ 20 | public class HttpRenderUtil { 21 | 22 | public static final String EMPTY_CONTENT = ""; 23 | 24 | private HttpRenderUtil() { 25 | 26 | } 27 | 28 | /** 29 | * response输出 30 | * 31 | * @param content 内容 32 | * @param renderType 返回类型 33 | * @return 响应对象 34 | */ 35 | public static FullHttpResponse render(Object content, RenderType renderType) { 36 | byte[] bytes = HttpRenderUtil.getBytes(content); 37 | ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes); 38 | FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf); 39 | RenderType type = renderType != null ? renderType : RenderType.JSON; 40 | response.headers().add(HttpHeaderNames.CONTENT_TYPE, type.getContentType()); 41 | response.headers().add(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(byteBuf.readableBytes())); 42 | return response; 43 | } 44 | 45 | /** 46 | * 404NotFoundResponse 47 | * 48 | * @return 响应对象 49 | */ 50 | public static FullHttpResponse getNotFoundResponse() { 51 | HtmlMaker htmlMaker = HtmlMakerFactory.instance().build(HtmlMakerEnum.STRING, DefaultHtmlMaker.class); 52 | String htmlTpl = Page404.HTML; 53 | String content = HtmlContentUtil.getPageContent(htmlMaker, htmlTpl, null); 54 | return render(content, RenderType.HTML); 55 | } 56 | 57 | /** 58 | * ServerErrorResponse 59 | * 60 | * @return 响应对象 61 | */ 62 | public static FullHttpResponse getServerErrorResponse() { 63 | JSONObject object = new JSONObject(); 64 | object.put("code", 500); 65 | object.put("message", "Server Internal Error!"); 66 | return render(object, RenderType.JSON); 67 | } 68 | 69 | /** 70 | * ErrorResponse 71 | * 72 | * @param errorMessage 错误信息 73 | * @return 响应对象 74 | */ 75 | public static FullHttpResponse getErrorResponse(String errorMessage) { 76 | JSONObject object = new JSONObject(); 77 | object.put("code", 300); 78 | object.put("message", errorMessage); 79 | return render(object, RenderType.JSON); 80 | } 81 | 82 | /** 83 | * BlockedResponse 84 | * 85 | * @return 响应对象 86 | */ 87 | public static FullHttpResponse getBlockedResponse() { 88 | JSONObject object = new JSONObject(); 89 | object.put("code", 1000); 90 | object.put("message", "Blocked by user defined interceptor"); 91 | return render(object, RenderType.JSON); 92 | } 93 | 94 | /** 95 | * 转换byte 96 | * 97 | * @param content 内容 98 | * @return 响应对象 99 | */ 100 | private static byte[] getBytes(Object content) { 101 | if (content == null) { 102 | return EMPTY_CONTENT.getBytes(CharsetUtil.UTF_8); 103 | } 104 | String data = content.toString(); 105 | data = (data == null || data.trim().length() == 0) ? EMPTY_CONTENT : data; 106 | return data.getBytes(CharsetUtil.UTF_8); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/view/HtmlKeyHolder.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.view; 2 | 3 | /** 4 | * @author houyi 5 | **/ 6 | public interface HtmlKeyHolder { 7 | 8 | /** 9 | * 未转义 10 | */ 11 | String START_NO_ESCAPE = "#["; 12 | 13 | /** 14 | * 对[转义 15 | */ 16 | String START_ESCAPE = "#\\["; 17 | 18 | String END = "]"; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/view/Page404.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.view; 2 | 3 | 4 | import cn.hutool.core.util.StrUtil; 5 | 6 | /** 7 | * @author houyi 8 | **/ 9 | public final class Page404 { 10 | 11 | private Page404() { 12 | 13 | } 14 | 15 | public static final String HTML; 16 | 17 | static { 18 | StringBuffer sb = new StringBuffer(); 19 | sb.append("").append(StrUtil.CRLF) 20 | .append("").append(StrUtil.CRLF) 21 | .append("").append(StrUtil.CRLF) 22 | .append(StrUtil.TAB).append("").append(StrUtil.CRLF) 23 | .append(StrUtil.TAB).append("404-Resource Not Found").append(StrUtil.CRLF) 24 | .append("").append(StrUtil.CRLF) 25 | .append("").append(StrUtil.CRLF) 26 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 27 | .append(StrUtil.TAB).append(StrUtil.TAB).append("Resource Not Found!").append(StrUtil.CRLF) 28 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 29 | .append("").append(StrUtil.CRLF) 30 | .append(""); 31 | HTML = sb.toString(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/view/Page500.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.view; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | 5 | /** 6 | * @author houyi 7 | **/ 8 | public final class Page500 { 9 | 10 | private Page500() { 11 | 12 | } 13 | 14 | public static final String HTML; 15 | 16 | static { 17 | StringBuffer sb = new StringBuffer(); 18 | sb.append("").append(StrUtil.CRLF) 19 | .append("").append(StrUtil.CRLF) 20 | .append("").append(StrUtil.CRLF) 21 | .append(StrUtil.TAB).append("").append(StrUtil.CRLF) 22 | .append(StrUtil.TAB).append("500-Server Internal Error").append(StrUtil.CRLF) 23 | .append("").append(StrUtil.CRLF) 24 | .append("").append(StrUtil.CRLF) 25 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 26 | .append(StrUtil.TAB).append(StrUtil.TAB).append("Server Internal Error!").append(StrUtil.CRLF) 27 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 28 | .append("").append(StrUtil.CRLF) 29 | .append(""); 30 | HTML = sb.toString(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/view/PageError.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.view; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | 5 | /** 6 | * @author houyi 7 | **/ 8 | public final class PageError { 9 | 10 | private PageError() { 11 | 12 | } 13 | 14 | public static final String HTML; 15 | 16 | static { 17 | StringBuffer sb = new StringBuffer(); 18 | sb.append("").append(StrUtil.CRLF) 19 | .append("").append(StrUtil.CRLF) 20 | .append("").append(StrUtil.CRLF) 21 | .append(StrUtil.TAB).append("").append(StrUtil.CRLF) 22 | .append(StrUtil.TAB).append("Error Occur").append(StrUtil.CRLF) 23 | .append("").append(StrUtil.CRLF) 24 | .append("").append(StrUtil.CRLF) 25 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 26 | .append(StrUtil.TAB).append(StrUtil.TAB).append("

").append("Error Occur,Cause:").append("

").append(StrUtil.CRLF) 27 | .append(StrUtil.TAB).append(StrUtil.TAB).append("

").append(HtmlKeyHolder.START_NO_ESCAPE + "errorMessage" + HtmlKeyHolder.END).append("

").append(StrUtil.CRLF) 28 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 29 | .append("").append(StrUtil.CRLF) 30 | .append(""); 31 | HTML = sb.toString(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/http/view/PageIndex.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.http.view; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | 5 | /** 6 | * @author houyi 7 | **/ 8 | public final class PageIndex { 9 | 10 | private PageIndex() { 11 | 12 | } 13 | 14 | public static final String HTML; 15 | 16 | static { 17 | StringBuffer sb = new StringBuffer(); 18 | sb.append("").append(StrUtil.CRLF) 19 | .append("").append(StrUtil.CRLF) 20 | .append("").append(StrUtil.CRLF) 21 | .append(StrUtil.TAB).append("").append(StrUtil.CRLF) 22 | .append(StrUtil.TAB).append("bitchat").append(StrUtil.CRLF) 23 | .append("").append(StrUtil.CRLF) 24 | .append("").append(StrUtil.CRLF) 25 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 26 | .append(StrUtil.TAB).append(StrUtil.TAB).append("Welcome to bitchat!").append(StrUtil.CRLF) 27 | .append(StrUtil.TAB).append("
").append(StrUtil.CRLF) 28 | .append("").append(StrUtil.CRLF) 29 | .append(""); 30 | HTML = sb.toString(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/lang/BeanMapper.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.lang; 2 | 3 | import ma.glasnost.orika.MapperFacade; 4 | import ma.glasnost.orika.MapperFactory; 5 | import ma.glasnost.orika.impl.DefaultMapperFactory; 6 | import ma.glasnost.orika.metadata.Type; 7 | import ma.glasnost.orika.metadata.TypeFactory; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * 简单封装orika, 实现深度的BeanOfClasssA<->BeanOfClassB复制 13 | * 不要用Apache Common BeanUtils进行类复制,每次就行反射查询对象的属性列表, 非常缓慢. 14 | * 参考:http://yuncode.net/code/c_53a3a3146869e59 15 | * 参考:SpringSide 16 | * 17 | * @author limu.zl 18 | */ 19 | @SuppressWarnings("ALL") 20 | public class BeanMapper { 21 | 22 | private static MapperFacade mapper; 23 | 24 | static { 25 | MapperFactory mapperFactory = new DefaultMapperFactory 26 | .Builder() 27 | .mapNulls(false) 28 | .build(); 29 | mapper = mapperFactory.getMapperFacade(); 30 | } 31 | 32 | /** 33 | * 简单的复制出新类型对象. 34 | *

35 | * 通过source.getClass() 获得源Class 36 | */ 37 | public static D map(S source, Class destinationClass) { 38 | return mapper.map(source, destinationClass); 39 | } 40 | 41 | 42 | /** 43 | * 简单的复制出新类型对象. 44 | *

45 | * 通过source.getClass() 获得源Class 46 | */ 47 | public static void map(S source, D d) { 48 | mapper.map(source, d); 49 | } 50 | 51 | 52 | /** 53 | * 极致性能的复制出新类型对象. 54 | *

55 | * 预先通过BeanMapper.getType() 静态获取并缓存Type类型,在此处传入 56 | */ 57 | public static D map(S source, Type sourceType, Type destinationType) { 58 | return mapper.map(source, sourceType, destinationType); 59 | } 60 | 61 | /** 62 | * 简单的复制出新对象列表到ArrayList 63 | *

64 | * 不建议使用mapper.mapAsList(Iterable,Class)接口, sourceClass需要反射,实在有点慢 65 | */ 66 | public static List mapList(Iterable sourceList, Class sourceClass, Class destinationClass) { 67 | return mapper.mapAsList(sourceList, TypeFactory.valueOf(sourceClass), TypeFactory.valueOf(destinationClass)); 68 | } 69 | 70 | /** 71 | * 极致性能的复制出新类型对象到ArrayList. 72 | *

73 | * 预先通过BeanMapper.getType() 静态获取并缓存Type类型,在此处传入 74 | */ 75 | public static List mapList(Iterable sourceList, Type sourceType, Type destinationType) { 76 | return mapper.mapAsList(sourceList, sourceType, destinationType); 77 | } 78 | 79 | /** 80 | * 简单复制出新对象列表到数组 81 | *

82 | * 通过source.getComponentType() 获得源Class 83 | */ 84 | public static D[] mapArray(final D[] destination, final S[] source, final Class destinationClass) { 85 | return mapper.mapAsArray(destination, source, destinationClass); 86 | } 87 | 88 | /** 89 | * 极致性能的复制出新类型对象到数组 90 | *

91 | * 预先通过BeanMapper.getType() 静态获取并缓存Type类型,在此处传入 92 | */ 93 | public static D[] mapArray(D[] destination, S[] source, Type sourceType, Type destinationType) { 94 | return mapper.mapAsArray(destination, source, sourceType, destinationType); 95 | } 96 | 97 | /** 98 | * 预先获取orika转换所需要的Type,避免每次转换. 99 | */ 100 | public static Type getType(final Class rawType) { 101 | return TypeFactory.valueOf(rawType); 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/lang/config/BaseConfig.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.lang.config; 2 | 3 | import org.aeonbits.owner.Config; 4 | 5 | /** 6 | * @author houyi 7 | */ 8 | @Config.Sources({"classpath:config/base-config.properties"}) 9 | public interface BaseConfig extends Config { 10 | 11 | /** 12 | * get the base package 13 | * 14 | * @return the base package 15 | */ 16 | @DefaultValue("io.bitchat") 17 | String basePackage(); 18 | 19 | /** 20 | * get the server port 21 | * 22 | * @return the server port 23 | */ 24 | @DefaultValue("8864") 25 | int serverPort(); 26 | 27 | /** 28 | * get the server internal error 29 | * 30 | * @return the server internal error 31 | */ 32 | @DefaultValue("Server Internal Error") 33 | String serverInternalError(); 34 | 35 | /** 36 | * get the readerIdleTime 37 | * 38 | * @return the readerIdleTime 39 | */ 40 | @DefaultValue("60") 41 | int readerIdleTime(); 42 | 43 | /** 44 | * get the pingInterval 45 | * 46 | * @return the pingInterval 47 | */ 48 | @DefaultValue("20") 49 | int pingInterval(); 50 | 51 | } 52 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/lang/config/ConfigFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.lang.config; 2 | 3 | import org.aeonbits.owner.Config; 4 | 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | /** 9 | *

10 | * A Config Factory which can 11 | * provide singleton Config instance 12 | *

13 | * 14 | * @author houyi 15 | */ 16 | public class ConfigFactory { 17 | 18 | private static Map, Object> pool = new ConcurrentHashMap<>(); 19 | 20 | private ConfigFactory() { 21 | 22 | } 23 | 24 | @SuppressWarnings("unchecked") 25 | public static T getConfig(Class clazz) { 26 | T config = (T) pool.get(clazz); 27 | if (null == config) { 28 | synchronized (ConfigFactory.class) { 29 | config = (T) pool.get(clazz); 30 | if (null == config) { 31 | config = org.aeonbits.owner.ConfigFactory.create(clazz); 32 | pool.putIfAbsent(clazz, config); 33 | } 34 | } 35 | } 36 | return config; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/lang/config/SnowflakeConfig.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.lang.config; 2 | 3 | import org.aeonbits.owner.Config; 4 | 5 | /** 6 | * @author houyi 7 | */ 8 | @Config.Sources({"classpath:config/snowflake-config.properties"}) 9 | public interface SnowflakeConfig extends Config { 10 | 11 | /** 12 | * get the worker id of the server 13 | * 14 | * @return the worker id 15 | */ 16 | @DefaultValue("1") 17 | long workerId(); 18 | 19 | /** 20 | * get the data center id of the server 21 | * 22 | * @return the data center id 23 | */ 24 | @DefaultValue("1") 25 | long dataCenterId(); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/lang/config/ThreadPoolConfig.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.lang.config; 2 | 3 | import org.aeonbits.owner.Config; 4 | 5 | /** 6 | * @author houyi 7 | */ 8 | @Config.Sources({"classpath:config/thread-pool-config.properties"}) 9 | public interface ThreadPoolConfig extends Config { 10 | 11 | /** 12 | * get the core pool size 13 | * 14 | * @return the core pool size 15 | */ 16 | @DefaultValue("10") 17 | int corePoolSize(); 18 | 19 | /** 20 | * get the max pool size 21 | * 22 | * @return the max pool size 23 | */ 24 | @DefaultValue("20") 25 | int maxPoolSize(); 26 | 27 | /** 28 | * get the temporary thread idle time 29 | * unit: seconds 30 | * 31 | * @return the temporary thread idle time 32 | */ 33 | @DefaultValue("5") 34 | long idealTime(); 35 | 36 | /** 37 | * get the blocking queue cap 38 | * 39 | * @return the blocking queue cap 40 | */ 41 | @DefaultValue("100") 42 | int blockingQueueCap(); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/lang/constants/ResultCode.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.lang.constants; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * @author houyi 7 | */ 8 | @Getter 9 | public enum ResultCode { 10 | 11 | SUCCESS(200, "SUCCESS"), 12 | INTERNAL_ERROR(301, "INTERNAL_ERROR"), 13 | RESOURCE_NOT_FOUND(404, "RESOURCE_NOT_FOUND"), 14 | BIZ_FAIL(3001, "BIZ_FAIL"), 15 | RECORD_ALREADY_EXISTS(4001, "RECORD_ALREADY_EXISTS"), 16 | PARAM_INVALID(4002, "PARAM_INVALID"), 17 | ; 18 | 19 | private int code; 20 | private String message; 21 | 22 | ResultCode(int code, String message) { 23 | this.code = code; 24 | this.message = message; 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/lang/constants/ServiceName.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.lang.constants; 2 | 3 | /** 4 | * @author houyi 5 | */ 6 | public interface ServiceName { 7 | 8 | String HEART_BEAT = "heartBeat"; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/lang/util/GenericsUtil.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.lang.util; 2 | 3 | import cn.hutool.core.util.NetUtil; 4 | 5 | import java.util.LinkedHashSet; 6 | 7 | /** 8 | * @author houyi 9 | */ 10 | public class GenericsUtil { 11 | 12 | /** 13 | * get local ip V4 14 | * 15 | * @return ipV4 16 | */ 17 | public static String getLocalIpV4() { 18 | LinkedHashSet ipV4Set = NetUtil.localIpv4s(); 19 | return ipV4Set.isEmpty() ? "" : ipV4Set.toArray()[0].toString(); 20 | } 21 | 22 | public static String unifiedProcessorName(String name) { 23 | return name == null ? null : name.toLowerCase(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/Command.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | 6 | import java.io.Serializable; 7 | import java.util.Map; 8 | 9 | /** 10 | *

11 | * A command model 12 | *

13 | * 14 | * @author houyi 15 | */ 16 | @Data 17 | @NoArgsConstructor 18 | public class Command implements Serializable { 19 | 20 | /** 21 | * the name of the command 22 | */ 23 | private String commandName; 24 | 25 | /** 26 | * the command content 27 | */ 28 | private Map content; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/DefaultPacket.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet; 2 | 3 | 4 | import io.bitchat.packet.factory.PacketFactory; 5 | import io.bitchat.serialize.SerializeAlgorithm; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | *

10 | * the only implement of {@link Packet} 11 | * user can create a packet by {@link PacketFactory} 12 | *

13 | * 14 | * @author houyi 15 | */ 16 | @NoArgsConstructor 17 | public class DefaultPacket extends Packet { 18 | 19 | @Override 20 | public byte algorithm() { 21 | return SerializeAlgorithm.PROTO_STUFF.getType(); 22 | } 23 | 24 | @Override 25 | public boolean handleAsync() { 26 | return false; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/Packet.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | *

9 | * A base abstract packet 10 | * Each Detailed Packet should extends the {@link Packet} 11 | *

12 | * 13 | *

14 | * The structure of a Packet is like blow: 15 | * +----------+----------+----------------------------+ 16 | * | size | value | intro | 17 | * +----------+----------+----------------------------+ 18 | * | 1 bytes | 0xBC | magic number | 19 | * | 1 bytes | | serialize algorithm | 20 | * | 1 bytes | | the type 1:req 2:res 3:cmd| 21 | * | 4 bytes | | content length | 22 | * | ? bytes | | the content | 23 | * +----------+----------+----------------------------+ 24 | *

25 | * 26 | * 27 | * @author houyi 28 | */ 29 | @Data 30 | public abstract class Packet implements Serializable { 31 | 32 | /** 33 | * the magic number of packet 34 | * 0xBC means BitChat 35 | */ 36 | public static byte PACKET_MAGIC = (byte) 0xBC; 37 | 38 | /** 39 | * magic number 40 | */ 41 | private byte magic = Packet.PACKET_MAGIC; 42 | 43 | /** 44 | * the unique id 45 | */ 46 | private long id; 47 | 48 | /** 49 | *

50 | * specify the type of the packet 51 | * 1-request 52 | * 2-response 53 | * 3-command 54 | *

55 | */ 56 | private byte type; 57 | 58 | /** 59 | * the request model 60 | */ 61 | private Request request; 62 | 63 | /** 64 | * the response 65 | */ 66 | private Payload payload; 67 | 68 | /** 69 | * the command 70 | */ 71 | private Command command; 72 | 73 | /** 74 | *

75 | * the serialize algorithm 76 | *

77 | */ 78 | public byte algorithm = algorithm(); 79 | 80 | /** 81 | * whether handler the packet 82 | * in async way 83 | */ 84 | private boolean handleAsync = handleAsync(); 85 | 86 | /** 87 | *

88 | * specify the serialize algorithm 89 | *

90 | * 91 | *

92 | * the packet will be encode and decode 93 | * by the serialize algorithm 94 | *

95 | * 96 | * @return the serialize algorithm 97 | */ 98 | public abstract byte algorithm(); 99 | 100 | /** 101 | *

102 | * whether handle the packet 103 | * in async way 104 | *

105 | * 106 | * @return the byte value of whether 107 | * handle packet in async way 108 | */ 109 | public abstract boolean handleAsync(); 110 | 111 | } 112 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/PacketType.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet; 2 | 3 | /** 4 | *

5 | * PacketType 6 | *

7 | * 8 | * @author houyi 9 | */ 10 | public interface PacketType { 11 | 12 | /** 13 | * packet type: request 14 | */ 15 | byte PACKET_TYPE_REQUEST = (byte) 1; 16 | 17 | /** 18 | * packet type: response 19 | */ 20 | byte PACKET_TYPE_RESPONSE = (byte) 2; 21 | 22 | /** 23 | * packet type: command 24 | */ 25 | byte PACKET_TYPE_COMMAND = (byte) 3; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/Payload.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @author houyi 10 | */ 11 | @Data 12 | @NoArgsConstructor 13 | public class Payload implements Serializable { 14 | 15 | private boolean success; 16 | 17 | private int code; 18 | 19 | private String msg; 20 | 21 | private Object result; 22 | 23 | public void setErrorMsg(int code, String msg) { 24 | this.success = false; 25 | this.code = code; 26 | this.msg = msg; 27 | } 28 | 29 | public void setSuccessMsg(int code, String msg) { 30 | this.success = true; 31 | this.code = code; 32 | this.msg = msg; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/PendingPackets.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.Map; 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | /** 10 | * @author houyi 11 | */ 12 | @Slf4j 13 | public class PendingPackets { 14 | 15 | private static Map> pendingRequests = new ConcurrentHashMap<>(); 16 | 17 | public static void add(Long id, CompletableFuture promise) { 18 | pendingRequests.putIfAbsent(id, promise); 19 | } 20 | 21 | public static CompletableFuture remove(Long id) { 22 | return pendingRequests.remove(id); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/Request.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | 6 | import java.io.Serializable; 7 | import java.util.Map; 8 | 9 | /** 10 | *

11 | * A request model 12 | *

13 | * 14 | * @author houyi 15 | */ 16 | @Data 17 | @NoArgsConstructor 18 | public class Request implements Serializable { 19 | 20 | /** 21 | * the name of the service 22 | */ 23 | private String serviceName; 24 | 25 | /** 26 | * the name of the method 27 | * if null will use doProcess() 28 | * as default 29 | */ 30 | private String methodName; 31 | 32 | /** 33 | * the request params 34 | */ 35 | private Map params; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/codec/PacketDecoder.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.codec; 2 | 3 | import io.bitchat.packet.DefaultPacket; 4 | import io.bitchat.packet.Packet; 5 | import io.bitchat.serialize.DefaultSerializerChooser; 6 | import io.bitchat.serialize.Serializer; 7 | import io.bitchat.serialize.SerializerChooser; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.handler.codec.DecoderException; 11 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 12 | 13 | /** 14 | *

15 | * the header field contains: 16 | *

    17 | *
  • magic---1byte
  • 18 | *
  • algorithm---1byte
  • 19 | *
  • type---1byte
  • 20 | *
21 | * so the length field offset is 3 22 | * the length field is 4 bytes 23 | *

24 | *

25 | * see {@link Packet} for detail 26 | * 27 | * @author houyi 28 | */ 29 | public class PacketDecoder extends LengthFieldBasedFrameDecoder { 30 | 31 | private SerializerChooser chooser; 32 | 33 | public static final int MAX_FRAME_LENGTH = Integer.MAX_VALUE; 34 | 35 | public static final int LENGTH_FIELD_OFFSET = 3; 36 | 37 | public static final int LENGTH_FIELD_LENGTH = 4; 38 | 39 | public PacketDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, SerializerChooser chooser) { 40 | super(maxFrameLength, lengthFieldOffset, lengthFieldLength); 41 | this.chooser = chooser != null ? chooser : DefaultSerializerChooser.getInstance(); 42 | } 43 | 44 | @Override 45 | protected Packet decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { 46 | // get the whole tcp package 47 | // decoded by super class 48 | ByteBuf decodedIn = (ByteBuf) super.decode(ctx, in); 49 | if (decodedIn == null) { 50 | return null; 51 | } 52 | 53 | // check the header length 54 | int leastPacketLen = LENGTH_FIELD_OFFSET + LENGTH_FIELD_LENGTH; 55 | if (decodedIn.readableBytes() < leastPacketLen) { 56 | throw new DecoderException("packet header length less than leastPacketLen=" + leastPacketLen); 57 | } 58 | 59 | byte magic = decodedIn.readByte(); 60 | byte algorithm = decodedIn.readByte(); 61 | Serializer serializer = chooser.choose(algorithm); 62 | if (serializer == null) { 63 | throw new DecoderException("serialize algorithm is invalid"); 64 | } 65 | 66 | byte type = decodedIn.readByte(); 67 | int len = decodedIn.readInt(); 68 | if (decodedIn.readableBytes() != len) { 69 | throw new DecoderException("packet content marked length is not equals to actual length"); 70 | } 71 | 72 | // read content 73 | byte[] content = new byte[len]; 74 | decodedIn.readBytes(content); 75 | 76 | return serializer.deserialize(content, DefaultPacket.class); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/codec/PacketEncoder.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.codec; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import io.bitchat.packet.Command; 5 | import io.bitchat.packet.Packet; 6 | import io.bitchat.packet.PacketType; 7 | import io.bitchat.packet.Request; 8 | import io.bitchat.serialize.DefaultSerializerChooser; 9 | import io.bitchat.serialize.SerializeAlgorithm; 10 | import io.bitchat.serialize.Serializer; 11 | import io.bitchat.serialize.SerializerChooser; 12 | import io.netty.buffer.ByteBuf; 13 | import io.netty.channel.ChannelHandlerContext; 14 | import io.netty.handler.codec.MessageToByteEncoder; 15 | import lombok.extern.slf4j.Slf4j; 16 | 17 | /** 18 | *

19 | * A Generic Packet Encoder 20 | *

21 | * 22 | * @author houyi 23 | */ 24 | @Slf4j 25 | public class PacketEncoder extends MessageToByteEncoder { 26 | 27 | private SerializerChooser chooser; 28 | 29 | public PacketEncoder(SerializerChooser chooser) { 30 | this.chooser = chooser != null ? chooser : DefaultSerializerChooser.getInstance(); 31 | } 32 | 33 | @Override 34 | protected void encode(ChannelHandlerContext channelHandlerContext, Packet packet, ByteBuf in) throws Exception { 35 | // check the packet 36 | if (!checkPacket(packet)) { 37 | throw new RuntimeException("checkPacket failed!"); 38 | } 39 | byte algorithm = packet.getAlgorithm(); 40 | Serializer serializer = chooser.choose(algorithm); 41 | // get packet content bytes 42 | byte[] content = serializer.serialize(packet); 43 | // do encode 44 | in.writeByte(packet.getMagic()); 45 | in.writeByte(algorithm); 46 | in.writeByte(packet.getType()); 47 | in.writeInt(content.length); 48 | in.writeBytes(content); 49 | } 50 | 51 | private boolean checkPacket(Packet packet) { 52 | byte algorithm = packet.getAlgorithm(); 53 | SerializeAlgorithm serializeAlgorithm = SerializeAlgorithm.getEnum(algorithm); 54 | if (serializeAlgorithm == null) { 55 | log.error("algorithm={} is invalid with packet={}", algorithm, packet); 56 | return false; 57 | } 58 | byte type = packet.getType(); 59 | if (type != PacketType.PACKET_TYPE_REQUEST && type != PacketType.PACKET_TYPE_RESPONSE && type != PacketType.PACKET_TYPE_COMMAND) { 60 | log.error("packet type={} is invalid with packet={}", type, packet); 61 | return false; 62 | } 63 | long id = packet.getId(); 64 | if (type != PacketType.PACKET_TYPE_COMMAND && id == 0) { 65 | log.error("id=0 is invalid with packet={}", packet); 66 | return false; 67 | } 68 | Request request = packet.getRequest(); 69 | if (type == PacketType.PACKET_TYPE_REQUEST) { 70 | if (request == null) { 71 | log.error("packet request can not be null with packet={}", packet); 72 | return false; 73 | } 74 | String serviceName = request.getServiceName(); 75 | if (StrUtil.isBlank(serviceName)) { 76 | log.error("serviceName can not be blank with packet={}", packet); 77 | return false; 78 | } 79 | } 80 | Command command = packet.getCommand(); 81 | if (type == PacketType.PACKET_TYPE_COMMAND) { 82 | if (command == null) { 83 | log.error("packet command can not be null with packet={}", packet); 84 | return false; 85 | } 86 | String commandName = command.getCommandName(); 87 | if (StrUtil.isBlank(commandName)) { 88 | log.error("commandName can not be blank with packet={}", packet); 89 | return false; 90 | } 91 | } 92 | return true; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/ctx/CommandProcessorContext.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.ctx; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.core.lang.ClassScaner; 5 | import cn.hutool.core.lang.Singleton; 6 | import cn.hutool.core.util.StrUtil; 7 | import io.bitchat.core.init.InitAble; 8 | import io.bitchat.core.init.InitOrder; 9 | import io.bitchat.lang.config.BaseConfig; 10 | import io.bitchat.lang.config.ConfigFactory; 11 | import io.bitchat.lang.util.GenericsUtil; 12 | import io.bitchat.packet.Command; 13 | import io.bitchat.packet.processor.CommandProcessor; 14 | import io.bitchat.packet.processor.Processor; 15 | import io.netty.channel.ChannelHandlerContext; 16 | import lombok.extern.slf4j.Slf4j; 17 | 18 | import java.lang.reflect.Modifier; 19 | import java.util.Map; 20 | import java.util.Set; 21 | import java.util.concurrent.ConcurrentHashMap; 22 | import java.util.concurrent.atomic.AtomicBoolean; 23 | 24 | /** 25 | * handle the command 26 | * 27 | * @author houyi 28 | */ 29 | @Slf4j 30 | @InitOrder(2) 31 | public class CommandProcessorContext implements InitAble { 32 | 33 | private static AtomicBoolean init = new AtomicBoolean(); 34 | 35 | private static Map processorHolder = new ConcurrentHashMap<>(); 36 | 37 | private CommandProcessorContext() { 38 | 39 | } 40 | 41 | public static CommandProcessorContext getInstance() { 42 | return Singleton.get(CommandProcessorContext.class); 43 | } 44 | 45 | @Override 46 | public void init() { 47 | initCommandProcessor(); 48 | } 49 | 50 | public void process(ChannelHandlerContext ctx, Command request) { 51 | String commandName = GenericsUtil.unifiedProcessorName(request.getCommandName()); 52 | CommandProcessor processor = commandName == null ? null : processorHolder.get(commandName); 53 | if (processor == null) { 54 | log.warn("CommandProcessor not found with commandName={}", commandName); 55 | return; 56 | } 57 | processor.process(ctx, request); 58 | } 59 | 60 | private void initCommandProcessor() { 61 | if (!init.compareAndSet(false, true)) { 62 | return; 63 | } 64 | BaseConfig baseConfig = ConfigFactory.getConfig(BaseConfig.class); 65 | Set> classSet = ClassScaner.scanPackageBySuper(baseConfig.basePackage(), CommandProcessor.class); 66 | if (CollectionUtil.isEmpty(classSet)) { 67 | log.warn("[CommandProcessorContext] No CommandProcessor found"); 68 | return; 69 | } 70 | for (Class clazz : classSet) { 71 | if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()) || !CommandProcessor.class.isAssignableFrom(clazz)) { 72 | continue; 73 | } 74 | try { 75 | // check whether the class has @Processor annotation 76 | // use name specified by @Processor first 77 | Processor processor = clazz.getAnnotation(Processor.class); 78 | String commandName = (processor != null && StrUtil.isNotBlank(processor.name())) ? GenericsUtil.unifiedProcessorName(processor.name()) : clazz.getName(); 79 | cacheCommandProcessor(commandName, clazz); 80 | } catch (Exception e) { 81 | log.warn("[CommandProcessorContext] cacheCommandProcessor failed", e); 82 | } 83 | } 84 | } 85 | 86 | @SuppressWarnings("unchecked") 87 | private void cacheCommandProcessor(String commandName, Class clazz) { 88 | if (processorHolder.containsKey(commandName)) { 89 | log.warn("[CommandProcessorContext] [Warning] commandName=[{}], CommandProcessor=[{}] already exists, please check the CommandProcessor", commandName, clazz.getCanonicalName()); 90 | return; 91 | } 92 | log.info("[CommandProcessorContext] Found commandName=[{}], CommandProcessor=[{}]", commandName, clazz.getCanonicalName()); 93 | // Each implements Class of CommandProcessor should have a NoArgument Constructor 94 | CommandProcessor requestProcessor = Singleton.get((Class) clazz); 95 | processorHolder.putIfAbsent(commandName, requestProcessor); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/ctx/RequestProcessorContext.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.ctx; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.core.lang.ClassScaner; 5 | import cn.hutool.core.lang.Singleton; 6 | import cn.hutool.core.text.StrFormatter; 7 | import cn.hutool.core.util.StrUtil; 8 | import io.bitchat.core.init.InitAble; 9 | import io.bitchat.core.init.InitOrder; 10 | import io.bitchat.lang.config.BaseConfig; 11 | import io.bitchat.lang.config.ConfigFactory; 12 | import io.bitchat.lang.constants.ResultCode; 13 | import io.bitchat.lang.util.GenericsUtil; 14 | import io.bitchat.packet.Payload; 15 | import io.bitchat.packet.factory.PayloadFactory; 16 | import io.bitchat.packet.Request; 17 | import io.bitchat.packet.processor.Processor; 18 | import io.bitchat.packet.processor.RequestProcessor; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import lombok.extern.slf4j.Slf4j; 21 | 22 | import java.lang.reflect.Modifier; 23 | import java.util.Map; 24 | import java.util.Set; 25 | import java.util.concurrent.ConcurrentHashMap; 26 | import java.util.concurrent.atomic.AtomicBoolean; 27 | 28 | /** 29 | * handle the request 30 | * 31 | * @author houyi 32 | */ 33 | @Slf4j 34 | @InitOrder(1) 35 | public class RequestProcessorContext implements InitAble { 36 | 37 | private static AtomicBoolean init = new AtomicBoolean(); 38 | 39 | private static Map processorHolder = new ConcurrentHashMap<>(); 40 | 41 | private RequestProcessorContext() { 42 | 43 | } 44 | 45 | public static RequestProcessorContext getInstance() { 46 | return Singleton.get(RequestProcessorContext.class); 47 | } 48 | 49 | @Override 50 | public void init() { 51 | initRequestProcessor(); 52 | } 53 | 54 | public Payload process(ChannelHandlerContext ctx, Request request) { 55 | String serviceName = GenericsUtil.unifiedProcessorName(request.getServiceName()); 56 | RequestProcessor processor = serviceName == null ? null : processorHolder.get(serviceName); 57 | if (processor == null) { 58 | return PayloadFactory.newErrorPayload(ResultCode.RESOURCE_NOT_FOUND.getCode(), StrFormatter.format("RequestProcessor not found with serviceName={}", serviceName)); 59 | } 60 | return processor.process(ctx, request); 61 | } 62 | 63 | private void initRequestProcessor() { 64 | if (!init.compareAndSet(false, true)) { 65 | return; 66 | } 67 | BaseConfig baseConfig = ConfigFactory.getConfig(BaseConfig.class); 68 | Set> classSet = ClassScaner.scanPackageBySuper(baseConfig.basePackage(), RequestProcessor.class); 69 | if (CollectionUtil.isEmpty(classSet)) { 70 | log.warn("[RequestProcessorContext] No RequestProcessor found"); 71 | return; 72 | } 73 | for (Class clazz : classSet) { 74 | if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()) || !RequestProcessor.class.isAssignableFrom(clazz)) { 75 | continue; 76 | } 77 | try { 78 | // check whether the class has @Processor annotation 79 | // use name specified by @Processor first 80 | Processor processor = clazz.getAnnotation(Processor.class); 81 | String serviceName = (processor != null && StrUtil.isNotBlank(processor.name())) ? GenericsUtil.unifiedProcessorName(processor.name()) : clazz.getName(); 82 | cacheRequestProcessor(serviceName, clazz); 83 | } catch (Exception e) { 84 | log.warn("[RequestProcessorContext] cacheRequestProcessor failed", e); 85 | } 86 | } 87 | } 88 | 89 | @SuppressWarnings("unchecked") 90 | private void cacheRequestProcessor(String serviceName, Class clazz) { 91 | if (processorHolder.containsKey(serviceName)) { 92 | log.warn("[RequestProcessorContext] [Warning] serviceName=[{}], RequestProcessor=[{}] already exists, please check the RequestProcessor", serviceName, clazz.getCanonicalName()); 93 | return; 94 | } 95 | log.info("[RequestProcessorContext] Found serviceName=[{}], RequestProcessor=[{}]", serviceName, clazz.getCanonicalName()); 96 | // Each implements Class of RequestProcess should have a NoArgument Constructor 97 | RequestProcessor requestProcessor = Singleton.get((Class) clazz); 98 | processorHolder.putIfAbsent(serviceName, requestProcessor); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/factory/CommandFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.factory; 2 | 3 | import io.bitchat.packet.Command; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @author houyi 9 | */ 10 | public class CommandFactory { 11 | 12 | public static Command newCommand(String commandName) { 13 | return CommandFactory.newCommand(commandName, null); 14 | } 15 | 16 | public static Command newCommand(String commandName, Map content) { 17 | Command command = new Command(); 18 | command.setCommandName(commandName); 19 | command.setContent(content); 20 | return command; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/factory/PacketFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.factory; 2 | 3 | import io.bitchat.core.id.IdFactory; 4 | import io.bitchat.core.id.SnowflakeIdFactory; 5 | import io.bitchat.lang.constants.ServiceName; 6 | import io.bitchat.packet.*; 7 | 8 | /** 9 | * @author houyi 10 | */ 11 | public class PacketFactory { 12 | 13 | private static IdFactory idFactory = SnowflakeIdFactory.getInstance(); 14 | 15 | public static Packet newRequestPacket(Request request, long id) { 16 | Packet packet = new DefaultPacket(); 17 | packet.setId(id); 18 | packet.setType(PacketType.PACKET_TYPE_REQUEST); 19 | packet.setRequest(request); 20 | return packet; 21 | } 22 | 23 | public static Packet newResponsePacket(Payload payload, long id) { 24 | Packet packet = new DefaultPacket(); 25 | packet.setId(id); 26 | packet.setType(PacketType.PACKET_TYPE_RESPONSE); 27 | packet.setPayload(payload); 28 | return packet; 29 | } 30 | 31 | public static Packet newCmdPacket(Command command) { 32 | // command packet can not provide id 33 | Packet packet = new DefaultPacket(); 34 | packet.setType(PacketType.PACKET_TYPE_COMMAND); 35 | packet.setCommand(command); 36 | return packet; 37 | } 38 | 39 | public static Packet newPingPacket() { 40 | Request request = new Request(); 41 | request.setServiceName(ServiceName.HEART_BEAT); 42 | Packet packet = PacketFactory.newRequestPacket(request, idFactory.nextId()); 43 | packet.setHandleAsync(false); 44 | return packet; 45 | } 46 | 47 | 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/factory/PayloadFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.factory; 2 | 3 | import io.bitchat.lang.constants.ResultCode; 4 | import io.bitchat.packet.Payload; 5 | 6 | /** 7 | * @author houyi 8 | */ 9 | public class PayloadFactory { 10 | 11 | public static Payload newErrorPayload(int code, String msg) { 12 | Payload payload = new Payload(); 13 | payload.setErrorMsg(code, msg); 14 | return payload; 15 | } 16 | 17 | public static Payload newSuccessPayload() { 18 | Payload payload = new Payload(); 19 | payload.setSuccessMsg(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage()); 20 | return payload; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/factory/RequestFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.factory; 2 | 3 | import io.bitchat.packet.Request; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @author houyi 9 | */ 10 | public class RequestFactory { 11 | 12 | public static Request newRequest(String serviceName) { 13 | return RequestFactory.newRequest(serviceName, null); 14 | } 15 | 16 | public static Request newRequest(String serviceName, String methodName) { 17 | return RequestFactory.newRequest(serviceName, methodName, null); 18 | } 19 | 20 | public static Request newRequest(String serviceName, String methodName, Map params) { 21 | Request request = new Request(); 22 | request.setServiceName(serviceName); 23 | request.setMethodName(methodName); 24 | request.setParams(params); 25 | return request; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/interceptor/Interceptor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.interceptor; 2 | 3 | import io.bitchat.packet.Packet; 4 | import io.bitchat.packet.Payload; 5 | import io.bitchat.packet.factory.PayloadFactory; 6 | import io.netty.channel.Channel; 7 | 8 | /** 9 | *

10 | * A pre handle interceptor 11 | *

12 | * 13 | * @author houyi 14 | **/ 15 | public abstract class Interceptor { 16 | 17 | /** 18 | * pre handle method of the interceptor 19 | * 20 | * @param channel the channel 21 | * @param packet the packet 22 | * @return the response 23 | */ 24 | public Payload preHandle(Channel channel, Packet packet) { 25 | return PayloadFactory.newSuccessPayload(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/interceptor/InterceptorBuilder.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.interceptor; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * User can build an active interceptor list 7 | * 8 | * @author houyi 9 | **/ 10 | public interface InterceptorBuilder { 11 | 12 | /** 13 | * build the interceptor 14 | * 15 | * @return the list of the interceptor 16 | */ 17 | List build(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/interceptor/InterceptorHandler.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.interceptor; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import io.bitchat.packet.Packet; 5 | import io.bitchat.packet.Payload; 6 | import io.bitchat.packet.factory.PayloadFactory; 7 | import io.netty.channel.Channel; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author houyi 13 | **/ 14 | public class InterceptorHandler { 15 | 16 | public static Payload preHandle(Channel channel, Packet packet) { 17 | List interceptors = InterceptorProvider.getInterceptors(); 18 | if (CollectionUtil.isEmpty(interceptors)) { 19 | return PayloadFactory.newSuccessPayload(); 20 | } 21 | for (Interceptor interceptor : interceptors) { 22 | Payload payload = interceptor.preHandle(channel, packet); 23 | if (!payload.isSuccess()) { 24 | return payload; 25 | } 26 | } 27 | return PayloadFactory.newSuccessPayload(); 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/interceptor/InterceptorProvider.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.interceptor; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.core.lang.ClassScaner; 5 | import io.bitchat.core.init.InitOrder; 6 | import io.bitchat.lang.config.BaseConfig; 7 | import io.bitchat.lang.config.ConfigFactory; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.Set; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * @author houyi 17 | **/ 18 | public class InterceptorProvider { 19 | 20 | private static volatile boolean loaded = false; 21 | 22 | private static volatile InterceptorBuilder builder = null; 23 | 24 | public static List getInterceptors() { 25 | // get user defined interceptor list by InterceptorBuilder first 26 | if (!loaded) { 27 | synchronized (InterceptorProvider.class) { 28 | if (!loaded) { 29 | BaseConfig baseConfig = ConfigFactory.getConfig(BaseConfig.class); 30 | Set> builders = ClassScaner.scanPackageBySuper(baseConfig.basePackage(), InterceptorBuilder.class); 31 | if (CollectionUtil.isNotEmpty(builders)) { 32 | try { 33 | for (Class cls : builders) { 34 | builder = (InterceptorBuilder) cls.newInstance(); 35 | break; 36 | } 37 | } catch (IllegalAccessException | InstantiationException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | loaded = true; 42 | } 43 | } 44 | } 45 | if (builder != null) { 46 | return builder.build(); 47 | } 48 | // scan the interceptors when can not get any interceptors by InterceptorBuilder 49 | return InterceptorsHolder.interceptors; 50 | } 51 | 52 | static class InterceptorsHolder { 53 | 54 | static List interceptors; 55 | 56 | static { 57 | interceptors = scanInterceptors(); 58 | } 59 | 60 | private static List scanInterceptors() { 61 | BaseConfig baseConfig = ConfigFactory.getConfig(BaseConfig.class); 62 | Set> classSet = ClassScaner.scanPackageBySuper(baseConfig.basePackage(), Interceptor.class); 63 | if (CollectionUtil.isEmpty(classSet)) { 64 | return Collections.emptyList(); 65 | } 66 | List wrappers = new ArrayList<>(classSet.size()); 67 | try { 68 | for (Class cls : classSet) { 69 | Interceptor interceptor = (Interceptor) cls.newInstance(); 70 | insertSorted(wrappers, interceptor); 71 | } 72 | } catch (IllegalAccessException | InstantiationException e) { 73 | e.printStackTrace(); 74 | } 75 | return wrappers.stream() 76 | .map(InterceptorWrapper::getInterceptor) 77 | .collect(Collectors.toList()); 78 | } 79 | 80 | private static void insertSorted(List list, Interceptor interceptor) { 81 | int order = resolveOrder(interceptor); 82 | int idx = 0; 83 | for (; idx < list.size(); idx++) { 84 | if (list.get(idx).getOrder() > order) { 85 | break; 86 | } 87 | } 88 | list.add(idx, new InterceptorWrapper(order, interceptor)); 89 | } 90 | 91 | private static int resolveOrder(Interceptor interceptor) { 92 | if (!interceptor.getClass().isAnnotationPresent(InitOrder.class)) { 93 | return InitOrder.LOWEST_PRECEDENCE; 94 | } else { 95 | return interceptor.getClass().getAnnotation(InitOrder.class).value(); 96 | } 97 | } 98 | 99 | private static class InterceptorWrapper { 100 | private final int order; 101 | private final Interceptor interceptor; 102 | 103 | InterceptorWrapper(int order, Interceptor interceptor) { 104 | this.order = order; 105 | this.interceptor = interceptor; 106 | } 107 | 108 | int getOrder() { 109 | return order; 110 | } 111 | 112 | Interceptor getInterceptor() { 113 | return interceptor; 114 | } 115 | } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/processor/AbstractCommandProcessor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.processor; 2 | 3 | import io.bitchat.packet.Command; 4 | import io.bitchat.packet.ctx.RequestProcessorContext; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | *

12 | * An abstract CommandProcessor 13 | * Every Class extends AbstractCommandProcessor should have a NoArgument Constructor 14 | * see {@link RequestProcessorContext#initRequestProcessor()} 15 | *

16 | * 17 | * @author houyi 18 | */ 19 | @Slf4j 20 | public abstract class AbstractCommandProcessor implements CommandProcessor { 21 | 22 | @Override 23 | public void process(ChannelHandlerContext ctx, Command command) { 24 | try { 25 | doProcess(ctx, command.getContent()); 26 | } catch (Exception e) { 27 | log.error("process occurred error, cause={}", e.getMessage(), e); 28 | } 29 | } 30 | 31 | /** 32 | * do process 33 | * 34 | * @param content the command content 35 | */ 36 | public abstract void doProcess(ChannelHandlerContext ctx, Map content); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/processor/AbstractRequestProcessor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.processor; 2 | 3 | import io.bitchat.lang.constants.ResultCode; 4 | import io.bitchat.packet.Payload; 5 | import io.bitchat.packet.Request; 6 | import io.bitchat.packet.ctx.RequestProcessorContext; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.util.Map; 11 | 12 | /** 13 | *

14 | * An abstract RequestProcessor 15 | * Every Class extends AbstractRequestProcessor should have a NoArgument Constructor 16 | * see {@link RequestProcessorContext#initRequestProcessor()} 17 | *

18 | * @author houyi 19 | */ 20 | @Slf4j 21 | public abstract class AbstractRequestProcessor implements RequestProcessor { 22 | 23 | @Override 24 | public Payload process(ChannelHandlerContext ctx, Request request) { 25 | Payload payload = new Payload(); 26 | try { 27 | return doProcess(ctx, request.getParams()); 28 | } catch (Exception e) { 29 | payload.setErrorMsg(ResultCode.INTERNAL_ERROR.getCode(), e.getMessage()); 30 | log.error("process occurred error, cause={}", e.getMessage(), e); 31 | } 32 | return payload; 33 | } 34 | 35 | /** 36 | * do process 37 | * 38 | * @param params the request params 39 | * @return the response 40 | */ 41 | public abstract Payload doProcess(ChannelHandlerContext ctx, Map params); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/processor/CommandProcessor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.processor; 2 | 3 | import io.bitchat.packet.Command; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | /** 7 | *

8 | * A command processor 9 | * is aim to process the command 10 | *

11 | * 12 | * @author houyi 13 | */ 14 | public interface CommandProcessor { 15 | 16 | /** 17 | * process the command 18 | * 19 | * @param ctx the ctx 20 | * @param command the command 21 | */ 22 | void process(ChannelHandlerContext ctx, Command command); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/processor/Processor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.processor; 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 | * 10 | *

11 | * specify the name of 12 | * each {@link RequestProcessor} 13 | * or {@link CommandProcessor} 14 | *

15 | * 16 | * 17 | * @author houyi 18 | */ 19 | @Target({ElementType.TYPE}) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | public @interface Processor { 22 | 23 | String name(); 24 | 25 | } -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/packet/processor/RequestProcessor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.packet.processor; 2 | 3 | import io.bitchat.packet.Payload; 4 | import io.bitchat.packet.Request; 5 | import io.netty.channel.ChannelHandlerContext; 6 | 7 | /** 8 | *

9 | * A request processor 10 | * is aim to process the request 11 | * and return a payload 12 | *

13 | * 14 | * @author houyi 15 | */ 16 | public interface RequestProcessor { 17 | 18 | /** 19 | * process the request 20 | * 21 | * @param ctx the ctx 22 | * @param request the request 23 | * @return the response 24 | */ 25 | Payload process(ChannelHandlerContext ctx, Request request); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/serialize/DefaultSerializerChooser.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.serialize; 2 | 3 | import cn.hutool.core.lang.Singleton; 4 | 5 | /** 6 | *

7 | * A default Serializer Chooser implement 8 | *

9 | * 10 | * @author houyi 11 | */ 12 | public class DefaultSerializerChooser implements SerializerChooser { 13 | 14 | private DefaultSerializerChooser() { 15 | 16 | } 17 | 18 | public static SerializerChooser getInstance() { 19 | return Singleton.get(DefaultSerializerChooser.class); 20 | } 21 | 22 | @Override 23 | public Serializer choose(byte serializeAlgorithm) { 24 | SerializeAlgorithm algorithm = SerializeAlgorithm.getEnum(serializeAlgorithm); 25 | if (algorithm == null) { 26 | return null; 27 | } 28 | switch (algorithm) { 29 | case JDK: { 30 | return JdkSerializer.getInstance(); 31 | } 32 | case FAST_JSON: { 33 | return FastJsonSerializer.getInstance(); 34 | } 35 | case HESSIAN: { 36 | return HessianSerializer.getInstance(); 37 | } 38 | case KRYO: { 39 | return KryoSerializer.getInstance(); 40 | } 41 | case PROTO_STUFF: { 42 | return ProtoStuffSerializer.getInstance(); 43 | } 44 | default: { 45 | return null; 46 | } 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/serialize/FastJsonSerializer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.serialize; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import cn.hutool.core.lang.Singleton; 5 | import com.alibaba.fastjson.JSON; 6 | 7 | /** 8 | * @author houyi 9 | */ 10 | public class FastJsonSerializer implements Serializer { 11 | 12 | private FastJsonSerializer() { 13 | 14 | } 15 | 16 | public static Serializer getInstance() { 17 | return Singleton.get(FastJsonSerializer.class); 18 | } 19 | 20 | @Override 21 | public byte[] serialize(Object object) { 22 | Assert.notNull(object, "the serialize object can not be null"); 23 | return JSON.toJSONBytes(object); 24 | } 25 | 26 | @Override 27 | public T deserialize(byte[] bytes, Class clazz) { 28 | Assert.notNull(bytes, "the deserialize bytes can not be null"); 29 | return JSON.parseObject(bytes, clazz); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/serialize/HessianSerializer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.serialize; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import cn.hutool.core.lang.Singleton; 5 | import com.caucho.hessian.io.Hessian2Input; 6 | import com.caucho.hessian.io.Hessian2Output; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.io.ByteArrayInputStream; 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.IOException; 12 | 13 | /** 14 | * @author houyi 15 | */ 16 | @Slf4j 17 | public class HessianSerializer implements Serializer { 18 | 19 | private HessianSerializer() { 20 | 21 | } 22 | 23 | public static Serializer getInstance() { 24 | return Singleton.get(HessianSerializer.class); 25 | } 26 | 27 | @Override 28 | public byte[] serialize(Object object) { 29 | Assert.notNull(object, "the serialize object can not be null"); 30 | byte[] bytes = null; 31 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 32 | Hessian2Output hessian2Output = new Hessian2Output(outputStream); 33 | try { 34 | hessian2Output.writeObject(object); 35 | hessian2Output.flush(); 36 | bytes = outputStream.toByteArray(); 37 | } catch (IOException e) { 38 | log.warn("HessianSerializer [serialize] error, cause:{}", e.getMessage(), e); 39 | } finally { 40 | try { 41 | outputStream.close(); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | return bytes; 47 | } 48 | 49 | @SuppressWarnings("unchecked") 50 | @Override 51 | public T deserialize(byte[] bytes, Class clazz) { 52 | Assert.notNull(bytes, "the deserialize bytes can not be null"); 53 | T object = null; 54 | ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); 55 | Hessian2Input hessian2Input = new Hessian2Input(inputStream); 56 | try { 57 | object = (T) hessian2Input.readObject(); 58 | } catch (IOException e) { 59 | log.warn("HessianSerializer [deserialize] error, cause:{}", e.getMessage(), e); 60 | } finally { 61 | try { 62 | inputStream.close(); 63 | } catch (IOException e) { 64 | e.printStackTrace(); 65 | } 66 | } 67 | return object; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/serialize/JdkSerializer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.serialize; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import cn.hutool.core.lang.Singleton; 5 | import cn.hutool.core.util.ObjectUtil; 6 | 7 | /** 8 | * @author houyi 9 | */ 10 | public class JdkSerializer implements Serializer { 11 | 12 | private JdkSerializer() { 13 | 14 | } 15 | 16 | public static Serializer getInstance() { 17 | return Singleton.get(JdkSerializer.class); 18 | } 19 | 20 | @Override 21 | public byte[] serialize(Object object) { 22 | Assert.notNull(object, "the serialize object can not be null"); 23 | return ObjectUtil.serialize(object); 24 | } 25 | 26 | @Override 27 | public T deserialize(byte[] bytes, Class clazz) { 28 | Assert.notNull(bytes, "the deserialize bytes can not be null"); 29 | return ObjectUtil.unserialize(bytes); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/serialize/KryoSerializer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.serialize; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import cn.hutool.core.lang.Singleton; 5 | import com.esotericsoftware.kryo.Kryo; 6 | import com.esotericsoftware.kryo.io.Input; 7 | import com.esotericsoftware.kryo.io.Output; 8 | import com.esotericsoftware.kryo.util.Pool; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import java.io.ByteArrayInputStream; 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.IOException; 14 | 15 | /** 16 | * @author houyi 17 | */ 18 | @Slf4j 19 | public class KryoSerializer implements Serializer { 20 | 21 | private KryoSerializer() { 22 | 23 | } 24 | 25 | public static Serializer getInstance() { 26 | return Singleton.get(KryoSerializer.class); 27 | } 28 | 29 | @Override 30 | public byte[] serialize(Object object) { 31 | Assert.notNull(object, "the serialize object can not be null"); 32 | byte[] bytes = null; 33 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 34 | Kryo kryo = PoolHolder.kryoPool.obtain(); 35 | Output output = PoolHolder.outputPool.obtain(); 36 | try { 37 | output.setOutputStream(outputStream); 38 | kryo.writeClassAndObject(output, object); 39 | output.flush(); 40 | bytes = outputStream.toByteArray(); 41 | } catch (Exception e) { 42 | log.warn("KryoSerializer [serialize] error, cause:{}", e.getMessage(), e); 43 | } finally { 44 | try { 45 | outputStream.close(); 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | } 49 | PoolHolder.kryoPool.free(kryo); 50 | PoolHolder.outputPool.free(output); 51 | } 52 | return bytes; 53 | } 54 | 55 | @SuppressWarnings("unchecked") 56 | @Override 57 | public T deserialize(byte[] bytes, Class clazz) { 58 | Assert.notNull(bytes, "the deserialize bytes can not be null"); 59 | T object = null; 60 | ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); 61 | Kryo kryo = PoolHolder.kryoPool.obtain(); 62 | Input input = PoolHolder.inputPool.obtain(); 63 | try { 64 | input.setBuffer(bytes); 65 | object = (T) kryo.readClassAndObject(input); 66 | } catch (Exception e) { 67 | log.warn("KryoSerializer [deserialize] error, cause:{}", e.getMessage(), e); 68 | } finally { 69 | try { 70 | inputStream.close(); 71 | } catch (IOException e) { 72 | e.printStackTrace(); 73 | } 74 | PoolHolder.kryoPool.free(kryo); 75 | PoolHolder.inputPool.free(input); 76 | } 77 | return object; 78 | } 79 | 80 | /** 81 | * Hold a Kryo|Output|Input pool 82 | */ 83 | private static class PoolHolder { 84 | 85 | private static Pool kryoPool = new Pool(true, false, 8) { 86 | @Override 87 | protected Kryo create() { 88 | Kryo kryo = new Kryo(); 89 | kryo.setRegistrationRequired(false); 90 | kryo.setReferences(true); 91 | return kryo; 92 | } 93 | }; 94 | 95 | private static Pool outputPool = new Pool(true, false, 16) { 96 | @Override 97 | protected Output create() { 98 | return new Output(1024, -1); 99 | } 100 | }; 101 | 102 | private static Pool inputPool = new Pool(true, false, 16) { 103 | @Override 104 | protected Input create() { 105 | return new Input(1024); 106 | } 107 | }; 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/serialize/ProtoStuffSerializer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.serialize; 2 | 3 | import cn.hutool.core.lang.Singleton; 4 | import io.protostuff.LinkedBuffer; 5 | import io.protostuff.ProtostuffIOUtil; 6 | import io.protostuff.Schema; 7 | import io.protostuff.runtime.RuntimeSchema; 8 | import org.objenesis.Objenesis; 9 | import org.objenesis.ObjenesisStd; 10 | 11 | import java.util.Map; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * @author houyi 16 | */ 17 | public class ProtoStuffSerializer implements Serializer { 18 | 19 | private final Object CACHE_LOCK = new Object(); 20 | 21 | private Map, Schema> cachedSchema = new ConcurrentHashMap<>(); 22 | 23 | private Objenesis objenesis = new ObjenesisStd(true); 24 | 25 | private ProtoStuffSerializer() { 26 | 27 | } 28 | 29 | public static Serializer getInstance() { 30 | return Singleton.get(ProtoStuffSerializer.class); 31 | } 32 | 33 | @SuppressWarnings("unchecked") 34 | @Override 35 | public byte[] serialize(Object object) { 36 | LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); 37 | byte[] bytes = null; 38 | try { 39 | Class clazz = object.getClass(); 40 | Schema schema = getSchema(clazz); 41 | bytes = ProtostuffIOUtil.toByteArray(object, schema, buffer); 42 | } catch (Exception e) { 43 | throw new IllegalStateException(e.getMessage(), e); 44 | } finally { 45 | buffer.clear(); 46 | } 47 | return bytes; 48 | } 49 | 50 | @Override 51 | public T deserialize(byte[] bytes, Class clazz) { 52 | T object = null; 53 | try { 54 | object = objenesis.newInstance(clazz); 55 | Schema schema = getSchema(clazz); 56 | ProtostuffIOUtil.mergeFrom(bytes, object, schema); 57 | } catch (Exception e) { 58 | throw new IllegalStateException(e.getMessage(), e); 59 | } 60 | return object; 61 | } 62 | 63 | @SuppressWarnings("unchecked") 64 | private Schema getSchema(Class clazz) { 65 | Schema schema = (Schema) cachedSchema.get(clazz); 66 | if (schema == null) { 67 | synchronized (CACHE_LOCK) { 68 | schema = (Schema) cachedSchema.get(clazz); 69 | if (schema == null) { 70 | schema = RuntimeSchema.getSchema(clazz); 71 | cachedSchema.putIfAbsent(clazz, schema); 72 | } 73 | } 74 | } 75 | return schema; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/serialize/SerializeAlgorithm.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.serialize; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | *

9 | * A serialize algorithm enum 10 | *

11 | * 12 | * @author houyi 13 | */ 14 | @Getter 15 | public enum SerializeAlgorithm { 16 | 17 | /** 18 | * JDK 19 | */ 20 | JDK((byte) 1), 21 | /** 22 | * fastJson 23 | */ 24 | FAST_JSON((byte) 2), 25 | /** 26 | * hessian 27 | */ 28 | HESSIAN((byte) 3), 29 | /** 30 | * kryo 31 | */ 32 | KRYO((byte) 4), 33 | /** 34 | * protoStuff 35 | */ 36 | PROTO_STUFF((byte) 5); 37 | 38 | private byte type; 39 | 40 | SerializeAlgorithm(byte type) { 41 | this.type = type; 42 | } 43 | 44 | /** 45 | * get the enum class 46 | * 47 | * @param type the type 48 | * @return the enum class 49 | */ 50 | public static SerializeAlgorithm getEnum(Byte type) { 51 | return type == null ? null : Arrays.stream(values()) 52 | .filter(t -> t.getType() == type) 53 | .findFirst() 54 | .orElse(null); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/serialize/Serializer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.serialize; 2 | 3 | /** 4 | *

5 | * A Serializer which can serialize or deserialize object 6 | *

7 | * 8 | * @author houyi 9 | */ 10 | public interface Serializer { 11 | 12 | /** 13 | * serialize the object 14 | * 15 | * @param object the object need to serialize 16 | * @return byte array 17 | */ 18 | byte[] serialize(Object object); 19 | 20 | /** 21 | * deserialize the byte array 22 | * 23 | * @param bytes the byte array need to deserialize 24 | * @param clazz the class type byte array will deserialize to 25 | * @return object 26 | */ 27 | T deserialize(byte[] bytes, Class clazz); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/serialize/SerializerChooser.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.serialize; 2 | 3 | /** 4 | *

5 | * Choose a Serializer according to the serialize algorithm 6 | *

7 | * 8 | * @author houyi 9 | */ 10 | public interface SerializerChooser { 11 | 12 | /** 13 | * choose a Serializer 14 | * 15 | * @param serializeAlgorithm the serialize algorithm 16 | * @return the Serializer 17 | */ 18 | Serializer choose(byte serializeAlgorithm); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/ws/Frame.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.ws; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.Map; 7 | 8 | /** 9 | * @author houyi 10 | * 11 | *

12 | * The structure of a Packet is like blow: 13 | * +----------+----------+----------------------------+ 14 | * | size | value | intro | 15 | * +----------+----------+----------------------------+ 16 | * | 1 bytes | 0xBF | magic number | 17 | * | 1 bytes | | the type 1:req 2:res 3:cmd| 18 | * | 4 bytes | | content length | 19 | * | ? bytes | | the content | 20 | * +----------+----------+----------------------------+ 21 | *

22 | * 23 | */ 24 | @Data 25 | public class Frame implements Serializable { 26 | 27 | /** 28 | * the magic number of frame 29 | * 0xBF means BitChat Frame 30 | */ 31 | public static byte FRAME_MAGIC = (byte) 0xBF; 32 | 33 | /** 34 | * magic number 35 | */ 36 | private byte magic = Frame.FRAME_MAGIC; 37 | 38 | /** 39 | * the unique id 40 | * JS中要创建一个long的数值比较麻烦 41 | * 所以改成String类型 42 | */ 43 | private String id; 44 | 45 | /** 46 | *

47 | * specify the type of the packet 48 | * 1-request 49 | * 2-response 50 | * 3-command 51 | *

52 | */ 53 | private byte type; 54 | 55 | // Request 56 | /** 57 | * the name of the service 58 | */ 59 | private String serviceName; 60 | 61 | /** 62 | * the name of the method 63 | */ 64 | private String methodName; 65 | 66 | /** 67 | * the request params 68 | * fix me 69 | * 如果通过protobuf js的库无法直接解析成Map 70 | * 所以通过在js中将参数转成JSON字符串 71 | * 然后再通过FastJson将字符串转成Map 72 | */ 73 | private String paramJson; 74 | 75 | 76 | // Payload 77 | /** 78 | * the status of request 79 | */ 80 | private boolean success; 81 | 82 | /** 83 | * the code of request 84 | */ 85 | private int code; 86 | 87 | /** 88 | * the errorMsg of request 89 | */ 90 | private String msg; 91 | 92 | /** 93 | * the result of request 94 | */ 95 | private String resultJson; 96 | 97 | 98 | // Command 99 | /** 100 | * the name of the command 101 | */ 102 | private String commandName; 103 | 104 | /** 105 | * the command content 106 | */ 107 | private String contentJson; 108 | 109 | 110 | } 111 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/ws/FrameFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.ws; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import io.bitchat.core.id.IdFactory; 5 | import io.bitchat.core.id.SnowflakeIdFactory; 6 | import io.bitchat.packet.PacketType; 7 | import io.bitchat.packet.Payload; 8 | 9 | /** 10 | * @author houyi 11 | */ 12 | public class FrameFactory { 13 | 14 | private static IdFactory idFactory = SnowflakeIdFactory.getInstance(); 15 | 16 | public static Frame newResponseFrame(Payload payload, String id) { 17 | Frame frame = new Frame(); 18 | frame.setId(id); 19 | frame.setType(PacketType.PACKET_TYPE_RESPONSE); 20 | frame.setSuccess(payload.isSuccess()); 21 | frame.setCode(payload.getCode()); 22 | frame.setMsg(payload.getMsg()); 23 | frame.setResultJson(JSON.toJSONString(payload.getResult())); 24 | return frame; 25 | } 26 | 27 | public static Frame newCmdFrame(String commandName, Object content) { 28 | // command frame 29 | Frame frame = new Frame(); 30 | frame.setId(String.valueOf(idFactory.nextId())); 31 | frame.setType(PacketType.PACKET_TYPE_COMMAND); 32 | frame.setCommandName(commandName); 33 | frame.setContentJson(JSON.toJSONString(content)); 34 | return frame; 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/ws/PendingFrames.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.ws; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.Map; 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | /** 10 | * @author houyi 11 | */ 12 | @Slf4j 13 | public class PendingFrames { 14 | 15 | private static Map> pendingFrames = new ConcurrentHashMap<>(); 16 | 17 | public static void add(String id, CompletableFuture promise) { 18 | pendingFrames.putIfAbsent(id, promise); 19 | } 20 | 21 | public static CompletableFuture remove(String id) { 22 | return pendingFrames.remove(id); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /bitchat-core/src/main/java/io/bitchat/ws/codec/FrameCodec.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.ws.codec; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import cn.hutool.core.lang.Singleton; 5 | import io.bitchat.serialize.ProtoStuffSerializer; 6 | import io.bitchat.serialize.Serializer; 7 | import io.bitchat.ws.Frame; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.buffer.ByteBufAllocator; 10 | import io.netty.channel.ChannelHandler; 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.handler.codec.MessageToMessageCodec; 13 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 14 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 15 | import lombok.extern.slf4j.Slf4j; 16 | 17 | import java.util.List; 18 | 19 | /** 20 | *

21 | * A Generic Frame Codec 22 | *

23 | * 24 | * @author houyi 25 | */ 26 | @Slf4j 27 | @ChannelHandler.Sharable 28 | public class FrameCodec extends MessageToMessageCodec { 29 | 30 | private static Serializer serializer = ProtoStuffSerializer.getInstance(); 31 | 32 | private FrameCodec() { 33 | 34 | } 35 | 36 | public static FrameCodec getInstance() { 37 | return Singleton.get(FrameCodec.class); 38 | } 39 | 40 | @Override 41 | protected void encode(ChannelHandlerContext ctx, Frame frame, List out) throws Exception { 42 | ByteBuf in = ByteBufAllocator.DEFAULT.buffer(); 43 | // check the frame 44 | if (!checkFrame(frame)) { 45 | throw new RuntimeException("checkFrame failed!"); 46 | } 47 | // get frame content bytes 48 | byte[] content = serializer.serialize(frame); 49 | // do encode 50 | in.writeByte(frame.getMagic()); 51 | in.writeByte(frame.getType()); 52 | in.writeInt(content.length); 53 | in.writeBytes(content); 54 | 55 | out.add(new BinaryWebSocketFrame(in)); 56 | } 57 | 58 | @Override 59 | protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List out) throws Exception { 60 | ByteBuf in = msg.content(); 61 | // leastPacketLen = 1byte(magic) + 1byte(packet type) + 4bytes(content length) 62 | int leastPacketLen = 6; 63 | // until we can read at least 6 bytes 64 | if (in.readableBytes() < leastPacketLen) { 65 | return; 66 | } 67 | // mark reader index at here 68 | // if no enough bytes arrived 69 | // we should wait and reset the 70 | // reader index to here 71 | in.markReaderIndex(); 72 | // do common check before decode 73 | byte magic = in.readByte(); 74 | Assert.state(magic == Frame.FRAME_MAGIC, "magic number is invalid"); 75 | byte type = in.readByte(); 76 | int len = in.readInt(); 77 | // until we have the entire packet received 78 | if (in.readableBytes() < len) { 79 | // after read some bytes: magic/algorithm/type/len 80 | // the left readable bytes length is less than len 81 | // so we need to wait until enough bytes received 82 | // but we must reset the reader index to we marked 83 | // before we return 84 | in.resetReaderIndex(); 85 | return; 86 | } 87 | // read content 88 | byte[] content = new byte[len]; 89 | in.readBytes(content); 90 | 91 | Frame frame = serializer.deserialize(content, Frame.class); 92 | out.add(frame); 93 | 94 | } 95 | 96 | private boolean checkFrame(Frame frame) { 97 | byte magic = frame.getMagic(); 98 | if (magic != Frame.FRAME_MAGIC) { 99 | log.error("magic={} is invalid with frame={}", magic, frame); 100 | return false; 101 | } 102 | return true; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /bitchat-core/src/main/resources/config/base-config.properties: -------------------------------------------------------------------------------- 1 | basePackage=io.bitchat 2 | serverPort=8864 3 | 4 | # the description sent to front end when server internal error occurred 5 | serverInternalError=Server Internal Error 6 | 7 | # the readerIdleTime for IdleStateChecker TimeUnit:Seconds 8 | readerIdleTime=60 9 | 10 | # the pingInterval for HealthyChecker TimeUnit:Seconds 11 | pingInterval=20 -------------------------------------------------------------------------------- /bitchat-core/src/main/resources/config/snowflake-config.properties: -------------------------------------------------------------------------------- 1 | workerId=1 2 | dataCenterId=1 -------------------------------------------------------------------------------- /bitchat-core/src/main/resources/config/thread-pool-config.properties: -------------------------------------------------------------------------------- 1 | corePoolSize=10 2 | maxPoolSize=20 3 | idealTime=5 4 | blockingQueueCap=100 -------------------------------------------------------------------------------- /bitchat-core/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 14 | 16 | ${LOG_FILE} 17 | 18 | ${FILE_LOG_PATTERN} 19 | 20 | 21 | ${LOG_FILE}.%d{yyyy-MM-dd}.%i.log 22 | 7 23 | 50MB 24 | 20GB 25 | 26 | 27 | 28 | 29 | 30 | ${CONSOLE_LOG_PATTERN} 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /bitchat-core/src/test/java/io/bitchat/core/IdTest.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.core; 2 | 3 | import io.bitchat.core.id.IdFactory; 4 | import io.bitchat.core.id.SnowflakeIdFactory; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | /** 13 | * @author houyi 14 | */ 15 | @Slf4j 16 | public class IdTest { 17 | 18 | private IdFactory idFactory1; 19 | private IdFactory idFactory2; 20 | 21 | @Before 22 | public void before() { 23 | idFactory1 = SnowflakeIdFactory.getInstance(1L); 24 | idFactory2 = SnowflakeIdFactory.getInstance(2L); 25 | } 26 | 27 | @Test 28 | public void testDeserialize() { 29 | log.info("idFactory1={}", idFactory1); 30 | log.info("idFactory2={}", idFactory2); 31 | Set set = new HashSet<>(); 32 | for (int i = 0; i < 100; i++) { 33 | Long id1 = idFactory1.nextId(); 34 | Long id2 = idFactory2.nextId(); 35 | log.info("idFactory1 nextId={}", id1); 36 | log.info("idFactory2 nextId={}", id2); 37 | set.add(id1); 38 | set.add(id2); 39 | } 40 | log.info("set size={}", set.size()); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /bitchat-core/src/test/java/io/bitchat/lang/config/ConfigTest.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.lang.config; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Test; 5 | 6 | /** 7 | * @author houyi 8 | */ 9 | @Slf4j 10 | public class ConfigTest { 11 | 12 | @Test 13 | public void testConfig() { 14 | BaseConfig baseConfig1 = ConfigFactory.getConfig(BaseConfig.class); 15 | BaseConfig baseConfig2 = ConfigFactory.getConfig(BaseConfig.class); 16 | log.info("baseConfig1={}, basePackage={}", baseConfig1.getClass(), baseConfig1.basePackage()); 17 | log.info("baseConfig2={}, basePackage={}", baseConfig2.getClass(), baseConfig2.basePackage()); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /bitchat-core/src/test/java/io/bitchat/serialize/SerializeTest.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.serialize; 2 | 3 | import io.bitchat.packet.Packet; 4 | import io.bitchat.packet.factory.PacketFactory; 5 | import io.bitchat.packet.Request; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | /** 11 | * @author houyi 12 | */ 13 | @Slf4j 14 | public class SerializeTest { 15 | 16 | private Serializer serializer; 17 | 18 | @Before 19 | public void before() { 20 | serializer = FastJsonSerializer.getInstance(); 21 | } 22 | 23 | @Test 24 | public void testDeserialize() { 25 | Request request = new Request(); 26 | request.setServiceName("io.bitchat.core.EchoService"); 27 | Packet requestPacket = PacketFactory.newRequestPacket(request, 123); 28 | requestPacket.setHandleAsync(false); 29 | byte[] content = serializer.serialize(requestPacket); 30 | Packet deserializePacket = serializer.deserialize(content, requestPacket.getClass()); 31 | log.info("deserializePacket={}", deserializePacket); 32 | log.info("algorithm={}", deserializePacket.getAlgorithm()); 33 | log.info("handleAsync={}", deserializePacket.isHandleAsync()); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /bitchat-router/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bitchat 7 | io.bitchat 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | bitchat-router 13 | 14 | 15 | -------------------------------------------------------------------------------- /bitchat-router/src/main/java/io/bitchat/router/RouterServer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.router; 2 | 3 | /** 4 | *

5 | * A router server node 6 | *

7 | * 8 | * @author houyi 9 | */ 10 | public interface RouterServer { 11 | 12 | /** 13 | * start the router server 14 | */ 15 | void start(); 16 | 17 | /** 18 | * stop the router server 19 | */ 20 | void stop(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /bitchat-router/src/main/java/io/bitchat/router/RouterServerAttr.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.router; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | /** 10 | *

11 | * A Config which holds the address and port of a router server 12 | *

13 | * 14 | * @author houyi 15 | */ 16 | @Data 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class RouterServerAttr { 21 | private String address; 22 | private Integer port; 23 | 24 | public boolean valid() { 25 | return StrUtil.isNotBlank(address) && port != null; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bitchat-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bitchat 7 | io.bitchat 8 | 1.0.0 9 | 10 | 4.0.0 11 | 12 | bitchat-server 13 | 14 | 15 | 16 | io.bitchat 17 | bitchat-core 18 | 19 | 20 | io.bitchat 21 | bitchat-router 22 | 23 | 24 | com.beust 25 | jcommander 26 | 27 | 28 | 29 | 30 | ${project.artifactId}-${project-version} 31 | 32 | 33 | org.apache.maven.plugins 34 | maven-compiler-plugin 35 | 3.1 36 | 37 | ${java.version} 38 | ${java.version} 39 | ${java.encoding} 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-shade-plugin 45 | 2.4.1 46 | 47 | false 48 | 49 | 50 | 51 | package 52 | 53 | shade 54 | 55 | 56 | 57 | 58 | *:* 59 | 60 | META-INF/*.SF 61 | META-INF/*.DSA 62 | META-INF/*.RSA 63 | 64 | 65 | 66 | 67 | 69 | io.bitchat.server.ServerShell 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/AbstractServer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import cn.hutool.core.util.IdUtil; 4 | import io.bitchat.core.ServerAttr; 5 | import io.bitchat.core.init.Initializer; 6 | import io.bitchat.lang.config.BaseConfig; 7 | import io.bitchat.lang.config.ConfigFactory; 8 | import io.bitchat.server.channel.ChannelListener; 9 | import io.bitchat.server.channel.DefaultChannelListener; 10 | import io.netty.bootstrap.ServerBootstrap; 11 | import io.netty.channel.ChannelFuture; 12 | import io.netty.channel.ChannelOption; 13 | import io.netty.channel.EventLoopGroup; 14 | import io.netty.channel.nio.NioEventLoopGroup; 15 | import io.netty.channel.socket.nio.NioServerSocketChannel; 16 | import io.netty.util.concurrent.Future; 17 | import io.netty.util.concurrent.GenericFutureListener; 18 | import lombok.extern.slf4j.Slf4j; 19 | 20 | import java.util.concurrent.atomic.AtomicBoolean; 21 | 22 | /** 23 | *

24 | * A abstract server 25 | *

26 | * 27 | * @author houyi 28 | */ 29 | @Slf4j 30 | public abstract class AbstractServer implements Server { 31 | 32 | /** 33 | * the server attribute 34 | */ 35 | private ServerAttr serverAttr; 36 | 37 | private EventLoopGroup bossGroup; 38 | private EventLoopGroup workerGroup; 39 | 40 | private ChannelListener channelListener; 41 | 42 | /** 43 | * whether the server is started 44 | */ 45 | private AtomicBoolean started = new AtomicBoolean(false); 46 | 47 | public AbstractServer(Integer serverPort) { 48 | this(serverPort, null); 49 | } 50 | 51 | public AbstractServer(Integer serverPort, ChannelListener channelListener) { 52 | int port = serverPort == null ? ConfigFactory.getConfig(BaseConfig.class).serverPort() : serverPort; 53 | this.serverAttr = ServerAttr.getLocalServer(port); 54 | this.channelListener = channelListener == null ? DefaultChannelListener.getInstance() : channelListener; 55 | } 56 | 57 | @Override 58 | public ServerAttr attribute() { 59 | return serverAttr; 60 | } 61 | 62 | @Override 63 | public void start() { 64 | // do init work before server start 65 | Initializer.init(); 66 | if (started.compareAndSet(false, true)) { 67 | doStart(serverAttr.getPort()); 68 | } 69 | } 70 | 71 | @Override 72 | public void stop() { 73 | if (!started.get()) { 74 | log.warn("Server hasn't started yet!"); 75 | return; 76 | } 77 | bossGroup.shutdownGracefully(); 78 | workerGroup.shutdownGracefully(); 79 | } 80 | 81 | public String id() { 82 | return IdUtil.objectId(); 83 | } 84 | 85 | public void doStart(int port) { 86 | bossGroup = new NioEventLoopGroup(); 87 | workerGroup = new NioEventLoopGroup(); 88 | long start = System.currentTimeMillis(); 89 | ServerBootstrap bootstrap = new ServerBootstrap(); 90 | bootstrap.option(ChannelOption.SO_BACKLOG, 1024); 91 | bootstrap.group(bossGroup, workerGroup) 92 | .channel(NioServerSocketChannel.class) 93 | .childHandler(new ServerInitializer(channelListener)); 94 | 95 | ChannelFuture future = bootstrap.bind(port); 96 | future.addListener(new GenericFutureListener>() { 97 | @Override 98 | public void operationComplete(Future future) throws Exception { 99 | if (future.isSuccess()) { 100 | long cost = System.currentTimeMillis() - start; 101 | log.info("[{}] Startup at port:{} cost:{}[ms]", AbstractServer.class.getSimpleName(), port, cost); 102 | // register to router after startup successfully 103 | registerToRouter(); 104 | } 105 | } 106 | }); 107 | } 108 | 109 | 110 | } 111 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/ClusterServer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import io.bitchat.router.RouterServerAttr; 5 | import io.bitchat.server.channel.ChannelListener; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | /** 9 | *

10 | * A cluster server 11 | *

12 | * 13 | * @author houyi 14 | */ 15 | @Slf4j 16 | public class ClusterServer extends AbstractServer { 17 | 18 | private RouterServerAttr routerServerAttr; 19 | 20 | public ClusterServer(Integer serverPort, RouterServerAttr routerServerAttr) { 21 | this(serverPort, null, routerServerAttr); 22 | } 23 | 24 | public ClusterServer(Integer serverPort, ChannelListener channelListener, RouterServerAttr routerServerAttr) { 25 | super(serverPort, channelListener); 26 | Assert.notNull(routerServerAttr, "routerServerAttr can not be null"); 27 | this.routerServerAttr = routerServerAttr; 28 | } 29 | 30 | @Override 31 | public void registerToRouter() { 32 | // register to router 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/HeartBeatProcessor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import io.bitchat.lang.constants.ServiceName; 4 | import io.bitchat.packet.Payload; 5 | import io.bitchat.packet.factory.PayloadFactory; 6 | import io.bitchat.packet.processor.AbstractRequestProcessor; 7 | import io.bitchat.packet.processor.Processor; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import java.util.Map; 12 | 13 | /** 14 | * @author houyi 15 | */ 16 | @Slf4j 17 | @Processor(name = ServiceName.HEART_BEAT) 18 | public class HeartBeatProcessor extends AbstractRequestProcessor { 19 | 20 | @Override 21 | public Payload doProcess(ChannelHandlerContext ctx, Map params) { 22 | log.debug("Return a Pong"); 23 | return PayloadFactory.newSuccessPayload(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/ProtocolDispatcher.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import io.bitchat.core.IdleStateChecker; 5 | import io.bitchat.lang.config.BaseConfig; 6 | import io.bitchat.lang.config.ConfigFactory; 7 | import io.bitchat.packet.Packet; 8 | import io.bitchat.packet.codec.PacketCodec; 9 | import io.bitchat.server.channel.ChannelListener; 10 | import io.bitchat.server.http.HttpHandler; 11 | import io.bitchat.server.packet.PacketHandler; 12 | import io.netty.buffer.ByteBuf; 13 | import io.netty.channel.ChannelHandlerContext; 14 | import io.netty.channel.ChannelPipeline; 15 | import io.netty.handler.codec.ByteToMessageDecoder; 16 | import io.netty.handler.codec.http.HttpObjectAggregator; 17 | import io.netty.handler.codec.http.HttpServerCodec; 18 | import io.netty.handler.stream.ChunkedWriteHandler; 19 | import lombok.extern.slf4j.Slf4j; 20 | 21 | import java.util.List; 22 | 23 | /** 24 | * dispatch different protocols 25 | *

26 | * see {@link io.netty.example.portunification.PortUnificationServerHandler} 27 | * 28 | * @author houyi 29 | */ 30 | @Slf4j 31 | public class ProtocolDispatcher extends ByteToMessageDecoder { 32 | 33 | private ChannelListener channelListener; 34 | 35 | public ProtocolDispatcher(ChannelListener channelListener) { 36 | Assert.notNull(channelListener, "channelListener can not be null"); 37 | this.channelListener = channelListener; 38 | } 39 | 40 | @Override 41 | public void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 42 | // Will use the first five bytes to detect a protocol. 43 | if (in.readableBytes() < 5) { 44 | return; 45 | } 46 | int readerIndex = in.readerIndex(); 47 | final int magic1 = in.getByte(readerIndex); 48 | final int magic2 = in.getByte(readerIndex + 1); 49 | if (isPacket(magic1)) { 50 | dispatchToPacket(ctx); 51 | } else if (isHttp(magic1, magic2)) { 52 | dispatchToHttp(ctx); 53 | } else { 54 | // Unknown protocol; discard everything and close the connection. 55 | in.clear(); 56 | ctx.close(); 57 | } 58 | } 59 | 60 | private boolean isPacket(int magic1) { 61 | return magic1 == Packet.PACKET_MAGIC; 62 | } 63 | 64 | private static boolean isHttp(int magic1, int magic2) { 65 | return 66 | magic1 == 'G' && magic2 == 'E' || // GET 67 | magic1 == 'P' && magic2 == 'O' || // POST 68 | magic1 == 'P' && magic2 == 'U' || // PUT 69 | magic1 == 'H' && magic2 == 'E' || // HEAD 70 | magic1 == 'O' && magic2 == 'P' || // OPTIONS 71 | magic1 == 'P' && magic2 == 'A' || // PATCH 72 | magic1 == 'D' && magic2 == 'E' || // DELETE 73 | magic1 == 'T' && magic2 == 'R' || // TRACE 74 | magic1 == 'C' && magic2 == 'O'; // CONNECT 75 | } 76 | 77 | private void dispatchToPacket(ChannelHandlerContext ctx) { 78 | ChannelPipeline pipeline = ctx.pipeline(); 79 | BaseConfig baseConfig = ConfigFactory.getConfig(BaseConfig.class); 80 | pipeline.addLast(new IdleStateChecker(baseConfig.readerIdleTime())); 81 | pipeline.addLast(new PacketCodec()); 82 | pipeline.addLast(PacketHandler.getInstance(channelListener)); 83 | // 将所有所需的ChannelHandler添加到pipeline之后,一定要将自身移除掉 84 | // 否则该Channel之后的请求仍会重新执行协议的分发,而这是要避免的 85 | pipeline.remove(this); 86 | // 将channelActive事件传递到PacketHandler 87 | ctx.fireChannelActive(); 88 | } 89 | 90 | private void dispatchToHttp(ChannelHandlerContext ctx) { 91 | ChannelPipeline pipeline = ctx.pipeline(); 92 | pipeline.addLast(new HttpServerCodec()); 93 | pipeline.addLast(new ChunkedWriteHandler()); 94 | // aggregate HttpRequest/HttpContent/LastHttpContent to FullHttpRequest 95 | pipeline.addLast(new HttpObjectAggregator(8096)); 96 | pipeline.addLast(HttpHandler.getInstance(channelListener)); 97 | pipeline.remove(this); 98 | // 将channelActive事件传递到HttpHandler 99 | ctx.fireChannelActive(); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/Server.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import io.bitchat.core.ServerAttr; 4 | 5 | /** 6 | *

7 | * A server node 8 | *

9 | * 10 | * @author houyi 11 | */ 12 | public interface Server { 13 | 14 | /** 15 | * return the attribute of current server 16 | * 17 | * @return the attribute of the server 18 | */ 19 | ServerAttr attribute(); 20 | 21 | /** 22 | * start the server 23 | */ 24 | void start(); 25 | 26 | /** 27 | * stop the server 28 | */ 29 | void stop(); 30 | 31 | /** 32 | * register to a router 33 | */ 34 | void registerToRouter(); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/ServerAttrHolder.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import io.bitchat.core.ServerAttr; 4 | 5 | /** 6 | * @author houyi 7 | */ 8 | public class ServerAttrHolder { 9 | 10 | private static ServerAttr serverAttr; 11 | 12 | public static void put(ServerAttr serverAttr) { 13 | ServerAttrHolder.serverAttr = serverAttr; 14 | } 15 | 16 | public static ServerAttr get() { 17 | return ServerAttrHolder.serverAttr; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/ServerBootstrap.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import cn.hutool.core.lang.Singleton; 5 | import io.bitchat.router.RouterServerAttr; 6 | import io.bitchat.server.channel.ChannelListener; 7 | 8 | /** 9 | *

10 | * A server bootstrap 11 | *

12 | * 13 | * @author houyi 14 | */ 15 | public class ServerBootstrap { 16 | 17 | private ServerMode serverMode = ServerMode.STAND_ALONE; 18 | 19 | private RouterServerAttr routerServerAttr; 20 | 21 | private ChannelListener channelListener; 22 | 23 | public ServerBootstrap serverMode(ServerMode serverMode) { 24 | this.serverMode = serverMode == null ? ServerMode.STAND_ALONE : serverMode; 25 | return this; 26 | } 27 | 28 | public ServerBootstrap routerServerAttr(RouterServerAttr routerServerAttr) { 29 | if (ServerMode.CLUSTER == serverMode) { 30 | Assert.notNull(routerServerAttr, "routerServerAttr can not be null"); 31 | Assert.isTrue(routerServerAttr.valid(), "routerServerAttr is invalid"); 32 | this.routerServerAttr = routerServerAttr; 33 | } 34 | return this; 35 | } 36 | 37 | public ServerBootstrap channelListener(Class channelListener) { 38 | this.channelListener = Singleton.get(channelListener); 39 | return this; 40 | } 41 | 42 | public void start(Integer serverPort) { 43 | ServerFactory factory = SimpleServerFactory.getInstance(); 44 | Server server; 45 | if (ServerMode.STAND_ALONE == serverMode) { 46 | server = factory.newServer(serverPort, channelListener); 47 | } else { 48 | Assert.notNull(routerServerAttr, "routerServerAttr can not be null cause you are starting the server in cluster mode"); 49 | Assert.isTrue(routerServerAttr.valid(), "routerServerAttr is invalid"); 50 | server = factory.newClusterServer(serverPort, channelListener, routerServerAttr); 51 | } 52 | server.start(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/ServerFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import io.bitchat.router.RouterServerAttr; 4 | import io.bitchat.server.channel.ChannelListener; 5 | 6 | /** 7 | *

8 | * A server factory 9 | *

10 | * 11 | * @author houyi 12 | */ 13 | public interface ServerFactory { 14 | 15 | /** 16 | * create a standalone server 17 | * 18 | * @param serverPort the server port 19 | * @return the server 20 | */ 21 | Server newServer(Integer serverPort); 22 | 23 | /** 24 | * create a standalone server 25 | * 26 | * @param serverPort the server port 27 | * @param channelListener the channelListener 28 | * @return the server 29 | */ 30 | Server newServer(Integer serverPort, ChannelListener channelListener); 31 | 32 | /** 33 | * create a cluster server 34 | * 35 | * @param serverPort the server port 36 | * @param routerServerAttr the router attr 37 | * @return the server 38 | */ 39 | Server newClusterServer(Integer serverPort, RouterServerAttr routerServerAttr); 40 | 41 | /** 42 | * create a cluster server 43 | * 44 | * @param serverPort the server port 45 | * @param channelListener the channelListener 46 | * @param routerServerAttr the router attr 47 | * @return the server 48 | */ 49 | Server newClusterServer(Integer serverPort, ChannelListener channelListener, RouterServerAttr routerServerAttr); 50 | 51 | /** 52 | * get current server 53 | * 54 | * @return current server 55 | */ 56 | Server currentServer(); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/ServerInitializer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import io.bitchat.server.channel.ChannelListener; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.channel.ChannelPipeline; 6 | import io.netty.channel.socket.SocketChannel; 7 | 8 | /** 9 | * @author houyi 10 | */ 11 | public class ServerInitializer extends ChannelInitializer { 12 | 13 | /** 14 | * the channel listener 15 | */ 16 | private ChannelListener channelListener; 17 | 18 | public ServerInitializer(ChannelListener channelListener) { 19 | this.channelListener = channelListener; 20 | } 21 | 22 | @Override 23 | protected void initChannel(SocketChannel channel) throws Exception { 24 | ChannelPipeline pipeline = channel.pipeline(); 25 | pipeline.addLast(new ProtocolDispatcher(channelListener)); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/ServerMode.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * @author houyi 9 | */ 10 | @Getter 11 | public enum ServerMode { 12 | 13 | /** 14 | * standalone mode 15 | */ 16 | STAND_ALONE(1, "standalone"), 17 | 18 | /** 19 | * cluster mode 20 | */ 21 | CLUSTER(2, "cluster"); 22 | 23 | private int mode; 24 | private String text; 25 | 26 | ServerMode(int mode, String text) { 27 | this.mode = mode; 28 | this.text = text; 29 | } 30 | 31 | public static ServerMode getEnum(Integer mode) { 32 | return mode == null ? null : Arrays.stream(values()) 33 | .filter(t -> t.getMode() == mode) 34 | .findFirst() 35 | .orElse(null); 36 | } 37 | 38 | public static String getText(Integer mode) { 39 | ServerMode anEnum = getEnum(mode); 40 | return anEnum == null ? null : anEnum.getText(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/ServerShell.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import com.beust.jcommander.JCommander; 4 | import com.beust.jcommander.Parameter; 5 | import io.bitchat.router.RouterServerAttr; 6 | 7 | /** 8 | *

9 | * A Main class that can be startup by jar or shell 10 | *

11 | * 12 | * @author houyi 13 | */ 14 | public class ServerShell { 15 | 16 | public static void main(String[] args) { 17 | ServerStartupParameter param = new ServerStartupParameter(); 18 | JCommander.newBuilder() 19 | .addObject(param) 20 | .build() 21 | .parse(args); 22 | ServerMode serverMode = ServerMode.getEnum(param.mode); 23 | RouterServerAttr routerServerAttr = RouterServerAttr.builder() 24 | .address(param.routerAddress) 25 | .port(param.routerPort) 26 | .build(); 27 | Integer serverPort = param.serverPort; 28 | 29 | ServerBootstrap bootstrap = new ServerBootstrap(); 30 | bootstrap.serverMode(serverMode) 31 | .routerServerAttr(routerServerAttr) 32 | .start(serverPort); 33 | } 34 | 35 | 36 | private static class ServerStartupParameter { 37 | 38 | @Parameter(names = "-mode", description = "Server mode. 1 : standalone mode 2 : cluster mode. If null will use default mode: standalone") 39 | private Integer mode; 40 | 41 | @Parameter(names = "-serverPort", description = "Server port. If null will use default port: 8864") 42 | private Integer serverPort; 43 | 44 | @Parameter(names = "-routerAddress", description = "Router address.") 45 | private String routerAddress; 46 | 47 | @Parameter(names = "-routerPort", description = "Router port.") 48 | private Integer routerPort; 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/ServerSpeaker.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import io.bitchat.core.ServerAttr; 4 | import io.bitchat.packet.Packet; 5 | 6 | /** 7 | *

8 | * A Server Speaker will transmit command between two servers 9 | *

10 | * 11 | * @author houyi 12 | */ 13 | public interface ServerSpeaker { 14 | 15 | /** 16 | * transmit a command to another Server 17 | * 18 | * @param serverAttr the server attr 19 | * @param packet the request packet 20 | * @return the response packet 21 | */ 22 | Packet speak(ServerAttr serverAttr, Packet packet); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/SessionIdKeeper.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | /** 4 | *

5 | * A session id keeper aim to get the session id of p2p or group chat 6 | *

7 | * 8 | * @author houyi 9 | */ 10 | public interface SessionIdKeeper { 11 | 12 | /** 13 | * return the session id of p2p chat 14 | * 15 | * @param userId one user id 16 | * @param partnerId another user id 17 | * @return the session id 18 | */ 19 | Long p2pSessionId(Long userId, Long partnerId); 20 | 21 | /** 22 | * return the session id of group chat 23 | * 24 | * @param groupId group id 25 | * @return the session id 26 | */ 27 | Long groupSessionId(Long groupId); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/SimpleServerFactory.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import cn.hutool.core.lang.Singleton; 4 | import io.bitchat.router.RouterServerAttr; 5 | import io.bitchat.server.channel.ChannelListener; 6 | 7 | /** 8 | * @author houyi 9 | */ 10 | public class SimpleServerFactory implements ServerFactory { 11 | 12 | private Server currentServer; 13 | 14 | private SimpleServerFactory() { 15 | 16 | } 17 | 18 | public static ServerFactory getInstance() { 19 | return Singleton.get(SimpleServerFactory.class); 20 | } 21 | 22 | @Override 23 | public Server newServer(Integer serverPort) { 24 | currentServer = new StandaloneServer(serverPort); 25 | ServerAttrHolder.put(currentServer.attribute()); 26 | return currentServer; 27 | } 28 | 29 | @Override 30 | public Server newServer(Integer serverPort, ChannelListener channelListener) { 31 | currentServer = new StandaloneServer(serverPort, channelListener); 32 | ServerAttrHolder.put(currentServer.attribute()); 33 | return currentServer; 34 | } 35 | 36 | @Override 37 | public Server newClusterServer(Integer serverPort, RouterServerAttr routerServerAttr) { 38 | currentServer = new ClusterServer(serverPort, routerServerAttr); 39 | ServerAttrHolder.put(currentServer.attribute()); 40 | return currentServer; 41 | } 42 | 43 | @Override 44 | public Server newClusterServer(Integer serverPort, ChannelListener channelListener, RouterServerAttr routerServerAttr) { 45 | currentServer = new ClusterServer(serverPort, channelListener, routerServerAttr); 46 | ServerAttrHolder.put(currentServer.attribute()); 47 | return currentServer; 48 | } 49 | 50 | @Override 51 | public Server currentServer() { 52 | return currentServer; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/StandaloneServer.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server; 2 | 3 | import io.bitchat.server.channel.ChannelListener; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | /** 7 | *

8 | * A standalone server 9 | *

10 | * 11 | * @author houyi 12 | */ 13 | @Slf4j 14 | public class StandaloneServer extends AbstractServer { 15 | 16 | public StandaloneServer(Integer serverPort) { 17 | super(serverPort); 18 | } 19 | 20 | public StandaloneServer(Integer serverPort, ChannelListener channelListener) { 21 | super(serverPort, channelListener); 22 | } 23 | 24 | @Override 25 | public void registerToRouter() { 26 | // do nothing 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/channel/ChannelHelper.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.channel; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelId; 5 | 6 | /** 7 | * @author houyi 8 | */ 9 | public class ChannelHelper { 10 | 11 | private static ChannelManager channelManager = DefaultChannelManager.getInstance(); 12 | 13 | private ChannelHelper() { 14 | 15 | } 16 | 17 | public static ChannelType getChannelType(Channel channel) { 18 | ChannelWrapper channelWrapper = getChannelWrapper(channel); 19 | return channelWrapper.getChannelType(); 20 | } 21 | 22 | public static ChannelWrapper getChannelWrapper(Channel channel) { 23 | return getChannelWrapper(channel.id()); 24 | } 25 | 26 | public static ChannelWrapper getChannelWrapper(ChannelId channelId) { 27 | return channelManager.getChannelWrapper(channelId); 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/channel/ChannelListener.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.channel; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | /** 6 | * @author houyi 7 | */ 8 | public interface ChannelListener { 9 | 10 | void channelActive(Channel channel, ChannelType channelType); 11 | 12 | void channelInactive(Channel channel); 13 | } 14 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/channel/ChannelManager.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.channel; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelId; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author houyi 10 | */ 11 | public interface ChannelManager { 12 | 13 | /** 14 | * add the Channel when 15 | * {@link io.bitchat.server.ws.FrameHandler#channelActive(io.netty.channel.ChannelHandlerContext)} 16 | * is triggered 17 | */ 18 | void addChannel(Channel channel, ChannelType channelType); 19 | 20 | /** 21 | * remove the Channel when 22 | * {@link io.bitchat.server.ws.FrameHandler#channelInactive(io.netty.channel.ChannelHandlerContext)} 23 | * is triggered 24 | */ 25 | void removeChannel(ChannelId channelId); 26 | 27 | ChannelWrapper getChannelWrapper(ChannelId channelId); 28 | 29 | ChannelWrapper getChannelWrapper(String longId); 30 | 31 | List getAllChannelWrappers(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/channel/ChannelType.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.channel; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * @author houyi 9 | */ 10 | @Getter 11 | public enum ChannelType { 12 | /** 13 | * Packet 14 | */ 15 | Packet(1), 16 | /** 17 | * WebSocket 18 | */ 19 | WebSocket(2); 20 | 21 | private int type; 22 | 23 | ChannelType(int type) { 24 | this.type = type; 25 | } 26 | 27 | public static ChannelType getChannelType(int type) { 28 | return Arrays.stream(values()) 29 | .filter(channelType -> channelType.getType() == type) 30 | .findFirst() 31 | .orElse(null); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/channel/ChannelWrapper.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.channel; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import io.netty.channel.Channel; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | /** 10 | * @author houyi 11 | */ 12 | @Getter 13 | @Setter 14 | @Builder 15 | public class ChannelWrapper { 16 | 17 | private Channel channel; 18 | 19 | private ChannelType channelType; 20 | 21 | @Override 22 | public String toString() { 23 | JSONObject channelInfo = new JSONObject(); 24 | channelInfo.put("shortId", channel.id().asShortText()); 25 | channelInfo.put("longId", channel.id().asLongText()); 26 | channelInfo.put("localAddress", channel.localAddress()); 27 | channelInfo.put("remoteAddress", channel.remoteAddress()); 28 | channelInfo.put("active", channel.isActive()); 29 | channelInfo.put("open", channel.isOpen()); 30 | channelInfo.put("registered", channel.isRegistered()); 31 | channelInfo.put("writable", channel.isWritable()); 32 | JSONObject object = new JSONObject(); 33 | object.put("channelInfo", channelInfo); 34 | object.put("channelType", channelType); 35 | return object.toJSONString(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/channel/DefaultChannelListener.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.channel; 2 | 3 | import cn.hutool.core.lang.Singleton; 4 | import io.bitchat.server.session.DefaultSessionManager; 5 | import io.bitchat.server.session.SessionHelper; 6 | import io.bitchat.server.session.SessionManager; 7 | import io.netty.channel.Channel; 8 | import io.netty.channel.ChannelId; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | /** 12 | * @author houyi 13 | */ 14 | @Slf4j 15 | public class DefaultChannelListener implements ChannelListener { 16 | 17 | private ChannelManager channelManager; 18 | private SessionManager sessionManager; 19 | 20 | private DefaultChannelListener() { 21 | channelManager = DefaultChannelManager.getInstance(); 22 | sessionManager = DefaultSessionManager.getInstance(); 23 | } 24 | 25 | public static ChannelListener getInstance() { 26 | return Singleton.get(DefaultChannelListener.class); 27 | } 28 | 29 | @Override 30 | public void channelActive(Channel channel, ChannelType channelType) { 31 | channelManager.addChannel(channel, channelType); 32 | log.info("Add a new Channel={}, channelType={}", channel, channelType); 33 | } 34 | 35 | @Override 36 | public void channelInactive(Channel channel) { 37 | ChannelId channelId = channel.id(); 38 | channelManager.removeChannel(channelId); 39 | sessionManager.removeSession(channelId); 40 | SessionHelper.markOffline(channel); 41 | log.info("Remove an inactive Channel={}", channel); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/channel/DefaultChannelManager.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.channel; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import cn.hutool.core.lang.Singleton; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelId; 7 | import io.netty.channel.group.ChannelGroup; 8 | import io.netty.channel.group.DefaultChannelGroup; 9 | import io.netty.util.AttributeKey; 10 | import io.netty.util.concurrent.GlobalEventExecutor; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | /** 18 | * @author houyi 19 | */ 20 | @Slf4j 21 | public class DefaultChannelManager implements ChannelManager { 22 | 23 | private static final AttributeKey CHANNEL_TYPE = AttributeKey.newInstance("channelType"); 24 | 25 | private ChannelGroup channels; 26 | 27 | private DefaultChannelManager() { 28 | channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 29 | } 30 | 31 | public static ChannelManager getInstance() { 32 | return Singleton.get(DefaultChannelManager.class); 33 | } 34 | 35 | @Override 36 | public void addChannel(Channel channel, ChannelType channelType) { 37 | Assert.notNull(channel, "channel can not be null"); 38 | Assert.notNull(channelType, "channelType can not be null"); 39 | channel.attr(CHANNEL_TYPE).set(channelType); 40 | channels.add(channel); 41 | } 42 | 43 | @Override 44 | public void removeChannel(ChannelId channelId) { 45 | Assert.notNull(channelId, "channelId can not be null"); 46 | channels.remove(channelId); 47 | } 48 | 49 | @Override 50 | public ChannelWrapper getChannelWrapper(ChannelId channelId) { 51 | Assert.notNull(channelId, "channelId can not be null"); 52 | if (channels.isEmpty()) { 53 | return null; 54 | } 55 | Channel channel = channels.find(channelId); 56 | return wrapChannel(channel); 57 | } 58 | 59 | @Override 60 | public ChannelWrapper getChannelWrapper(String longId) { 61 | Assert.notNull(longId, "longId can not be null"); 62 | if (channels.isEmpty()) { 63 | return null; 64 | } 65 | Channel channel = channels.stream() 66 | .filter(item -> item.id().asLongText().equals(longId)) 67 | .findFirst() 68 | .orElse(null); 69 | return wrapChannel(channel); 70 | } 71 | 72 | @Override 73 | public List getAllChannelWrappers() { 74 | if (channels.isEmpty()) { 75 | return Collections.emptyList(); 76 | } 77 | return channels.stream() 78 | .map(this::wrapChannel) 79 | .collect(Collectors.toList()); 80 | } 81 | 82 | private ChannelWrapper wrapChannel(Channel channel) { 83 | return channel == null ? null : ChannelWrapper.builder() 84 | .channel(channel) 85 | .channelType(channel.attr(CHANNEL_TYPE).get()) 86 | .build(); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/http/HttpExecutor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.http; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.core.lang.Singleton; 5 | import io.bitchat.core.executor.AbstractExecutor; 6 | import io.bitchat.http.HttpContext; 7 | import io.bitchat.http.controller.ControllerProxy; 8 | import io.bitchat.http.controller.ProxyInvocation; 9 | import io.bitchat.http.exception.InvocationException; 10 | import io.bitchat.http.util.HttpRenderUtil; 11 | import io.netty.handler.codec.http.FullHttpResponse; 12 | import io.netty.handler.codec.http.HttpHeaderNames; 13 | import io.netty.handler.codec.http.HttpRequest; 14 | import io.netty.handler.codec.http.HttpResponse; 15 | import io.netty.handler.codec.http.cookie.Cookie; 16 | import io.netty.handler.codec.http.cookie.ServerCookieEncoder; 17 | import lombok.extern.slf4j.Slf4j; 18 | 19 | import java.util.Set; 20 | 21 | /** 22 | * @author houyi 23 | */ 24 | @Slf4j 25 | public class HttpExecutor extends AbstractExecutor { 26 | 27 | private ControllerContext controllerContext; 28 | 29 | private HttpExecutor() { 30 | this.controllerContext = ControllerContext.getInstance(); 31 | } 32 | 33 | public static HttpExecutor getInstance() { 34 | return Singleton.get(HttpExecutor.class); 35 | } 36 | 37 | 38 | @Override 39 | public HttpResponse doExecute(Object... request) { 40 | HttpRequest httpRequest = (HttpRequest) request[0]; 41 | // 暂存请求对象 42 | // 将request存储到ThreadLocal中去,便于后期在其他地方获取并使用 43 | HttpContext.currentContext().setRequest(httpRequest); 44 | HttpResponse response = null; 45 | try { 46 | // 处理业务逻辑 47 | response = invoke(httpRequest); 48 | } catch (Exception e) { 49 | log.error("Server Internal Error,cause:", e); 50 | response = getErrorResponse(e); 51 | } finally { 52 | // 构造响应头 53 | buildHeaders(response, HttpContext.currentContext()); 54 | // 释放ThreadLocal对象 55 | HttpContext.clear(); 56 | } 57 | return response; 58 | } 59 | 60 | private HttpResponse invoke(HttpRequest request) throws Exception { 61 | // 根据路由获得具体的ControllerProxy 62 | ControllerProxy controllerProxy = controllerContext.getProxy(request.method(), request.uri()); 63 | if (controllerProxy == null) { 64 | return HttpRenderUtil.getNotFoundResponse(); 65 | } 66 | // 调用用户自定义的Controller,获得结果 67 | Object result = ProxyInvocation.invoke(controllerProxy); 68 | return HttpRenderUtil.render(result, controllerProxy.getRenderType()); 69 | } 70 | 71 | private HttpResponse getErrorResponse(Exception e) { 72 | HttpResponse response; 73 | if (e instanceof IllegalArgumentException || e instanceof InvocationException) { 74 | response = HttpRenderUtil.getErrorResponse(e.getMessage()); 75 | } else { 76 | response = HttpRenderUtil.getServerErrorResponse(); 77 | } 78 | return response; 79 | } 80 | 81 | private void buildHeaders(HttpResponse response, HttpContext httpContext) { 82 | if (response == null) { 83 | return; 84 | } 85 | FullHttpResponse fullHttpResponse = (FullHttpResponse) response; 86 | fullHttpResponse.headers().add(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(fullHttpResponse.content().readableBytes())); 87 | // 写cookie 88 | Set cookies = httpContext.getCookies(); 89 | if (CollectionUtil.isNotEmpty(cookies)) { 90 | for (Cookie cookie : cookies) { 91 | fullHttpResponse.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie)); 92 | } 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/packet/PacketExecutor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.packet; 2 | 3 | import cn.hutool.core.lang.Singleton; 4 | import io.bitchat.core.executor.AbstractExecutor; 5 | import io.bitchat.packet.Packet; 6 | import io.bitchat.packet.factory.PacketFactory; 7 | import io.bitchat.packet.Payload; 8 | import io.bitchat.packet.ctx.RequestProcessorContext; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | /** 13 | * @author houyi 14 | */ 15 | @Slf4j 16 | public class PacketExecutor extends AbstractExecutor { 17 | 18 | private RequestProcessorContext requestHandler; 19 | 20 | private PacketExecutor() { 21 | this.requestHandler = RequestProcessorContext.getInstance(); 22 | } 23 | 24 | public static PacketExecutor getInstance() { 25 | return Singleton.get(PacketExecutor.class); 26 | } 27 | 28 | @SuppressWarnings("unchecked") 29 | @Override 30 | public Packet doExecute(Object... request) { 31 | ChannelHandlerContext ctx = (ChannelHandlerContext) request[0]; 32 | Packet packet = (Packet) request[1]; 33 | Payload payload = requestHandler.process(ctx, packet.getRequest()); 34 | return PacketFactory.newResponsePacket(payload, packet.getId()); 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/rest/StatusController.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.rest; 2 | 3 | import io.bitchat.http.RenderType; 4 | import io.bitchat.http.RequestMethod; 5 | import io.bitchat.http.controller.Controller; 6 | import io.bitchat.http.controller.Mapping; 7 | import io.bitchat.server.channel.ChannelManager; 8 | import io.bitchat.server.channel.ChannelWrapper; 9 | import io.bitchat.server.channel.DefaultChannelManager; 10 | import io.bitchat.server.session.DefaultSessionManager; 11 | import io.bitchat.server.session.Session; 12 | import io.bitchat.server.session.SessionManager; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * @author houyi 18 | */ 19 | @Controller(path = "/status") 20 | public class StatusController { 21 | 22 | private ChannelManager channelManager = DefaultChannelManager.getInstance(); 23 | 24 | private SessionManager sessionManager = DefaultSessionManager.getInstance(); 25 | 26 | 27 | @Mapping(path = "/channels", requestMethod = RequestMethod.GET, renderType = RenderType.JSON) 28 | public List channelWrappers() { 29 | return channelManager.getAllChannelWrappers(); 30 | } 31 | 32 | @Mapping(path = "/sessions", requestMethod = RequestMethod.GET, renderType = RenderType.JSON) 33 | public List sessions() { 34 | return sessionManager.getAllSessions(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/session/AbstractSessionManager.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.session; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.core.lang.Assert; 5 | import cn.hutool.core.util.IdUtil; 6 | import io.bitchat.server.channel.ChannelType; 7 | import io.netty.channel.ChannelId; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.util.*; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * @author houyi 16 | */ 17 | @Slf4j 18 | public abstract class AbstractSessionManager implements SessionManager { 19 | 20 | private static Map sessionMap; 21 | 22 | public AbstractSessionManager() { 23 | sessionMap = new ConcurrentHashMap<>(); 24 | } 25 | 26 | public abstract Session newSession(String sessionId); 27 | 28 | @Override 29 | public boolean exists(ChannelType channelType, long userId) { 30 | List sessions = getAllSessions(); 31 | if (CollectionUtil.isEmpty(sessions)) { 32 | return false; 33 | } 34 | Session existsSession = sessions.stream() 35 | .filter(session -> session.channelType() == channelType && session.userId() == userId) 36 | .findFirst() 37 | .orElse(null); 38 | return existsSession != null; 39 | } 40 | 41 | @Override 42 | public Session newSession() { 43 | String sessionId = IdUtil.objectId(); 44 | return newSession(sessionId); 45 | } 46 | 47 | @Override 48 | public void bound(Session session, ChannelId channelId, long userId) { 49 | Assert.notNull(session, "session can not be null"); 50 | Assert.notNull(channelId, "channelId can not be null"); 51 | session.bound(channelId, userId); 52 | sessionMap.putIfAbsent(session.sessionId(), session); 53 | log.info("bound a new session, session={}, channelId={}", session, channelId); 54 | } 55 | 56 | @Override 57 | public void removeSession(ChannelId channelId) { 58 | Assert.notNull(channelId, "channelId can not be null"); 59 | Collection sessions = allSession(); 60 | if (CollectionUtil.isEmpty(sessions)) { 61 | return; 62 | } 63 | Iterator iterator = sessions.iterator(); 64 | while (iterator.hasNext()) { 65 | Session session = iterator.next(); 66 | if (session.channelId() == channelId) { 67 | iterator.remove(); 68 | log.info("remove a session, session={}, channelId={}", session, channelId); 69 | break; 70 | } 71 | } 72 | } 73 | 74 | @Override 75 | public Session getSession(String sessionId) { 76 | Assert.notNull(sessionId, "sessionId can not be null"); 77 | return sessionMap.get(sessionId); 78 | } 79 | 80 | @Override 81 | public List getSessionsByUserId(long userId) { 82 | Collection sessions = allSession(); 83 | if (CollectionUtil.isEmpty(sessions)) { 84 | return Collections.emptyList(); 85 | } 86 | return sessions.stream() 87 | .filter(session -> session.userId() == userId) 88 | .collect(Collectors.toList()); 89 | } 90 | 91 | @Override 92 | public List getSessionsByUserIdAndChannelType(long userId, ChannelType channelType) { 93 | Collection sessions = allSession(); 94 | if (CollectionUtil.isEmpty(sessions)) { 95 | return Collections.emptyList(); 96 | } 97 | return sessions.stream() 98 | .filter(session -> session.userId() == userId) 99 | .filter(session -> channelType == null || session.channelType() == channelType) 100 | .collect(Collectors.toList()); 101 | } 102 | 103 | @Override 104 | public List getAllSessions() { 105 | return CollectionUtil.newArrayList(allSession()); 106 | } 107 | 108 | private Collection allSession() { 109 | return sessionMap.values(); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/session/DefaultSession.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.session; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import com.alibaba.fastjson.JSONObject; 5 | import io.bitchat.server.channel.ChannelHelper; 6 | import io.bitchat.server.channel.ChannelType; 7 | import io.bitchat.server.channel.ChannelWrapper; 8 | import io.netty.channel.Channel; 9 | import io.netty.channel.ChannelId; 10 | 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | 13 | /** 14 | * @author houyi 15 | */ 16 | public class DefaultSession implements Session { 17 | 18 | private String sessionId; 19 | private long userId; 20 | private ChannelId channelId; 21 | private Channel channel; 22 | private ChannelType channelType; 23 | 24 | private AtomicBoolean bounded; 25 | 26 | public DefaultSession(String sessionId) { 27 | this.sessionId = sessionId; 28 | this.bounded = new AtomicBoolean(false); 29 | } 30 | 31 | @Override 32 | public String sessionId() { 33 | return sessionId; 34 | } 35 | 36 | @Override 37 | public void bound(ChannelId channelId, long userId) { 38 | if (bounded.compareAndSet(false, true)) { 39 | ChannelWrapper channelWrapper = ChannelHelper.getChannelWrapper(channelId); 40 | Assert.notNull(channelWrapper, "channelId does not exists"); 41 | this.channelId = channelId; 42 | this.userId = userId; 43 | this.channel = channelWrapper.getChannel(); 44 | this.channelType = channelWrapper.getChannelType(); 45 | } 46 | } 47 | 48 | @Override 49 | public long userId() { 50 | if (!bounded.get()) { 51 | throw new IllegalStateException("Not bounded yet, Please call bound first"); 52 | } 53 | return userId; 54 | } 55 | 56 | @Override 57 | public ChannelId channelId() { 58 | if (!bounded.get()) { 59 | throw new IllegalStateException("Not bounded yet, Please call bound first"); 60 | } 61 | return channelId; 62 | } 63 | 64 | @Override 65 | public ChannelType channelType() { 66 | if (!bounded.get()) { 67 | throw new IllegalStateException("Not bounded yet, Please call bound first"); 68 | } 69 | return channelType; 70 | } 71 | 72 | @Override 73 | public void writeAndFlush(Object msg) { 74 | if (!bounded.get()) { 75 | throw new IllegalStateException("Not bounded yet, Please call bound first"); 76 | } 77 | channel.writeAndFlush(msg); 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | JSONObject object = new JSONObject(); 83 | object.put("sessionId", sessionId); 84 | object.put("userId", userId); 85 | object.put("shortId", channelId.asShortText()); 86 | object.put("longId", channelId.asLongText()); 87 | object.put("channelType", channelType); 88 | return object.toJSONString(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/session/DefaultSessionManager.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.session; 2 | 3 | import cn.hutool.core.lang.Singleton; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | /** 7 | * @author houyi 8 | */ 9 | @Slf4j 10 | public class DefaultSessionManager extends AbstractSessionManager { 11 | 12 | private DefaultSessionManager() { 13 | 14 | } 15 | 16 | public static SessionManager getInstance() { 17 | return Singleton.get(DefaultSessionManager.class); 18 | } 19 | 20 | @Override 21 | public Session newSession(String sessionId) { 22 | return new DefaultSession(sessionId); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/session/Session.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.session; 2 | 3 | import io.bitchat.server.channel.ChannelType; 4 | import io.netty.channel.ChannelId; 5 | 6 | /** 7 | * A Session is bound with an unique Channel 8 | * 9 | * @author houyi 10 | */ 11 | public interface Session { 12 | 13 | String sessionId(); 14 | 15 | /** 16 | * bound the session with an unique channel 17 | */ 18 | void bound(ChannelId channelId, long userId); 19 | 20 | long userId(); 21 | 22 | ChannelId channelId(); 23 | 24 | ChannelType channelType(); 25 | 26 | void writeAndFlush(Object msg); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/session/SessionHelper.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.session; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.util.AttributeKey; 5 | 6 | /** 7 | * @author houyi 8 | */ 9 | public class SessionHelper { 10 | 11 | private static final AttributeKey SESSION_ID = AttributeKey.newInstance("sessionId"); 12 | 13 | private SessionHelper() { 14 | 15 | } 16 | 17 | /** 18 | * mark this channel with attribute sessionId 19 | * 20 | * @param channel the channel 21 | */ 22 | public static void markOnline(Channel channel, String sessionId) { 23 | channel.attr(SESSION_ID).set(sessionId); 24 | } 25 | 26 | 27 | /** 28 | * mark this channel with attribute sessionId 29 | * 30 | * @param channel the channel 31 | */ 32 | public static void markOffline(Channel channel) { 33 | channel.attr(SESSION_ID).set(null); 34 | } 35 | 36 | /** 37 | * check whether the channel is login 38 | * 39 | * @param channel the channel 40 | * @return true if logged in otherwise false 41 | */ 42 | public static boolean hasLogin(Channel channel) { 43 | return channel.hasAttr(SESSION_ID) && channel.attr(SESSION_ID).get() != null; 44 | } 45 | 46 | public static String getSessionId(Channel channel) { 47 | return channel.hasAttr(SESSION_ID) ? channel.attr(SESSION_ID).get() : null; 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/session/SessionManager.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.session; 2 | 3 | import io.bitchat.server.channel.ChannelType; 4 | import io.netty.channel.ChannelId; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author houyi 10 | */ 11 | public interface SessionManager { 12 | 13 | boolean exists(ChannelType channelType, long userId); 14 | 15 | Session newSession(); 16 | 17 | void bound(Session session, ChannelId channelId, long userId); 18 | 19 | void removeSession(ChannelId channelId); 20 | 21 | Session getSession(String sessionId); 22 | 23 | List getSessionsByUserId(long userId); 24 | 25 | List getSessionsByUserIdAndChannelType(long userId, ChannelType channelType); 26 | 27 | List getAllSessions(); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/ws/FrameExecutor.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.ws; 2 | 3 | import cn.hutool.core.lang.Singleton; 4 | import cn.hutool.core.util.StrUtil; 5 | import com.alibaba.fastjson.JSON; 6 | import io.bitchat.core.executor.AbstractExecutor; 7 | import io.bitchat.lang.BeanMapper; 8 | import io.bitchat.packet.Payload; 9 | import io.bitchat.packet.ctx.RequestProcessorContext; 10 | import io.bitchat.packet.Request; 11 | import io.bitchat.ws.Frame; 12 | import io.bitchat.ws.FrameFactory; 13 | import io.netty.channel.ChannelHandlerContext; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | import java.util.Map; 17 | 18 | /** 19 | * @author houyi 20 | */ 21 | @Slf4j 22 | public class FrameExecutor extends AbstractExecutor { 23 | 24 | private RequestProcessorContext processorContext; 25 | 26 | private FrameExecutor() { 27 | this.processorContext = RequestProcessorContext.getInstance(); 28 | } 29 | 30 | public static FrameExecutor getInstance() { 31 | return Singleton.get(FrameExecutor.class); 32 | } 33 | 34 | @SuppressWarnings("unchecked") 35 | @Override 36 | public Frame doExecute(Object... request) { 37 | ChannelHandlerContext ctx = (ChannelHandlerContext) request[0]; 38 | Frame frame = (Frame) request[1]; 39 | Request req = BeanMapper.map(frame, Request.class); 40 | String paramJson = frame.getParamJson(); 41 | if (StrUtil.isNotBlank(paramJson)) { 42 | // 将json转换成Map 43 | req.setParams(JSON.parseObject(paramJson, Map.class)); 44 | } 45 | Payload payload = processorContext.process(ctx, req); 46 | return FrameFactory.newResponseFrame(payload, frame.getId()); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /bitchat-server/src/main/java/io/bitchat/server/ws/FrameHandler.java: -------------------------------------------------------------------------------- 1 | package io.bitchat.server.ws; 2 | 3 | import cn.hutool.core.lang.Assert; 4 | import cn.hutool.core.lang.Singleton; 5 | import io.bitchat.server.channel.ChannelType; 6 | import io.bitchat.ws.PendingFrames; 7 | import io.bitchat.core.executor.Executor; 8 | import io.bitchat.packet.PacketType; 9 | import io.bitchat.server.channel.ChannelListener; 10 | import io.bitchat.ws.Frame; 11 | import io.netty.channel.ChannelHandler; 12 | import io.netty.channel.ChannelHandlerContext; 13 | import io.netty.channel.SimpleChannelInboundHandler; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | import java.util.concurrent.CompletableFuture; 17 | 18 | /** 19 | *

20 | * Server frame ChannelHandler 21 | *

22 | * 23 | * @author houyi 24 | */ 25 | @Slf4j 26 | @ChannelHandler.Sharable 27 | public class FrameHandler extends SimpleChannelInboundHandler { 28 | 29 | private Executor executor; 30 | 31 | private ChannelListener channelListener; 32 | 33 | private FrameHandler() { 34 | 35 | } 36 | 37 | private FrameHandler(ChannelListener channelListener) { 38 | Assert.notNull(channelListener, "channelListener can not be null"); 39 | this.executor = FrameExecutor.getInstance(); 40 | this.channelListener = channelListener; 41 | } 42 | 43 | public static FrameHandler getInstance(ChannelListener channelListener) { 44 | return Singleton.get(FrameHandler.class, channelListener); 45 | } 46 | 47 | @Override 48 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 49 | log.debug("FrameHandler received an active channel:{}", ctx.channel()); 50 | channelListener.channelActive(ctx.channel(), ChannelType.WebSocket); 51 | } 52 | 53 | @Override 54 | public void channelRead0(ChannelHandlerContext ctx, Frame frame) { 55 | byte type = frame.getType(); 56 | if (type == PacketType.PACKET_TYPE_REQUEST) { 57 | onRequest(ctx, frame); 58 | } else { 59 | onResponse(frame); 60 | } 61 | } 62 | 63 | @Override 64 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 65 | log.debug("FrameHandler received an inactive channel will remove it:{}", ctx.channel()); 66 | channelListener.channelInactive(ctx.channel()); 67 | } 68 | 69 | @Override 70 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 71 | log.error("Exception occurred cause={} will close the channel:{}", cause.getMessage(), ctx.channel(), cause); 72 | ctx.close(); 73 | } 74 | 75 | private void onRequest(ChannelHandlerContext ctx, Frame frame) { 76 | Frame response = executor.execute(ctx, frame); 77 | writeResponse(ctx, response); 78 | } 79 | 80 | private void writeResponse(ChannelHandlerContext ctx, Frame response) { 81 | if (response != null) { 82 | ctx.channel().writeAndFlush(response); 83 | } 84 | } 85 | 86 | private void onResponse(Frame frame) { 87 | CompletableFuture pending = PendingFrames.remove(frame.getId()); 88 | if (pending != null) { 89 | // the response will be handled by client 90 | // after the client future has been notified 91 | // to be completed 92 | pending.complete(frame); 93 | } 94 | } 95 | 96 | 97 | } 98 | --------------------------------------------------------------------------------