├── .dockerignore ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── generate-ca.sh └── generate-server-keystore.sh ├── build.sbt ├── example ├── Dockerfile ├── ca_cert.pem ├── influent-server.jks ├── plain │ ├── docker-compose.yml │ └── fluent.conf └── secure │ ├── docker-compose.yml │ └── fluent.conf ├── influent-java-example └── src │ └── main │ ├── java │ └── example │ │ ├── Counter.java │ │ ├── Environment.java │ │ ├── Print.java │ │ ├── SecurityCounter.java │ │ └── TLSCounter.java │ └── resources │ └── logback.xml ├── influent-java └── src │ ├── main │ └── java │ │ └── influent │ │ ├── EventEntry.java │ │ ├── EventStream.java │ │ ├── Tag.java │ │ ├── forward │ │ ├── CheckPingResult.java │ │ ├── ForwardCallback.java │ │ ├── ForwardClient.java │ │ ├── ForwardClientNode.java │ │ ├── ForwardClientUser.java │ │ ├── ForwardOption.java │ │ ├── ForwardRequest.java │ │ ├── ForwardSecurity.java │ │ ├── ForwardServer.java │ │ ├── MsgPackPingDecoder.java │ │ ├── MsgpackForwardRequestDecoder.java │ │ ├── NioForwardConnection.java │ │ ├── NioForwardServer.java │ │ └── NioUdpHeartbeatServer.java │ │ └── internal │ │ └── msgpack │ │ ├── DecodeResult.java │ │ ├── InfluentByteBuffer.java │ │ ├── MsgpackIncrementalUnpacker.java │ │ └── MsgpackStreamUnpacker.java │ └── test │ ├── resources │ └── mockito-extensions │ │ └── org.mockito.plugins.MockMaker │ └── scala │ └── influent │ ├── forward │ ├── MsgpackForwardRequestDecoderSpec.scala │ ├── NioForwardConnectionSpec.scala │ └── NioUdpHeartbeatServerSpec.scala │ └── internal │ └── msgpack │ ├── MsgpackIncrementalUnpackerSpec.scala │ ├── MsgpackStreamUnpackerSpec.scala │ └── MsgpackUnpackerArbitrary.scala ├── influent-transport └── src │ ├── main │ └── java │ │ └── influent │ │ ├── exception │ │ └── InfluentIOException.java │ │ └── internal │ │ ├── nio │ │ ├── NioAttachment.java │ │ ├── NioChannelConfig.java │ │ ├── NioEventLoop.java │ │ ├── NioEventLoopPool.java │ │ ├── NioEventLoopTask.java │ │ ├── NioRoundRobinEventLoopPool.java │ │ ├── NioSelectionKey.java │ │ ├── NioServerSocketChannel.java │ │ ├── NioSingleThreadEventLoopPool.java │ │ ├── NioTcpAcceptor.java │ │ ├── NioTcpChannel.java │ │ ├── NioTcpConfig.java │ │ ├── NioTcpPlaintextChannel.java │ │ ├── NioTcpTlsChannel.java │ │ ├── NioTlsEngine.java │ │ └── NioUdpChannel.java │ │ └── util │ │ ├── Exceptions.java │ │ ├── Futures.java │ │ ├── Inet4Network.java │ │ ├── Inet6Network.java │ │ ├── InetNetwork.java │ │ └── ThreadSafeQueue.java │ └── test │ ├── resources │ └── mockito-extensions │ │ └── org.mockito.plugins.MockMaker │ └── scala │ └── influent │ └── internal │ ├── nio │ ├── NioEventLoopSpec.scala │ ├── NioEventLoopTaskSpec.scala │ ├── NioRoundRobinEventLoopPoolSpec.scala │ ├── NioSelectionKeySpec.scala │ ├── NioServerSocketChannelSpec.scala │ ├── NioSingleThreadEventLoopPoolSpec.scala │ ├── NioTcpAcceptorSpec.scala │ ├── NioTcpChannelSpec.scala │ ├── NioTcpConfigSpec.scala │ ├── NioTcpPlaintextChannelSpec.scala │ └── NioUdpChannelSpec.scala │ └── util │ ├── FuturesSpec.scala │ ├── Inet4NetworkSpec.scala │ ├── Inet6NetworkSpec.scala │ ├── InetNetworkSpec.scala │ └── ThreadSafeQueueSpec.scala ├── project ├── build.properties ├── formatting-java.xml └── plugins.sbt └── publish.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !influent-java/src 3 | !influent-java-example/src 4 | !influent-transport/src 5 | !project/build.properties 6 | !project/plugins.sbt 7 | !build.sbt 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | target 3 | *.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | dist: xenial 4 | 5 | script: 6 | - sbt -J-Xmx3784m test 7 | 8 | jdk: 9 | - openjdk8 10 | - openjdk11 11 | 12 | sudo: true 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Influent 2 | 3 | [![Build Status](https://travis-ci.org/okumin/influent.svg?branch=master)](https://travis-ci.org/okumin/influent) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.okumin/influent-java/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.okumin/influent-java) 5 | [![javadoc](http://javadoc-badge.appspot.com/com.okumin/influent-java.svg)](http://javadoc-badge.appspot.com/com.okumin/influent-java/index.html) 6 | 7 | Influent is a library to implement a Fluentd's forward server on the JVM. 8 | 9 | ## Protocol 10 | 11 | `influent.forward.ForwardServer` is almost compatible with [Forward Protocol Specification v1](https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1). 12 | 13 | This is the protocol for Fluentd's forward plugin. 14 | 15 | * [forward Input Plugin](http://docs.fluentd.org/articles/in_forward) 16 | * [forward Output Plugin](http://docs.fluentd.org/articles/out_forward) 17 | 18 | Influent is a server implementation, so behaves as like `in_forward`. 19 | 20 | There are some features that Influent does not support now. 21 | See also the `TODO` section. 22 | 23 | ## Advantages over Fluentd 24 | 25 | There are some reasons why Influent is developed. 26 | 27 | ### Java integration 28 | 29 | Influent enables users to handle Fluentd's events by Java. 30 | This means that they can use directly their domain logic written in Java or Java client APIs for some middleware. 31 | 32 | ### High performance 33 | 34 | JVM has high performance and Java has good thread API and IO API. 35 | Influent makes it possible to upgrade performance for some applications. 36 | 37 | ## TODO 38 | 39 | * handshake phase implementation 40 | * CompressedPackedForward mode implementation 41 | * TLS support 42 | * load test and performance improvement 43 | * Scala API 44 | 45 | ## Usage 46 | 47 | ### Dependency 48 | 49 | #### Maven 50 | 51 | ``` 52 | 53 | com.okumin 54 | influent-java 55 | 0.3.0 56 | 57 | ``` 58 | 59 | ### How to use 60 | 61 | Give `ForwardServer` the callback function that receives `EventStream`. 62 | If you want to write `EventStreams` to stdout, 63 | 64 | ```java 65 | // The callback function 66 | ForwardCallback callback = ForwardCallback.ofSyncConsumer( 67 | stream -> System.out.println(stream), 68 | Executors.newFixedThreadPool(1) 69 | ); 70 | 71 | // Constructs a new server 72 | int port = 24224; 73 | ForwardServer server = new ForwardServer 74 | .Builder(callback) 75 | .localAddress(port) 76 | .build(); 77 | 78 | // Starts the server on a new thread 79 | server.start(); 80 | 81 | Thread.sleep(60 * 1000); 82 | 83 | // ForwardServer#shutdown returns a CompletableFuture 84 | CompletableFuture stopping = server.shutdown(); 85 | // The future will be completed when the server is terminated 86 | stopping.get(); 87 | ``` 88 | 89 | Execute the above code, and send a message by `fluent-cat` command. 90 | 91 | ``` 92 | $ echo '{"foo": "bar", "scores": [33, 4]}' | fluent-cat mofu 93 | ``` 94 | 95 | The received `EventStream` is written to stdout. 96 | 97 | ``` 98 | EventStream(Tag(mofu), [EventEntry(2016-11-13T13:10:59Z,{"foo":"bar","scores":[33,4]})]) 99 | ``` 100 | -------------------------------------------------------------------------------- /bin/generate-ca.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | mkdir -p out 4 | chmod 700 out 5 | 6 | while true; do 7 | echo -n "Enter private key password for CA:" 8 | read -s password 9 | if [ "${#password}" -ge 4 ]; then 10 | echo 11 | break; 12 | else 13 | echo 14 | echo "Password must be at least 4 characters" 15 | continue 16 | fi 17 | done 18 | 19 | while true; do 20 | echo -n "Enter Country Name for CA [US]:" 21 | read country_name 22 | if [ -z "${country_name}" ]; then 23 | country_name=US 24 | break; 25 | elif [ "${#country_name}" -eq 2 ]; then 26 | break; 27 | else 28 | echo "Country Name must be 2 characters" 29 | continue 30 | fi 31 | done 32 | 33 | echo -n "Enter State or Province Name for CA [CA]:" 34 | read state_name 35 | if [ -z "${state_name}" ]; then 36 | state_name=CA 37 | fi 38 | 39 | echo -n "Enter Locality Name (eg, city) for CA [Mountain View]:" 40 | read locality_name 41 | if [ -z "${locality_name}" ]; then 42 | locality_name="Mountain View" 43 | fi 44 | 45 | echo -n "Organization Name (eg, company) for CA [Influent]:" 46 | read organization_name 47 | if [ -z "${organization_name}" ]; then 48 | organization_name="Influent" 49 | fi 50 | 51 | echo -n "Organizational Unit Name (eg, section) for CA []:" 52 | read organization_unit_name 53 | if [ -z "${organization_unit_name}" ]; then 54 | organization_unit_name="" 55 | fi 56 | 57 | echo -n "Common Name (e.g. server FQDN or YOUR name) [Influent CA]:" 58 | read common_name 59 | if [ -z "${common_name}" ]; then 60 | common_name="Influent CA" 61 | fi 62 | 63 | echo -n "Certificate valid days [36500]:" 64 | read validity_days 65 | if [ -z "${validity_days}" ]; then 66 | validity_days=36500 67 | fi 68 | 69 | openssl req \ 70 | -new \ 71 | -x509 \ 72 | -newkey rsa:4096 \ 73 | -out out/ca_cert.pem \ 74 | -keyout out/ca_key.pem \ 75 | -days ${validity_days} \ 76 | -passout pass:${password} \ 77 | -subj "/C=${country_name}/ST=${state_name}/L=${locality_name}/O=${organization_name}/OU=${organization_unit_name}/CN=${common_name}" 78 | 79 | chmod 600 out/ca_key.pem 80 | 81 | echo "out/ca_key.pem and out/ca_cert.pem are generated." 82 | echo "You can check ca_key.pem by 'openssl rsa -text -in out/ca_key.pem'" 83 | echo "You can check ca_cert.pem by 'openssl x509 -text -in out/ca_cert.pem'" 84 | -------------------------------------------------------------------------------- /bin/generate-server-keystore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | mkdir -p out 4 | chmod 700 out 5 | 6 | echo -n "Enter the certificate filename of CA [out/ca_cert.pem]:" 7 | read ca_cert_filename 8 | if [ -z "${ca_cert_filename}" ]; then 9 | ca_cert_filename="out/ca_cert.pem" 10 | fi 11 | 12 | echo -n "Enter the private key filename of CA [out/ca_key.pem]:" 13 | read ca_key_filename 14 | if [ -z "${ca_key_filename}" ]; then 15 | ca_key_filename="out/ca_key.pem" 16 | fi 17 | 18 | while true; do 19 | echo -n "Enter private key password of existent CA:" 20 | read -s ca_key_password 21 | if [ "${#ca_key_password}" -ge 4 ]; then 22 | echo 23 | break; 24 | else 25 | echo 26 | echo "Password must be at least 4 characters" 27 | continue 28 | fi 29 | done 30 | 31 | while true; do 32 | echo -n "Enter key store password for server:" 33 | read -s key_store_password 34 | if [ "${#key_store_password}" -ge 6 ]; then 35 | echo 36 | break; 37 | else 38 | echo 39 | echo "Password must be at least 6 characters" 40 | continue 41 | fi 42 | done 43 | 44 | while true; do 45 | echo -n "Enter private key password for server:" 46 | read -s key_password 47 | if [ "${#key_password}" -ge 6 ]; then 48 | echo 49 | break; 50 | else 51 | echo 52 | echo "Password must be at least 6 characters" 53 | continue 54 | fi 55 | done 56 | 57 | echo -n "Enter a name of the cert name of CA [influent-ca]:" 58 | read ca_cert_name 59 | if [ -z "${ca_cert_name}" ]; then 60 | ca_cert_name=influent-ca 61 | fi 62 | 63 | echo -n "Enter a name of the keypair name for server [influent-server]:" 64 | read keypair_name 65 | if [ -z "${keypair_name}" ]; then 66 | keypair_name=influent-server 67 | fi 68 | 69 | while true; do 70 | echo -n "Enter Country Name for server [US]:" 71 | read country_name 72 | if [ -z "${country_name}" ]; then 73 | country_name=US 74 | break; 75 | elif [ "${#country_name}" -eq 2 ]; then 76 | break; 77 | else 78 | echo "Country Name must be 2 characters" 79 | continue 80 | fi 81 | done 82 | 83 | echo -n "Enter State or Province Name for server [CA]:" 84 | read state_name 85 | if [ -z "${state_name}" ]; then 86 | state_name=CA 87 | fi 88 | 89 | echo -n "Enter Locality Name (eg, city) for server [Mountain View]:" 90 | read locality_name 91 | if [ -z "${locality_name}" ]; then 92 | locality_name="Mountain View" 93 | fi 94 | 95 | echo -n "Organization Name (eg, company) for server [Influent]:" 96 | read organization_name 97 | if [ -z "${organization_name}" ]; then 98 | organization_name="Influent" 99 | fi 100 | 101 | echo -n "Organizational Unit Name (eg, section) for server []:" 102 | read organization_unit_name 103 | if [ -z "${organization_unit_name}" ]; then 104 | organization_unit_name="" 105 | fi 106 | 107 | echo -n "Common Name (e.g. server host name):" 108 | read common_name 109 | if [ -z "${common_name}" ]; then 110 | common_name="Influent Server" 111 | fi 112 | 113 | echo -n "Certificate valid days [36500]:" 114 | read validity_days 115 | if [ -z "${validity_days}" ]; then 116 | validity_days=36500 117 | fi 118 | 119 | keytool \ 120 | -genkeypair \ 121 | -alias ${keypair_name} \ 122 | -keyalg RSA \ 123 | -keysize 4096 \ 124 | -dname "CN=${common_name}, OU=${organization_unit_name}, O=${organization_name}, L=${locality_name}, ST=${state_name}, C=${country_name}" \ 125 | -validity ${validity_days} \ 126 | -keypass ${key_password} \ 127 | -keystore out/influent-server.jks \ 128 | -storepass ${key_store_password} 129 | 130 | chmod 600 out/influent-server.jks 131 | 132 | keytool \ 133 | -certreq \ 134 | -alias ${keypair_name} \ 135 | -file out/influent-server.csr \ 136 | -keypass ${key_password} \ 137 | -keystore out/influent-server.jks \ 138 | -storepass ${key_store_password} 139 | 140 | openssl \ 141 | x509 \ 142 | -req \ 143 | -in out/influent-server.csr \ 144 | -out out/influent-server.crt \ 145 | -passin pass:${ca_key_password} \ 146 | -days ${validity_days} \ 147 | -CA ${ca_cert_filename} \ 148 | -CAkey ${ca_key_filename} \ 149 | -CAcreateserial 150 | 151 | keytool \ 152 | -import \ 153 | -alias ${ca_cert_name} \ 154 | -file ${ca_cert_filename} \ 155 | -keystore out/influent-server.jks \ 156 | -storepass ${key_store_password} << EOF 157 | y 158 | EOF 159 | 160 | keytool \ 161 | -import \ 162 | -alias ${keypair_name} \ 163 | -file out/influent-server.crt \ 164 | -keypass ${key_password} \ 165 | -keystore out/influent-server.jks \ 166 | -storepass ${key_store_password} 167 | 168 | rm out/*.srl 169 | rm out/influent-server.csr 170 | rm out/influent-server.crt 171 | 172 | echo "out/influent-server.jks are generated." 173 | echo "You can check influent-server.jks by 'keytool -v -list -keystore out/influent-server.jks'" 174 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | 2 | lazy val root = (project in file(".")).aggregate(influentJava, influentTransport) 3 | 4 | lazy val influentJava = (project in file("influent-java")) 5 | .settings(commonSettings: _*) 6 | .settings(javaSettings: _*) 7 | .settings(publishSettings: _*) 8 | .settings( 9 | name := "influent-java", 10 | libraryDependencies ++= Seq( 11 | "org.msgpack" % "msgpack-core" % "0.8.16" 12 | ) 13 | ) 14 | .dependsOn(influentTransport) 15 | .enablePlugins(AutomateHeaderPlugin) 16 | 17 | lazy val influentTransport = (project in file("influent-transport")) 18 | .settings(commonSettings: _*) 19 | .settings(javaSettings: _*) 20 | .settings(publishSettings: _*) 21 | .settings( 22 | name := "influent-transport" 23 | ) 24 | .enablePlugins(AutomateHeaderPlugin) 25 | 26 | lazy val influentJavaExample = (project in file("influent-java-example")) 27 | .settings(commonSettings: _*) 28 | .settings(javaSettings: _*) 29 | .settings( 30 | name := "influent-java-example", 31 | libraryDependencies ++= Seq( 32 | "ch.qos.logback" % "logback-classic" % "1.2.3" 33 | ), 34 | assemblyJarName in assembly := "influent-java-example.jar" 35 | ).dependsOn(influentJava) 36 | .enablePlugins(AutomateHeaderPlugin) 37 | 38 | lazy val commonSettings = Seq( 39 | organization := "com.okumin", 40 | version := "0.4.0-M1", 41 | scalaVersion := "2.12.6", 42 | fork in Test := true, 43 | javacOptions in (Compile, doc) ++= Seq("-locale", "en_US"), 44 | javacOptions in (Compile, compile) ++= Seq("-encoding", "UTF-8"), 45 | libraryDependencies ++= Seq( 46 | "org.slf4j" % "slf4j-api" % "1.7.25", 47 | "org.mockito" % "mockito-core" % "2.28.2" % "test", 48 | "org.scalatest" %% "scalatest" % "3.0.5" % "test", 49 | "org.scalacheck" %% "scalacheck" % "1.14.0" % "test" 50 | ), 51 | // sbt-header settings 52 | organizationName := "okumin", 53 | startYear := Some(2016), 54 | licenses += ("Apache-2.0", new URL("https://www.apache.org/licenses/LICENSE-2.0.txt")) 55 | ) 56 | 57 | lazy val javaSettings = Seq( 58 | crossPaths := false, 59 | autoScalaLibrary := false 60 | ) 61 | 62 | lazy val publishSettings = Seq( 63 | publishMavenStyle := true, 64 | publishArtifact in Test := false, 65 | pomIncludeRepository := { _ => false }, 66 | publishTo := { 67 | val nexus = "https://oss.sonatype.org/" 68 | if (isSnapshot.value) 69 | Some("snapshots" at nexus + "content/repositories/snapshots") 70 | else 71 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 72 | }, 73 | pomExtra := { 74 | https://github.com/okumin/influent 75 | 76 | git@github.com:okumin/influent.git 77 | scm:git:git@github.com:okumin/influent.git 78 | 79 | 80 | 81 | okumin 82 | okumin 83 | http://okumin.com/ 84 | 85 | 86 | } 87 | ) 88 | -------------------------------------------------------------------------------- /example/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8 AS builder 2 | 3 | WORKDIR /influent-build 4 | COPY ./build.sbt ./build.sbt 5 | COPY ./project ./project 6 | 7 | RUN apt-get update \ 8 | && apt-get install apt-transport-https \ 9 | && echo "deb https://dl.bintray.com/sbt/debian /" > /etc/apt/sources.list.d/sbt.list \ 10 | && apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 \ 11 | && apt-get update \ 12 | && apt-get install sbt \ 13 | && sbt update 14 | 15 | COPY ./ ./ 16 | RUN sbt "project influentJavaExample" "assembly" 17 | 18 | FROM openjdk:8 19 | 20 | WORKDIR /influent 21 | COPY --from=builder /influent-build/influent-java-example/target/influent-java-example.jar ./influent-java-example.jar 22 | ENTRYPOINT ["java", "-classpath", "./influent-java-example.jar", "example.Print"] 23 | -------------------------------------------------------------------------------- /example/ca_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFPjCCAyYCCQD8CZ7WWS81TTANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxETAPBgNVBAoM 4 | CEluZmx1ZW50MRkwFwYDVQQDDBBJbmZsdWVudCBUZXN0IENBMCAXDTE4MTAxODE2 5 | MTExOFoYDzIxMTgwOTI0MTYxMTE4WjBgMQswCQYDVQQGEwJVUzELMAkGA1UECAwC 6 | Q0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxETAPBgNVBAoMCEluZmx1ZW50MRkw 7 | FwYDVQQDDBBJbmZsdWVudCBUZXN0IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A 8 | MIICCgKCAgEAqQ3PUqOBynX1NR9QAAb1reS2qcTrobOkPUV11+tqS0Bpq256dMNr 9 | u3BFgoPmyO8kzR407z3CH9IXPknyKtTGXtYVtPuKg4L1x7NS6xlONMxlmMUw/RKr 10 | 0NGpayZhx9OoKM34GOsQhOktvbBbKMt/ann9uFSBIcMg2PDygHVogmaWGvQ5IZ2U 11 | wTb+bM9+rMrNo37EVLrppLo1VFgrEetXoWFQfkQIedQIa8IEWeLTCqHpBv5HcJqj 12 | YwwdhJTRrmLbWmwexJxTL/wLO/EfgEFTFJ9uSEhjYbDon8H4YMl5JFZPGo77aJ64 13 | yLTnXG0/UNd8MZO37yttHHIphRMAECPb6N4CN6WQ3uUp5inJBunQSAKnIyhzQOyw 14 | P46yLPUUP2QDvTjoRWJSAs/j5SZ8MFCZkBG1dD78JYxg+SwlAjtCda2iw99oRCkO 15 | DKMHH4gU9HCt5nkY9x3v6EXSdtWGRh99FEAad14TXdhpHo3LDfLubyDRwsKlQkig 16 | o9+M4SGualnMTMxmg4sLNFp/9XglTRkF5YfSUJe2NVL9kvYdc8oNLfL8Hhakm0Na 17 | ue3VoZDYKwPIJ1sc76oJpmuMH1bQEey5qMmAwehlQ3s4+P319WQZV87bgQ3YjqD3 18 | vrDT+RfF/6y+j5kUNpd2X6SBVrEtndMIai9Ki0MSQRCBcHtFJXSQN1MCAwEAATAN 19 | BgkqhkiG9w0BAQsFAAOCAgEASXOr8m4kefY7A9KQWKJESb4uw3mu32WLZvKAQpQ0 20 | 0mfUIa0Pm74aFrWJaFKaFOpicx7T8OJLRXQpg6qIkvGiTfsVCspAPLtpGfbNxNUG 21 | yPrcsUKNtQ9eF/El2eGRb9HctYJXoJQdEf1O3A1LmbzxNNaWmYvlj5d6Vfb91b1t 22 | +vLNY4AX/tWhXdTwd1slLXpVVaMBsoUEHpFVbzlwcQVZHi2kn7DGYggo7zzrg2el 23 | j5xwPQpzOVL/ZKy/SATVaf/nbSjzuZxTGwTp2Zfm9sV3tSvwsqcZaHJ0rlV+6qAk 24 | AVwa1eYSREDhlHr5/7bwfJAguyKbMpPzBgIV4yxZ85N2eWaSLlGw9J99Sfm7Hd6S 25 | B20pqBTDEGMjn4SM1n2FSviPdW014GvXnJ9mBAGE8HS+dEwfjxW2wupAUEKH9guR 26 | gww4BkViR6Soq4k4ZJE1clXsUgEmhiGTNnfOUeLlbzezUeWIu6ac/xMxo7e/W9qA 27 | j+NWFQtfm5hoD1nA88A56avulJ5aVO/28BbQdt84rLU4T/jYsu96DMJAQq9kXj9Z 28 | kc7laKcK0pRUH2Miz28D6wkFdqAUKeP7SegUwjvt0IcUqI+695zPDC2FLmTL3lXH 29 | btfib+MEDcdVQ0i4IFArjXQQb5Pfmo6MGSAS6NOWWd/O+yYn9h2DKFJT4SyAhOWO 30 | CbQ= 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /example/influent-server.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okumin/influent/143d5215315ae550a4d2c554b92c55e587122bf3/example/influent-server.jks -------------------------------------------------------------------------------- /example/plain/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | influent-server: 4 | build: 5 | context: ../.. 6 | dockerfile: example/Dockerfile 7 | environment: 8 | DURATION_SECONDS: '60' 9 | INFLUENT_LOG_LEVEL: INFO 10 | fluentd-agent: 11 | image: fluent/fluentd 12 | volumes: 13 | - ./fluent.conf:/fluentd/etc/fluent.conf 14 | -------------------------------------------------------------------------------- /example/plain/fluent.conf: -------------------------------------------------------------------------------- 1 | 2 | @type dummy 3 | dummy {"hello":"world"} 4 | auto_increment_key count 5 | tag dummy 6 | 7 | 8 | @type copy 9 | 10 | @type forward 11 | flush_interval 1s 12 | require_ack_response true 13 | 14 | 15 | host influent-server 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/secure/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | influent-server: 4 | build: 5 | context: ../.. 6 | dockerfile: example/Dockerfile 7 | environment: 8 | DURATION_SECONDS: '60' 9 | TLS_ENABLED: 'true' 10 | HANDSHAKE_ENABLED: 'false' 11 | INFLUENT_LOG_LEVEL: INFO 12 | volumes: 13 | - ../influent-server.jks:/influent/influent-server.jks 14 | fluentd-agent: 15 | image: fluent/fluentd 16 | volumes: 17 | - ./fluent.conf:/fluentd/etc/fluent.conf 18 | - ../ca_cert.pem:/fluentd/ca_cert.pem 19 | -------------------------------------------------------------------------------- /example/secure/fluent.conf: -------------------------------------------------------------------------------- 1 | 2 | @type dummy 3 | dummy {"hello":"world"} 4 | auto_increment_key count 5 | tag dummy 6 | 7 | 8 | @type copy 9 | 10 | @type forward 11 | flush_interval 1s 12 | require_ack_response true 13 | 14 | transport tls 15 | tls_cert_path /fluentd/ca_cert.pem 16 | 17 | 18 | host influent-server 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /influent-java-example/src/main/java/example/Counter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package example; 18 | 19 | import influent.forward.ForwardCallback; 20 | import influent.forward.ForwardServer; 21 | import java.util.concurrent.CompletableFuture; 22 | import java.util.concurrent.atomic.AtomicLong; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | public class Counter { 27 | private static final class Reporter implements Runnable { 28 | private final AtomicLong counter = new AtomicLong(); 29 | 30 | void add(final int up) { 31 | counter.addAndGet(up); 32 | } 33 | 34 | @Override 35 | public void run() { 36 | long lastChecked = System.currentTimeMillis(); 37 | while (true) { 38 | try { 39 | Thread.sleep(100); 40 | } catch (final InterruptedException e) { 41 | break; 42 | } 43 | final long now = System.currentTimeMillis(); 44 | if (now - lastChecked >= 1000) { 45 | lastChecked = now; 46 | final long current = counter.getAndSet(0); 47 | logger.info("{} requests/sec", current); 48 | } 49 | } 50 | } 51 | } 52 | 53 | private static final Logger logger = LoggerFactory.getLogger(Counter.class); 54 | 55 | public static void main(final String[] args) { 56 | final int workerPoolSize = Integer.parseInt(args[0]); 57 | 58 | final Reporter reporter = new Reporter(); 59 | 60 | final ForwardCallback callback = ForwardCallback.of(stream -> { 61 | reporter.add(stream.getEntries().size()); 62 | return CompletableFuture.completedFuture(null); 63 | }); 64 | 65 | final ForwardServer server = new ForwardServer 66 | .Builder(callback) 67 | .workerPoolSize(workerPoolSize) 68 | .build(); 69 | server.start(); 70 | new Thread(reporter).start(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /influent-java-example/src/main/java/example/Environment.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | class Environment { 4 | private Environment() { 5 | throw new AssertionError(); 6 | } 7 | 8 | static String get(final String name, final String defaultValue) { 9 | final String value = System.getenv(name); 10 | return value != null ? value : defaultValue; 11 | } 12 | 13 | static long getLong(final String name, final long defaultValue) { 14 | final String value = System.getenv(name); 15 | return value != null ? Long.valueOf(value) : defaultValue; 16 | } 17 | 18 | static boolean getBoolean(final String name, final boolean defaultValue) { 19 | final String value = System.getenv(name); 20 | return value != null ? Boolean.valueOf(value) : defaultValue; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /influent-java-example/src/main/java/example/Print.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package example; 18 | 19 | import influent.forward.ForwardCallback; 20 | import influent.forward.ForwardSecurity; 21 | import influent.forward.ForwardServer; 22 | import java.util.concurrent.Executors; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | public final class Print { 27 | private static final Logger logger = LoggerFactory.getLogger(Print.class); 28 | 29 | public static void main(final String[] args) throws Exception { 30 | final long durationSeconds = Environment.getLong("DURATION_SECONDS", 60); 31 | final boolean tlsEnabled = Environment.getBoolean("TLS_ENABLED", false); 32 | final String tlsKeystorePath = Environment.get("TLS_KEYSTORE_PATH", "/influent/influent-server.jks"); 33 | final String tlsKeystorePassword = Environment.get("TLS_KEYSTORE_PASSWORD", "password"); 34 | final String tlsKeyPassword = Environment.get("TLS_KEY_PASSWORD", "password"); 35 | final boolean handshakeEnabled = Environment.getBoolean("HANDSHAKE_ENABLED", false); 36 | final String handshakeHostname = Environment.get("HANDSHAKE_HOSTNAME", "influent-server"); 37 | final String handshakeSharedKey = Environment.get("HANDSHAKE_SHARED_KEY", "shared_key"); 38 | 39 | final ForwardCallback callback = ForwardCallback.ofSyncConsumer( 40 | stream -> logger.info(stream.toString()), 41 | Executors.newWorkStealingPool() 42 | ); 43 | 44 | final ForwardServer.Builder builder = new ForwardServer.Builder(callback); 45 | if (tlsEnabled) { 46 | builder 47 | .sslEnabled(true) 48 | .keystorePath(tlsKeystorePath) 49 | .keystorePassword(tlsKeystorePassword) 50 | .keyPassword(tlsKeyPassword); 51 | } 52 | if (handshakeEnabled) { 53 | final ForwardSecurity security = new ForwardSecurity 54 | .Builder() 55 | .selfHostname(handshakeHostname) 56 | .sharedKey(handshakeSharedKey) 57 | .build(); 58 | builder.security(security); 59 | } 60 | 61 | final ForwardServer server = builder.build(); 62 | server.start(); 63 | 64 | // ForwardServer#start returns immediately 65 | Thread.sleep(durationSeconds * 1000); 66 | 67 | server.shutdown().get(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /influent-java-example/src/main/java/example/SecurityCounter.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import influent.forward.ForwardCallback; 4 | import influent.forward.ForwardSecurity; 5 | import influent.forward.ForwardServer; 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.atomic.AtomicLong; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | public class SecurityCounter { 12 | private static final class Reporter implements Runnable { 13 | private final AtomicLong counter = new AtomicLong(); 14 | 15 | void add(final int up) { 16 | counter.addAndGet(up); 17 | } 18 | 19 | @Override 20 | public void run() { 21 | long lastChecked = System.currentTimeMillis(); 22 | while (true) { 23 | try { 24 | Thread.sleep(100); 25 | } catch (final InterruptedException e) { 26 | break; 27 | } 28 | final long now = System.currentTimeMillis(); 29 | if (now - lastChecked >= 1000) { 30 | lastChecked = now; 31 | final long current = counter.getAndSet(0); 32 | logger.info("{} requests/sec", current); 33 | } 34 | } 35 | } 36 | } 37 | 38 | private static final Logger logger = LoggerFactory.getLogger(Counter.class); 39 | 40 | public static void main(final String[] args) { 41 | final int workerPoolSize = Integer.parseInt(args[0]); 42 | 43 | final Reporter reporter = new Reporter(); 44 | 45 | final ForwardCallback callback = ForwardCallback.of(stream -> { 46 | reporter.add(stream.getEntries().size()); 47 | return CompletableFuture.completedFuture(null); 48 | }); 49 | 50 | final ForwardSecurity security = new ForwardSecurity 51 | .Builder() 52 | .selfHostname("input.testing.local") 53 | .sharedKey("secure_communication_is_awesome") 54 | .build(); 55 | final ForwardServer server = new ForwardServer 56 | .Builder(callback) 57 | .workerPoolSize(workerPoolSize) 58 | .security(security) 59 | .build(); 60 | server.start(); 61 | new Thread(reporter).start(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /influent-java-example/src/main/java/example/TLSCounter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package example; 18 | 19 | import influent.forward.ForwardCallback; 20 | import influent.forward.ForwardServer; 21 | import java.util.concurrent.CompletableFuture; 22 | import java.util.concurrent.atomic.AtomicLong; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | public class TLSCounter { 27 | private static final class Reporter implements Runnable { 28 | private final AtomicLong counter = new AtomicLong(); 29 | 30 | void add(final int up) { 31 | counter.addAndGet(up); 32 | } 33 | 34 | @Override 35 | public void run() { 36 | long lastChecked = System.currentTimeMillis(); 37 | while (true) { 38 | try { 39 | Thread.sleep(100); 40 | } catch (final InterruptedException e) { 41 | break; 42 | } 43 | final long now = System.currentTimeMillis(); 44 | if (now - lastChecked >= 1000) { 45 | lastChecked = now; 46 | final long current = counter.getAndSet(0); 47 | logger.info("{} requests/sec", current); 48 | } 49 | } 50 | } 51 | } 52 | 53 | private static final Logger logger = LoggerFactory.getLogger(TLSCounter.class); 54 | 55 | public static void main(final String[] args) { 56 | if (args.length != 4) { 57 | usage(); 58 | return; 59 | } 60 | final int workerPoolSize = Integer.parseInt(args[0]); 61 | final String keystorePath = args[1]; 62 | final String keystorePassword = args[2]; 63 | final String keyPassword = args[3]; 64 | 65 | final Reporter reporter = new Reporter(); 66 | 67 | final ForwardCallback callback = ForwardCallback.of(stream -> { 68 | reporter.add(stream.getEntries().size()); 69 | return CompletableFuture.completedFuture(null); 70 | }); 71 | 72 | final ForwardServer server = new ForwardServer 73 | .Builder(callback) 74 | .workerPoolSize(workerPoolSize) 75 | .sslEnabled(true) 76 | .tlsVersions("TLSv1.2") 77 | .keystorePath(keystorePath) 78 | .keystorePassword(keystorePassword) 79 | .keyPassword(keyPassword) 80 | .build(); 81 | server.start(); 82 | new Thread(reporter).start(); 83 | } 84 | 85 | private static void usage() { 86 | System.out.println("Must specify 4 arguments."); 87 | System.out.println(); 88 | System.out.println("e.g. 4 /path/to/server.jks keystorePassowrd keyPassword"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /influent-java-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/EventEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent; 18 | 19 | import java.time.Instant; 20 | import java.util.Objects; 21 | import org.msgpack.value.ImmutableMapValue; 22 | 23 | /** 24 | * An event entry. 25 | * 26 | *

This is not immutable when the {@code record} has mutable {@code org.msgpack.value.Value}. But 27 | * instances of {@code EventEntry} that this library returns are guaranteed immutable. 28 | */ 29 | public final class EventEntry { 30 | private final Instant time; 31 | private final ImmutableMapValue record; 32 | 33 | private EventEntry(final Instant time, final ImmutableMapValue record) { 34 | this.time = Objects.requireNonNull(time); 35 | this.record = Objects.requireNonNull(record); 36 | } 37 | 38 | /** 39 | * Creates an {@code EventEntry}. 40 | * 41 | * @param time the event time 42 | * @param record the record 43 | * @return the new {@code EventEntry} 44 | * @throws NullPointerException if the time or the record are null 45 | */ 46 | public static EventEntry of(final Instant time, final ImmutableMapValue record) { 47 | return new EventEntry(time, record); 48 | } 49 | 50 | /** @return the event time */ 51 | public Instant getTime() { 52 | return time; 53 | } 54 | 55 | /** @return the record */ 56 | public ImmutableMapValue getRecord() { 57 | return record; 58 | } 59 | 60 | /** {@inheritDoc} */ 61 | @Override 62 | public boolean equals(final Object o) { 63 | if (this == o) { 64 | return true; 65 | } 66 | if (o == null || getClass() != o.getClass()) { 67 | return false; 68 | } 69 | final EventEntry that = (EventEntry) o; 70 | return Objects.equals(getTime(), that.getTime()) 71 | && Objects.equals(getRecord(), that.getRecord()); 72 | } 73 | 74 | /** {@inheritDoc} */ 75 | @Override 76 | public int hashCode() { 77 | return Objects.hash(getTime(), getRecord()); 78 | } 79 | 80 | /** {@inheritDoc} */ 81 | @Override 82 | public String toString() { 83 | return "EventEntry(" + getTime() + ',' + getRecord() + ')'; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/EventStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent; 18 | 19 | import java.util.Collections; 20 | import java.util.List; 21 | import java.util.Objects; 22 | 23 | /** 24 | * An event stream. 25 | * 26 | *

This is not immutable when the origin of {@code entries} is mutated. But instances of {@code 27 | * EventStream} that this library returns are guaranteed immutable. 28 | */ 29 | public final class EventStream { 30 | private final Tag tag; 31 | private final List entries; 32 | 33 | private EventStream(final Tag tag, final List entries) { 34 | this.tag = Objects.requireNonNull(tag); 35 | this.entries = Collections.unmodifiableList(Objects.requireNonNull(entries)); 36 | } 37 | 38 | /** 39 | * Creates an {@code EventStream}. 40 | * 41 | * @param tag the tag 42 | * @param entries the entries 43 | * @return the new {@code EventStream} 44 | * @throws NullPointerException if the tag or the entries are null 45 | */ 46 | public static EventStream of(final Tag tag, final List entries) { 47 | return new EventStream(tag, entries); 48 | } 49 | 50 | /** @return the tag */ 51 | public Tag getTag() { 52 | return tag; 53 | } 54 | 55 | /** @return the unmodifiable list of {@code EventEntry} */ 56 | public List getEntries() { 57 | return entries; 58 | } 59 | 60 | /** {@inheritDoc} */ 61 | @Override 62 | public boolean equals(final Object o) { 63 | if (this == o) { 64 | return true; 65 | } 66 | if (o == null || getClass() != o.getClass()) { 67 | return false; 68 | } 69 | final EventStream that = (EventStream) o; 70 | return Objects.equals(getTag(), that.getTag()) 71 | && Objects.equals(getEntries(), that.getEntries()); 72 | } 73 | 74 | /** {@inheritDoc} */ 75 | @Override 76 | public int hashCode() { 77 | return Objects.hash(getTag(), getEntries()); 78 | } 79 | 80 | /** {@inheritDoc} */ 81 | @Override 82 | public String toString() { 83 | return "EventStream(" + tag + ", " + entries + ")"; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/Tag.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent; 18 | 19 | import java.util.Objects; 20 | 21 | /** 22 | * A tag of fluentd's event. 23 | * 24 | *

Instances of {@code Tag} are immutable. 25 | */ 26 | public final class Tag implements Comparable { 27 | private final String name; 28 | 29 | private Tag(final String name) { 30 | this.name = Objects.requireNonNull(name); 31 | } 32 | 33 | /** 34 | * Creates a {@code Tag}. 35 | * 36 | * @param name the tag name 37 | * @return the tag with the given name 38 | * @throws NullPointerException if the name is null 39 | */ 40 | public static Tag of(final String name) { 41 | return new Tag(name); 42 | } 43 | 44 | /** @return the tag name */ 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | /** 50 | * Compares two tags lexicographically. 51 | * 52 | * @param o the {@code Tag} to be compared 53 | * @return the value 0 if the name of this tag is equal to that of the argument a value less than 54 | * 0 if the name of this tag is lexicographically less than that of the argument a value 55 | * greater than 0 if the name of this tag is lexicographically greater than that of the 56 | * argument 57 | */ 58 | @Override 59 | public int compareTo(final Tag o) { 60 | return getName().compareTo(o.getName()); 61 | } 62 | 63 | /** {@inheritDoc} */ 64 | @Override 65 | public boolean equals(final Object o) { 66 | if (this == o) { 67 | return true; 68 | } 69 | if (o == null || getClass() != o.getClass()) { 70 | return false; 71 | } 72 | final Tag tag = (Tag) o; 73 | return Objects.equals(getName(), tag.getName()); 74 | } 75 | 76 | /** {@inheritDoc} */ 77 | @Override 78 | public int hashCode() { 79 | return Objects.hash(getName()); 80 | } 81 | 82 | /** {@inheritDoc} */ 83 | @Override 84 | public String toString() { 85 | return "Tag(" + getName() + ')'; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/forward/CheckPingResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward; 18 | 19 | public class CheckPingResult { 20 | private final boolean succeeded; 21 | private final String sharedKeySalt; 22 | private final String sharedKey; 23 | private final String reason; 24 | 25 | public static CheckPingResult success(String sharedKeySalt, String sharedKey) { 26 | return new CheckPingResult(true, sharedKeySalt, sharedKey, null); 27 | } 28 | 29 | public static CheckPingResult failure(String reason) { 30 | return new CheckPingResult(false, null, null, reason); 31 | } 32 | 33 | public CheckPingResult(boolean succeeded, String sharedKeySalt, String sharedKey, String reason) { 34 | this.succeeded = succeeded; 35 | this.sharedKeySalt = sharedKeySalt; 36 | this.sharedKey = sharedKey; 37 | this.reason = reason; 38 | } 39 | 40 | public boolean isSucceeded() { 41 | return succeeded; 42 | } 43 | 44 | public String getSharedKeySalt() { 45 | return sharedKeySalt; 46 | } 47 | 48 | public String getSharedKey() { 49 | return sharedKey; 50 | } 51 | 52 | public String getReason() { 53 | return reason; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/forward/ForwardCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward; 18 | 19 | import java.util.concurrent.CompletableFuture; 20 | import java.util.concurrent.Executor; 21 | import java.util.function.Consumer; 22 | import java.util.function.Function; 23 | 24 | import influent.EventStream; 25 | 26 | /** The callback function that consumes {@code EventStreams}. */ 27 | @FunctionalInterface 28 | public interface ForwardCallback { 29 | /** 30 | * Creates the {@code ForwardCallback}. See also ForwardCallback#consume. 31 | * 32 | * @param consumer the callback function 33 | * @return the {@code ForwardCallback} 34 | */ 35 | static ForwardCallback of(final Function> consumer) { 36 | return consumer::apply; 37 | } 38 | 39 | /** 40 | * Creates a {@code ForwardCallback} from the synchronous {@code Consumer} and the {@code 41 | * Executor}. 42 | * 43 | * @param consumer the synchronous {@code Consumer} 44 | * @param executor the {@code Executor} that executes {@code consumer} 45 | * @return the {@code ForwardCallback} 46 | */ 47 | static ForwardCallback ofSyncConsumer( 48 | final Consumer consumer, final Executor executor) { 49 | return stream -> CompletableFuture.runAsync(() -> consumer.accept(stream), executor); 50 | } 51 | 52 | /** 53 | * Consumes an {@code EventStream}. 54 | * 55 | *

{@code ForwardCallback#consume} must not be blocked since it is invoked on an event loop 56 | * thread. If there are some IO operation or a CPU intensive processing, those must be executed on 57 | * the another thread. 58 | * 59 | *

This method receives an {@code EventStream} and returns a {@code CompletableFuture}. When 60 | * the {@code CompletableFuture} succeeds, Influent assumes that the {@code EventStream} is 61 | * completely consumed and may send an ack response to the client. 62 | * 63 | *

When the {@code CompletableFuture} succeeds, Influent never sends an ack. 64 | * 65 | * @param stream the {@code EventStream} 66 | * @return the result of this consumption 67 | */ 68 | CompletableFuture consume(EventStream stream); 69 | } 70 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/forward/ForwardClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | /** Client ip/network authentication & per_host shared key */ 24 | public class ForwardClient { 25 | private String host = null; 26 | private String network = null; 27 | private String sharedKey = null; 28 | private List usernames = new ArrayList<>(); 29 | 30 | public ForwardClient(String host, String network, String sharedKey, List usernames) { 31 | this.host = host; 32 | this.network = network; 33 | this.sharedKey = sharedKey; 34 | this.usernames = usernames; 35 | } 36 | 37 | public String getHost() { 38 | return host; 39 | } 40 | 41 | public String getNetwork() { 42 | return network; 43 | } 44 | 45 | public String getSharedKey() { 46 | return sharedKey; 47 | } 48 | 49 | public List getUsernames() { 50 | return usernames; 51 | } 52 | 53 | public static class Builder { 54 | private String host = null; 55 | private String network = null; 56 | private String sharedKey = null; 57 | private List usernames = new ArrayList<>(); 58 | 59 | private Builder() {} 60 | 61 | /** 62 | * Create new ForwardClient.Builder with given host 63 | * 64 | * @param host The IP address or host name of the client 65 | * @return new builder 66 | */ 67 | public static Builder ofHost(String host) { 68 | Builder builder = new Builder(); 69 | builder.host = host; 70 | return builder; 71 | } 72 | 73 | /** 74 | * Create new ForwardClient.Builder with given network 75 | * 76 | * @param network Network address specification 77 | * @return new builder 78 | */ 79 | public static Builder ofNetwork(String network) { 80 | Builder builder = new Builder(); 81 | builder.network = network; 82 | return builder; 83 | } 84 | 85 | /** 86 | * Set shared key per client 87 | * 88 | * @param sharedKey Shared key per client 89 | * @return this builder 90 | */ 91 | public Builder sharedKey(String sharedKey) { 92 | this.sharedKey = sharedKey; 93 | return this; 94 | } 95 | 96 | /** 97 | * Set usernames to authenticate client 98 | * 99 | * @param usernames Usernames to authenticate client 100 | * @return this builder 101 | */ 102 | public Builder usernames(String... usernames) { 103 | Collections.addAll(this.usernames, usernames); 104 | return this; 105 | } 106 | 107 | public ForwardClient build() { 108 | return new ForwardClient(host, network, sharedKey, usernames); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/forward/ForwardClientNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward; 18 | 19 | import influent.internal.util.InetNetwork; 20 | import java.net.InetAddress; 21 | import java.net.UnknownHostException; 22 | import java.util.List; 23 | 24 | public class ForwardClientNode { 25 | private InetAddress sourceAddress = null; 26 | private InetNetwork network = null; 27 | private String sharedKey = null; 28 | private List usernames = null; 29 | 30 | public ForwardClientNode(ForwardClient client, String globalSharedKey) { 31 | if (client.getHost() != null) { 32 | try { 33 | sourceAddress = InetAddress.getByName(client.getHost()); 34 | } catch (UnknownHostException e) { 35 | e.printStackTrace(); 36 | // TODO throw exception 37 | } 38 | } 39 | if (client.getNetwork() != null) { 40 | network = InetNetwork.getBySpec(client.getNetwork()); 41 | } 42 | sharedKey = client.getSharedKey() == null ? globalSharedKey : client.getSharedKey(); 43 | usernames = client.getUsernames(); 44 | } 45 | 46 | public boolean isMatched(InetAddress remoteAddress) { 47 | if (sourceAddress != null) { 48 | return sourceAddress.equals(remoteAddress); 49 | } else { 50 | return network.contains(remoteAddress); 51 | } 52 | } 53 | 54 | public String getSharedKey() { 55 | return sharedKey; 56 | } 57 | 58 | public List getUsernames() { 59 | return usernames; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/forward/ForwardClientUser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward; 18 | 19 | public class ForwardClientUser { 20 | private final String username; 21 | private final String password; 22 | 23 | public ForwardClientUser(final String username, final String password) { 24 | this.username = username; 25 | this.password = password; 26 | } 27 | 28 | public String getUsername() { 29 | return username; 30 | } 31 | 32 | public String getPassword() { 33 | return password; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/forward/ForwardOption.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward; 18 | 19 | import java.util.Objects; 20 | import java.util.Optional; 21 | 22 | final class ForwardOption { 23 | private static final ForwardOption EMPTY = new ForwardOption(null, null); 24 | 25 | private final String chunk; 26 | private final String compressed; 27 | 28 | private ForwardOption(final String chunk, final String compressed) { 29 | this.chunk = chunk; 30 | this.compressed = compressed; 31 | } 32 | 33 | static ForwardOption of(final String chunk, final String compressed) { 34 | return new ForwardOption(chunk, compressed); 35 | } 36 | 37 | static ForwardOption empty() { 38 | return EMPTY; 39 | } 40 | 41 | Optional getChunk() { 42 | return Optional.ofNullable(chunk); 43 | } 44 | 45 | Optional getCompressed() { 46 | return Optional.ofNullable(compressed); 47 | } 48 | 49 | @Override 50 | public boolean equals(final Object o) { 51 | if (this == o) { 52 | return true; 53 | } 54 | if (o == null || getClass() != o.getClass()) { 55 | return false; 56 | } 57 | final ForwardOption that = (ForwardOption) o; 58 | return Objects.equals(getChunk(), that.getChunk()) 59 | && Objects.equals(getCompressed(), that.getCompressed()); 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | return Objects.hash(getChunk(), getCompressed()); 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return "ForwardOption(" + getChunk() + "," + getCompressed() + ")"; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/forward/ForwardRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward; 18 | 19 | import java.util.Objects; 20 | import influent.EventStream; 21 | 22 | final class ForwardRequest { 23 | private final EventStream stream; 24 | private final ForwardOption option; 25 | 26 | private ForwardRequest(final EventStream stream, final ForwardOption option) { 27 | this.stream = stream; 28 | this.option = option; 29 | } 30 | 31 | static ForwardRequest of(final EventStream stream, final ForwardOption option) { 32 | return new ForwardRequest(stream, option); 33 | } 34 | 35 | EventStream getStream() { 36 | return stream; 37 | } 38 | 39 | ForwardOption getOption() { 40 | return option; 41 | } 42 | 43 | @Override 44 | public boolean equals(final Object o) { 45 | if (this == o) { 46 | return true; 47 | } 48 | if (o == null || getClass() != o.getClass()) { 49 | return false; 50 | } 51 | final ForwardRequest that = (ForwardRequest) o; 52 | return Objects.equals(getStream(), that.getStream()) 53 | && Objects.equals(getOption(), that.getOption()); 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return Objects.hash(getStream(), getOption()); 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return "ForwardRequest(" + getStream() + "," + getOption() + ")"; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/forward/ForwardSecurity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward; 18 | 19 | import java.net.InetAddress; 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | import java.util.stream.Collectors; 26 | 27 | public class ForwardSecurity { 28 | private String selfHostname = null; 29 | private String sharedKey = null; 30 | private boolean userAuthEnabled = false; 31 | private boolean anonymousSourceAllowed = true; 32 | private boolean enabled; 33 | 34 | private List clients = new ArrayList<>(); 35 | private List users = new ArrayList<>(); 36 | private List nodes = new ArrayList<>(); 37 | 38 | public ForwardSecurity() { 39 | this.enabled = false; 40 | } 41 | 42 | public ForwardSecurity( 43 | String selfHostname, 44 | String sharedKey, 45 | boolean userAuthEnabled, 46 | boolean anonymousSourceAllowed, 47 | List clients, 48 | List users) { 49 | this.selfHostname = selfHostname; 50 | this.sharedKey = sharedKey; 51 | this.userAuthEnabled = userAuthEnabled; 52 | this.anonymousSourceAllowed = anonymousSourceAllowed; 53 | this.clients.addAll(clients); 54 | this.users.addAll(users); 55 | this.enabled = true; 56 | 57 | for (ForwardClient client : this.clients) { 58 | nodes.add(new ForwardClientNode(client, this.sharedKey)); 59 | } 60 | } 61 | 62 | public boolean isEnabled() { 63 | return enabled; 64 | } 65 | 66 | public boolean isAnonymousSourceAllowed() { 67 | return anonymousSourceAllowed; 68 | } 69 | 70 | public boolean isUserAuthEnabled() { 71 | return userAuthEnabled; 72 | } 73 | 74 | public Optional findNode(InetAddress remoteAddress) { 75 | return nodes.stream().filter(n -> n.isMatched(remoteAddress)).findFirst(); 76 | } 77 | 78 | public String getSelfHostname() { 79 | return selfHostname; 80 | } 81 | 82 | public String getSharedKey() { 83 | return sharedKey; 84 | } 85 | 86 | public List findAuthenticateUsers(ForwardClientNode node, String username) { 87 | if (node == null || node.getUsernames().isEmpty()) { 88 | return users 89 | .stream() 90 | .filter(user -> user.getUsername().equals(username)) 91 | .collect(Collectors.toList()); 92 | } else { 93 | return users 94 | .stream() 95 | .filter( 96 | user -> node.getUsernames().contains(username) && user.getUsername().equals(username)) 97 | .collect(Collectors.toList()); 98 | } 99 | } 100 | 101 | public static class Builder { 102 | private String selfHostname = null; 103 | private String sharedKey = null; 104 | private boolean userAuthEnabled = false; 105 | private boolean anonymousSourceAllowed = true; 106 | 107 | private List clients = new ArrayList<>(); 108 | private List users = new ArrayList<>(); 109 | 110 | /** 111 | * Set the hostname 112 | * 113 | * @param selfHostname The hostname 114 | * @return this builder 115 | */ 116 | public Builder selfHostname(String selfHostname) { 117 | this.selfHostname = selfHostname; 118 | return this; 119 | } 120 | 121 | /** 122 | * Set shared key for authentication 123 | * 124 | * @param sharedKey Shared key for authentication 125 | * @return this builder 126 | */ 127 | public Builder sharedKey(String sharedKey) { 128 | this.sharedKey = sharedKey; 129 | return this; 130 | } 131 | 132 | /** 133 | * Use user base authentication or not 134 | * 135 | * @param userAuthEnabled If true, use user based authentication 136 | * @return this builder 137 | */ 138 | public Builder enableUserAuth(boolean userAuthEnabled) { 139 | this.userAuthEnabled = userAuthEnabled; 140 | return this; 141 | } 142 | 143 | /** 144 | * Allow anonymous source or not. Clients are required if not allowed anonymous source. 145 | * 146 | * @param anonymousSourceAllowed If true, allow anonymous source. Otherwise clients are 147 | * required. 148 | * @return this builder 149 | */ 150 | public Builder allowAnonymousSource(boolean anonymousSourceAllowed) { 151 | this.anonymousSourceAllowed = anonymousSourceAllowed; 152 | return this; 153 | } 154 | 155 | /** 156 | * Add username and passowrd for user based authentication 157 | * 158 | * @param username The username for authentication 159 | * @param password The password for authentication 160 | * @return this builder 161 | */ 162 | public Builder addUser(final String username, final String password) { 163 | users.add(new ForwardClientUser(username, password)); 164 | return this; 165 | } 166 | 167 | /** 168 | * Add client ip/network authentication & per_host shared key 169 | * 170 | * @param client The ForwardClient 171 | * @return this builder 172 | */ 173 | public Builder addClient(ForwardClient client) { 174 | clients.add(client); 175 | return this; 176 | } 177 | 178 | public ForwardSecurity build() { 179 | // TODO Check required parameters 180 | return new ForwardSecurity( 181 | selfHostname, sharedKey, userAuthEnabled, anonymousSourceAllowed, clients, users); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/forward/MsgPackPingDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward; 18 | 19 | import org.msgpack.value.ImmutableArrayValue; 20 | import org.msgpack.value.ImmutableValue; 21 | import org.msgpack.value.Value; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import java.security.MessageDigest; 25 | import java.security.NoSuchAlgorithmException; 26 | 27 | final class MsgPackPingDecoder { 28 | private static final Logger logger = LoggerFactory.getLogger(MsgPackPingDecoder.class); 29 | 30 | private final ForwardSecurity security; 31 | private final ForwardClientNode node; 32 | private final byte[] nonce; 33 | private final byte[] userAuth; 34 | 35 | public MsgPackPingDecoder( 36 | ForwardSecurity security, ForwardClientNode node, byte[] nonce, byte[] userAuth) { 37 | this.security = security; 38 | this.node = node; 39 | this.nonce = nonce; 40 | this.userAuth = userAuth; 41 | } 42 | 43 | public CheckPingResult decode(ImmutableValue value) { 44 | logger.debug("decoding ping {}", value); 45 | if (!value.isArrayValue()) { 46 | error("A ping message must be an array", value); 47 | } 48 | ImmutableArrayValue arrayValue = value.asArrayValue(); 49 | if (arrayValue.size() != 6) { 50 | error("A ping message must have 6 elements", value); 51 | } 52 | String ping = decodeString(arrayValue.get(0)); 53 | if (!ping.equals(("PING"))) { 54 | error("Invalid ping message", value); 55 | } 56 | String clientHostname = decodeString(arrayValue.get(1)); 57 | String sharedKeySalt = decodeString(arrayValue.get(2)); // TODO Support both String and byte[] 58 | String sharedKeyHexDigest = decodeString(arrayValue.get(3)); 59 | String username = decodeString(arrayValue.get(4)); 60 | String passwordDigest = decodeString(arrayValue.get(5)); 61 | 62 | if (node == null && !security.isAnonymousSourceAllowed()) { 63 | // FIXME add remote address to message 64 | String message = "Anonymous client disallowed."; 65 | logger.warn(message); 66 | return CheckPingResult.failure(message); 67 | } 68 | 69 | String sharedKey = null; 70 | try { 71 | sharedKey = node != null ? node.getSharedKey() : security.getSharedKey(); 72 | MessageDigest md = MessageDigest.getInstance("SHA-512"); 73 | md.update(sharedKeySalt.getBytes()); 74 | md.update(clientHostname.getBytes()); 75 | md.update(nonce); 76 | md.update(sharedKey.getBytes()); 77 | String serverSideDigest = generateHexString(md.digest()); 78 | if (!sharedKeyHexDigest.equals(serverSideDigest)) { 79 | // FIXME Add remote address to log 80 | logger.warn("Shared key mismatch: {}", clientHostname); 81 | return CheckPingResult.failure("Shared key mismatch"); 82 | } 83 | 84 | if (security.isUserAuthEnabled()) { 85 | boolean userAuthenticationSucceeded = 86 | security 87 | .findAuthenticateUsers(node, username) 88 | .stream() 89 | .anyMatch( 90 | user -> { 91 | md.reset(); 92 | md.update(userAuth); 93 | md.update(username.getBytes()); 94 | md.update(user.getPassword().getBytes()); 95 | String serverSidePasswordDigest = generateHexString(md.digest()); 96 | return passwordDigest.equals(serverSidePasswordDigest); 97 | }); 98 | if (!userAuthenticationSucceeded) { 99 | // FIXME Add remote address to log 100 | logger.info("Authentication failed: hostname={}, username={}", clientHostname, username); 101 | return CheckPingResult.failure("username/password mismatch"); 102 | } 103 | } 104 | } catch (NoSuchAlgorithmException e) { 105 | error(e.getMessage(), value, e); 106 | } 107 | 108 | return CheckPingResult.success(sharedKeySalt, sharedKey); 109 | } 110 | 111 | private String decodeString(final Value value) { 112 | return value == null ? null : value.asStringValue().asString(); 113 | } 114 | 115 | private byte[] decodeByteArray(final Value value) { 116 | return value == null ? null : value.asBinaryValue().asByteArray(); 117 | } 118 | 119 | private String generateHexString(final byte[] digest) { 120 | StringBuilder sb = new StringBuilder(); 121 | for (byte b : digest) { 122 | sb.append(String.format("%02x", b)); 123 | } 124 | return sb.toString(); 125 | } 126 | 127 | private IllegalArgumentException error(final String message, final Value value) { 128 | throw new IllegalArgumentException(message + " value = " + value); 129 | } 130 | 131 | private IllegalArgumentException error( 132 | final String message, final Value value, final Throwable cause) { 133 | throw new IllegalArgumentException(message + " value = " + value, cause); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/forward/NioForwardServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward; 18 | 19 | import influent.internal.nio.NioChannelConfig; 20 | import influent.internal.nio.NioEventLoop; 21 | import influent.internal.nio.NioEventLoopPool; 22 | import influent.internal.nio.NioTcpAcceptor; 23 | import influent.internal.nio.NioTcpConfig; 24 | import influent.internal.nio.NioTcpPlaintextChannel; 25 | import influent.internal.nio.NioTcpTlsChannel; 26 | import influent.internal.nio.NioTlsEngine; 27 | import java.net.SocketAddress; 28 | import java.nio.channels.SocketChannel; 29 | import java.util.concurrent.CompletableFuture; 30 | import java.util.concurrent.ThreadFactory; 31 | import java.util.function.Consumer; 32 | 33 | /** A {@code ForwardServer} implemented by NIO. */ 34 | final class NioForwardServer implements ForwardServer { 35 | private final NioEventLoop bossEventLoop; 36 | private final NioEventLoopPool workerEventLoopPool; 37 | 38 | /** 39 | * Creates a {@code NioForwardServer}. 40 | * 41 | * @param localAddress local address to be bound 42 | * @param callback invoked when a request arrives 43 | * @param chunkSizeLimit the allowable size of chunks 44 | * @param tcpConfig the TCP config 45 | * @param workerPoolSize the event loop pool size for workers 46 | * @param channelConfig the channel configuration 47 | * @throws IllegalArgumentException if any of parameter is invalid e.g. the local address is 48 | * already used 49 | * @throws influent.exception.InfluentIOException if some IO error occurs 50 | */ 51 | NioForwardServer( 52 | final SocketAddress localAddress, 53 | final ForwardCallback callback, 54 | final long chunkSizeLimit, 55 | final NioTcpConfig tcpConfig, 56 | final int workerPoolSize, 57 | final NioChannelConfig channelConfig, 58 | final ForwardSecurity security) { 59 | bossEventLoop = NioEventLoop.open(); 60 | workerEventLoopPool = NioEventLoopPool.open(workerPoolSize); 61 | final Consumer channelFactory; 62 | if (channelConfig.isSslEnabled()) { 63 | channelFactory = 64 | (socketChannel) -> 65 | new NioForwardConnection( 66 | NioTcpTlsChannel.open( 67 | socketChannel, 68 | workerEventLoopPool.next(), 69 | tcpConfig, 70 | NioTlsEngine.createServerEngine(channelConfig.createSSLEngine())), 71 | callback, 72 | chunkSizeLimit, 73 | security); 74 | } else { 75 | channelFactory = 76 | (socketChannel) -> 77 | new NioForwardConnection( 78 | NioTcpPlaintextChannel.open(socketChannel, workerEventLoopPool.next(), tcpConfig), 79 | callback, 80 | chunkSizeLimit, 81 | security); 82 | } 83 | NioTcpAcceptor.open(localAddress, bossEventLoop, channelFactory, tcpConfig); 84 | new NioUdpHeartbeatServer(localAddress, bossEventLoop); 85 | } 86 | 87 | /** {@inheritDoc} */ 88 | @Override 89 | public void start(final ThreadFactory threadFactory) { 90 | workerEventLoopPool.start(threadFactory); 91 | threadFactory.newThread(bossEventLoop).start(); 92 | } 93 | 94 | /** {@inheritDoc} */ 95 | @Override 96 | public CompletableFuture shutdown() { 97 | return bossEventLoop.shutdown().thenCompose(x -> workerEventLoopPool.shutdown()); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/forward/NioUdpHeartbeatServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward; 18 | 19 | import influent.internal.nio.NioAttachment; 20 | import influent.internal.nio.NioEventLoop; 21 | import influent.internal.nio.NioUdpChannel; 22 | import influent.internal.nio.NioUdpChannel.Op; 23 | import influent.internal.util.ThreadSafeQueue; 24 | import java.net.SocketAddress; 25 | import java.nio.ByteBuffer; 26 | import java.util.EnumSet; 27 | import java.util.Optional; 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | 31 | /** 32 | * A heartbeat server for forward protocol. 33 | * 34 | *

{@code NioUdpHeartbeatServer} is not thread-safe and expected to be executed on the event loop 35 | * thread. 36 | */ 37 | final class NioUdpHeartbeatServer implements NioAttachment { 38 | private static final byte RESPONSE_BYTE = 0; 39 | private static final int SOCKET_BUFFER_SIZE = 8; 40 | private static final Logger logger = LoggerFactory.getLogger(NioUdpHeartbeatServer.class); 41 | 42 | private final ByteBuffer response = ByteBuffer.allocate(Byte.BYTES).put(RESPONSE_BYTE); 43 | final ThreadSafeQueue replyTo = new ThreadSafeQueue<>(); 44 | private final ByteBuffer receiveBuffer = ByteBuffer.allocate(1); 45 | 46 | private final NioUdpChannel channel; 47 | 48 | NioUdpHeartbeatServer(final NioUdpChannel channel) { 49 | this.channel = channel; 50 | } 51 | 52 | /** 53 | * Constructs a new {@code NioUdpHeartbeatServer}. 54 | * 55 | * @param localAddress the local address 56 | * @throws IllegalArgumentException if the local address is invalid or already used 57 | * @throws influent.exception.InfluentIOException if some IO error occurs 58 | */ 59 | NioUdpHeartbeatServer(final SocketAddress localAddress, final NioEventLoop eventLoop) { 60 | this(NioUdpChannel.open(eventLoop, localAddress, SOCKET_BUFFER_SIZE, SOCKET_BUFFER_SIZE)); 61 | channel.register(EnumSet.of(Op.READ), this); 62 | } 63 | 64 | /** Sends heartbeat responses. {@code NioUdpHeartbeatServer#onWritable} never fails. */ 65 | @Override 66 | public void onWritable() { 67 | if (sendResponses()) { 68 | channel.disable(Op.WRITE); 69 | } 70 | } 71 | 72 | private boolean sendResponses() { 73 | while (replyTo.nonEmpty()) { 74 | try { 75 | final SocketAddress target = replyTo.peek(); 76 | response.rewind(); 77 | if (channel.send(response, target)) { 78 | replyTo.dequeue(); 79 | } else { 80 | return false; // unsent responses are remaining 81 | } 82 | } catch (final Exception e) { 83 | logger.error("Failed sending a response.", e); 84 | } 85 | } 86 | return true; 87 | } 88 | 89 | /** Receives heartbeat requests. {@code NioUdpHeartbeatServer#onReadable} never fails. */ 90 | @Override 91 | public void onReadable() { 92 | while (true) { 93 | receiveBuffer.rewind(); 94 | try { 95 | receiveBuffer.rewind(); 96 | final Optional source = channel.receive(receiveBuffer); 97 | if (source.isPresent()) { 98 | logger.debug("Received a heartbeat request from {}.", source); 99 | replyTo.enqueue(source.get()); 100 | } else { 101 | break; 102 | } 103 | } catch (final Exception e) { 104 | logger.error("Failed receiving a request.", e); 105 | } 106 | } 107 | 108 | channel.enable(Op.WRITE); 109 | } 110 | 111 | /** {@inheritDoc} */ 112 | @Override 113 | public void close() { 114 | channel.close(); 115 | logger.info("The heartbeat server bound with {} closed.", channel.getLocalAddress()); 116 | } 117 | 118 | /** {@inheritDoc} */ 119 | @Override 120 | public String toString() { 121 | return "NioUdpHeartbeatServer(" + channel.getLocalAddress() + ")"; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/internal/msgpack/DecodeResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.msgpack; 18 | 19 | import org.msgpack.value.ImmutableValue; 20 | 21 | abstract class DecodeResult { 22 | private static final class Complete extends DecodeResult { 23 | private final ImmutableValue value; 24 | 25 | Complete(final ImmutableValue value) { 26 | this.value = value; 27 | } 28 | 29 | @Override 30 | boolean isCompleted() { 31 | return true; 32 | } 33 | 34 | @Override 35 | MsgpackIncrementalUnpacker next() { 36 | throw new IllegalStateException(); 37 | } 38 | 39 | @Override 40 | ImmutableValue value() { 41 | return value; 42 | } 43 | } 44 | 45 | private static final class Continue extends DecodeResult { 46 | private final MsgpackIncrementalUnpacker next; 47 | 48 | Continue(final MsgpackIncrementalUnpacker next) { 49 | this.next = next; 50 | } 51 | 52 | @Override 53 | boolean isCompleted() { 54 | return false; 55 | } 56 | 57 | @Override 58 | MsgpackIncrementalUnpacker next() { 59 | return next; 60 | } 61 | 62 | @Override 63 | ImmutableValue value() { 64 | throw new IllegalStateException(); 65 | } 66 | } 67 | 68 | static DecodeResult complete(final ImmutableValue result) { 69 | return new Complete(result); 70 | } 71 | 72 | static DecodeResult next(final MsgpackIncrementalUnpacker next) { 73 | return new Continue(next); 74 | } 75 | 76 | abstract boolean isCompleted(); 77 | 78 | abstract MsgpackIncrementalUnpacker next(); 79 | 80 | abstract ImmutableValue value(); 81 | } 82 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/internal/msgpack/InfluentByteBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.msgpack; 18 | 19 | import java.nio.ByteBuffer; 20 | import java.util.Deque; 21 | import java.util.LinkedList; 22 | import java.util.function.Supplier; 23 | import influent.internal.nio.NioTcpChannel; 24 | 25 | final class InfluentByteBuffer { 26 | private final Deque buffers = new LinkedList<>(); 27 | private long remaining = 0; 28 | private long bufferSizeLimit; 29 | 30 | InfluentByteBuffer(final long bufferSizeLimit) { 31 | this.bufferSizeLimit = bufferSizeLimit; 32 | } 33 | 34 | void push(final ByteBuffer buffer) { 35 | remaining += buffer.remaining(); 36 | buffers.addLast(buffer.slice()); 37 | } 38 | 39 | private ByteBuffer peek() { 40 | return buffers.getFirst(); 41 | } 42 | 43 | private void trim() { 44 | if (!peek().hasRemaining()) { 45 | buffers.removeFirst(); 46 | } 47 | } 48 | 49 | private void getFromHead(final ByteBuffer dst) { 50 | final ByteBuffer head = peek(); 51 | if (head.remaining() <= dst.remaining()) { 52 | remaining -= head.remaining(); 53 | dst.put(head); 54 | } else { 55 | final int length = dst.remaining(); 56 | remaining -= length; 57 | dst.put(head.array(), head.arrayOffset() + head.position(), length); 58 | head.position(head.position() + length); 59 | } 60 | 61 | trim(); 62 | } 63 | 64 | boolean feed(final Supplier supplier) { 65 | // TODO: optimization 66 | while (remaining < bufferSizeLimit) { 67 | final ByteBuffer buffer = supplier.get(); 68 | if (buffer == null) { 69 | return false; 70 | } 71 | 72 | push(buffer); 73 | } 74 | return true; 75 | } 76 | 77 | boolean hasRemaining() { 78 | return remaining != 0; 79 | } 80 | 81 | long remaining() { 82 | return remaining; 83 | } 84 | 85 | void get(final ByteBuffer dst) { 86 | while (dst.hasRemaining() && hasRemaining()) { 87 | getFromHead(dst); 88 | } 89 | } 90 | 91 | byte getByte() { 92 | final byte head = peek().get(); 93 | remaining -= 1; 94 | trim(); 95 | return head; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /influent-java/src/main/java/influent/internal/msgpack/MsgpackStreamUnpacker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.msgpack; 18 | 19 | import java.nio.ByteBuffer; 20 | import java.util.LinkedList; 21 | import java.util.NoSuchElementException; 22 | import java.util.Queue; 23 | import java.util.function.Supplier; 24 | import org.msgpack.value.ImmutableValue; 25 | import influent.exception.InfluentIOException; 26 | import influent.internal.nio.NioTcpChannel; 27 | 28 | /** 29 | * An unpacker for a MessagePack stream. 30 | * 31 | *

This is expected to be used in only Influent project. 32 | * 33 | *

{@code MsgpackStreamUnpacker} is not thread-safe. 34 | */ 35 | public final class MsgpackStreamUnpacker { 36 | private final InfluentByteBuffer buffer; 37 | private final long chunkSizeLimit; 38 | 39 | private final Queue unpackedValues = new LinkedList<>(); 40 | private MsgpackIncrementalUnpacker currentUnpacker = FormatUnpacker.getInstance(); 41 | private long currentChunkSize = 0; 42 | 43 | /** 44 | * Constructs a new {@code MsgpackStreamUnpacker}. 45 | * 46 | * @param chunkSizeLimit the allowable chunk size {@code feed} fails when the size of reading 47 | * chunk exceeds the limit 48 | */ 49 | public MsgpackStreamUnpacker(final long chunkSizeLimit) { 50 | this.buffer = new InfluentByteBuffer(chunkSizeLimit); 51 | this.chunkSizeLimit = chunkSizeLimit; 52 | } 53 | 54 | /** 55 | * Reads buffers from a {@code ReadableByteChannel}. 56 | * 57 | * @param supplier supplier to produce ByteBuffer 58 | * @param channel channel 59 | * @throws InfluentIOException when it fails reading from the channel or the chunk size exceeds 60 | * the limit 61 | */ 62 | public void feed(final Supplier supplier, final NioTcpChannel channel) { 63 | boolean toBeContinued = true; 64 | while (toBeContinued) { 65 | toBeContinued = buffer.feed(supplier); 66 | unpack(channel); 67 | } 68 | } 69 | 70 | // fails when the chunk size exceeds the limit 71 | private void unpack(final NioTcpChannel channel) { 72 | while (buffer.hasRemaining()) { 73 | try { 74 | currentChunkSize += buffer.remaining(); 75 | final DecodeResult result = currentUnpacker.unpack(buffer); 76 | currentChunkSize -= buffer.remaining(); 77 | if (result.isCompleted()) { 78 | unpackedValues.offer(result.value()); 79 | currentUnpacker = FormatUnpacker.getInstance(); 80 | currentChunkSize = 0; 81 | } else if (currentChunkSize >= chunkSizeLimit) { 82 | channel.close(); 83 | throw new InfluentIOException( 84 | "The chunk size exceeds the limit. size = " 85 | + buffer.remaining() 86 | + ", limit = " 87 | + chunkSizeLimit); 88 | } else { 89 | currentUnpacker = result.next(); 90 | break; 91 | } 92 | } catch (final InfluentIOException e) { 93 | throw e; 94 | } catch (final Exception e) { 95 | channel.close(); 96 | throw new InfluentIOException("Failed unpacking.", e); 97 | } 98 | } 99 | } 100 | 101 | /** @return true if this {@code MsgpackStreamUnpacker} can return the next value */ 102 | public boolean hasNext() { 103 | return !unpackedValues.isEmpty(); 104 | } 105 | 106 | /** 107 | * @return the next {@code ImmutableValue} 108 | * @throws NoSuchElementException when no next value is found 109 | */ 110 | public ImmutableValue next() { 111 | return unpackedValues.remove(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /influent-java/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /influent-java/src/test/scala/influent/forward/NioUdpHeartbeatServerSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.forward 18 | 19 | import java.net.{InetSocketAddress, SocketAddress} 20 | import java.nio.ByteBuffer 21 | import java.util.Optional 22 | 23 | import influent.exception.InfluentIOException 24 | import influent.internal.nio.NioUdpChannel 25 | import influent.internal.nio.NioUdpChannel.Op 26 | import org.mockito.Mockito._ 27 | import org.scalatest.WordSpec 28 | import org.scalatest.mockito.MockitoSugar 29 | 30 | class NioUdpHeartbeatServerSpec extends WordSpec with MockitoSugar { 31 | private[this] def response: ByteBuffer = { 32 | val buffer = ByteBuffer.allocate(1).put(0: Byte) 33 | buffer.flip() 34 | buffer 35 | } 36 | 37 | "onWritable" should { 38 | "flush the response buffer" in { 39 | val channel = mock[NioUdpChannel] 40 | val server = new NioUdpHeartbeatServer(channel) 41 | val targets = Seq( 42 | new InetSocketAddress(8001), 43 | new InetSocketAddress(8002), 44 | new InetSocketAddress(8003) 45 | ) 46 | targets.foreach { target => 47 | server.replyTo.enqueue(target) 48 | when(channel.send(response, target)).thenReturn(true) 49 | } 50 | 51 | assert(server.onWritable() === ()) 52 | 53 | targets.foreach { target => 54 | verify(channel).send(response, target) 55 | } 56 | verify(channel).disable(Op.WRITE) 57 | verifyNoMoreInteractions(channel) 58 | } 59 | 60 | "not disable OP_WRITE" when { 61 | "all responses are not flushed" in { 62 | val channel = mock[NioUdpChannel] 63 | val server = new NioUdpHeartbeatServer(channel) 64 | val targets = Seq( 65 | new InetSocketAddress(8001), 66 | new InetSocketAddress(8002), 67 | new InetSocketAddress(8003) 68 | ) 69 | targets.foreach(server.replyTo.enqueue) 70 | when(channel.send(response, targets.head)).thenReturn(true) 71 | when(channel.send(response, targets(1))).thenReturn(false) 72 | 73 | assert(server.onWritable() === ()) 74 | 75 | verify(channel).send(response, targets.head) 76 | verify(channel).send(response, targets(1)) 77 | verifyNoMoreInteractions(channel) 78 | } 79 | } 80 | 81 | "not fail" when { 82 | "some IO error occurs" in { 83 | val channel = mock[NioUdpChannel] 84 | val server = new NioUdpHeartbeatServer(channel) 85 | val targets = Seq( 86 | new InetSocketAddress(8001), 87 | new InetSocketAddress(8002), 88 | new InetSocketAddress(8003) 89 | ) 90 | targets.foreach(server.replyTo.enqueue) 91 | when(channel.send(response, targets.head)).thenReturn(true) 92 | when(channel.send(response, targets(1))) 93 | .thenThrow(new InfluentIOException()).thenReturn(true) 94 | when(channel.send(response, targets(2))).thenReturn(true) 95 | 96 | assert(server.onWritable() === ()) 97 | 98 | verify(channel).send(response, targets.head) 99 | verify(channel, times(2)).send(response, targets(1)) 100 | verify(channel).send(response, targets(2)) 101 | verify(channel).disable(Op.WRITE) 102 | verifyNoMoreInteractions(channel) 103 | } 104 | } 105 | } 106 | 107 | "onReadable" should { 108 | "receives heartbeat requests" in { 109 | val channel = mock[NioUdpChannel] 110 | 111 | val server = new NioUdpHeartbeatServer(channel) 112 | val source1 = new InetSocketAddress(8001) 113 | val source2 = new InetSocketAddress(8002) 114 | when(channel.receive(ByteBuffer.allocate(1))) 115 | .thenReturn(Optional.of(source1), Optional.of(source2), Optional.empty()) 116 | 117 | assert(server.onReadable() === ()) 118 | 119 | verify(channel, times(3)).receive(ByteBuffer.allocate(1)) 120 | assert(server.replyTo.dequeue() === source1) 121 | assert(server.replyTo.dequeue() === source2) 122 | assert(!server.replyTo.nonEmpty()) 123 | verify(channel).enable(Op.WRITE) 124 | verifyNoMoreInteractions(channel) 125 | } 126 | 127 | "not fail" when { 128 | "some IO error occurs" in { 129 | val channel = mock[NioUdpChannel] 130 | val server = new NioUdpHeartbeatServer(channel) 131 | val source: SocketAddress = new InetSocketAddress(8000) 132 | when(channel.receive(ByteBuffer.allocate(1))) 133 | .thenThrow(new InfluentIOException()) 134 | .thenReturn(Optional.of(source), Optional.empty()) 135 | 136 | assert(server.onReadable() === ()) 137 | verify(channel, times(3)).receive(ByteBuffer.allocate(1)) 138 | assert(server.replyTo.dequeue() === source) 139 | assert(!server.replyTo.nonEmpty()) 140 | } 141 | } 142 | } 143 | 144 | "close" should { 145 | "close the channel and inform the supervisor" in { 146 | val channel = mock[NioUdpChannel] 147 | val server = new NioUdpHeartbeatServer(channel) 148 | assert(server.close() === ()) 149 | verify(channel).close() 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /influent-java/src/test/scala/influent/internal/msgpack/MsgpackIncrementalUnpackerSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.msgpack 18 | 19 | import influent.internal.msgpack.MsgpackUnpackerArbitrary._ 20 | import java.nio.ByteBuffer 21 | import org.msgpack.core.MessagePack 22 | import org.msgpack.value.ImmutableValue 23 | import org.scalacheck.Shrink 24 | import org.scalatest.WordSpec 25 | import org.scalatest.prop.GeneratorDrivenPropertyChecks 26 | 27 | class MsgpackIncrementalUnpackerSpec extends WordSpec with GeneratorDrivenPropertyChecks { 28 | "MsgpackIncrementalUnpacker" should { 29 | "decode msgpack values" in { 30 | implicit val shrinkValue: Shrink[ImmutableValue] = Shrink.shrinkAny 31 | implicit val shrinkInt: Shrink[Int] = Shrink.shrinkAny 32 | 33 | forAll { (value: ImmutableValue, groupSize: Int) => 34 | whenever(groupSize > 0) { 35 | val packer = MessagePack.newDefaultBufferPacker() 36 | packer.packValue(value) 37 | val asBytes = packer.toByteArray 38 | 39 | var unpacker: MsgpackIncrementalUnpacker = FormatUnpacker.getInstance() 40 | val buffer = new InfluentByteBuffer(Int.MaxValue) 41 | val chunks = asBytes.grouped(groupSize).toList 42 | chunks.init.foreach { bytes => 43 | val buf = ByteBuffer.allocate(1024) 44 | buf.put(bytes).flip() 45 | buffer.push(buf) 46 | val result = unpacker.unpack(buffer) 47 | assert(!result.isCompleted) 48 | unpacker = result.next() 49 | } 50 | 51 | val buf = ByteBuffer.allocate(1024) 52 | buf.put(chunks.last).flip() 53 | buffer.push(buf) 54 | 55 | val actual = unpacker.unpack(buffer) 56 | assert(actual.isCompleted) 57 | assert(actual.value() === value) 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /influent-java/src/test/scala/influent/internal/msgpack/MsgpackUnpackerArbitrary.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.msgpack 18 | 19 | import org.msgpack.value._ 20 | import org.scalacheck.Arbitrary.arbitrary 21 | import org.scalacheck.{Arbitrary, Gen} 22 | import scala.collection.JavaConverters._ 23 | 24 | object MsgpackUnpackerArbitrary { 25 | implicit lazy val arbValue: Arbitrary[ImmutableValue] = Arbitrary(genValue(0)) 26 | 27 | private[this] def genValue(level: Int): Gen[ImmutableValue] = { 28 | def genScalar: Gen[ImmutableValue] = Gen.oneOf( 29 | arbitrary[ImmutableBinaryValue], 30 | arbitrary[ImmutableBooleanValue], 31 | arbitrary[ImmutableIntegerValue], 32 | arbitrary[ImmutableFloatValue], 33 | arbitrary[ImmutableNilValue], 34 | arbitrary[ImmutableStringValue], 35 | arbitrary[ImmutableExtensionValue] 36 | ) 37 | def genCollection(level: Int): Gen[ImmutableValue] = Gen.oneOf( 38 | genArray(level), 39 | genMap(level) 40 | ) 41 | level match { 42 | case 2 => genScalar 43 | case x => Gen.frequency(50 -> genScalar, 1 -> genCollection(x + 1)) 44 | } 45 | } 46 | 47 | implicit lazy val arbBinary: Arbitrary[ImmutableBinaryValue] = Arbitrary { 48 | Gen.listOf(Arbitrary.arbByte.arbitrary).map(_.toArray).map(ValueFactory.newBinary) 49 | } 50 | 51 | implicit lazy val arbBoolean: Arbitrary[ImmutableBooleanValue] = Arbitrary { 52 | arbitrary[Boolean].map(ValueFactory.newBoolean) 53 | } 54 | 55 | implicit lazy val arbInteger: Arbitrary[ImmutableIntegerValue] = Arbitrary(Gen.oneOf( 56 | arbitrary[Long].map(ValueFactory.newInteger), 57 | arbitrary[BigInt].filter { value => 58 | value.bitLength <= 63 || value.bitLength == 64 && value.signum == 1 59 | }.map(_.bigInteger).map(ValueFactory.newInteger) 60 | )) 61 | 62 | implicit lazy val arbFloat: Arbitrary[ImmutableFloatValue] = Arbitrary(Gen.oneOf( 63 | arbitrary[Float].map(ValueFactory.newFloat), 64 | arbitrary[Double].map(ValueFactory.newFloat) 65 | )) 66 | 67 | implicit lazy val arbNil: Arbitrary[ImmutableNilValue] = Arbitrary(Gen.const(ValueFactory.newNil())) 68 | 69 | implicit lazy val arbString: Arbitrary[ImmutableStringValue] = Arbitrary { 70 | Gen.alphaStr.map(ValueFactory.newString) 71 | } 72 | 73 | implicit lazy val arbExtension: Arbitrary[ImmutableExtensionValue] = Arbitrary { 74 | for { 75 | extType <- Arbitrary.arbByte.arbitrary 76 | data <- Gen.listOf(Arbitrary.arbByte.arbitrary).map(_.toArray) 77 | } yield ValueFactory.newExtension(extType, data) 78 | } 79 | 80 | private[this] def genArray(level: Int): Gen[ImmutableArrayValue] = { 81 | Gen.listOf(genValue(level)).map(_.asJava).map(ValueFactory.newArray) 82 | } 83 | 84 | implicit lazy val arbArray: Arbitrary[ImmutableArrayValue] = Arbitrary(genArray(0)) 85 | 86 | private[this] def genMap(level: Int): Gen[ImmutableMapValue] = { 87 | val genKV = for { 88 | k <- genValue(level) 89 | v <- genValue(level) 90 | } yield (k, v) 91 | Gen.mapOf(genKV).flatMap { kvs => 92 | kvs.map { case (k, v) => (k: Value, v: Value) } 93 | }.map(_.asJava).map { x => ValueFactory.newMap(x) } 94 | } 95 | 96 | implicit lazy val arbMap: Arbitrary[ImmutableMapValue] = Arbitrary(genMap(0)) 97 | } 98 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/exception/InfluentIOException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.exception; 18 | 19 | /** IO error. */ 20 | public final class InfluentIOException extends RuntimeException { 21 | /** 22 | * Constructs a new {@code InfluentIOException}. 23 | * 24 | * @param message the error message 25 | * @param cause the cause 26 | */ 27 | public InfluentIOException(final String message, final Throwable cause) { 28 | super(message, cause); 29 | } 30 | 31 | /** 32 | * Constructs a new {@code InfluentIOException}. 33 | * 34 | * @param message the error message 35 | */ 36 | public InfluentIOException(final String message) { 37 | super(message); 38 | } 39 | 40 | /** Constructs a new {@code InfluentIOException}. */ 41 | public InfluentIOException() { 42 | super(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioAttachment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import influent.exception.InfluentIOException; 20 | 21 | /** An attachment for new IO operations. */ 22 | public interface NioAttachment extends AutoCloseable { 23 | /** 24 | * Handles a read event. {@code NioAttachment} is closed when {@code onReadable} throws an 25 | * exception. 26 | * 27 | * @throws InfluentIOException when some IO error occurs 28 | * @throws UnsupportedOperationException {@code onReadable} is not supported 29 | */ 30 | default void onReadable() { 31 | throw new UnsupportedOperationException(this + " does not support onReadable"); 32 | } 33 | 34 | /** 35 | * Handles a write event. {@code NioAttachment} is closed when {@code onWritable} throws an 36 | * exception. 37 | * 38 | * @throws InfluentIOException when some IO error occurs 39 | * @throws UnsupportedOperationException {@code onWritable} is not supported 40 | */ 41 | default void onWritable() { 42 | throw new UnsupportedOperationException(this + " does not support onWritable"); 43 | } 44 | 45 | /** 46 | * Handles an accept event. {@code NioAttachment} is closed when {@code onAcceptable} throws an 47 | * exception. 48 | * 49 | * @throws InfluentIOException when some IO error occurs 50 | * @throws UnsupportedOperationException {@code onAcceptable} is not supported 51 | */ 52 | default void onAcceptable() { 53 | throw new UnsupportedOperationException(this + " does not support onAcceptable"); 54 | } 55 | 56 | /** 57 | * Handles a connect event. {@code NioAttachment} is closed when {@code onConnectable} throws an 58 | * exception. 59 | * 60 | * @throws InfluentIOException when some IO error occurs 61 | * @throws UnsupportedOperationException {@code onConnectable} is not supported 62 | */ 63 | default void onConnectable() { 64 | throw new UnsupportedOperationException(this + " does not support onConnectable"); 65 | } 66 | 67 | /** Terminates this attachment. */ 68 | void close(); 69 | } 70 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioChannelConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import java.io.FileInputStream; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.security.KeyManagementException; 23 | import java.security.KeyStore; 24 | import java.security.KeyStoreException; 25 | import java.security.NoSuchAlgorithmException; 26 | import java.security.SecureRandom; 27 | import java.security.UnrecoverableKeyException; 28 | import java.security.cert.CertificateException; 29 | import javax.net.ssl.KeyManager; 30 | import javax.net.ssl.KeyManagerFactory; 31 | import javax.net.ssl.SSLContext; 32 | import javax.net.ssl.SSLEngine; 33 | import javax.net.ssl.SSLException; 34 | import influent.exception.InfluentIOException; 35 | 36 | public class NioChannelConfig { 37 | 38 | private boolean sslEnabled = false; 39 | private String host; 40 | private int port; 41 | private String[] tlsVersions; 42 | private String[] ciphers; 43 | private SSLContext context; 44 | 45 | public NioChannelConfig() { 46 | sslEnabled = false; 47 | context = null; 48 | tlsVersions = null; 49 | } 50 | 51 | public NioChannelConfig( 52 | final String host, 53 | final int port, 54 | final boolean sslEnabled, 55 | final String[] tlsVersions, 56 | final String[] ciphers, 57 | final String keystorePath, 58 | final String keystorePassword, 59 | final String keyPassword) { 60 | this.host = host; 61 | this.port = port; 62 | this.sslEnabled = sslEnabled; 63 | this.tlsVersions = tlsVersions; 64 | this.ciphers = ciphers; 65 | try { 66 | if (isSslEnabled()) { 67 | context = SSLContext.getInstance("TLS"); 68 | context.init( 69 | createKeyManagers(keystorePath, keystorePassword, keyPassword), 70 | null, 71 | new SecureRandom()); 72 | } 73 | } catch (final NoSuchAlgorithmException e) { 74 | throw new AssertionError(e); 75 | } catch (final KeyManagementException e) { 76 | e.printStackTrace(); 77 | } 78 | } 79 | 80 | public SSLEngine createSSLEngine() { 81 | final SSLEngine engine = context.createSSLEngine(host, port); 82 | engine.setUseClientMode(false); 83 | engine.setEnabledProtocols(tlsVersions); 84 | if (ciphers != null) { 85 | engine.setEnabledCipherSuites(ciphers); 86 | } 87 | try { 88 | engine.beginHandshake(); 89 | } catch (final SSLException e) { 90 | throw new InfluentIOException("Failed beginning a handshake.", e); 91 | } 92 | // TODO configure engine 93 | return engine; 94 | } 95 | 96 | private KeyManager[] createKeyManagers( 97 | final String filepath, final String keystorePassword, final String keyPassword) { 98 | try { 99 | final KeyStore keyStore = KeyStore.getInstance("JKS"); 100 | try (InputStream keyStoreIS = new FileInputStream(filepath)) { 101 | keyStore.load(keyStoreIS, keystorePassword.toCharArray()); 102 | } 103 | final KeyManagerFactory kmf = 104 | KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 105 | kmf.init(keyStore, keyPassword.toCharArray()); 106 | return kmf.getKeyManagers(); 107 | } catch (final IOException e) { 108 | e.printStackTrace(); 109 | } catch (final CertificateException 110 | | UnrecoverableKeyException 111 | | NoSuchAlgorithmException 112 | | KeyStoreException e) { 113 | e.printStackTrace(); 114 | } 115 | return null; 116 | } 117 | 118 | public boolean isSslEnabled() { 119 | return sslEnabled; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioEventLoop.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import influent.exception.InfluentIOException; 20 | import influent.internal.nio.NioEventLoopTask.Register; 21 | import influent.internal.nio.NioEventLoopTask.Select; 22 | import influent.internal.nio.NioEventLoopTask.UpdateInterestSet; 23 | import influent.internal.util.Exceptions; 24 | import influent.internal.util.Futures; 25 | import influent.internal.util.ThreadSafeQueue; 26 | import java.io.IOException; 27 | import java.nio.channels.ClosedSelectorException; 28 | import java.nio.channels.SelectableChannel; 29 | import java.nio.channels.SelectionKey; 30 | import java.nio.channels.Selector; 31 | import java.util.Set; 32 | import java.util.concurrent.CompletableFuture; 33 | import java.util.concurrent.atomic.AtomicReference; 34 | import org.slf4j.Logger; 35 | import org.slf4j.LoggerFactory; 36 | 37 | /** 38 | * An event loop for non-blocking IO. 39 | * 40 | *

{@code NioEventLoop} is unconditionally thread-safe. {@code NioEventLoop#run} is expected to 41 | * be executed on one exclusive thread. 42 | */ 43 | public final class NioEventLoop implements Runnable { 44 | private enum State { 45 | IDLE, 46 | ACTIVE, 47 | STOPPING, 48 | TERMINATED 49 | } 50 | 51 | private static final Logger logger = LoggerFactory.getLogger(NioEventLoop.class); 52 | 53 | private final Selector selector; 54 | private final ThreadSafeQueue tasks = new ThreadSafeQueue<>(); 55 | private final AtomicReference state = new AtomicReference<>(State.IDLE); 56 | private final CompletableFuture shutdownFuture = new CompletableFuture<>(); 57 | 58 | NioEventLoop(final Selector selector) { 59 | this.selector = selector; 60 | } 61 | 62 | /** 63 | * Creates a new {@code NioEventLoop}. 64 | * 65 | * @return the {@code NioEventLoop} 66 | * @throws InfluentIOException if a selector cannot be created 67 | */ 68 | public static NioEventLoop open() { 69 | try { 70 | return new NioEventLoop(Selector.open()); 71 | } catch (final IOException e) { 72 | throw new InfluentIOException("A selector cannot be created.", e); 73 | } 74 | } 75 | 76 | /** 77 | * Executes this event loop. 78 | * 79 | * @throws IllegalStateException if this event loop has already started 80 | */ 81 | @Override 82 | public void run() { 83 | if (!state.compareAndSet(State.IDLE, State.ACTIVE)) { 84 | throw new IllegalStateException("This NioEventLoop is " + state.get()); 85 | } 86 | 87 | try { 88 | loop(); 89 | } finally { 90 | cleanup(); 91 | state.set(State.TERMINATED); 92 | shutdownFuture.complete(null); 93 | } 94 | } 95 | 96 | private void loop() { 97 | while (state.get() == State.ACTIVE) { 98 | while (tasks.nonEmpty()) { 99 | try { 100 | tasks.dequeue().run(); 101 | } catch (final Exception e) { 102 | logger.error("NioEventLoopTask failed.", e); 103 | } 104 | } 105 | tasks.enqueue(Select.of(selector)); 106 | } 107 | } 108 | 109 | /** 110 | * Registers a channel with this event loop. 111 | * 112 | *

Operations are done asynchronously. 113 | * 114 | * @param channel the channel 115 | * @param key the {@code NioSelectionKey} 116 | * @param ops the interest set 117 | * @param attachment the {@code NioAttachment} 118 | */ 119 | void register( 120 | final SelectableChannel channel, 121 | final NioSelectionKey key, 122 | final int ops, 123 | final NioAttachment attachment) { 124 | addTask(Register.of(selector, channel, key, ops, attachment)); 125 | } 126 | 127 | /** 128 | * Enables the given interest set on the given {@code NioSelectionKey}. 129 | * 130 | *

Operations are done asynchronously. 131 | * 132 | * @param key the {@code NioSelectionKey} 133 | * @param ops the interest set to be enabled 134 | */ 135 | void enableInterestSet(final NioSelectionKey key, final int ops) { 136 | addTask(UpdateInterestSet.of(key, current -> current | ops)); 137 | } 138 | 139 | /** 140 | * Disables the given interest set on the given {@code NioSelectionKey}. 141 | * 142 | *

Operations are done asynchronously. 143 | * 144 | * @param key the {@code NioSelectionKey} 145 | * @param ops the interest set to be disabled 146 | */ 147 | void disableInterestSet(final NioSelectionKey key, final int ops) { 148 | addTask(UpdateInterestSet.of(key, current -> current & ~ops)); 149 | } 150 | 151 | private void addTask(final NioEventLoopTask task) { 152 | tasks.enqueue(task); 153 | // TODO: optimization 154 | selector.wakeup(); 155 | } 156 | 157 | private void cleanup() { 158 | try { 159 | final Set keys = selector.keys(); 160 | keys.forEach( 161 | key -> { 162 | final NioAttachment attachment = (NioAttachment) key.attachment(); 163 | Exceptions.ignore(attachment::close, "Failed closing the attachment. " + attachment); 164 | }); 165 | Exceptions.ignore(selector::close, "Failed closing the selector"); 166 | } catch (final ClosedSelectorException e) { 167 | throw new AssertionError(e); 168 | } 169 | } 170 | 171 | /** 172 | * Stops this event loop. Shutdown operations are executed asynchronously and {@code 173 | * NioEventLoop#shutdown} returns a {@code CompletedFuture}. 174 | * 175 | * @return {@code CompletableFuture} the future that will be completed when this event loop stops 176 | * @throws IllegalStateException when this event loop is not started 177 | */ 178 | public CompletableFuture shutdown() { 179 | if (state.get() == State.IDLE) { 180 | throw new IllegalStateException("This NioEventLoop has not yet been started."); 181 | } 182 | if (state.compareAndSet(State.ACTIVE, State.STOPPING)) { 183 | selector.wakeup(); 184 | } 185 | return Futures.followerOf(shutdownFuture); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioEventLoopPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import java.util.concurrent.CompletableFuture; 20 | import java.util.concurrent.ThreadFactory; 21 | 22 | /** 23 | * A pool of {@code NioEventLoop}. 24 | * 25 | *

{@code NioEventLoop} is unconditionally thread-safe. 26 | */ 27 | public interface NioEventLoopPool { 28 | /** 29 | * Creates the new {@code NioEventLoopPool} which contains the given size of {@code 30 | * NioEventLoops}. The larger {@code poolSize} is given, the larger number of threads concurrently 31 | * run. 32 | * 33 | * @param poolSize the size of {@code NioEventLoopPool} 34 | * @return the new {@code NioEventLoopPool} 35 | * @throws influent.exception.InfluentIOException if some of event loops cannot be created 36 | */ 37 | static NioEventLoopPool open(final int poolSize) { 38 | if (poolSize <= 0) { 39 | throw new IllegalArgumentException("poolSize must be greater than 0."); 40 | } 41 | if (poolSize == 1) { 42 | return NioSingleThreadEventLoopPool.open(); 43 | } else { 44 | return NioRoundRobinEventLoopPool.open(poolSize); 45 | } 46 | } 47 | 48 | /** 49 | * Starts all the event loop. 50 | * 51 | * @param threadFactory the factory of event loop threads 52 | * @throws IllegalStateException if this event loop has already started 53 | */ 54 | void start(final ThreadFactory threadFactory); 55 | 56 | /** @return the next {@code NioEventLoop} */ 57 | NioEventLoop next(); 58 | 59 | /** 60 | * Stops all the {@code NioEventLoop}. Shutdown operations are executed asynchronously and {@code 61 | * NioEventLoopPool#shutdown} returns a {@code CompletedFuture}. 62 | * 63 | * @return {@code CompletableFuture} the future that will be completed when all the event loop 64 | * stops 65 | * @throws IllegalStateException when this event loop pool is not started 66 | */ 67 | CompletableFuture shutdown(); 68 | } 69 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioEventLoopTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import influent.internal.util.Exceptions; 20 | import java.io.IOException; 21 | import java.nio.channels.CancelledKeyException; 22 | import java.nio.channels.ClosedSelectorException; 23 | import java.nio.channels.IllegalBlockingModeException; 24 | import java.nio.channels.IllegalSelectorException; 25 | import java.nio.channels.SelectableChannel; 26 | import java.nio.channels.SelectionKey; 27 | import java.nio.channels.Selector; 28 | import java.util.Iterator; 29 | import java.util.Set; 30 | import java.util.function.IntUnaryOperator; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | 34 | /** Tasks of {@code NioEventLoop}. */ 35 | interface NioEventLoopTask { 36 | /** Registers a new channel. */ 37 | final class Register implements NioEventLoopTask { 38 | private static final Logger logger = LoggerFactory.getLogger(Register.class); 39 | 40 | private final Selector selector; 41 | private final SelectableChannel channel; 42 | private final NioSelectionKey key; 43 | private final int ops; 44 | private final NioAttachment attachment; 45 | 46 | private Register( 47 | final Selector selector, 48 | final SelectableChannel channel, 49 | final NioSelectionKey key, 50 | final int ops, 51 | final NioAttachment attachment) { 52 | this.selector = selector; 53 | this.channel = channel; 54 | this.key = key; 55 | this.ops = ops; 56 | this.attachment = attachment; 57 | } 58 | 59 | static Register of( 60 | final Selector selector, 61 | final SelectableChannel channel, 62 | final NioSelectionKey key, 63 | final int ops, 64 | final NioAttachment attachment) { 65 | return new Register(selector, channel, key, ops, attachment); 66 | } 67 | 68 | @Override 69 | public void run() { 70 | try { 71 | key.bind(channel.configureBlocking(false).register(selector, ops, attachment)); 72 | } catch (final ClosedSelectorException 73 | | IllegalBlockingModeException 74 | | IllegalSelectorException e) { 75 | throw new AssertionError(e); 76 | } catch (final CancelledKeyException | IllegalArgumentException | IOException e) { 77 | // ClosedChannelException is an IOException 78 | logger.error("NioEventLoopTask.Register with " + attachment + " threw an exception.", e); 79 | } 80 | } 81 | } 82 | 83 | /** Updates an interest set. */ 84 | final class UpdateInterestSet implements NioEventLoopTask { 85 | private static final Logger logger = LoggerFactory.getLogger(UpdateInterestSet.class); 86 | 87 | private final NioSelectionKey key; 88 | private final IntUnaryOperator updater; 89 | 90 | private UpdateInterestSet(final NioSelectionKey key, final IntUnaryOperator updater) { 91 | this.key = key; 92 | this.updater = updater; 93 | } 94 | 95 | static UpdateInterestSet of(final NioSelectionKey key, final IntUnaryOperator updater) { 96 | return new UpdateInterestSet(key, updater); 97 | } 98 | 99 | @Override 100 | public void run() { 101 | try { 102 | final SelectionKey underlying = key.unwrap(); 103 | final int current = underlying.interestOps(); 104 | final int updated = updater.applyAsInt(current); 105 | if (updated != current) { 106 | underlying.interestOps(updated); 107 | } 108 | } catch (final CancelledKeyException e) { 109 | logger.debug("The key for UpdateInterestSet is cancelled."); 110 | } catch (final IllegalArgumentException e) { 111 | logger.error("UpdateInterestSet threw an exception.", e); 112 | } 113 | } 114 | } 115 | 116 | /** Selects and proceeds IO operations. */ 117 | final class Select implements NioEventLoopTask { 118 | private static final Logger logger = LoggerFactory.getLogger(Select.class); 119 | 120 | private final Selector selector; 121 | 122 | private Select(final Selector selector) { 123 | this.selector = selector; 124 | } 125 | 126 | static Select of(final Selector selector) { 127 | return new Select(selector); 128 | } 129 | 130 | @Override 131 | public void run() { 132 | final int ready = select(); 133 | if (ready == 0) { 134 | return; 135 | } 136 | 137 | final Set keys = selectedKeys(); 138 | final Iterator iterator = keys.iterator(); 139 | 140 | while (iterator.hasNext()) { 141 | final SelectionKey key = iterator.next(); 142 | final NioAttachment attachment = (NioAttachment) key.attachment(); 143 | logger.debug("Selected key for {}", attachment); 144 | 145 | try { 146 | if (key.isReadable()) { 147 | attachment.onReadable(); 148 | } 149 | if (key.isWritable()) { 150 | attachment.onWritable(); 151 | } 152 | if (key.isAcceptable()) { 153 | attachment.onAcceptable(); 154 | } 155 | if (key.isConnectable()) { 156 | attachment.onConnectable(); 157 | } 158 | } catch (final CancelledKeyException e) { 159 | logger.debug("The key has already been cancelled."); 160 | Exceptions.ignore(attachment::close, "Failed closing " + attachment); 161 | } catch (final Exception e) { 162 | logger.debug("An error occurred when handling an event.", e); 163 | Exceptions.ignore(attachment::close, "Failed closing " + attachment); 164 | } 165 | 166 | iterator.remove(); 167 | } 168 | } 169 | 170 | private int select() { 171 | try { 172 | return selector.select(); 173 | } catch (final ClosedSelectorException e) { 174 | throw new AssertionError(e); 175 | } catch (final IOException e) { 176 | logger.error("`select` failed.", e); 177 | return 0; 178 | } 179 | } 180 | 181 | private Set selectedKeys() { 182 | try { 183 | return selector.selectedKeys(); 184 | } catch (final ClosedSelectorException e) { 185 | throw new AssertionError(e); 186 | } 187 | } 188 | } 189 | 190 | /** Executes this task. */ 191 | void run(); 192 | } 193 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioRoundRobinEventLoopPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import java.util.concurrent.CompletableFuture; 20 | import java.util.concurrent.ThreadFactory; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | 23 | /** 24 | * An {@code NioEventLoopPool} running multiple {@code NioEventLoops} and choose {@code 25 | * NioEventLoop} in round-robin fashion. 26 | */ 27 | final class NioRoundRobinEventLoopPool implements NioEventLoopPool { 28 | private final NioEventLoop[] eventLoops; 29 | private final AtomicInteger counter = new AtomicInteger(0); 30 | 31 | NioRoundRobinEventLoopPool(final NioEventLoop[] eventLoops) { 32 | this.eventLoops = eventLoops; 33 | } 34 | 35 | /** 36 | * @return the new NioRoundRobinEventLoopPool 37 | * @throws influent.exception.InfluentIOException if some of event loops cannot be created 38 | */ 39 | static NioEventLoopPool open(final int poolSize) { 40 | final NioEventLoop[] eventLoops = new NioEventLoop[poolSize]; 41 | for (int i = 0; i < poolSize; ++i) { 42 | eventLoops[i] = NioEventLoop.open(); 43 | } 44 | return new NioRoundRobinEventLoopPool(eventLoops); 45 | } 46 | 47 | @Override 48 | public void start(final ThreadFactory threadFactory) { 49 | for (final NioEventLoop eventLoop : eventLoops) { 50 | threadFactory.newThread(eventLoop).start(); 51 | } 52 | } 53 | 54 | @Override 55 | public NioEventLoop next() { 56 | return eventLoops[Math.abs(counter.getAndIncrement() % eventLoops.length)]; 57 | } 58 | 59 | @Override 60 | public CompletableFuture shutdown() { 61 | final CompletableFuture[] results = new CompletableFuture[eventLoops.length]; 62 | for (int i = 0; i < eventLoops.length; ++i) { 63 | results[i] = eventLoops[i].shutdown(); 64 | } 65 | return CompletableFuture.allOf(results); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioSelectionKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import java.nio.channels.SelectionKey; 20 | 21 | /** 22 | * A wrapped {@code SelectionKey}. 23 | * 24 | *

{@code NioSelectionKey} has a feature to bind the underlying {@code SelectionKey} lazily. 25 | * 26 | *

{@code NioSelectionKey} is not thread-safe. All the method should be invoked by an event loop 27 | * thread. 28 | */ 29 | final class NioSelectionKey { 30 | private SelectionKey key; 31 | 32 | private NioSelectionKey() {} 33 | 34 | static NioSelectionKey create() { 35 | return new NioSelectionKey(); 36 | } 37 | 38 | SelectionKey unwrap() { 39 | return key; 40 | } 41 | 42 | void bind(final SelectionKey key) { 43 | if (this.key != null) { 44 | throw new IllegalStateException("This NioSelectionKey is already bound."); 45 | } 46 | this.key = key; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioServerSocketChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import influent.exception.InfluentIOException; 20 | import influent.internal.util.Exceptions; 21 | import java.io.IOException; 22 | import java.net.SocketAddress; 23 | import java.net.SocketOption; 24 | import java.net.StandardSocketOptions; 25 | import java.nio.channels.AlreadyBoundException; 26 | import java.nio.channels.AsynchronousCloseException; 27 | import java.nio.channels.NotYetBoundException; 28 | import java.nio.channels.SelectionKey; 29 | import java.nio.channels.ServerSocketChannel; 30 | import java.nio.channels.SocketChannel; 31 | import java.nio.channels.UnsupportedAddressTypeException; 32 | 33 | /** A adapter of {@code ServerSocketChannel}. The main purpose is to handle complex errors. */ 34 | final class NioServerSocketChannel { 35 | private final ServerSocketChannel channel; 36 | private final SocketAddress localAddress; 37 | 38 | final NioSelectionKey key = NioSelectionKey.create(); 39 | 40 | NioServerSocketChannel(final ServerSocketChannel channel, final SocketAddress localAddress) { 41 | this.channel = channel; 42 | this.localAddress = localAddress; 43 | } 44 | 45 | private static ServerSocketChannel newChannel() { 46 | try { 47 | return ServerSocketChannel.open(); 48 | } catch (final IOException e) { 49 | throw new InfluentIOException("ServerSocketChannel#open failed", e); 50 | } 51 | } 52 | 53 | private static void bind( 54 | final ServerSocketChannel channel, final SocketAddress localAddress, final int backlog) { 55 | try { 56 | channel.bind(localAddress, backlog); 57 | } catch (final AlreadyBoundException | UnsupportedAddressTypeException e) { 58 | throw new IllegalArgumentException("ServerSocketChannel#bind failed", e); 59 | } catch (final SecurityException e) { 60 | throw new AssertionError(e); 61 | } catch (final IOException e) { 62 | // ClosedChannelException is an IOException 63 | throw new InfluentIOException("ServerSocketChannel#bind failed", e); 64 | } 65 | } 66 | 67 | private static void setOption( 68 | final ServerSocketChannel channel, final SocketOption name, final T value) { 69 | try { 70 | channel.setOption(name, value); 71 | } catch (final UnsupportedOperationException | IllegalArgumentException e) { 72 | throw new AssertionError(e); 73 | } catch (final IOException e) { 74 | // ClosedChannelException is an IOException 75 | throw new InfluentIOException("ServerSocketChannel#setOption failed", e); 76 | } 77 | } 78 | 79 | /** 80 | * Opens a {@code NioServerSocketChannel}. 81 | * 82 | * @param localAddress the server's address 83 | * @return {@code ServerSocketChannel} 84 | * @throws IllegalArgumentException when the local address is illegal or already bound 85 | * @throws InfluentIOException when ServerSocketChannel#open fails 86 | */ 87 | static NioServerSocketChannel open(final SocketAddress localAddress, final NioTcpConfig config) { 88 | final ServerSocketChannel channel = newChannel(); 89 | setOption(channel, StandardSocketOptions.SO_REUSEADDR, true); 90 | bind(channel, localAddress, config.getBacklog().orElse(0)); 91 | config 92 | .getReceiveBufferSize() 93 | .ifPresent( 94 | (receiveBufferSize) -> 95 | setOption(channel, StandardSocketOptions.SO_RCVBUF, receiveBufferSize)); 96 | return new NioServerSocketChannel(channel, localAddress); 97 | } 98 | 99 | /** 100 | * Accepts a new connection. 101 | * 102 | * @return the new {@code SocketChannel} 103 | * @throws InfluentIOException when ServerSocketChannel#accept fails typically, this channel is 104 | * already closed 105 | */ 106 | SocketChannel accept() { 107 | try { 108 | return channel.accept(); 109 | } catch (final NotYetBoundException | SecurityException | AsynchronousCloseException e) { 110 | // ClosedByInterruptException is an AsynchronousCloseException 111 | throw new AssertionError(e); 112 | } catch (final IOException e) { 113 | // ClosedChannelException is an IOException 114 | throw new InfluentIOException("NioTcpAcceptor failed accepting.", e); 115 | } 116 | } 117 | 118 | /** Closes this {@code NioServerSocketChannel}. */ 119 | void close() { 120 | Exceptions.ignore(channel::close, "The acceptor bound with " + localAddress + " closed."); 121 | } 122 | 123 | /** 124 | * Returns server's address. 125 | * 126 | *

This method does not return null even if this channel is closed. 127 | */ 128 | SocketAddress getLocalAddress() { 129 | return localAddress; 130 | } 131 | 132 | /** 133 | * Registers this channel to the selector. This operation is done asynchronously. 134 | * 135 | * @param eventLoop the event loop 136 | * @param attachment the attachment 137 | */ 138 | void register(final NioEventLoop eventLoop, final NioAttachment attachment) { 139 | eventLoop.register(channel, key, SelectionKey.OP_ACCEPT, attachment); 140 | } 141 | 142 | /** {@inheritDoc} */ 143 | @Override 144 | public String toString() { 145 | return "NioServerSocketChannel(" + localAddress + ')'; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioSingleThreadEventLoopPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import java.util.concurrent.CompletableFuture; 20 | import java.util.concurrent.ThreadFactory; 21 | 22 | /** An {@code NioEventLoopPool} running one {@code NioEventLoop}. */ 23 | final class NioSingleThreadEventLoopPool implements NioEventLoopPool { 24 | private final NioEventLoop eventLoop; 25 | 26 | NioSingleThreadEventLoopPool(final NioEventLoop eventLoop) { 27 | this.eventLoop = eventLoop; 28 | } 29 | 30 | /** 31 | * @return the new NioSingleThreadEventLoopPool 32 | * @throws influent.exception.InfluentIOException if an event loop cannot be created 33 | */ 34 | static NioEventLoopPool open() { 35 | return new NioSingleThreadEventLoopPool(NioEventLoop.open()); 36 | } 37 | 38 | /** {@inheritDoc} */ 39 | @Override 40 | public void start(final ThreadFactory threadFactory) { 41 | threadFactory.newThread(eventLoop).start(); 42 | } 43 | 44 | /** {@inheritDoc} */ 45 | @Override 46 | public NioEventLoop next() { 47 | return eventLoop; 48 | } 49 | 50 | /** {@inheritDoc} */ 51 | @Override 52 | public CompletableFuture shutdown() { 53 | return eventLoop.shutdown(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioTcpAcceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import influent.exception.InfluentIOException; 20 | import java.net.SocketAddress; 21 | import java.nio.channels.SocketChannel; 22 | import java.util.function.Consumer; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | /** 27 | * A TCP acceptor. 28 | * 29 | *

{@code NioTcpAcceptor} is not thread-safe and expected to be executed on the event loop 30 | * thread. 31 | */ 32 | public final class NioTcpAcceptor implements NioAttachment { 33 | private static final Logger logger = LoggerFactory.getLogger(NioTcpAcceptor.class); 34 | 35 | private final NioServerSocketChannel serverSocketChannel; 36 | private final Consumer callback; 37 | 38 | NioTcpAcceptor( 39 | final NioServerSocketChannel serverSocketChannel, final Consumer callback) { 40 | this.serverSocketChannel = serverSocketChannel; 41 | this.callback = callback; 42 | } 43 | 44 | /** 45 | * Creates a new {@code NioTcpAcceptor}. 46 | * 47 | * @param localAddress the local address to bind 48 | * @param eventLoop the {@code NioEventLoop} 49 | * @param callback the callback function which is invoked on acceptances 50 | * @param tcpConfig the {@code NioTcpConfig} 51 | * @throws IllegalArgumentException if the given local address is invalid or already used 52 | * @throws InfluentIOException if some IO error occurs 53 | */ 54 | public static NioTcpAcceptor open( 55 | final SocketAddress localAddress, 56 | final NioEventLoop eventLoop, 57 | final Consumer callback, 58 | final NioTcpConfig tcpConfig) { 59 | final NioServerSocketChannel serverSocketChannel = 60 | NioServerSocketChannel.open(localAddress, tcpConfig); 61 | final NioTcpAcceptor acceptor = new NioTcpAcceptor(serverSocketChannel, callback); 62 | serverSocketChannel.register(eventLoop, acceptor); 63 | logger.info("A NioTcpAcceptor is bound with {}.", localAddress); 64 | return acceptor; 65 | } 66 | 67 | /** Handles an accept event. This method never fails. */ 68 | @Override 69 | public void onAcceptable() { 70 | while (true) { 71 | try { 72 | final SocketChannel channel = serverSocketChannel.accept(); 73 | if (channel == null) { 74 | break; 75 | } 76 | callback.accept(channel); 77 | } catch (final Exception e) { 78 | logger.error("NioTcpAcceptor failed accepting.", e); 79 | } 80 | } 81 | } 82 | 83 | /** Closes this {@code NioTcpAcceptor}. */ 84 | @Override 85 | public void close() { 86 | serverSocketChannel.close(); 87 | logger.info("The acceptor bound with {} closed.", serverSocketChannel.getLocalAddress()); 88 | } 89 | 90 | /** {@inheritDoc} */ 91 | @Override 92 | public String toString() { 93 | return "NioTcpAcceptor(" + serverSocketChannel.getLocalAddress() + ")"; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioTcpChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import influent.exception.InfluentIOException; 20 | import java.net.SocketAddress; 21 | import java.nio.ByteBuffer; 22 | import java.nio.channels.SelectionKey; 23 | import java.util.Set; 24 | 25 | /** 26 | * A non-blocking {@code SocketChannel}. 27 | * 28 | *

Some operations of NioTcpChannel are not thread-safe. 29 | */ 30 | public interface NioTcpChannel extends AutoCloseable { 31 | enum Op { 32 | /** OP_READ */ 33 | READ(SelectionKey.OP_READ), 34 | /** OP_WRITE */ 35 | WRITE(SelectionKey.OP_WRITE); 36 | 37 | private final int bit; 38 | 39 | Op(final int bit) { 40 | this.bit = bit; 41 | } 42 | 43 | int getBit() { 44 | return bit; 45 | } 46 | 47 | static int bits(final Set ops) { 48 | return ops.stream().mapToInt(Op::getBit).reduce(0, (x, y) -> x | y); 49 | } 50 | } 51 | 52 | /** 53 | * Writes bytes to the socket buffer. 54 | * 55 | *

This method is not guaranteed to be thread-safe. 56 | * 57 | * @param src the buffer 58 | * @return true when some bytes are written 59 | * @throws InfluentIOException if some IO error occurs 60 | */ 61 | boolean write(final ByteBuffer src); 62 | 63 | /** 64 | * Reads bytes from the socket buffer. 65 | * 66 | *

This method is not guaranteed to be thread-safe. 67 | * 68 | * @param dst the buffer 69 | * @return true when some bytes are read 70 | * @throws InfluentIOException if some IO error occurs 71 | */ 72 | boolean read(final ByteBuffer dst); 73 | 74 | /** 75 | * Registers the this channel to the given {@code NioEventLoop}. 76 | * 77 | *

This method is thread-safe. 78 | * 79 | * @param ops the operations to be enabled 80 | * @param attachment the {@code NioAttachment} 81 | */ 82 | void register(final Set ops, final NioAttachment attachment); 83 | 84 | /** 85 | * Enables the given operation. 86 | * 87 | *

This method is thread-safe and operations are done asynchronously. 88 | * 89 | * @param op the operation to be enabled 90 | */ 91 | void enable(final Op op); 92 | 93 | /** 94 | * Disables the given operation. 95 | * 96 | *

This method is thread-safe and operations are done asynchronously. 97 | * 98 | * @param op the operation to be disabled 99 | */ 100 | void disable(final Op op); 101 | 102 | /** 103 | * Closes the {@code SocketChannel}. 104 | * 105 | *

This method is not guaranteed to be thread-safe. 106 | */ 107 | @Override 108 | void close(); 109 | 110 | /** 111 | * Returns true if this channel is open 112 | * 113 | *

This method is not guaranteed to be thread-safe. 114 | */ 115 | boolean isOpen(); 116 | 117 | /** 118 | * Returns the remote address 119 | * 120 | *

This method is thread-safe. 121 | */ 122 | SocketAddress getRemoteAddress(); 123 | } 124 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioTcpConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import java.util.Objects; 20 | import java.util.OptionalInt; 21 | 22 | public final class NioTcpConfig { 23 | public static final class Builder { 24 | private int backlog = 0; 25 | private int sendBufferSize = 0; 26 | private int receiveBufferSize = 0; 27 | private boolean keepAliveEnabled = true; 28 | private boolean tcpNoDelayEnabled = true; 29 | 30 | /** 31 | * Sets the maximum number of pending connections for a server. 32 | * 33 | * @param backlog the maximum number of pending connections when 0 is given, the default value 34 | * of JDK is used 35 | * @return this builder 36 | * @throws IllegalArgumentException when the size is less than 0 37 | */ 38 | public Builder backlog(final int backlog) { 39 | if (backlog < 0) { 40 | throw new IllegalArgumentException( 41 | "Backlog must be greater than or equal to 0. value = " + backlog); 42 | } 43 | this.backlog = backlog; 44 | return this; 45 | } 46 | 47 | /** 48 | * Sets the SO_SNDBUF. 49 | * 50 | * @param sendBufferSize the size of socket send buffers when 0 is given, the default value is 51 | * used 52 | * @return this builder 53 | * @throws IllegalArgumentException when the size is less than 0 54 | */ 55 | public Builder sendBufferSize(final int sendBufferSize) { 56 | if (sendBufferSize < 0) { 57 | throw new IllegalArgumentException( 58 | "Buffer size must be greater than or equal to 0. value = " + sendBufferSize); 59 | } 60 | this.sendBufferSize = sendBufferSize; 61 | return this; 62 | } 63 | 64 | /** 65 | * Sets the SO_RCVBUF. 66 | * 67 | * @param receiveBufferSize the size of socket receive buffers when 0 is given, the default 68 | * value is used 69 | * @return this builder 70 | * @throws IllegalArgumentException when the size is less than 0 71 | */ 72 | public Builder receiveBufferSize(final int receiveBufferSize) { 73 | if (receiveBufferSize < 0) { 74 | throw new IllegalArgumentException( 75 | "Buffer size must be greater than or equal to 0. value = " + receiveBufferSize); 76 | } 77 | this.receiveBufferSize = receiveBufferSize; 78 | return this; 79 | } 80 | 81 | /** 82 | * Sets the SO_KEEPALIVE. 83 | * 84 | * @param keepAliveEnabled whether the SO_KEEPALIVE is enabled or not 85 | * @return this builder 86 | */ 87 | public Builder keepAliveEnabled(final boolean keepAliveEnabled) { 88 | this.keepAliveEnabled = keepAliveEnabled; 89 | return this; 90 | } 91 | 92 | /** 93 | * Sets the TCP_NODELAY. 94 | * 95 | * @param tcpNoDelayEnabled whether TCP_NODELAY is enabled or not 96 | * @return this builder 97 | */ 98 | public Builder tcpNoDelayEnabled(final boolean tcpNoDelayEnabled) { 99 | this.tcpNoDelayEnabled = tcpNoDelayEnabled; 100 | return this; 101 | } 102 | 103 | public NioTcpConfig build() { 104 | return new NioTcpConfig( 105 | backlog, sendBufferSize, receiveBufferSize, keepAliveEnabled, tcpNoDelayEnabled); 106 | } 107 | } 108 | 109 | private final int backlog; 110 | private final int sendBufferSize; 111 | private final int receiveBufferSize; 112 | private final boolean keepAliveEnabled; 113 | private final boolean tcpNoDelayEnabled; 114 | 115 | NioTcpConfig( 116 | final int backlog, 117 | final int sendBufferSize, 118 | final int receiveBufferSize, 119 | final boolean keepAliveEnabled, 120 | final boolean tcpNoDelayEnabled) { 121 | this.backlog = backlog; 122 | this.sendBufferSize = sendBufferSize; 123 | this.receiveBufferSize = receiveBufferSize; 124 | this.keepAliveEnabled = keepAliveEnabled; 125 | this.tcpNoDelayEnabled = tcpNoDelayEnabled; 126 | } 127 | 128 | /** 129 | * Returns the maximum number of pending connections for a server. Use default value when {@code 130 | * OptionalInt.empty()} is returned. 131 | */ 132 | OptionalInt getBacklog() { 133 | return backlog == 0 ? OptionalInt.empty() : OptionalInt.of(backlog); 134 | } 135 | 136 | /** Returns the SO_SNDBUF. Use default value when {@code OptionalInt.empty()} is returned. */ 137 | OptionalInt getSendBufferSize() { 138 | return sendBufferSize == 0 ? OptionalInt.empty() : OptionalInt.of(sendBufferSize); 139 | } 140 | 141 | /** Returns the SO_RCVBUF. Use default value when {@code OptionalInt.empty()} is returned. */ 142 | OptionalInt getReceiveBufferSize() { 143 | return receiveBufferSize == 0 ? OptionalInt.empty() : OptionalInt.of(receiveBufferSize); 144 | } 145 | 146 | /** Returns the SO_KEEPALIVE. */ 147 | boolean getKeepAliveEnabled() { 148 | return keepAliveEnabled; 149 | } 150 | 151 | /** Returns the TCP_NODELAY. */ 152 | boolean getTcpNoDelayEnabled() { 153 | return tcpNoDelayEnabled; 154 | } 155 | 156 | /** {@inheritDoc} */ 157 | @Override 158 | public boolean equals(final Object o) { 159 | if (this == o) { 160 | return true; 161 | } 162 | if (o == null || getClass() != o.getClass()) { 163 | return false; 164 | } 165 | final NioTcpConfig that = (NioTcpConfig) o; 166 | return backlog == that.backlog 167 | && sendBufferSize == that.sendBufferSize 168 | && receiveBufferSize == that.receiveBufferSize 169 | && keepAliveEnabled == that.keepAliveEnabled 170 | && tcpNoDelayEnabled == that.tcpNoDelayEnabled; 171 | } 172 | 173 | /** {@inheritDoc} */ 174 | @Override 175 | public int hashCode() { 176 | return Objects.hash( 177 | backlog, sendBufferSize, receiveBufferSize, keepAliveEnabled, tcpNoDelayEnabled); 178 | } 179 | 180 | /** {@inheritDoc} */ 181 | @Override 182 | public String toString() { 183 | return "NioTcpConfig{" 184 | + "backlog=" 185 | + backlog 186 | + ", sendBufferSize=" 187 | + sendBufferSize 188 | + ", receiveBufferSize=" 189 | + receiveBufferSize 190 | + ", keepAliveEnabled=" 191 | + keepAliveEnabled 192 | + ", tcpNoDelayEnabled=" 193 | + tcpNoDelayEnabled 194 | + '}'; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioTcpPlaintextChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import influent.exception.InfluentIOException; 20 | import influent.internal.util.Exceptions; 21 | import java.io.IOException; 22 | import java.net.SocketAddress; 23 | import java.net.SocketOption; 24 | import java.net.StandardSocketOptions; 25 | import java.nio.ByteBuffer; 26 | import java.nio.channels.AsynchronousCloseException; 27 | import java.nio.channels.ClosedChannelException; 28 | import java.nio.channels.NotYetConnectedException; 29 | import java.nio.channels.SocketChannel; 30 | import java.util.Set; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | 34 | /** A non-blocking {@code SocketChannel}. */ 35 | public final class NioTcpPlaintextChannel implements NioTcpChannel { 36 | private static final Logger logger = LoggerFactory.getLogger(NioTcpPlaintextChannel.class); 37 | 38 | private final SocketChannel channel; 39 | private final NioEventLoop eventLoop; 40 | private final SocketAddress remoteAddress; 41 | 42 | final NioSelectionKey key = NioSelectionKey.create(); 43 | 44 | NioTcpPlaintextChannel( 45 | final SocketChannel channel, 46 | final NioEventLoop eventLoop, 47 | final SocketAddress remoteAddress) { 48 | this.channel = channel; 49 | this.eventLoop = eventLoop; 50 | this.remoteAddress = remoteAddress; 51 | } 52 | 53 | private static SocketAddress getRemoteAddress(final SocketChannel channel) { 54 | try { 55 | return channel.getRemoteAddress(); 56 | } catch (final IOException e) { 57 | // ClosedChannelException is an IOException 58 | throw new InfluentIOException("SocketChannel#getRemoteAddress failed", e); 59 | } 60 | } 61 | 62 | private static void setOption( 63 | final SocketChannel channel, final SocketOption name, final T value) { 64 | try { 65 | channel.setOption(name, value); 66 | } catch (final UnsupportedOperationException | IllegalArgumentException e) { 67 | throw new AssertionError(e); 68 | } catch (final IOException e) { 69 | // ClosedChannelException is an IOException 70 | throw new InfluentIOException("SocketChannel#setOption failed", e); 71 | } 72 | } 73 | 74 | /** 75 | * Creates a new {@code NioTcpPlaintextChannel}. 76 | * 77 | * @param channel the accepted {@code SocketChannel} 78 | * @param eventLoop the {@code NioEventLoop} 79 | * @param tcpConfig the {@code NioTcpConfig} 80 | * @throws InfluentIOException if some IO error occurs 81 | */ 82 | public static NioTcpPlaintextChannel open( 83 | final SocketChannel channel, final NioEventLoop eventLoop, final NioTcpConfig tcpConfig) { 84 | try { 85 | final SocketAddress remoteAddress = getRemoteAddress(channel); 86 | tcpConfig 87 | .getSendBufferSize() 88 | .ifPresent( 89 | (sendBufferSize) -> 90 | setOption(channel, StandardSocketOptions.SO_SNDBUF, sendBufferSize)); 91 | setOption(channel, StandardSocketOptions.SO_KEEPALIVE, tcpConfig.getKeepAliveEnabled()); 92 | setOption(channel, StandardSocketOptions.TCP_NODELAY, tcpConfig.getTcpNoDelayEnabled()); 93 | return new NioTcpPlaintextChannel(channel, eventLoop, remoteAddress); 94 | } catch (final Exception e) { 95 | closeChannel(channel, Exceptions.orNull(channel::getRemoteAddress)); 96 | throw e; 97 | } 98 | } 99 | 100 | /** {@inheritDoc} */ 101 | @Override 102 | public boolean write(final ByteBuffer src) { 103 | try { 104 | return channel.write(src) > 0; 105 | } catch (final NotYetConnectedException | AsynchronousCloseException e) { 106 | // ClosedByInterruptException is an AsynchronousCloseException 107 | throw new AssertionError(e); 108 | } catch (final IOException e) { 109 | // ClosedChannelException is an IOException 110 | close(); 111 | final String message = "This channel is broken. remote address = " + getRemoteAddress(); 112 | throw new InfluentIOException(message, e); 113 | } 114 | } 115 | 116 | /** {@inheritDoc} */ 117 | @Override 118 | public boolean read(final ByteBuffer dst) { 119 | try { 120 | final int readSize = channel.read(dst); 121 | if (readSize == -1) { 122 | close(); 123 | } 124 | return readSize > 0; 125 | } catch (final NotYetConnectedException | AsynchronousCloseException e) { 126 | // ClosedByInterruptException is an AsynchronousCloseException 127 | throw new AssertionError(e); 128 | } catch (final ClosedChannelException e) { 129 | close(); 130 | return false; 131 | } catch (final IOException e) { 132 | close(); 133 | final String message = "This channel is broken. remote address = " + getRemoteAddress(); 134 | throw new InfluentIOException(message, e); 135 | } 136 | } 137 | 138 | /** {@inheritDoc} */ 139 | @Override 140 | public void register(final Set ops, final NioAttachment attachment) { 141 | eventLoop.register(channel, key, Op.bits(ops), attachment); 142 | } 143 | 144 | /** {@inheritDoc} */ 145 | @Override 146 | public void enable(final Op op) { 147 | eventLoop.enableInterestSet(key, op.getBit()); 148 | } 149 | 150 | /** {@inheritDoc} */ 151 | @Override 152 | public void disable(final Op op) { 153 | eventLoop.disableInterestSet(key, op.getBit()); 154 | } 155 | 156 | /** {@inheritDoc} */ 157 | @Override 158 | public void close() { 159 | logger.debug("Closes the channel with {}.", remoteAddress); 160 | closeChannel(channel, getRemoteAddress()); 161 | } 162 | 163 | private static void closeChannel(final SocketChannel channel, final SocketAddress remoteAddress) { 164 | Exceptions.ignore(channel::close, "Failed closing the socket channel." + remoteAddress); 165 | } 166 | 167 | /** {@inheritDoc} */ 168 | @Override 169 | public boolean isOpen() { 170 | return Exceptions.orFalse(channel::isOpen); 171 | } 172 | 173 | /** {@inheritDoc} */ 174 | @Override 175 | public SocketAddress getRemoteAddress() { 176 | return remoteAddress; 177 | } 178 | 179 | /** {@inheritDoc} */ 180 | @Override 181 | public String toString() { 182 | return "NioTcpPlaintextChannel(" + getRemoteAddress() + ")"; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/nio/NioTlsEngine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio; 18 | 19 | import influent.exception.InfluentIOException; 20 | import java.nio.ByteBuffer; 21 | import java.nio.ReadOnlyBufferException; 22 | import javax.net.ssl.SSLEngine; 23 | import javax.net.ssl.SSLEngineResult; 24 | import javax.net.ssl.SSLEngineResult.HandshakeStatus; 25 | import javax.net.ssl.SSLException; 26 | 27 | /** A wrapped {@code SSLEngine}. */ 28 | public final class NioTlsEngine { 29 | private final SSLEngine engine; 30 | 31 | private NioTlsEngine(final SSLEngine engine) { 32 | this.engine = engine; 33 | } 34 | 35 | /** 36 | * Creates a {@code NioTlsEngine} with server mode. 37 | * 38 | * @param engine the {@code SSLEngine} 39 | * @return the {@code NioTlsEngine} 40 | */ 41 | public static NioTlsEngine createServerEngine(final SSLEngine engine) { 42 | if (engine.getUseClientMode()) { 43 | // TODO: configure in this class 44 | throw new AssertionError(); 45 | } 46 | return new NioTlsEngine(engine); 47 | } 48 | 49 | /** Returns the current handshake status. */ 50 | HandshakeStatus getHandshakeStatus() { 51 | return engine.getHandshakeStatus(); 52 | } 53 | 54 | /** 55 | * Encodes application data into network data. 56 | * 57 | * @param src application data 58 | * @param dst network data 59 | * @return the {@code SSLEngineResult} 60 | * @throws InfluentIOException when some operations about TLS fails 61 | * @throws ReadOnlyBufferException when the {@code dst} is a read-only buffer 62 | * @throws IllegalArgumentException when the {@code src} or the {@code dst} are {@code null} 63 | */ 64 | SSLEngineResult wrap(final ByteBuffer src, final ByteBuffer dst) { 65 | try { 66 | return engine.wrap(src, dst); 67 | } catch (final SSLException e) { 68 | throw new InfluentIOException("Illegal SSL/TLS processing was detected.", e); 69 | } catch (final IllegalStateException e) { 70 | throw new AssertionError(e); 71 | } 72 | } 73 | 74 | /** 75 | * Decodes network data into application data. 76 | * 77 | * @param src network data 78 | * @param dst application data 79 | * @return the {@code SSLEngineResult} 80 | * @throws InfluentIOException when some operations about TLS fails 81 | * @throws ReadOnlyBufferException when the {@code dst} is a read-only buffer 82 | * @throws IllegalArgumentException when the {@code src} or the {@code dst} are {@code null} 83 | */ 84 | SSLEngineResult unwrap(final ByteBuffer src, final ByteBuffer dst) { 85 | try { 86 | return engine.unwrap(src, dst); 87 | } catch (final SSLException e) { 88 | throw new InfluentIOException("Illegal SSL/TLS processing was detected.", e); 89 | } catch (final IllegalStateException e) { 90 | throw new AssertionError(e); 91 | } 92 | } 93 | 94 | /** Run all the delegated task. */ 95 | void completeDelegatedTasks() { 96 | while (true) { 97 | final Runnable task = engine.getDelegatedTask(); 98 | if (task == null) { 99 | break; 100 | } 101 | task.run(); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/util/Exceptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.util; 18 | 19 | import java.util.concurrent.Callable; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | /** 24 | * Utilities for exceptions. 25 | * 26 | *

This is expected to be used in only Influent project. 27 | */ 28 | public final class Exceptions { 29 | /** 30 | * A callable block. 31 | * 32 | *

This is the same as {@code Runnable} except that {@code Block#run} may throw some {@code 33 | * Exception}. 34 | */ 35 | @FunctionalInterface 36 | public interface Block { 37 | void run() throws Exception; 38 | } 39 | 40 | /** Boolean specialized callable. */ 41 | @FunctionalInterface 42 | public interface BooleanCallable { 43 | boolean call() throws Exception; 44 | } 45 | 46 | private static final Logger logger = LoggerFactory.getLogger(Exceptions.class); 47 | 48 | private Exceptions() { 49 | throw new AssertionError(); 50 | } 51 | 52 | /** 53 | * Executes the given processing and discards the error when some error occurs. 54 | * 55 | * @param f the processing 56 | * @param messageOnError the error message to be logged on an error 57 | */ 58 | public static void ignore(final Block f, final String messageOnError) { 59 | try { 60 | f.run(); 61 | } catch (final Exception e) { 62 | logger.error(messageOnError, e); 63 | } 64 | } 65 | 66 | /** 67 | * Executes the given processing and return null if the processing throws an exception. 68 | * 69 | * @param f the processing 70 | * @param the type of the {@code Callable} 71 | * @return the return value of {@code f} or null when {@code f} fails 72 | */ 73 | public static T orNull(final Callable f) { 74 | try { 75 | return f.call(); 76 | } catch (final Exception e) { 77 | return null; 78 | } 79 | } 80 | 81 | /** 82 | * Executes the given processing and return false if the processing throws an exception. 83 | * 84 | * @param f the processing 85 | * @return true if {@code f} returns true 86 | */ 87 | public static boolean orFalse(final BooleanCallable f) { 88 | try { 89 | return f.call(); 90 | } catch (final Exception e) { 91 | return false; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/util/Futures.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.util; 18 | 19 | import java.util.concurrent.CompletableFuture; 20 | 21 | /** 22 | * Utilities for {@code CompletableFuture}. 23 | * 24 | *

This is expected to be used in only Influent project. 25 | */ 26 | public final class Futures { 27 | private Futures() { 28 | throw new AssertionError(); 29 | } 30 | 31 | /** 32 | * Creates a new {@code CompletableFuture} that will be completed when the given original future 33 | * is completed. But the original future will not completed even when the new one is completed. 34 | * 35 | *

{@code CompletableFuture}'s completion is a mutable state and can be changed by anyone after 36 | * the future is passed. The owner of a future can pass that safely if using this function. 37 | * 38 | * @param original the original future 39 | * @param the type of {@code CompletableFuture}'s value 40 | * @return the future that depends on the original future but the original future does not depend 41 | * on 42 | */ 43 | public static CompletableFuture followerOf(final CompletableFuture original) { 44 | return original.thenApply(java.util.function.Function.identity()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/util/Inet4Network.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.util; 18 | 19 | import java.math.BigInteger; 20 | import java.net.Inet4Address; 21 | import java.net.InetAddress; 22 | import java.net.UnknownHostException; 23 | 24 | public class Inet4Network implements InetNetwork { 25 | private static final byte[] MAX = { 26 | (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 27 | }; 28 | private static final BigInteger MAX_VALUE = new BigInteger(1, MAX); 29 | private InetAddress network; 30 | private InetAddress netmask; 31 | 32 | public Inet4Network(InetAddress base, int netmask) { 33 | this.netmask = generateNetmask(netmask); 34 | this.network = InetNetwork.maskIP(base, this.netmask); 35 | } 36 | 37 | @Override 38 | public boolean contains(InetAddress address) { 39 | if (!(address instanceof Inet4Address)) { 40 | return false; 41 | } 42 | return network.equals(InetNetwork.maskIP(address, netmask)); 43 | } 44 | 45 | private InetAddress generateNetmask(int mask) { 46 | int bits = 32 - mask; 47 | // int netmask = 0xFFFFFFFF - ((1 << bits) - 1); 48 | BigInteger netmask = 49 | MAX_VALUE.subtract(BigInteger.ONE.shiftLeft(bits).subtract(BigInteger.ONE)); 50 | byte[] address = new byte[4]; 51 | for (int i = 0; i < 4; i++) { 52 | int b = 32 - 8 * (i + 1); 53 | address[i] = (netmask.shiftRight(b).and(BigInteger.valueOf(0xFF))).byteValue(); 54 | } 55 | 56 | try { 57 | return InetAddress.getByAddress(address); 58 | } catch (UnknownHostException e) { 59 | return null; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/util/Inet6Network.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.util; 18 | 19 | import java.math.BigInteger; 20 | import java.net.InetAddress; 21 | import java.net.UnknownHostException; 22 | 23 | public class Inet6Network implements InetNetwork { 24 | private static final byte[] MAX = { 25 | (byte) 0xff, 26 | (byte) 0xff, 27 | (byte) 0xff, 28 | (byte) 0xff, 29 | (byte) 0xff, 30 | (byte) 0xff, 31 | (byte) 0xff, 32 | (byte) 0xff, 33 | (byte) 0xff, 34 | (byte) 0xff, 35 | (byte) 0xff, 36 | (byte) 0xff, 37 | (byte) 0xff, 38 | (byte) 0xff, 39 | (byte) 0xff, 40 | (byte) 0xff 41 | }; 42 | private static final BigInteger MAX_VALUE = new BigInteger(1, MAX); 43 | private InetAddress network; 44 | private InetAddress netmask; 45 | 46 | public Inet6Network(InetAddress base, int netmask) { 47 | this.netmask = generateNetmask(netmask); 48 | this.network = InetNetwork.maskIP(base, this.netmask); 49 | } 50 | 51 | @Override 52 | public boolean contains(InetAddress address) { 53 | return network.equals(InetNetwork.maskIP(address, netmask)); 54 | } 55 | 56 | private InetAddress generateNetmask(int mask) { 57 | int bits = 128 - mask; 58 | // 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - (1 << bits) -1 59 | BigInteger netmask = 60 | MAX_VALUE.subtract(BigInteger.ONE.shiftLeft(bits).subtract(BigInteger.ONE)); 61 | byte[] address = new byte[16]; 62 | for (int i = 0; i < 16; i++) { 63 | int b = 128 - 8 * (i + 1); 64 | address[i] = (netmask.shiftRight(b).and(BigInteger.valueOf(0xFF))).byteValue(); 65 | } 66 | 67 | try { 68 | return InetAddress.getByAddress(address); 69 | } catch (UnknownHostException e) { 70 | return null; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/util/InetNetwork.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.util; 18 | 19 | import java.net.InetAddress; 20 | import java.net.UnknownHostException; 21 | 22 | public interface InetNetwork { 23 | static InetNetwork getBySpec(String spec) { 24 | String[] addressComponents = spec.split("/", 2); 25 | if (addressComponents.length != 2) { 26 | throw new IllegalArgumentException("Invalid network: " + spec); 27 | } 28 | try { 29 | int netmask = Integer.parseInt(addressComponents[1]); 30 | InetAddress base = InetAddress.getByName(addressComponents[0]); 31 | if (base.getAddress().length == 16) { 32 | return new Inet6Network(base, netmask); 33 | } else { 34 | return new Inet4Network(base, netmask); 35 | } 36 | } catch (NumberFormatException | UnknownHostException e) { 37 | throw new IllegalArgumentException("Invalid network: " + spec, e); 38 | } 39 | } 40 | 41 | static InetAddress maskIP(InetAddress ip, InetAddress netmask) { 42 | return maskIP(ip.getAddress(), netmask.getAddress()); 43 | } 44 | 45 | static InetAddress maskIP(byte[] ip, byte[] mask) { 46 | if (ip.length != mask.length) { 47 | throw new IllegalArgumentException("IP address and mask must be of the same length."); 48 | } 49 | byte[] masked = new byte[ip.length]; 50 | 51 | for (int i = 0; i < ip.length; i++) { 52 | masked[i] = (byte) (ip[i] & mask[i]); 53 | } 54 | 55 | try { 56 | return InetAddress.getByAddress(masked); 57 | } catch (UnknownHostException e) { 58 | return null; 59 | } 60 | } 61 | 62 | boolean contains(InetAddress address); 63 | } 64 | -------------------------------------------------------------------------------- /influent-transport/src/main/java/influent/internal/util/ThreadSafeQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.util; 18 | 19 | import java.util.concurrent.BlockingQueue; 20 | import java.util.concurrent.LinkedBlockingQueue; 21 | 22 | /** 23 | * A thread-safe queue. {@code ThreadSafeQueue} is designed for non-blocking applications, so its 24 | * APIs never block threads. 25 | * 26 | *

This is expected to be used in only Influent project. 27 | * 28 | *

{@code ThreadSafeQueue} is unconditionally thread-safe. 29 | * 30 | * @param the type of elements 31 | */ 32 | public final class ThreadSafeQueue { 33 | private final BlockingQueue queue = new LinkedBlockingQueue<>(); 34 | 35 | /** 36 | * Adds an element to this {@code ThreadSafeQueue}. 37 | * 38 | * @param element the element to add 39 | * @return {@code true} if the element was added to this queue, else {@code false} 40 | * @throws ClassCastException if the class of the specified element prevents it from being added 41 | * to this queue 42 | * @throws NullPointerException if the specified element is null 43 | * @throws IllegalArgumentException if some property of the specified element prevents it from 44 | * being added to this queue 45 | */ 46 | public boolean enqueue(final E element) { 47 | return queue.offer(element); 48 | } 49 | 50 | /** 51 | * Removes the head element from this {@code ThreadSafeQueue}. 52 | * 53 | * @return the head element if this queue is non-empty, otherwise {@code null} 54 | */ 55 | public E dequeue() { 56 | return queue.poll(); 57 | } 58 | 59 | /** 60 | * Peeks the head element. 61 | * 62 | * @return the head element if this queue is non-empty, otherwise {@code null} 63 | */ 64 | public E peek() { 65 | return queue.peek(); 66 | } 67 | 68 | /** 69 | * Tests that this {@code ThreadSafeQueue} is empty. 70 | * 71 | * @return true if this {@code ThreadSafeQueue} is empty 72 | */ 73 | public boolean isEmpty() { 74 | return queue.isEmpty(); 75 | } 76 | 77 | /** 78 | * Tests that this {@code ThreadSafeQueue} is non-empty. 79 | * 80 | * @return true if this {@code ThreadSafeQueue} is non-empty 81 | */ 82 | public boolean nonEmpty() { 83 | return !queue.isEmpty(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /influent-transport/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/nio/NioEventLoopSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio 18 | 19 | import java.io.IOException 20 | import java.nio.channels.{SelectableChannel, SelectionKey, Selector} 21 | import java.util 22 | 23 | import org.mockito.Mockito._ 24 | import org.scalatest.WordSpec 25 | import org.scalatest.mockito.MockitoSugar 26 | 27 | class NioEventLoopSpec extends WordSpec with MockitoSugar { 28 | private[this] def withEventLoop(loop: NioEventLoop)(f: NioEventLoop => Unit): Unit = { 29 | val thread = new Thread(loop) 30 | thread.setUncaughtExceptionHandler((_: Thread, e: Throwable) => e.printStackTrace()) 31 | thread.start() 32 | f(loop) 33 | loop.shutdown().get() 34 | } 35 | 36 | "run" should { 37 | "continue selecting and clean up on its complete" in { 38 | val selector = mock[Selector] 39 | when(selector.select()).thenReturn(0).thenThrow(new IOException) 40 | 41 | val attachment1 = mock[NioAttachment] 42 | val key1 = mock[SelectionKey] 43 | when(key1.attachment()).thenReturn(attachment1, Nil: _*) 44 | val attachment2 = mock[NioAttachment] 45 | val key2 = mock[SelectionKey] 46 | when(key2.attachment()).thenReturn(attachment2, Nil: _*) 47 | val keys = new util.LinkedHashSet[SelectionKey]() 48 | keys.add(key1) 49 | keys.add(key2) 50 | 51 | when(selector.keys()).thenReturn(keys) 52 | val loop = new NioEventLoop(selector) 53 | new Thread(loop).start() 54 | Thread.sleep(100) 55 | loop.shutdown().get() 56 | 57 | verify(selector, atLeastOnce()).select() 58 | verify(selector).keys() 59 | verify(attachment1).close() 60 | verify(attachment2).close() 61 | verify(selector).close() 62 | } 63 | 64 | "fail with IllegalStateException" when { 65 | "it has already started" in { 66 | val loop = NioEventLoop.open() 67 | new Thread(loop).start() 68 | // Prevent `loop.run()` from overtaking the above thread 69 | Thread.sleep(1000) 70 | 71 | assertThrows[IllegalStateException](loop.run()) 72 | loop.shutdown().get() 73 | } 74 | } 75 | } 76 | 77 | "register" should { 78 | "add a Register task" in { 79 | val selector = Selector.open() 80 | withEventLoop(new NioEventLoop(selector)) { loop => 81 | val ops = SelectionKey.OP_READ | SelectionKey.OP_WRITE 82 | val attachment = new NioAttachment { 83 | override protected def close(): Unit = () 84 | } 85 | val javaKey = mock[SelectionKey] 86 | val channel = mock[SelectableChannel] 87 | when(channel.configureBlocking(false)).thenReturn(channel) 88 | when(channel.register(selector, ops, attachment)).thenReturn(javaKey) 89 | val key = mock[NioSelectionKey] 90 | assert(loop.register(channel, key, ops, attachment) === ()) 91 | 92 | Thread.sleep(1000) 93 | verify(channel).configureBlocking(false) 94 | verify(channel).register(selector, ops, attachment) 95 | verify(key).bind(javaKey) 96 | } 97 | } 98 | } 99 | 100 | "enableInterestSet" should { 101 | "add an UpdateInterestSet task" in { 102 | withEventLoop(NioEventLoop.open()) { loop => 103 | val javaKey = mock[SelectionKey] 104 | when(javaKey.interestOps()).thenReturn(SelectionKey.OP_READ) 105 | val key = NioSelectionKey.create() 106 | key.bind(javaKey) 107 | 108 | assert(loop.enableInterestSet(key, SelectionKey.OP_WRITE) === ()) 109 | 110 | Thread.sleep(1000) 111 | verify(javaKey).interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE) 112 | } 113 | } 114 | } 115 | 116 | "disableInterestSet" should { 117 | "add an UpdateInterestSet task" in { 118 | withEventLoop(NioEventLoop.open()) { loop => 119 | val javaKey = mock[SelectionKey] 120 | when(javaKey.interestOps()).thenReturn(SelectionKey.OP_READ | SelectionKey.OP_WRITE) 121 | val key = NioSelectionKey.create() 122 | key.bind(javaKey) 123 | 124 | assert(loop.disableInterestSet(key, SelectionKey.OP_WRITE) === ()) 125 | 126 | Thread.sleep(1000) 127 | verify(javaKey).interestOps(SelectionKey.OP_READ) 128 | } 129 | } 130 | } 131 | 132 | "shutdown" should { 133 | "clean attachments up and complete the Future" in { 134 | val selector = Selector.open() 135 | val loop = new NioEventLoop(selector) 136 | new Thread(loop).start() 137 | 138 | Thread.sleep(1000) 139 | val actual = loop.shutdown() 140 | actual.get() 141 | assert(!selector.isOpen) 142 | } 143 | 144 | "do nothing" when { 145 | "the event loop is not active" in { 146 | val loop = NioEventLoop.open() 147 | new Thread(loop).start() 148 | 149 | Thread.sleep(1000) 150 | loop.shutdown().get() 151 | loop.shutdown().get() 152 | } 153 | } 154 | 155 | "fail with IllegalStateException" when { 156 | "the event loop has not been started yet" in { 157 | val loop = NioEventLoop.open() 158 | assertThrows[IllegalStateException](loop.shutdown()) 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/nio/NioRoundRobinEventLoopPoolSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio 18 | 19 | import java.util.concurrent.CompletableFuture 20 | import org.mockito.Mockito._ 21 | import org.scalatest.WordSpec 22 | import org.scalatest.mockito.MockitoSugar 23 | 24 | class NioRoundRobinEventLoopPoolSpec extends WordSpec with MockitoSugar { 25 | "next" should { 26 | "return an event loop in round-robin" in { 27 | val eventLoops = (0 until 5).map { _ => mock[NioEventLoop] } 28 | val pool = new NioRoundRobinEventLoopPool(eventLoops.toArray) 29 | val actual = (0 until 5).map { _ => 30 | pool.next() 31 | } 32 | assert(actual.toSet.size === 5) 33 | assert(actual.toSet === eventLoops.toSet) 34 | } 35 | } 36 | 37 | "shutdown" should { 38 | "shutdown all the event loop" in { 39 | val eventLoops = (0 until 5).map { _ => mock[NioEventLoop] } 40 | val futures = eventLoops.map { eventLoop => 41 | val future = new CompletableFuture[Void]() 42 | when(eventLoop.shutdown()).thenReturn(future) 43 | future 44 | } 45 | val pool = new NioRoundRobinEventLoopPool(eventLoops.toArray) 46 | val actual = pool.shutdown() 47 | eventLoops.foreach { eventLoop => 48 | verify(eventLoop).shutdown() 49 | } 50 | 51 | assert(!actual.isDone) 52 | futures.head.complete(null) 53 | assert(!actual.isDone) 54 | futures.tail.foreach(_.complete(null)) 55 | assert(actual.isDone) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/nio/NioSelectionKeySpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio 18 | 19 | import java.nio.channels.SelectionKey 20 | 21 | import org.scalatest.WordSpec 22 | import org.scalatest.mockito.MockitoSugar 23 | 24 | class NioSelectionKeySpec extends WordSpec with MockitoSugar { 25 | "bind" should { 26 | "set the underlying SelectionKey" when { 27 | "the NioSelectionKey is not yet bound" in { 28 | val javaKey = mock[SelectionKey] 29 | val key = NioSelectionKey.create() 30 | assert(key.bind(javaKey) === ()) 31 | assert(key.unwrap() === javaKey) 32 | } 33 | } 34 | 35 | "fail" when { 36 | "the NioSelectionKey is already bound" in { 37 | val javaKey1 = mock[SelectionKey] 38 | val javaKey2 = mock[SelectionKey] 39 | val key = NioSelectionKey.create() 40 | 41 | assert(key.bind(javaKey1) === ()) 42 | assert(key.unwrap() === javaKey1) 43 | 44 | assertThrows[IllegalStateException] { 45 | key.bind(javaKey2) 46 | } 47 | assert(key.unwrap() === javaKey1) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/nio/NioServerSocketChannelSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio 18 | 19 | import java.io.IOException 20 | import java.net.InetSocketAddress 21 | import java.nio.channels.{ClosedChannelException, SelectionKey, ServerSocketChannel, SocketChannel} 22 | 23 | import influent.exception.InfluentIOException 24 | import org.mockito.Mockito._ 25 | import org.scalatest.WordSpec 26 | import org.scalatest.mockito.MockitoSugar 27 | import org.scalatest.prop.GeneratorDrivenPropertyChecks 28 | 29 | class NioServerSocketChannelSpec 30 | extends WordSpec with GeneratorDrivenPropertyChecks with MockitoSugar { 31 | private[this] val localAddress = new InetSocketAddress("127.0.0.1", 24224) 32 | 33 | "accept" should { 34 | "invoke ServerSocketChannel#accept" in { 35 | val underlying = mock[ServerSocketChannel] 36 | val socketChannel = mock[SocketChannel] 37 | when(underlying.accept()).thenReturn(socketChannel) 38 | val channel = new NioServerSocketChannel(underlying, localAddress) 39 | val actual = channel.accept() 40 | assert(actual === socketChannel) 41 | verify(underlying).accept() 42 | } 43 | 44 | "fail with InfluentIOException" when { 45 | "some IO error occurs" in { 46 | val errors = Seq(new ClosedChannelException, new IOException) 47 | errors.foreach { error => 48 | val underlying = mock[ServerSocketChannel] 49 | when(underlying.accept()).thenThrow(error) 50 | val channel = new NioServerSocketChannel(underlying, localAddress) 51 | assertThrows[InfluentIOException] { 52 | channel.accept() 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | "close" should { 60 | "invoke ServerSocketChannel#close" in { 61 | val underlying = mock[ServerSocketChannel] 62 | val channel = new NioServerSocketChannel(underlying, localAddress) 63 | assert(channel.close() === ()) 64 | verify(underlying).close() 65 | } 66 | 67 | "not fail" when { 68 | "ServerSocketChannel#close fails" in { 69 | val underlying = mock[ServerSocketChannel] 70 | when(underlying.close()).thenThrow(new IOException()) 71 | val channel = new NioServerSocketChannel(underlying, localAddress) 72 | assert(channel.close() === ()) 73 | verify(underlying).close() 74 | } 75 | } 76 | } 77 | 78 | "register" should { 79 | "invoke NioEventLoop#register" in { 80 | val underlying = mock[ServerSocketChannel] 81 | val channel = new NioServerSocketChannel(underlying, localAddress) 82 | val eventLoop = mock[NioEventLoop] 83 | val attachment = mock[NioAttachment] 84 | assert(channel.register(eventLoop, attachment) === ()) 85 | 86 | verify(eventLoop).register(underlying, channel.key, SelectionKey.OP_ACCEPT, attachment) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/nio/NioSingleThreadEventLoopPoolSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio 18 | 19 | import org.mockito.Mockito._ 20 | import org.scalatest.WordSpec 21 | import org.scalatest.mockito.MockitoSugar 22 | 23 | class NioSingleThreadEventLoopPoolSpec extends WordSpec with MockitoSugar { 24 | "shutdown" should { 25 | "shutdown the running event loop" in { 26 | val eventLoop = mock[NioEventLoop] 27 | val pool = new NioSingleThreadEventLoopPool(eventLoop) 28 | pool.shutdown() 29 | verify(eventLoop).shutdown() 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/nio/NioTcpAcceptorSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio 18 | 19 | import java.nio.channels.SocketChannel 20 | import java.util.function.Consumer 21 | 22 | import influent.exception.InfluentIOException 23 | import org.mockito.Mockito._ 24 | import org.scalatest.WordSpec 25 | import org.scalatest.mockito.MockitoSugar 26 | 27 | class NioTcpAcceptorSpec extends WordSpec with MockitoSugar { 28 | private[this] val nopCallback = new Consumer[SocketChannel] { 29 | override def accept(t: SocketChannel): Unit = () 30 | } 31 | 32 | "onAcceptable" should { 33 | "accept a new connection" in { 34 | val serverSocketChannel = mock[NioServerSocketChannel] 35 | val channel1 = mock[SocketChannel] 36 | val channel2 = mock[SocketChannel] 37 | 38 | when(serverSocketChannel.accept()).thenReturn(channel1, channel2, null) 39 | val callback = mock[Consumer[SocketChannel]] 40 | 41 | val acceptor = new NioTcpAcceptor(serverSocketChannel, callback) 42 | assert(acceptor.onAcceptable() === ()) 43 | 44 | verify(serverSocketChannel, times(3)).accept() 45 | verify(callback).accept(channel1) 46 | verify(callback).accept(channel2) 47 | verifyNoMoreInteractions(callback) 48 | } 49 | 50 | "not fail" when { 51 | "it fails accepting" in { 52 | val serverSocketChannel = mock[NioServerSocketChannel] 53 | val channel = mock[SocketChannel] 54 | when(serverSocketChannel.accept()) 55 | .thenThrow(new InfluentIOException()) 56 | .thenReturn(channel, null) 57 | val callback = mock[Consumer[SocketChannel]] 58 | 59 | val acceptor = new NioTcpAcceptor(serverSocketChannel, callback) 60 | assert(acceptor.onAcceptable() === ()) 61 | 62 | verify(serverSocketChannel, times(3)).accept() 63 | verify(callback).accept(channel) 64 | verifyNoMoreInteractions(callback) 65 | } 66 | 67 | "the callback function fails" in { 68 | val serverSocketChannel = mock[NioServerSocketChannel] 69 | val channel1 = mock[SocketChannel] 70 | val channel2 = mock[SocketChannel] 71 | when(serverSocketChannel.accept()).thenReturn(channel1, channel2, null) 72 | val callback = mock[Consumer[SocketChannel]] 73 | when(callback.accept(channel1)).thenThrow(new RuntimeException) 74 | 75 | val acceptor = new NioTcpAcceptor(serverSocketChannel, callback) 76 | assert(acceptor.onAcceptable() === ()) 77 | 78 | verify(serverSocketChannel, times(3)).accept() 79 | verify(callback).accept(channel1) 80 | verify(callback).accept(channel2) 81 | verifyNoMoreInteractions(callback) 82 | } 83 | } 84 | } 85 | 86 | "close" should { 87 | "close the server socket channel" in { 88 | val serverSocketChannel = mock[NioServerSocketChannel] 89 | val acceptor = new NioTcpAcceptor(serverSocketChannel, nopCallback) 90 | assert(acceptor.close() === ()) 91 | verify(serverSocketChannel).close() 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/nio/NioTcpChannelSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio 18 | 19 | import java.nio.channels.SelectionKey 20 | import java.util 21 | 22 | import influent.internal.nio.NioTcpChannel.Op 23 | import org.scalatest.WordSpec 24 | 25 | class NioTcpChannelSpec extends WordSpec { 26 | "Op.bits" should { 27 | "compute the union of the given operations" in { 28 | assert(Op.bits(util.EnumSet.of(Op.READ)) === SelectionKey.OP_READ) 29 | assert(Op.bits(util.EnumSet.of(Op.WRITE)) === SelectionKey.OP_WRITE) 30 | assert(Op.bits(util.EnumSet.of(Op.READ, Op.WRITE)) === 31 | (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) 32 | assert(Op.bits(util.EnumSet.of(Op.WRITE, Op.READ)) === 33 | (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/nio/NioTcpConfigSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio 18 | 19 | import influent.internal.nio.NioTcpConfig.Builder 20 | import java.util.OptionalInt 21 | import org.scalacheck.{Arbitrary, Gen} 22 | import org.scalatest.WordSpec 23 | import org.scalatest.prop.GeneratorDrivenPropertyChecks 24 | 25 | class NioTcpConfigSpec extends WordSpec with GeneratorDrivenPropertyChecks { 26 | "Builder" should { 27 | "return the default configuration" when { 28 | "there is no mutation" in { 29 | val actual = new Builder().build() 30 | val expected = new NioTcpConfig(0, 0, 0, true, true) 31 | assert(actual === expected) 32 | } 33 | 34 | "the default values are given" in { 35 | val actual = new Builder() 36 | .backlog(0) 37 | .sendBufferSize(0) 38 | .receiveBufferSize(0) 39 | .keepAliveEnabled(true) 40 | .tcpNoDelayEnabled(true) 41 | .build() 42 | val expected = new NioTcpConfig(0, 0, 0, true, true) 43 | assert(actual === expected) 44 | } 45 | } 46 | 47 | "configure the given values" in { 48 | val gen: Gen[(Int, Int, Int, Boolean, Boolean)] = { 49 | for { 50 | backlog <- Gen.chooseNum(0, Int.MaxValue) 51 | sendBufferSize <- Gen.chooseNum(0, Int.MaxValue) 52 | receiveBufferSize <- Gen.chooseNum(0, Int.MaxValue) 53 | keepAliveEnabled <- Arbitrary.arbBool.arbitrary 54 | tcpNoDelayEnabled <- Arbitrary.arbBool.arbitrary 55 | } yield (backlog, sendBufferSize, receiveBufferSize, keepAliveEnabled, tcpNoDelayEnabled) 56 | } 57 | 58 | forAll(gen) { 59 | case (backlog, sendBufferSize, receiveBufferSize, keepAliveEnabled, tcpNoDelayEnabled) => 60 | val actual = new Builder() 61 | .backlog(backlog) 62 | .sendBufferSize(sendBufferSize) 63 | .receiveBufferSize(receiveBufferSize) 64 | .keepAliveEnabled(keepAliveEnabled) 65 | .tcpNoDelayEnabled(tcpNoDelayEnabled) 66 | .build() 67 | val expected = new NioTcpConfig( 68 | backlog, 69 | sendBufferSize, 70 | receiveBufferSize, 71 | keepAliveEnabled, 72 | tcpNoDelayEnabled 73 | ) 74 | assert(actual === expected) 75 | } 76 | } 77 | 78 | "fail" when { 79 | "the given backlog is illegal" in { 80 | forAll(Gen.negNum[Int]) { value => 81 | assertThrows[IllegalArgumentException] { 82 | new Builder().backlog(value) 83 | } 84 | } 85 | } 86 | 87 | "the given sendBufferSize is illegal" in { 88 | forAll(Gen.negNum[Int]) { value => 89 | assertThrows[IllegalArgumentException] { 90 | new Builder().sendBufferSize(value) 91 | } 92 | } 93 | } 94 | 95 | "the given receiveBufferSize is illegal" in { 96 | forAll(Gen.negNum[Int]) { value => 97 | assertThrows[IllegalArgumentException] { 98 | new Builder().receiveBufferSize(value) 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | "getBacklog" should { 106 | "return the value" when { 107 | "the value > 0" in { 108 | forAll(Gen.posNum[Int]) { value => 109 | val actual = new Builder().backlog(value).build() 110 | assert(actual.getBacklog === OptionalInt.of(value)) 111 | } 112 | } 113 | } 114 | 115 | "return Optional.empty()" when { 116 | "the value == 0" in { 117 | val actual = new Builder().backlog(0).build() 118 | assert(actual.getBacklog === OptionalInt.empty()) 119 | } 120 | } 121 | } 122 | 123 | "getSendBufferSize" should { 124 | "return the value" when { 125 | "the value > 0" in { 126 | forAll(Gen.posNum[Int]) { value => 127 | val actual = new Builder().sendBufferSize(value).build() 128 | assert(actual.getSendBufferSize === OptionalInt.of(value)) 129 | } 130 | } 131 | } 132 | 133 | "return Optional.empty()" when { 134 | "the value == 0" in { 135 | val actual = new Builder().sendBufferSize(0).build() 136 | assert(actual.getSendBufferSize === OptionalInt.empty()) 137 | } 138 | } 139 | } 140 | 141 | "getReceiveBufferSize" should { 142 | "return the value" when { 143 | "the value > 0" in { 144 | forAll(Gen.posNum[Int]) { value => 145 | val actual = new Builder().receiveBufferSize(value).build() 146 | assert(actual.getReceiveBufferSize === OptionalInt.of(value)) 147 | } 148 | } 149 | } 150 | 151 | "return Optional.empty()" when { 152 | "the value == 0" in { 153 | val actual = new Builder().receiveBufferSize(0).build() 154 | assert(actual.getReceiveBufferSize === OptionalInt.empty()) 155 | } 156 | } 157 | } 158 | 159 | "getKeepAliveEnabled" should { 160 | "return the value" in { 161 | forAll { value: Boolean => 162 | val actual = new Builder().keepAliveEnabled(value).build() 163 | assert(actual.getKeepAliveEnabled === value) 164 | } 165 | } 166 | } 167 | 168 | "getTcpNoDelayEnabled" should { 169 | "return the value" in { 170 | forAll { value: Boolean => 171 | val actual = new Builder().tcpNoDelayEnabled(value).build() 172 | assert(actual.getTcpNoDelayEnabled === value) 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/nio/NioTcpPlaintextChannelSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.nio 18 | 19 | import java.io.IOException 20 | import java.net.InetSocketAddress 21 | import java.nio.ByteBuffer 22 | import java.nio.channels.{ClosedChannelException, SelectionKey, SocketChannel} 23 | import java.util 24 | 25 | import influent.exception.InfluentIOException 26 | import influent.internal.nio.NioTcpChannel.Op 27 | import org.mockito.Mockito._ 28 | import org.scalatest.WordSpec 29 | import org.scalatest.mockito.MockitoSugar 30 | 31 | class NioTcpPlaintextChannelSpec extends WordSpec with MockitoSugar { 32 | private[this] val remoteAddress = new InetSocketAddress("127.0.0.1", 24224) 33 | 34 | "write" should { 35 | "write bytes to the channel" in { 36 | val src = ByteBuffer.allocate(8) 37 | val socketChannel = mock[SocketChannel] 38 | when(socketChannel.write(src)).thenReturn(4) 39 | val channel = new NioTcpPlaintextChannel(socketChannel, mock[NioEventLoop], remoteAddress) 40 | 41 | assert(channel.write(src) === true) 42 | verify(socketChannel).write(src) 43 | verify(socketChannel, never()).close() 44 | } 45 | 46 | "fail with InfluentIOException" when { 47 | "it fails writing" in { 48 | val src = ByteBuffer.allocate(8) 49 | val socketChannel = mock[SocketChannel] 50 | when(socketChannel.write(src)).thenThrow(new IOException()) 51 | val channel = new NioTcpPlaintextChannel(socketChannel, mock[NioEventLoop], remoteAddress) 52 | 53 | assertThrows[InfluentIOException](channel.write(src)) 54 | verify(socketChannel).write(src) 55 | verify(socketChannel).close() 56 | } 57 | } 58 | } 59 | 60 | "read" should { 61 | "read bytes from the channel" in { 62 | val dst = ByteBuffer.allocate(8) 63 | val socketChannel = mock[SocketChannel] 64 | when(socketChannel.read(dst)).thenReturn(4) 65 | val channel = new NioTcpPlaintextChannel(socketChannel, mock[NioEventLoop], remoteAddress) 66 | 67 | assert(channel.read(dst) === true) 68 | verify(socketChannel, never()).close() 69 | } 70 | 71 | "do nothing" when { 72 | "the socket buffer is empty" in { 73 | val dst = ByteBuffer.allocate(8) 74 | val socketChannel = mock[SocketChannel] 75 | when(socketChannel.read(dst)).thenReturn(0) 76 | val channel = new NioTcpPlaintextChannel(socketChannel, mock[NioEventLoop], remoteAddress) 77 | 78 | assert(channel.read(dst) === false) 79 | verify(socketChannel, never()).close() 80 | } 81 | } 82 | 83 | "close channel" when { 84 | "the channel returns -1" in { 85 | val dst = ByteBuffer.allocate(8) 86 | val socketChannel = mock[SocketChannel] 87 | when(socketChannel.read(dst)).thenReturn(-1) 88 | val channel = new NioTcpPlaintextChannel(socketChannel, mock[NioEventLoop], remoteAddress) 89 | 90 | assert(channel.read(dst) === false) 91 | verify(socketChannel).close() 92 | } 93 | 94 | "the stream is completed" in { 95 | val dst = ByteBuffer.allocate(8) 96 | val socketChannel = mock[SocketChannel] 97 | when(socketChannel.read(dst)).thenThrow(new ClosedChannelException()) 98 | val channel = new NioTcpPlaintextChannel(socketChannel, mock[NioEventLoop], remoteAddress) 99 | 100 | assert(channel.read(dst) === false) 101 | verify(socketChannel).close() 102 | } 103 | } 104 | 105 | "fail with InfluentIOException" when { 106 | "it fails reading" in { 107 | val dst = ByteBuffer.allocate(8) 108 | val socketChannel = mock[SocketChannel] 109 | when(socketChannel.read(dst)).thenThrow(new IOException()) 110 | val channel = new NioTcpPlaintextChannel(socketChannel, mock[NioEventLoop], remoteAddress) 111 | 112 | assertThrows[InfluentIOException](channel.read(dst)) 113 | verify(socketChannel).close() 114 | } 115 | } 116 | } 117 | 118 | "register" should { 119 | "registers this channel to the event loop" in { 120 | val socketChannel = mock[SocketChannel] 121 | val eventLoop = mock[NioEventLoop] 122 | val channel = new NioTcpPlaintextChannel(socketChannel, eventLoop, remoteAddress) 123 | 124 | val attachment = mock[NioAttachment] 125 | 126 | assert(channel.register(util.EnumSet.of(Op.READ), attachment) === ()) 127 | verify(eventLoop).register(socketChannel, channel.key, SelectionKey.OP_READ, attachment) 128 | 129 | assert(channel.register(util.EnumSet.of(Op.WRITE), attachment) === ()) 130 | verify(eventLoop).register(socketChannel, channel.key, SelectionKey.OP_WRITE, attachment) 131 | 132 | assert(channel.register(util.EnumSet.of(Op.READ, Op.WRITE), attachment) === ()) 133 | verify(eventLoop).register(socketChannel, channel.key, SelectionKey.OP_READ | SelectionKey.OP_WRITE, attachment) 134 | } 135 | } 136 | 137 | "enable" should { 138 | "enable the given operation" in { 139 | val eventLoop = mock[NioEventLoop] 140 | val channel = new NioTcpPlaintextChannel(mock[SocketChannel], eventLoop, remoteAddress) 141 | 142 | assert(channel.enable(Op.READ) === ()) 143 | verify(eventLoop).enableInterestSet(channel.key, SelectionKey.OP_READ) 144 | 145 | assert(channel.enable(Op.WRITE) === ()) 146 | verify(eventLoop).enableInterestSet(channel.key, SelectionKey.OP_WRITE) 147 | } 148 | } 149 | 150 | "disable" should { 151 | "enable the given operation" in { 152 | val eventLoop = mock[NioEventLoop] 153 | val channel = new NioTcpPlaintextChannel(mock[SocketChannel], eventLoop, remoteAddress) 154 | 155 | assert(channel.disable(Op.READ) === ()) 156 | verify(eventLoop).disableInterestSet(channel.key, SelectionKey.OP_READ) 157 | 158 | assert(channel.disable(Op.WRITE) === ()) 159 | verify(eventLoop).disableInterestSet(channel.key, SelectionKey.OP_WRITE) 160 | } 161 | } 162 | 163 | "close" should { 164 | "close the socket channel" in { 165 | val socketChannel = mock[SocketChannel] 166 | val channel = new NioTcpPlaintextChannel(socketChannel, mock[NioEventLoop], remoteAddress) 167 | assert(channel.close() === ()) 168 | verify(socketChannel).close() 169 | } 170 | 171 | "not fail" when { 172 | "it fails closing the socket channel" in { 173 | val socketChannel = mock[SocketChannel] 174 | when(socketChannel.close()).thenThrow(new IOException) 175 | val channel = new NioTcpPlaintextChannel(socketChannel, mock[NioEventLoop], remoteAddress) 176 | assert(channel.close() === ()) 177 | verify(socketChannel).close() 178 | } 179 | } 180 | } 181 | 182 | "isOpen" should { 183 | "return true" when { 184 | "the channel is open" in { 185 | val socketChannel = mock[SocketChannel] 186 | when(socketChannel.isOpen).thenReturn(true) 187 | val channel = new NioTcpPlaintextChannel(socketChannel, mock[NioEventLoop], remoteAddress) 188 | assert(channel.isOpen) 189 | } 190 | } 191 | 192 | "return false" when { 193 | "the channel is closed" in { 194 | val socketChannel = mock[SocketChannel] 195 | when(socketChannel.isOpen).thenReturn(false) 196 | val channel = new NioTcpPlaintextChannel(socketChannel, mock[NioEventLoop], remoteAddress) 197 | assert(!channel.isOpen) 198 | } 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/util/FuturesSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.util 18 | 19 | import java.util.concurrent.CompletableFuture 20 | import org.scalatest.Matchers._ 21 | import org.scalatest.WordSpec 22 | 23 | class FuturesSpec extends WordSpec { 24 | private[this] def assertNotCompleted(future: CompletableFuture[Int]): Unit = { 25 | assert(!future.isDone) 26 | assert(!future.isCompletedExceptionally) 27 | assert(!future.isCancelled) 28 | } 29 | private[this] def assertSuccessful(future: CompletableFuture[Int], value: Int): Unit = { 30 | assert(future.isDone) 31 | assert(!future.isCompletedExceptionally) 32 | assert(!future.isCancelled) 33 | assert(future.get() === value) 34 | } 35 | private[this] def assertFailure(future: CompletableFuture[Int]): Unit = { 36 | assert(future.isDone) 37 | assert(future.isCompletedExceptionally) 38 | assert(!future.isCancelled) 39 | } 40 | private[this] def assertCancelled(future: CompletableFuture[Int]): Unit = { 41 | assert(future.isDone) 42 | assert(future.isCompletedExceptionally) 43 | assert(future.isCancelled) 44 | } 45 | 46 | "followerOf" should { 47 | "return a future that is completed" when { 48 | "the given future is succeeded" in { 49 | val original = new CompletableFuture[Int]() 50 | val follower = Futures.followerOf(original) 51 | assertNotCompleted(original) 52 | assertNotCompleted(follower) 53 | 54 | original.complete(1) 55 | assertSuccessful(original, 1) 56 | assertSuccessful(follower, 1) 57 | } 58 | 59 | "the given future is failed" in { 60 | val original = new CompletableFuture[Int]() 61 | val follower = Futures.followerOf(original) 62 | assertNotCompleted(original) 63 | assertNotCompleted(follower) 64 | 65 | original.completeExceptionally(new RuntimeException) 66 | assertFailure(original) 67 | assertFailure(follower) 68 | } 69 | 70 | "the given future is cancelled" in { 71 | val original = new CompletableFuture[Int]() 72 | val follower = Futures.followerOf(original) 73 | assertNotCompleted(original) 74 | assertNotCompleted(follower) 75 | 76 | original.cancel(true) 77 | assertCancelled(original) 78 | // Cancellations are not propagated 79 | assertFailure(follower) 80 | } 81 | } 82 | 83 | "not complete the given future" when { 84 | "the returned future is completed" in { 85 | val original = new CompletableFuture[Int]() 86 | val follower = Futures.followerOf(original) 87 | assertNotCompleted(original) 88 | assertNotCompleted(follower) 89 | 90 | follower.complete(1) 91 | assertNotCompleted(original) 92 | assertSuccessful(follower, 1) 93 | 94 | original.complete(2) 95 | assertSuccessful(original, 2) 96 | assertSuccessful(follower, 1) 97 | } 98 | 99 | "the returned future is completed exceptionally" in { 100 | val original = new CompletableFuture[Int]() 101 | val follower = Futures.followerOf(original) 102 | assertNotCompleted(original) 103 | assertNotCompleted(follower) 104 | 105 | follower.completeExceptionally(new RuntimeException) 106 | assertNotCompleted(original) 107 | assertFailure(follower) 108 | 109 | original.complete(1) 110 | assertSuccessful(original, 1) 111 | assertFailure(follower) 112 | } 113 | 114 | "the returned future is cancelled" in { 115 | val original = new CompletableFuture[Int]() 116 | val follower = Futures.followerOf(original) 117 | assertNotCompleted(original) 118 | assertNotCompleted(follower) 119 | 120 | follower.cancel(true) 121 | assertNotCompleted(original) 122 | assertCancelled(follower) 123 | 124 | original.complete(1) 125 | assertSuccessful(original, 1) 126 | assertCancelled(follower) 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/util/Inet4NetworkSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.util 18 | 19 | import java.net.InetAddress 20 | 21 | import org.scalatest.Matchers._ 22 | import org.scalatest.prop.TableDrivenPropertyChecks._ 23 | import org.scalatest.WordSpec 24 | 25 | class Inet4NetworkSpec extends WordSpec { 26 | "contains" should { 27 | val data1 = Table( 28 | ("address", "isContained"), 29 | ("192.168.1.1", true), 30 | ("192.168.1.254", true), 31 | ("192.168.2.1", false) 32 | ) 33 | forAll(data1) { (address, isContained) => 34 | s"return $isContained" when { 35 | s"$address is given" in { 36 | val network = InetNetwork.getBySpec("192.168.1.0/24") 37 | assert(network.contains(InetAddress.getByName(address)) === isContained) 38 | } 39 | } 40 | } 41 | val data2 = Table( 42 | ("address", "isContained"), 43 | ("10.1.10.1", true), 44 | ("10.1.20.1", true), 45 | ("10.1.31.254", true), 46 | ("10.1.40.1", false), 47 | ("10.1.50.1", false) 48 | ) 49 | forAll(data2) { (address, isContained) => 50 | s"return $isContained" when { 51 | s"$address is given" in { 52 | val network = InetNetwork.getBySpec("10.1.0.0/19") 53 | assert(network.contains(InetAddress.getByName(address)) === isContained) 54 | } 55 | } 56 | } 57 | 58 | "return false" when { 59 | "IPv6 address is given" in { 60 | val network = InetNetwork.getBySpec("10.1.0.0/19") 61 | assert(network.contains(InetAddress.getByName("::10.1.10.1")) === false) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/util/Inet6NetworkSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.util 18 | 19 | import java.net.InetAddress 20 | 21 | import org.scalatest.Matchers._ 22 | import org.scalatest.prop.TableDrivenPropertyChecks._ 23 | import org.scalatest.WordSpec 24 | 25 | class Inet6NetworkSpec extends WordSpec { 26 | "contains" should { 27 | val data1 = Table( 28 | ("address", "isContained"), 29 | ("2001::1", true), 30 | ("2001::dead:beaf:1:1:1", true), 31 | ("2001:1:0::1", false) 32 | ) 33 | forAll(data1) { (address, isContained) => 34 | s"return $isContained" when { 35 | s"$address is given" in { 36 | val network = InetNetwork.getBySpec("2001::/48") 37 | assert(network.contains(InetAddress.getByName(address)) === isContained) 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/util/InetNetworkSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.util 18 | 19 | import org.scalatest.Matchers._ 20 | import org.scalatest.prop.TableDrivenPropertyChecks._ 21 | import org.scalatest.WordSpec 22 | 23 | class InetNetworkSpec extends WordSpec { 24 | "getBySpec" should { 25 | "return Inet4Network instance" when { 26 | "IPv4 network" in { 27 | val network = InetNetwork.getBySpec("192.168.1.0/24") 28 | assert(network.isInstanceOf[Inet4Network]) 29 | } 30 | } 31 | 32 | "return Inet6Network instance" when { 33 | "IPv6 network" in { 34 | val network = InetNetwork.getBySpec("2001::/48") 35 | assert(network.isInstanceOf[Inet6Network]) 36 | } 37 | } 38 | 39 | "throw exception" when { 40 | val data = Table( 41 | ("address"), 42 | ("2001::1:::0/48"), 43 | ("192.168.1.1.0/20"), 44 | ("192.168.1.0/xx"), 45 | ("192.168.1.1"), 46 | ("2001::1") 47 | ) 48 | forAll(data) { (address) => 49 | s"given invalid network $address" in { 50 | intercept[IllegalArgumentException] { InetNetwork.getBySpec(address) } 51 | } 52 | } 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /influent-transport/src/test/scala/influent/internal/util/ThreadSafeQueueSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 okumin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * 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, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package influent.internal.util 18 | 19 | import org.scalatest.WordSpec 20 | import org.scalatest.prop.GeneratorDrivenPropertyChecks 21 | 22 | class ThreadSafeQueueSpec extends WordSpec with GeneratorDrivenPropertyChecks { 23 | "ThreadSafeQueue" should { 24 | "behave like a FIFO queue" in { 25 | forAll { messages: Seq[Int] => 26 | val queue = new ThreadSafeQueue[Int]() 27 | assert(queue.isEmpty) 28 | assert(!queue.nonEmpty()) 29 | assert(queue.dequeue() === null) 30 | 31 | messages.foreach { m => 32 | queue.enqueue(m) 33 | assert(!queue.isEmpty) 34 | assert(queue.nonEmpty()) 35 | } 36 | 37 | whenever(messages.nonEmpty) { 38 | assert(queue.peek() === messages.head) 39 | assert(!queue.isEmpty) 40 | assert(queue.nonEmpty()) 41 | } 42 | 43 | val elements = (1 to messages.size).foldLeft(Vector.empty[Int]) { (acc, _) => 44 | acc :+ queue.dequeue() 45 | } 46 | assert(elements === messages) 47 | 48 | assert(queue.isEmpty) 49 | assert(!queue.nonEmpty()) 50 | assert(queue.dequeue() === null) 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.7") 4 | 5 | addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.0.0") 6 | 7 | addSbtPlugin("com.lightbend.sbt" % "sbt-java-formatter" % "0.4.1") 8 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sbt publishSigned 4 | 5 | --------------------------------------------------------------------------------