├── .gitignore ├── README.md ├── ReverseProxy.iml ├── doc └── benchmark.md ├── pom.xml ├── resources ├── backend_server.json ├── backend_server.properties └── ip_blacklist.json └── src ├── main ├── kotlin │ └── com │ │ └── bbz │ │ └── network │ │ └── reverseproxy │ │ ├── Launcher.kt │ │ ├── ReverseProxyServer.kt │ │ ├── ReverseProxyServerBootstrap.kt │ │ ├── config │ │ ├── DefaultNetWorkConfig.kt │ │ ├── DefaultServerConfig.kt │ │ └── DefaultThreadPoolConfig.kt │ │ ├── core │ │ ├── ClientToProxyConnection.kt │ │ ├── DefaultReverseProxyServer.kt │ │ ├── ProxyConnection.kt │ │ ├── ProxyToServerConnection.kt │ │ ├── ReverseProxyException.kt │ │ ├── ReverseProxyInitializer.kt │ │ ├── concurrent │ │ │ ├── CategorizedThreadFactory.kt │ │ │ ├── ProxyThreadPools.kt │ │ │ ├── ServerGroup.kt │ │ │ └── ThreadPoolConfiguration.kt │ │ ├── filter │ │ │ ├── HttpFilter.kt │ │ │ ├── HttpFilterAdapter.kt │ │ │ └── impl │ │ │ │ └── BlackListFilter.kt │ │ ├── misc │ │ │ ├── ConnectionState.kt │ │ │ └── ErrorCode.kt │ │ └── thread │ │ │ ├── ServerGroup.kt │ │ │ └── ThreadPoolConfiguration.kt │ │ ├── pojo │ │ └── BackendServer.kt │ │ ├── route │ │ ├── RoutePolicy.kt │ │ └── impl │ │ │ ├── IpHashPolicy.kt │ │ │ └── RoundRobinPolicy.kt │ │ └── utils │ │ ├── DataConverter.kt │ │ ├── JsonUtils.kt │ │ ├── PropertiesUtil.kt │ │ └── ProxyUtils.kt └── resources │ └── logback.xml └── test └── java └── com └── bbz └── network └── reverseproxy ├── core ├── AbstractProxyTest.kt ├── ClientToProxyConnectedFilterTest.kt ├── ClientToProxyConnectionTest.kt ├── HttpFilterTest.kt ├── ProxyToClientResponseFilterTest.kt └── filter │ └── impl │ └── BlackListFilterTest.kt ├── route └── impl │ ├── IpHashPolicyTest.kt │ └── RoundRobinPolicyTest.kt └── utils ├── DataConverterTest.kt ├── PropertiesUtilTest.kt └── SocketClientUtil.kt /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea/ 3 | hs_err_pid* 4 | ### Kotlin template 5 | # Compiled class file 6 | *.class 7 | 8 | # Log file 9 | *.log 10 | 11 | # BlueJ files 12 | *.ctxt 13 | 14 | # Mobile Tools for Java (J2ME) 15 | .mtj.tmp/ 16 | 17 | # Package Files # 18 | *.jar 19 | *.war 20 | *.ear 21 | *.zip 22 | *.tar.gz 23 | *.rar 24 | 25 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 26 | .idea/ 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReverseProxy 2 | 反向代理服务器,企图比拼nginx的反向代理功能 3 | 4 | ### 用法 5 | 6 | * 修改uri 7 | ```java 8 | 9 | val bootstrap = DefaultReverseProxyServer.bootstrap() 10 | .withPort(8000) 11 | .withHttpFilter(object : HttpFilterAdapter(){ 12 | override fun clientToProxyRequest(httpObject: HttpObject): HttpResponse? { 13 | if (httpObject is HttpRequest) { 14 | httpObject.uri = "/test"//修改uri 15 | } 16 | return null 17 | } 18 | }) 19 | bootstrap.start() 20 | 21 | ``` 22 | 23 | * ip黑名单 24 | ```java 25 | 26 | val bootstrap = DefaultReverseProxyServer.bootstrap() 27 | .withPort(8000) 28 | .withHttpFilter(BlackListFilter()) 29 | bootstrap.start() 30 | 31 | ``` 32 | 33 | * 采用ip hash 进行轮询 34 | ```java 35 | val bootstrap = DefaultReverseProxyServer.bootstrap() 36 | .withRoutePolice(IpHashPolicy()) 37 | .withPort(8000) 38 | bootstrap.start() 39 | 40 | ``` 41 | 42 | * 根据uri进行路由 43 | ```java 44 | val bootstrap = DefaultReverseProxyServer.bootstrap() 45 | .withRoutePolice(object : RoutePolicy { 46 | override fun getBackendServerAddress(request: HttpRequest, channel: Channel): InetSocketAddress? { 47 | return when (request.uri()) { 48 | "user" -> InetSocketAddress("user.api.com", 80) 49 | "prouduct" -> InetSocketAddress("product.api.com", 80) 50 | else -> InetSocketAddress("else.api.com", 80) 51 | } 52 | } 53 | }) 54 | .withPort(8000) 55 | .withHttpFilter(BlackListFilter()) 56 | bootstrap.start() 57 | 58 | ``` 59 | 60 | ### 性能 61 | [请点击](https://github.com/babizhu/ReverseProxy/blob/master/doc/benchmark.md) . 62 | -------------------------------------------------------------------------------- /ReverseProxy.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /doc/benchmark.md: -------------------------------------------------------------------------------- 1 | # ReverseProxy 性能比较 2 | 3 | 采用netty自带的HexDumpProxy作为性能基准tm 4 | 5 | 系统架构: 6 | ab--代理服务器---vertx做的一个简单web服务器 7 | ## 代码修改 8 | 9 | * Launcher.kt 10 | 11 | 修改ResourceLeakDetector.Level 12 | 13 | * logback.xml 14 | 15 | 修改日志输出类型 16 | 17 | 18 | ## 性能 19 | 20 | ### 直连 21 | ` ab -k -n 1000000 -r -c 1000 http://localhost:9090/test` 22 | 23 | * Concurrency Level: 1000 24 | * Time taken for tests: 17.010 seconds 25 | * Complete requests: 1000000 26 | * Failed requests: 0 27 | * Keep-Alive requests: 1000000 28 | * Total transferred: 67000000 bytes 29 | * HTML transferred: 5000000 bytes 30 | * Requests per second: `58790.12` [#/sec] (mean) 31 | * Time per request: 17.010 [ms] (mean) 32 | * Time per request: 0.017 [ms] (mean, across all concurrent requests) 33 | * Transfer rate: 3846.62 [Kbytes/sec] received 34 | 35 | ### ReverseProxy 36 | ` ab -k -n 1000000 -r -c 1000 http://localhost:8000/test` 37 | 38 | * Concurrency Level: 1000 39 | * Time taken for tests: 19.856 seconds 40 | * Complete requests: 1000000 41 | * Failed requests: 0 42 | * Keep-Alive requests: 1000000 43 | * Total transferred: 67000000 bytes 44 | * HTML transferred: 5000000 bytes 45 | * Requests per second: `50362.00` [#/sec] (mean) 46 | * Time per request: 19.856 [ms] (mean) 47 | * Time per request: 0.020 [ms] (mean, across all concurrent requests) 48 | * Transfer rate: 3295.17 [Kbytes/sec] received 49 | 50 | 51 | ` ab -n 1000000 -r -c 1000 http://localhost:8000/test(不带-k参数)` 52 | 53 | * Concurrency Level: 1000 54 | * Time taken for tests: 56.786 seconds 55 | * Failed requests: 0 56 | * Total transferred: 43000000 bytes 57 | * HTML transferred: 5000000 bytes 58 | * Requests per second: `17610.00` [#/sec] (mean) 59 | * Time per request: 56.786 [ms] (mean) 60 | * Time per request: 0.057 [ms] (mean, across all concurrent requests) 61 | * Transfer rate: 739.48 [Kbytes/sec] received 62 | 63 | ### nginx 64 | 65 | ` ab -k -n 1000000 -r -c 1000 http://localhost:8080/test` 66 | 67 | * Concurrency Level: 1000 68 | * Time taken for tests: 66.159 seconds 69 | * Complete requests: 1000000 70 | * Failed requests: 1000437 71 | * (Connect: 0, Receive: 437, Length: 999104, Exceptions: 896) 72 | * Non-2xx responses: 131 73 | * Keep-Alive requests: 989561 74 | * Total transferred: 134863158 bytes 75 | * HTML transferred: 5021327 bytes 76 | * Requests per second: `15115.12` [#/sec] (mean) 77 | * Time per request: 0.066 [ms] (mean, across all concurrent requests) 78 | * Transfer rate: 1990.70 [Kbytes/sec] received 79 | 80 | ` ab -n 1000000 -r -c 1000 http://localhost:8080/test(不带-k参数)` 81 | 82 | * Concurrency Level: 1000 83 | * Time taken for tests: 63.032 seconds 84 | * Complete requests: 1000000 85 | * Failed requests: 1005682 86 | * (Connect: 0, Receive: 5718, Length: 993128, Exceptions: 6836) 87 | * Non-2xx responses: 67 88 | * Total transferred: 129122921 bytes 89 | * HTML transferred: 4978839 bytes 90 | * Requests per second: `15865.07` [#/sec] (mean) 91 | * Time per request: 63.032 [ms] (mean) 92 | * Time per request: 0.063 [ms] (mean, across all concurrent requests) 93 | * Transfer rate: 2000.53 [Kbytes/sec] received -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.bbz.network 8 | ReverseProxy 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 1.2.31 13 | 14 | 15 | 16 | 17 | org.apache.httpcomponents 18 | httpclient 19 | 4.5.3 20 | test 21 | 22 | 23 | com.google.guava 24 | guava 25 | 24.0-jre 26 | 27 | 28 | org.apache.commons 29 | commons-lang3 30 | 3.5 31 | 32 | 33 | 34 | org.jetbrains.kotlin 35 | kotlin-test 36 | ${kotlin.version} 37 | test 38 | 39 | 40 | org.jetbrains.kotlin 41 | kotlin-stdlib-jdk8 42 | ${kotlin.version} 43 | 44 | 45 | ch.qos.logback 46 | logback-classic 47 | 1.2.3 48 | 49 | 50 | io.netty 51 | netty-all 52 | 4.1.22.Final 53 | 54 | 55 | junit 56 | junit 57 | RELEASE 58 | 59 | 60 | com.alibaba 61 | fastjson 62 | 1.2.46 63 | 64 | 65 | 66 | 67 | src/main/kotlin 68 | 69 | 70 | org.jetbrains.kotlin 71 | kotlin-maven-plugin 72 | ${kotlin.version} 73 | 74 | 75 | compile 76 | compile 77 | 78 | compile 79 | 80 | 81 | 82 | test-compile 83 | test-compile 84 | 85 | test-compile 86 | 87 | 88 | 89 | 90 | 1.8 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-compiler-plugin 96 | 97 | 98 | compile 99 | compile 100 | 101 | compile 102 | 103 | 104 | 105 | testCompile 106 | test-compile 107 | 108 | testCompile 109 | 110 | 111 | 112 | 113 | 114 | 115 | org.apache.maven.plugins 116 | maven-jar-plugin 117 | 2.4 118 | 119 | 120 | 121 | true 122 | lib/ 123 | 124 | com.bbz.network.reverseproxy.LauncherKt 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | org.apache.maven.plugins 135 | maven-dependency-plugin 136 | 137 | 138 | copy 139 | package 140 | 141 | copy-dependencies 142 | 143 | 144 | 145 | ${project.build.directory}/lib 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /resources/backend_server.json: -------------------------------------------------------------------------------- 1 | { 2 | "backend_servers": [ 3 | { 4 | "ip": "localhost", 5 | "port": 9090, 6 | "weight": 4000, 7 | "down": false 8 | }, 9 | { 10 | "ip": "127.0.0.1", 11 | "port": 9091, 12 | "weight": 1, 13 | "down": false 14 | }, 15 | { 16 | "ip": "127.0.0.1", 17 | "port": 9092, 18 | "weight": 2, 19 | "down": false 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /resources/backend_server.properties: -------------------------------------------------------------------------------- 1 | server=localhost:9090 2 | -------------------------------------------------------------------------------- /resources/ip_blacklist.json: -------------------------------------------------------------------------------- 1 | { 2 | "blacklist": [ 3 | "192.168.1.123", 4 | "192.168.1.143", 5 | "192.168.1.133" 6 | ] 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/Launcher.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy 2 | 3 | import com.bbz.network.reverseproxy.core.DefaultReverseProxyServer 4 | import com.bbz.network.reverseproxy.route.impl.IpHashPolicy 5 | import io.netty.util.ResourceLeakDetector 6 | 7 | //class Launcher { 8 | // 9 | //} 10 | /** 11 | * macos 12 | * ab -k -r -c 100 -n 100000 http://localhost:8000/ 13 | * Concurrency Level: 100 14 | * Time taken for tests: 11.212 seconds 15 | * Complete requests: 100000 16 | * Failed requests: 0 17 | * Keep-Alive requests: 99064 18 | * Total transferred: 84995320 bytes 19 | * HTML transferred: 61200000 bytes 20 | * Requests per second: 8918.64 [#/sec] (mean)----7450(带上http codec之后的数据) 21 | * Time per request: 11.212 [ms] (mean) 22 | * Time per request: 0.112 [ms] (mean, across all concurrent requests) 23 | * Transfer rate: 7402.76 [Kbytes/sec] received 24 | */ 25 | fun main(args: Array) { 26 | 27 | //注意,这个选项对性能有很大影响,正式发布版本需要移除 28 | ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID) 29 | // ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED) 30 | // ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED) 31 | 32 | val bootstrap = DefaultReverseProxyServer.bootstrap() 33 | // .bootstrapFromFile("./littleproxy.properties") 34 | .withPort(8000) 35 | // .withRoutePolice(IpHashPolicy()) 36 | .withConnectTimeout(3000) 37 | bootstrap.start() 38 | 39 | // ReverseProxy().start() 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/ReverseProxyServer.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy 2 | 3 | import com.bbz.network.reverseproxy.route.RoutePolicy 4 | import java.net.InetSocketAddress 5 | 6 | interface ReverseProxyServer { 7 | fun getIdleConnectionTimeout(): Int 8 | 9 | fun setIdleConnectionTimeout(idleConnectionTimeout: Int) 10 | 11 | /** 12 | * Returns the maximum time to wait, in milliseconds, to connect to a server. 13 | */ 14 | fun getConnectTimeoutMs(): Int 15 | 16 | /** 17 | * Sets the maximum time to wait, in milliseconds, to connect to a server. 18 | */ 19 | fun setConnectTimeoutMs(connectTimeoutMs: Int) 20 | 21 | /** 22 | * 23 | * 24 | * Clone the existing server, with a port 1 higher and everything else the 25 | * same. If the proxy was started with port 0 (JVM-assigned port), the cloned proxy will also use a JVM-assigned 26 | * port. 27 | * 28 | * 29 | * 30 | * 31 | * The new server will share event loops with the original server. The event 32 | * loops will use whatever name was given to the first server in the clone 33 | * group. The server group will not terminate until the original server and all clones terminate. 34 | * 35 | * 36 | * @return a bootstrap that allows customizing and starting the cloned 37 | * server 38 | */ 39 | // fun clone(): ReverseProxyServerBootstrap 40 | 41 | /** 42 | * Stops the server and all related clones. Waits for traffic to stop before shutting down. 43 | */ 44 | fun stop() 45 | 46 | /** 47 | * Stops the server and all related clones immediately, without waiting for traffic to stop. 48 | */ 49 | fun abort() 50 | 51 | /** 52 | * Return the address on which this proxy is listening. 53 | * 54 | * @return 55 | */ 56 | fun getListenAddress(): InetSocketAddress 57 | 58 | fun getRoutePolice():RoutePolicy 59 | 60 | /** 61 | * 62 | * 63 | * Set the read/write throttle bandwidths (in bytes/second) for this proxy. 64 | * 65 | * @param readThrottleBytesPerSecond 66 | * @param writeThrottleBytesPerSecond 67 | */ 68 | fun setThrottle(readThrottleBytesPerSecond: Long, writeThrottleBytesPerSecond: Long) 69 | } 70 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/ReverseProxyServerBootstrap.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy 2 | 3 | import com.bbz.network.reverseproxy.core.concurrent.ThreadPoolConfiguration 4 | import com.bbz.network.reverseproxy.core.filter.HttpFilter 5 | import com.bbz.network.reverseproxy.route.RoutePolicy 6 | import java.net.InetSocketAddress 7 | 8 | interface ReverseProxyServerBootstrap { 9 | 10 | /** 11 | * 12 | * 13 | * Give the server a name (used for naming threads, useful for logging). 14 | * 15 | * 16 | * 17 | * @param name 18 | * @return 19 | */ 20 | fun withName(name: String): ReverseProxyServerBootstrap 21 | 22 | /** 23 | * 24 | * 25 | * Listen for incoming connections on the given address. 26 | * 27 | * 28 | * 29 | * 30 | * Default = [bound ip]:8080 31 | * 32 | * 33 | * @param address 34 | * @return 35 | */ 36 | fun withListenAddress(address: InetSocketAddress): ReverseProxyServerBootstrap 37 | 38 | /** 39 | * 40 | * 41 | * Listen for incoming connections on the given port. 42 | * 43 | * 44 | * 45 | * 46 | * Default = 8080 47 | * 48 | * 49 | * @param port 50 | * @return 51 | */ 52 | fun withPort(port: Int): ReverseProxyServerBootstrap 53 | 54 | 55 | 56 | /** 57 | * 58 | * 59 | * Specify an [SslEngineSource] to use for encrypting inbound 60 | * connections. Enabling this will enable SSL client authentication 61 | * by default (see [.withAuthenticateSslClients]) 62 | * 63 | * 64 | * 65 | * 66 | * Default = null 67 | * 68 | * 69 | * 70 | * 71 | * Note - This and [.withManInTheMiddle] are 72 | * mutually exclusive. 73 | * 74 | * 75 | * @param sslEngineSource 76 | * @return 77 | */ 78 | // fun withSslEngineSource( 79 | // sslEngineSource: SslEngineSource): ReverseProxyServerBootstrap 80 | 81 | /** 82 | * 83 | * 84 | * Specify whether or not to authenticate inbound SSL clients (only applies 85 | * if [.withSslEngineSource] has been set). 86 | * 87 | * 88 | * 89 | * 90 | * Default = true 91 | * 92 | * 93 | * @param authenticateSslClients 94 | * @return 95 | */ 96 | // fun withAuthenticateSslClients( 97 | // authenticateSslClients: Boolean): ReverseProxyServerBootstrap 98 | 99 | /** 100 | * 101 | * 102 | // * Specify a [ProxyAuthenticator] to use for doing basic HTTP 103 | * authentication of clients. 104 | * 105 | * 106 | * 107 | * 108 | * Default = null 109 | * 110 | * 111 | * @param proxyAuthenticator 112 | * @return 113 | */ 114 | // fun withProxyAuthenticator( 115 | // proxyAuthenticator: ProxyAuthenticator): ReverseProxyServerBootstrap 116 | 117 | /** 118 | * 119 | * 120 | // * Specify a [ChainedProxyManager] to use for chaining requests to 121 | * another proxy. 122 | * 123 | * 124 | * 125 | * 126 | * Default = null 127 | * 128 | * 129 | * @param chainProxyManager 130 | * @return 131 | */ 132 | // fun withChainProxyManager( 133 | // chainProxyManager: ChainedProxyManager): ReverseProxyServerBootstrap 134 | 135 | /** 136 | * 137 | * 138 | // * Specify an [MitmManager] to use for making this proxy act as an SSL 139 | * man in the middle 140 | * 141 | * 142 | * 143 | * 144 | * Default = null 145 | * 146 | * 147 | * 148 | * 149 | * Note - This and [.withSslEngineSource] are 150 | * mutually exclusive. 151 | * 152 | * 153 | * @param mitmManager 154 | * @return 155 | */ 156 | // fun withManInTheMiddle( 157 | // mitmManager: MitmManager): ReverseProxyServerBootstrap 158 | 159 | /** 160 | * 161 | * 162 | * Specify a [HttpFiltersSource] to use for filtering requests and/or 163 | * responses through this proxy. 164 | * 165 | * 166 | * 167 | * 168 | * Default = null 169 | * 170 | * 171 | * @param filtersSource 172 | * @return 173 | */ 174 | // fun withFiltersSource( 175 | // filtersSource: HttpFiltersSource): ReverseProxyServerBootstrap 176 | 177 | /** 178 | * 179 | * 180 | * Specify whether or not to use secure DNS lookups for outbound 181 | * connections. 182 | * 183 | * 184 | * 185 | * 186 | * Default = false 187 | * 188 | * 189 | * @param useDnsSec 190 | * @return 191 | */ 192 | // fun withUseDnsSec( 193 | // useDnsSec: Boolean): ReverseProxyServerBootstrap 194 | 195 | /** 196 | * 197 | * 198 | * Specify whether or not to run this proxy as a transparent proxy. 199 | * 200 | * 201 | * 202 | * 203 | * Default = false 204 | * 205 | * 206 | * @param transparent 207 | * @return 208 | */ 209 | // fun withTransparent( 210 | // transparent: Boolean): ReverseProxyServerBootstrap 211 | 212 | /** 213 | * 214 | * 215 | * Specify the timeout after which to disconnect idle connections, in 216 | * seconds. 217 | * 218 | * 219 | * 220 | * 221 | * Default = 70 222 | * 223 | * 224 | * @param idleConnectionTimeout 225 | * @return 226 | */ 227 | fun withIdleConnectionTimeout( 228 | idleConnectionTimeout: Int): ReverseProxyServerBootstrap 229 | 230 | /** 231 | * 232 | * 233 | * Specify the timeout for connecting to the upstream server on a new 234 | * connection, in milliseconds. 235 | * 236 | * 237 | * 238 | * 239 | * Default = 40000 240 | * 241 | * 242 | * @param connectTimeout 243 | * @return 244 | */ 245 | fun withConnectTimeout( 246 | connectTimeout: Int): ReverseProxyServerBootstrap 247 | 248 | /** 249 | * Specify a custom [HostResolver] for resolving server addresses. 250 | * 251 | * @param serverResolver 252 | * @return 253 | */ 254 | // fun withServerResolver(serverResolver: HostResolver): ReverseProxyServerBootstrap 255 | 256 | /** 257 | * 258 | * 259 | * Add an [ActivityTracker] for tracking activity in this proxy. 260 | * 261 | * 262 | * @param activityTracker 263 | * @return 264 | */ 265 | // fun plusActivityTracker(activityTracker: ActivityTracker): ReverseProxyServerBootstrap 266 | 267 | /** 268 | * 269 | * 270 | * Specify the read and/or write bandwidth throttles for this proxy server. 0 indicates not throttling. 271 | * 272 | * @param readThrottleBytesPerSecond 273 | * @param writeThrottleBytesPerSecond 274 | * @return 275 | */ 276 | fun withThrottling(readThrottleBytesPerSecond: Long, writeThrottleBytesPerSecond: Long): ReverseProxyServerBootstrap 277 | 278 | /** 279 | * All outgoing-communication of the proxy-instance is goin' to be routed via the given network-interface 280 | * 281 | * @param inetSocketAddress to be used for outgoing communication 282 | */ 283 | // fun withNetworkInterface(inetSocketAddress: InetSocketAddress): ReverseProxyServerBootstrap 284 | 285 | fun withMaxInitialLineLength(maxInitialLineLength: Int): ReverseProxyServerBootstrap 286 | 287 | fun withMaxHeaderSize(maxHeaderSize: Int): ReverseProxyServerBootstrap 288 | 289 | fun withMaxChunkSize(maxChunkSize: Int): ReverseProxyServerBootstrap 290 | 291 | fun withRoutePolice(routePolicy: RoutePolicy): ReverseProxyServerBootstrap 292 | 293 | /** 294 | * When true, the proxy will accept requests that appear to be directed at an origin server (i.e. the URI in the HTTP 295 | * request will contain an origin-form, rather than an absolute-form, as specified in RFC 7230, section 5.3). 296 | * This is useful when the proxy is acting as a gateway/reverse proxy. **Note:** This feature should not be 297 | * enabled when running as a forward proxy; doing so may cause an infinite loop if the client requests the URI of the proxy. 298 | * 299 | // * @param allowRequestToOriginServer when true, the proxy will accept origin-form HTTP requests 300 | */ 301 | // fun withAllowRequestToOriginServer(allowRequestToOriginServer: Boolean): ReverseProxyServerBootstrap 302 | 303 | /** 304 | * Sets the alias to use when adding Via headers to incoming and outgoing HTTP messages. The alias may be any 305 | * pseudonym, or if not specified, defaults to the hostname of the local machine. See RFC 7230, section 5.7.1. 306 | * 307 | * @param alias the pseudonym to add to Via headers 308 | */ 309 | fun withProxyAlias(alias: String): ReverseProxyServerBootstrap 310 | 311 | fun withHttpFilter(httpFilter: HttpFilter):ReverseProxyServerBootstrap 312 | /** 313 | * 314 | * 315 | * Build and starts the server. 316 | * 317 | * 318 | * @return the newly built and started server 319 | */ 320 | fun start(): ReverseProxyServer 321 | 322 | /** 323 | * Set the configuration parameters for the proxy's concurrent pools. 324 | * 325 | * @param threadPoolConfiguration concurrent pool configuration 326 | * @return proxy server bootstrap for chaining 327 | */ 328 | fun withThreadPoolConfiguration(threadPoolConfiguration: ThreadPoolConfiguration): ReverseProxyServerBootstrap 329 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/config/DefaultNetWorkConfig.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.config 2 | 3 | object DefaultNetWorkConfig { 4 | 5 | 6 | const val TRAFFIC_SHAPING_CHECK_INTERVAL_MS = 250L 7 | const val MAX_INITIAL_LINE_LENGTH_DEFAULT = 8192 8 | const val MAX_HEADER_SIZE_DEFAULT = 8192 * 2 9 | const val MAX_CHUNK_SIZE_DEFAULT = 8192 * 2 10 | 11 | /** 12 | * 缺省监听的端口 13 | */ 14 | const val PORT = 8000 15 | 16 | /** 17 | * Returns the maximum time to wait, in milliseconds, to connect to a server. 18 | */ 19 | const val CONNECT_TIME_OUT_MS = 40000 20 | const val IDLE_CONNECTION_TIMEOUT_SECOND = 70 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/config/DefaultServerConfig.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.config 2 | 3 | object DefaultServerConfig { 4 | const val FALLBACK_PROXY_ALIAS = "BigBangReverseProxy" 5 | 6 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/config/DefaultThreadPoolConfig.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.config 2 | 3 | object DefaultThreadPoolConfig { 4 | /** 5 | * acceptor concurrent number 6 | */ 7 | const val ACCEPTOR_THREAD_NUM = 1 8 | 9 | /** 10 | * 工作线程数量 11 | */ 12 | val WORKER_THREAD_NUM = Runtime.getRuntime().availableProcessors() * 2 13 | 14 | const val THREAD_NAME = "BigBangReverseProxy" 15 | 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/ClientToProxyConnection.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core 2 | 3 | import com.bbz.network.reverseproxy.core.misc.ConnectionState 4 | import com.bbz.network.reverseproxy.core.misc.ErrorCode 5 | import com.bbz.network.reverseproxy.utils.ProxyUtils 6 | import io.netty.buffer.Unpooled 7 | import io.netty.channel.ChannelHandlerContext 8 | import io.netty.channel.EventLoop 9 | import io.netty.handler.codec.http.* 10 | import org.slf4j.LoggerFactory 11 | import java.net.InetSocketAddress 12 | 13 | class ClientToProxyConnection(proxyServer: DefaultReverseProxyServer) : ProxyConnection(proxyServer) { 14 | 15 | /** 16 | * 当前的状态 17 | */ 18 | private var state = ConnectionState.BACKEND_SERVER_DISCONNECTED 19 | 20 | private var proxyToServerConnection: ProxyToServerConnection? = null 21 | private var currentRequest: HttpRequest? = null 22 | private var waitToWriteHttpContent: HttpContent? = null 23 | private var backendServerAddress: InetSocketAddress? = null 24 | 25 | companion object { 26 | private val log = LoggerFactory.getLogger(ClientToProxyConnection::class.java) 27 | } 28 | 29 | override fun channelActive(ctx: ChannelHandlerContext) { 30 | proxyServer.httpFilter?.let { 31 | val response = it.clientToProxyConnected(ctx) 32 | response?.let { 33 | respondWithShortCircuitResponse(response) 34 | } 35 | } 36 | } 37 | 38 | private fun connectToBackendServer(request: HttpRequest) { 39 | backendServerAddress = getBackendServerAddress(request) 40 | if (backendServerAddress != null) { 41 | proxyToServerConnection = ProxyToServerConnection(proxyServer, this, backendServerAddress!!) 42 | this.currentRequest = request 43 | } else { 44 | writeBadGateway(ErrorCode.BACKEND_SERVER_NOT_FOUND) 45 | } 46 | } 47 | 48 | override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { 49 | val httpObject = msg as HttpObject 50 | proxyServer.httpFilter?.let { 51 | val response = it.clientToProxyRequest(httpObject) 52 | response?.let { 53 | respondWithShortCircuitResponse(response) 54 | return 55 | } 56 | } 57 | 58 | 59 | when (state) { 60 | ConnectionState.ESTABLISHED -> { 61 | proxyToServerConnection!!.writeToServer(httpObject) 62 | if (msg is HttpContent) { 63 | if (msg.content().refCnt() != 0 && msg.content() != Unpooled.EMPTY_BUFFER) { 64 | throw Exception("{} refCnt() != 0") 65 | } 66 | } 67 | } 68 | // ConnectionState.DISCONNECT_REQUESTED -> { 69 | // /** 70 | // * netty自带的http解码器一次性会解析两个部分出来: 71 | // * 一个是http request,一个是http content(如果没有则是empty content) 72 | // * 如果在获取http request之后解析backend server address失败,即使调用了close(), 73 | // * 系统还是会调用channelRead(),这里不手动释放msg的话,会造成内存泄漏 74 | // */ 75 | // releaseHttpContent(msg) 76 | // } 77 | ConnectionState.BACKEND_SERVER_DISCONNECTED -> { 78 | when (msg) { 79 | is HttpRequest -> { 80 | if (msg.decoderResult().isFailure) { 81 | writeBadGateway(ErrorCode.DECODE_FAILURE) 82 | return 83 | } 84 | readHttpRequestInit(msg) 85 | } 86 | is HttpContent -> this.waitToWriteHttpContent = msg 87 | else -> log.error("为什么达到这个状态?msg = {}", msg) 88 | } 89 | } 90 | else -> log.error("为什么达到这个状态?msg = {}", msg) 91 | } 92 | } 93 | 94 | /** 95 | * 客户端连接建立后,第一次收到Http Request请求,后面再收到的Http Request请求不会执行到这里 96 | * 建立和backend server到连接 97 | */ 98 | private fun readHttpRequestInit(msg: HttpRequest) { 99 | channel.config().isAutoRead = false 100 | connectToBackendServer(msg) 101 | } 102 | 103 | override fun disconnect() { 104 | log.debug("disconnect:{}", channel) 105 | super.disconnect() 106 | } 107 | 108 | override fun channelInactive(ctx: ChannelHandlerContext) { 109 | log.debug("{} channelInactive", ctx.channel()) 110 | waitToWriteHttpContent?.let { 111 | val byteBuf = it.content() 112 | if (byteBuf.refCnt() != 0 && byteBuf != Unpooled.EMPTY_BUFFER) { 113 | releaseHttpContent(it) 114 | } 115 | } 116 | proxyToServerConnection?.disconnect() 117 | } 118 | 119 | @Suppress("OverridingDeprecatedMember") 120 | override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { 121 | exceptionOccurAndDisconnect(cause) 122 | } 123 | 124 | 125 | fun writeToClient(msg: Any) { 126 | 127 | // channel.writeAndFlush(msg)// 128 | channel.writeAndFlush(msg).addListener({ 129 | if (it.isSuccess) { 130 | proxyToServerConnection!!.resumeRead() 131 | } else { 132 | exceptionOccurAndDisconnect(it.cause()) 133 | } 134 | }) 135 | } 136 | 137 | fun eventloop(): EventLoop { 138 | return channel.eventLoop() 139 | } 140 | 141 | /** 142 | * 服务器连接失败 143 | */ 144 | internal fun serverConnectionFailed(cause: Throwable) { 145 | log.debug( 146 | "Connection to upstream server or chained proxy failed: {} ", 147 | backendServerAddress, 148 | cause) 149 | writeBadGateway(cause) 150 | } 151 | 152 | /** 153 | * 服务器连接成功 154 | */ 155 | internal fun serverConnectionSucceeded() { 156 | log.debug("Connection to upstream server success: {}", backendServerAddress) 157 | state = ConnectionState.ESTABLISHED 158 | proxyToServerConnection!!.writeToServer(currentRequest!!) 159 | waitToWriteHttpContent?.let { 160 | proxyToServerConnection!!.writeToServer(it) 161 | if (it.content().refCnt() != 0 && it.content() != Unpooled.EMPTY_BUFFER) { 162 | throw Exception("{} refCnt() != 0") 163 | } 164 | } 165 | 166 | } 167 | 168 | private fun writeBadGateway(errorCode: ErrorCode) { 169 | 170 | log.error("writeBadGateway failed {}", errorCode) 171 | 172 | val body = "Bad Gateway: " + currentRequest?.uri() + "
" + errorCode 173 | val response = ProxyUtils.createFullHttpResponse(HttpVersion.HTTP_1_0, HttpResponseStatus.BAD_GATEWAY, body) 174 | respondWithShortCircuitResponse(response) 175 | } 176 | 177 | private fun writeBadGateway(cause: Throwable) { 178 | val body = "Bad Gateway: " + currentRequest?.uri() + "
" + cause.message 179 | val response = ProxyUtils.createFullHttpResponse(HttpVersion.HTTP_1_0, HttpResponseStatus.BAD_GATEWAY, body) 180 | respondWithShortCircuitResponse(response) 181 | } 182 | 183 | /** 184 | * 直接响应客户端,通常是报500错,完成之后主动关闭连接 185 | */ 186 | private fun respondWithShortCircuitResponse(httpResponse: HttpResponse) { 187 | // we are sending a response to the client, so we are done handling this request 188 | this.currentRequest = null 189 | // state = ConnectionState.DISCONNECT_REQUESTED 190 | 191 | 192 | // allow short-circuit messages to close the connection. normally the Connection header would be stripped when modifying 193 | // the message for proxying, so save the keep-alive status before the modifications are made. 194 | // val isKeepAlive = HttpHeaders.isKeepAlive(httpResponse) 195 | 196 | // if the response is not a Bad Gateway or Gateway Timeout, modify the headers "as if" the short-circuit response were proxied 197 | // val statusCode = httpResponse.status.code() 198 | // if (statusCode != HttpResponseStatus.BAD_GATEWAY.code() && statusCode != HttpResponseStatus.GATEWAY_TIMEOUT.code()) { 199 | // modifyResponseHeadersToReflectProxying(httpResponse) 200 | // } 201 | 202 | // restore the keep alive status, if it was overwritten when modifying headers for proxying 203 | // HttpHeaders.setKeepAlive(httpResponse, isKeepAlive) 204 | 205 | writeToClient(httpResponse) 206 | 207 | // if (ProxyUtils.isLastChunk(httpResponse)) { 208 | // writeEmptyBuffer() 209 | // } 210 | 211 | // if (!HttpHeaders.isKeepAlive(httpResponse)) { 212 | // disconnect() 213 | // return false 214 | // } 215 | // 216 | // return true 217 | // if (!HttpUtil.isKeepAlive(httpResponse)) {///要注意各种情况下的内存泄漏 218 | // disconnect() 219 | // } 220 | 221 | disconnect() 222 | 223 | } 224 | 225 | /** 226 | * 根据request计算应该连接哪个远程服务器 227 | */ 228 | private fun getBackendServerAddress(currentRequest: HttpRequest): InetSocketAddress? { 229 | 230 | return proxyServer.getRoutePolice().getBackendServerAddress(currentRequest, channel) 231 | } 232 | 233 | 234 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/DefaultReverseProxyServer.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core 2 | 3 | import com.bbz.network.reverseproxy.ReverseProxyServer 4 | import com.bbz.network.reverseproxy.ReverseProxyServerBootstrap 5 | import com.bbz.network.reverseproxy.config.DefaultNetWorkConfig 6 | import com.bbz.network.reverseproxy.config.DefaultServerConfig 7 | import com.bbz.network.reverseproxy.config.DefaultThreadPoolConfig 8 | import com.bbz.network.reverseproxy.core.concurrent.ServerGroup 9 | import com.bbz.network.reverseproxy.core.concurrent.ThreadPoolConfiguration 10 | import com.bbz.network.reverseproxy.core.filter.HttpFilter 11 | import com.bbz.network.reverseproxy.route.RoutePolicy 12 | import com.bbz.network.reverseproxy.route.impl.RoundRobinPolicy 13 | import com.bbz.network.reverseproxy.utils.ProxyUtils 14 | import io.netty.bootstrap.ServerBootstrap 15 | import io.netty.channel.Channel 16 | import io.netty.channel.ChannelOption 17 | import io.netty.channel.epoll.EpollChannelOption 18 | import io.netty.channel.group.DefaultChannelGroup 19 | import io.netty.channel.socket.nio.NioServerSocketChannel 20 | import io.netty.handler.traffic.GlobalTrafficShapingHandler 21 | import io.netty.util.concurrent.GlobalEventExecutor 22 | import org.slf4j.LoggerFactory 23 | import java.net.InetSocketAddress 24 | import java.util.concurrent.TimeUnit 25 | import java.util.concurrent.atomic.AtomicBoolean 26 | 27 | @Suppress("unused") 28 | class DefaultReverseProxyServer private constructor(private val serverGroup: ServerGroup, 29 | private var listenAddress: InetSocketAddress, 30 | private var idleConnectionTimeout: Int, 31 | private var connectTimeoutMs: Int, 32 | readThrottleBytesPerSecond: Long, 33 | writeThrottleBytesPerSecond: Long, 34 | private var proxyAlias: String, 35 | val maxInitialLineLength: Int, 36 | val maxHeaderSize: Int, 37 | val maxChunkSize: Int, 38 | private val routePolicy: RoutePolicy, 39 | val httpFilter: HttpFilter?) : ReverseProxyServer { 40 | 41 | private val stopped = AtomicBoolean(false) 42 | private val allChannels = DefaultChannelGroup("Reverse-Proxy-Server", GlobalEventExecutor.INSTANCE) 43 | 44 | private val jvmShutdownHook = Thread(Runnable { abort() }, "Reverse-Proxy-JVM-shutdown-hook") 45 | @Volatile 46 | private var globalTrafficShapingHandler: GlobalTrafficShapingHandler? = null 47 | 48 | companion object { 49 | 50 | private val log = LoggerFactory.getLogger(DefaultReverseProxyServer::class.java) 51 | fun bootstrap(): ReverseProxyServerBootstrap { 52 | return DefaultReverseProxyServerBootstrap() 53 | } 54 | 55 | } 56 | 57 | override fun getRoutePolice(): RoutePolicy { 58 | return routePolicy 59 | } 60 | 61 | override fun getIdleConnectionTimeout(): Int { 62 | return idleConnectionTimeout 63 | } 64 | 65 | override fun setIdleConnectionTimeout(idleConnectionTimeout: Int) { 66 | this.idleConnectionTimeout = idleConnectionTimeout 67 | } 68 | 69 | override fun getConnectTimeoutMs(): Int { 70 | return connectTimeoutMs 71 | } 72 | 73 | override fun setConnectTimeoutMs(connectTimeoutMs: Int) { 74 | this.connectTimeoutMs = connectTimeoutMs 75 | } 76 | 77 | override fun stop() { 78 | doStop(true) 79 | } 80 | 81 | override fun abort() { 82 | doStop(false) 83 | 84 | } 85 | 86 | private fun doStop(graceful: Boolean) { 87 | // only stop the server if it hasn't already been stopped 88 | if (stopped.compareAndSet(false, true)) { 89 | if (graceful) { 90 | log.info("Shutting down proxy server gracefully") 91 | } else { 92 | log.info("Shutting down proxy server immediately (non-graceful)") 93 | } 94 | 95 | closeAllChannels(graceful) 96 | serverGroup.unregisteProxyServer(this, graceful) 97 | 98 | // remove the shutdown hook that was added when the proxy was started, since it has now been stopped 99 | try { 100 | Runtime.getRuntime().removeShutdownHook(jvmShutdownHook) 101 | } catch (e: IllegalStateException) { 102 | // ignore -- IllegalStateException means the VM is already shutting down 103 | } 104 | 105 | log.info("Done shutting down proxy server") 106 | } 107 | } 108 | 109 | private fun closeAllChannels(graceful: Boolean) { 110 | log.info("Closing all channels " + if (graceful) "(graceful)" else "(non-graceful)") 111 | 112 | val future = allChannels.close() 113 | 114 | // if this is a graceful shutdown, log any channel closing failures. if this isn't a graceful shutdown, ignore them. 115 | if (graceful) { 116 | try { 117 | future.await(10, TimeUnit.SECONDS) 118 | } catch (e: InterruptedException) { 119 | Thread.currentThread().interrupt() 120 | log.warn("Interrupted while waiting for channels to shut down gracefully.") 121 | } 122 | 123 | if (!future.isSuccess) { 124 | for (cf in future) { 125 | if (!cf.isSuccess) { 126 | log.info("Unable to close channel. Cause of failure for {} is {}", cf.channel(), cf.cause()) 127 | } 128 | } 129 | } 130 | } 131 | } 132 | 133 | override fun getListenAddress(): InetSocketAddress { 134 | return listenAddress 135 | } 136 | 137 | override fun setThrottle(readThrottleBytesPerSecond: Long, writeThrottleBytesPerSecond: Long) { 138 | 139 | if (globalTrafficShapingHandler != null) { 140 | globalTrafficShapingHandler!!.configure(writeThrottleBytesPerSecond, readThrottleBytesPerSecond) 141 | } else { 142 | // don't create a GlobalTrafficShapingHandler if throttling was not enabled and is still not enabled 143 | if (readThrottleBytesPerSecond > 0 || writeThrottleBytesPerSecond > 0) { 144 | globalTrafficShapingHandler = createGlobalTrafficShapingHandler(readThrottleBytesPerSecond, writeThrottleBytesPerSecond) 145 | } 146 | } 147 | } 148 | 149 | 150 | /** 151 | * Creates a new GlobalTrafficShapingHandler for this HttpProxyServer, using this proxy's proxyToServerEventLoop. 152 | * 153 | * @param readThrottleBytesPerSecond 154 | * @param writeThrottleBytesPerSecond 155 | * 156 | * @return 157 | */ 158 | private fun createGlobalTrafficShapingHandler(readThrottleBytesPerSecond: Long, writeThrottleBytesPerSecond: Long): GlobalTrafficShapingHandler { 159 | val proxyToServerEventLoop = this.serverGroup.getWorkerPool() 160 | return GlobalTrafficShapingHandler(proxyToServerEventLoop, 161 | writeThrottleBytesPerSecond, 162 | readThrottleBytesPerSecond, 163 | DefaultNetWorkConfig.TRAFFIC_SHAPING_CHECK_INTERVAL_MS, 164 | java.lang.Long.MAX_VALUE) 165 | } 166 | 167 | private fun start(): DefaultReverseProxyServer { 168 | if (!serverGroup.isStopped()) { 169 | log.info("Starting reverse proxy at address: " + this.listenAddress) 170 | 171 | serverGroup.registerProxyServer(this) 172 | 173 | doStart() 174 | } else { 175 | throw IllegalStateException("Attempted to start reverse proxy, but proxy's server group is already stopped") 176 | } 177 | 178 | 179 | return this 180 | } 181 | 182 | private fun doStart() { 183 | // val initializer = object : ChannelInitializer() { 184 | // @Throws(Exception::class) 185 | // override fun initChannel(ch: Channel) { 186 | // ClientToProxyConnection( 187 | // this@DefaultReverseProxyServer, 188 | // ch.pipeline(), 189 | // globalTrafficShapingHandler) 190 | // } 191 | // } 192 | val serverBootstrap = ServerBootstrap() 193 | .group( 194 | serverGroup.getAcceptorPool(), 195 | serverGroup.getWorkerPool()) 196 | .channel(NioServerSocketChannel::class.java) 197 | .option(ChannelOption.SO_BACKLOG, 1024) // (5) 198 | .option(ChannelOption.SO_REUSEADDR, true) 199 | // .option(ChannelOption.SO_RCVBUF, 10 * 1024) 200 | // .option(ChannelOption.SO_SNDBUF, 10 * 1024) 201 | .option(EpollChannelOption.SO_REUSEPORT, true) 202 | // .childOption(ChannelOption.AUTO_READ,false) 203 | .childOption(ChannelOption.TCP_NODELAY, true) 204 | .childOption(ChannelOption.SO_KEEPALIVE, true) 205 | // .childHandler(initializer) 206 | .childHandler(ReverseProxyInitializer(this)) 207 | 208 | val future = serverBootstrap.bind(listenAddress) 209 | 210 | future.addListener({ 211 | if (future.isSuccess) { 212 | registerChannel(future.channel()) 213 | this.listenAddress = future.channel().localAddress() as InetSocketAddress 214 | 215 | } 216 | }).awaitUninterruptibly() 217 | 218 | 219 | val cause = future.cause() 220 | if (cause != null) { 221 | throw RuntimeException(cause) 222 | } 223 | this.listenAddress = future.channel().localAddress() as InetSocketAddress 224 | log.info("Reverse Proxy started at address: " + this.listenAddress) 225 | // this.boundAddress = future.channel().localAddress() as InetSocketAddress 226 | 227 | Runtime.getRuntime().addShutdownHook(jvmShutdownHook) 228 | } 229 | 230 | /** 231 | * Register a new [Channel] with this server, for later closing. 232 | * 233 | * @param channel 234 | */ 235 | fun registerChannel(channel: Channel) { 236 | allChannels.add(channel) 237 | } 238 | 239 | private class DefaultReverseProxyServerBootstrap : ReverseProxyServerBootstrap { 240 | 241 | 242 | private var threadName = DefaultThreadPoolConfig.THREAD_NAME 243 | private val serverGroup: ServerGroup? = null 244 | private var listenAddress: InetSocketAddress? = null 245 | private var port = DefaultNetWorkConfig.PORT 246 | // private var allowLocalOnly = true 247 | // private var sslEngineSource: SslEngineSource? = null 248 | // private var authenticateSslClients = true 249 | // private var proxyAuthenticator: ProxyAuthenticator? = null 250 | // private var chainProxyManager: ChainedProxyManager? = null 251 | // private var mitmManager: MitmManager? = null 252 | // private var filtersSource: HttpFiltersSource = HttpFiltersSourceAdapter() 253 | // private var transparent = false 254 | private var idleConnectionTimeout = DefaultNetWorkConfig.IDLE_CONNECTION_TIMEOUT_SECOND 255 | // private val activityTrackers = ConcurrentLinkedQueue() 256 | private var connectTimeoutMs = DefaultNetWorkConfig.CONNECT_TIME_OUT_MS 257 | // private var serverResolver: HostResolver = DefaultHostResolver() 258 | private var readThrottleBytesPerSecond: Long = 0 259 | private var writeThrottleBytesPerSecond: Long = 0 260 | // private var localAddress: InetSocketAddress? = null 261 | private var proxyAlias: String? = null 262 | 263 | private var maxInitialLineLength = DefaultNetWorkConfig.MAX_INITIAL_LINE_LENGTH_DEFAULT 264 | private var maxHeaderSize = DefaultNetWorkConfig.MAX_HEADER_SIZE_DEFAULT 265 | private var maxChunkSize = DefaultNetWorkConfig.MAX_CHUNK_SIZE_DEFAULT 266 | private var threadPoolConfiguration = ThreadPoolConfiguration() 267 | private var httpFilter:HttpFilter? = null 268 | /** 269 | * 路由策略,缺省采用轮训策略 270 | */ 271 | private var routePolice: RoutePolicy = RoundRobinPolicy() 272 | 273 | override fun withName(name: String): ReverseProxyServerBootstrap { 274 | this.threadName = name 275 | return this 276 | } 277 | override fun withHttpFilter(httpFilter: HttpFilter):ReverseProxyServerBootstrap { 278 | this.httpFilter = httpFilter 279 | return this 280 | } 281 | override fun withListenAddress(address: InetSocketAddress): ReverseProxyServerBootstrap { 282 | this.listenAddress = address 283 | return this 284 | } 285 | 286 | override fun withPort(port: Int): ReverseProxyServerBootstrap { 287 | this.port = port 288 | return this 289 | } 290 | 291 | override fun withIdleConnectionTimeout(idleConnectionTimeout: Int): ReverseProxyServerBootstrap { 292 | this.idleConnectionTimeout = idleConnectionTimeout 293 | return this 294 | } 295 | 296 | override fun withConnectTimeout(connectTimeout: Int): ReverseProxyServerBootstrap { 297 | this.connectTimeoutMs = connectTimeout 298 | return this 299 | } 300 | 301 | override fun withThrottling(readThrottleBytesPerSecond: Long, writeThrottleBytesPerSecond: Long): ReverseProxyServerBootstrap { 302 | this.readThrottleBytesPerSecond = readThrottleBytesPerSecond 303 | this.writeThrottleBytesPerSecond = writeThrottleBytesPerSecond 304 | return this 305 | } 306 | 307 | override fun withMaxInitialLineLength(maxInitialLineLength: Int): ReverseProxyServerBootstrap { 308 | this.maxInitialLineLength = maxInitialLineLength 309 | return this 310 | } 311 | 312 | override fun withMaxHeaderSize(maxHeaderSize: Int): ReverseProxyServerBootstrap { 313 | this.maxHeaderSize = maxHeaderSize 314 | return this 315 | } 316 | 317 | 318 | override fun withRoutePolice(routePolicy: RoutePolicy): ReverseProxyServerBootstrap { 319 | this.routePolice = routePolicy 320 | return this 321 | 322 | } 323 | 324 | override fun withMaxChunkSize(maxChunkSize: Int): ReverseProxyServerBootstrap { 325 | this.maxChunkSize = maxChunkSize 326 | return this 327 | } 328 | 329 | override fun withProxyAlias(alias: String): ReverseProxyServerBootstrap { 330 | this.proxyAlias = alias 331 | return this 332 | } 333 | 334 | override fun withThreadPoolConfiguration(threadPoolConfiguration: ThreadPoolConfiguration): ReverseProxyServerBootstrap { 335 | this.threadPoolConfiguration = threadPoolConfiguration 336 | return this 337 | } 338 | 339 | override fun start(): ReverseProxyServer { 340 | return build().start() 341 | } 342 | 343 | private fun build(): DefaultReverseProxyServer { 344 | val serverGroup: ServerGroup = serverGroup 345 | ?: ServerGroup(threadName, threadPoolConfiguration) 346 | 347 | val listenAddress = listenAddress ?: InetSocketAddress(port) 348 | val proxyAlias = (proxyAlias) ?: ProxyUtils.getHostName() 349 | ?: DefaultServerConfig.FALLBACK_PROXY_ALIAS 350 | 351 | return DefaultReverseProxyServer(serverGroup, 352 | listenAddress, 353 | idleConnectionTimeout, connectTimeoutMs, readThrottleBytesPerSecond, writeThrottleBytesPerSecond, 354 | proxyAlias, maxInitialLineLength, maxHeaderSize, maxChunkSize, routePolice,httpFilter 355 | ) 356 | } 357 | } 358 | 359 | init { 360 | if (writeThrottleBytesPerSecond > 0 || readThrottleBytesPerSecond > 0) { 361 | this.globalTrafficShapingHandler = createGlobalTrafficShapingHandler(readThrottleBytesPerSecond, writeThrottleBytesPerSecond) 362 | } else { 363 | this.globalTrafficShapingHandler = null 364 | } 365 | } 366 | 367 | 368 | } 369 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/ProxyConnection.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core 2 | 3 | import io.netty.buffer.Unpooled 4 | import io.netty.channel.Channel 5 | import io.netty.channel.ChannelFutureListener 6 | import io.netty.channel.ChannelHandlerContext 7 | import io.netty.channel.ChannelInboundHandlerAdapter 8 | import io.netty.handler.timeout.IdleStateEvent 9 | import io.netty.util.ReferenceCountUtil 10 | import org.slf4j.LoggerFactory 11 | import java.io.IOException 12 | import java.util.concurrent.RejectedExecutionException 13 | 14 | abstract class ProxyConnection(protected val proxyServer: DefaultReverseProxyServer) : ChannelInboundHandlerAdapter() { 15 | protected lateinit var channel: Channel 16 | 17 | companion object { 18 | private val log = LoggerFactory.getLogger(ProxyConnection::class.java) 19 | } 20 | 21 | override fun channelRegistered(ctx: ChannelHandlerContext) { 22 | this.channel = ctx.channel() 23 | proxyServer.registerChannel(channel) 24 | } 25 | 26 | /** 27 | * Closes the specified channel after all queued write requests are flushed. 28 | */ 29 | private fun closeOnFlush() { 30 | channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE) 31 | } 32 | 33 | fun resumeRead() { 34 | channel.read() 35 | } 36 | 37 | /** 38 | * This method is called when the underlying [Channel] times out due 39 | * to an idle timeout. 40 | */ 41 | private fun timedOut() { 42 | log.debug("{} timeout", channel) 43 | disconnect() 44 | } 45 | 46 | open fun disconnect() { 47 | if (channel.isActive) { 48 | closeOnFlush() 49 | } 50 | } 51 | 52 | override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { 53 | try { 54 | if (evt is IdleStateEvent) { 55 | timedOut() 56 | } 57 | } finally { 58 | super.userEventTriggered(ctx, evt) 59 | } 60 | } 61 | 62 | protected open fun exceptionOccurAndDisconnect(cause: Throwable) { 63 | when (cause) { 64 | is IOException -> { 65 | // IOExceptions are expected errors, for example when a server drops the connection. rather than flood 66 | // the logs with stack traces for these expected exceptions, log the message at the INFO level and the 67 | // stack trace at the DEBUG level. 68 | log.info("An IOException occurred on {}: {}", this::class.java.name, cause.message) 69 | } 70 | is RejectedExecutionException -> { 71 | log.info("An executor rejected a read or write operation on the " + this::class.java.name + " (this is normal if the proxy is shutting down). Message: ", cause.message) 72 | log.debug("A RejectedExecutionException occurred on " + this::class.java.name, cause) 73 | } 74 | else -> log.error("Caught an exception on {} : {}" , this::class.java.name, cause) 75 | } 76 | disconnect() 77 | } 78 | 79 | protected fun releaseHttpContent(msg: Any) { 80 | ReferenceCountUtil.release(msg) 81 | } 82 | 83 | @Suppress("unused") 84 | protected fun stopAutoReading() { 85 | log.debug("Stopped reading") 86 | this.channel.config().isAutoRead = false 87 | } 88 | 89 | // /** 90 | // * Call this to resume reading. 91 | // */ 92 | // protected fun resumeReading() { 93 | // log.debug("Resumed reading") 94 | // this.channel.config().isAutoRead = true 95 | // } 96 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/ProxyToServerConnection.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core 2 | 3 | import io.netty.bootstrap.Bootstrap 4 | import io.netty.channel.ChannelHandlerContext 5 | import io.netty.channel.ChannelInitializer 6 | import io.netty.channel.ChannelOption 7 | import io.netty.channel.socket.SocketChannel 8 | import io.netty.channel.socket.nio.NioSocketChannel 9 | import io.netty.handler.codec.http.HttpClientCodec 10 | import io.netty.handler.codec.http.HttpObject 11 | import io.netty.handler.timeout.IdleStateHandler 12 | import org.slf4j.LoggerFactory 13 | import java.net.InetSocketAddress 14 | 15 | @Suppress("OverridingDeprecatedMember") 16 | class ProxyToServerConnection(proxyServer: DefaultReverseProxyServer, 17 | private val clientToProxyConnection: ClientToProxyConnection, 18 | private val backendServerAddress: InetSocketAddress) 19 | : ProxyConnection(proxyServer) { 20 | 21 | // internal lateinit var remoteAddress: InetSocketAddress 22 | 23 | companion object { 24 | private val log = LoggerFactory.getLogger(ProxyToServerConnection::class.java) 25 | } 26 | 27 | init { 28 | connect() 29 | } 30 | 31 | override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { 32 | // proxyServer.httpFilter?.let { 33 | // val response = it.proxyToClientResponse(msg as HttpObject) 34 | // response?.let { 35 | // clientToProxyConnection.writeToClient(it) 36 | // disconnect() 37 | // releaseHttpContent(msg)//这里有很大问题 38 | // return 39 | // } 40 | // } 41 | 42 | clientToProxyConnection.writeToClient(msg) 43 | } 44 | 45 | override fun channelInactive(ctx: ChannelHandlerContext) { 46 | log.debug("{} channelInactive", ctx.channel()) 47 | clientToProxyConnection.disconnect() 48 | } 49 | 50 | 51 | override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { 52 | clientToProxyConnection.disconnect() 53 | } 54 | 55 | 56 | private fun connect() { 57 | val b = Bootstrap() 58 | b.group(clientToProxyConnection.eventloop()) 59 | .channel(NioSocketChannel::class.java) 60 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, proxyServer.getConnectTimeoutMs()) 61 | .option(ChannelOption.AUTO_READ,false) 62 | .handler(object : ChannelInitializer() { 63 | override fun initChannel(ch: SocketChannel) { 64 | ch.pipeline().addLast("codec", HttpClientCodec()) 65 | ch.pipeline().addLast( 66 | "idle", 67 | IdleStateHandler(0, 0, proxyServer.getIdleConnectionTimeout())) 68 | 69 | ch.pipeline().addLast("handler", this@ProxyToServerConnection) 70 | } 71 | }) 72 | b.connect(backendServerAddress).addListener({ 73 | if (it.isSuccess) { 74 | resumeRead() 75 | clientToProxyConnection.serverConnectionSucceeded() 76 | } else { 77 | clientToProxyConnection.serverConnectionFailed(it.cause()) 78 | } 79 | }) 80 | } 81 | 82 | fun writeToServer(msg: HttpObject) { 83 | 84 | // if (channel.isActive) { 85 | channel.writeAndFlush(msg).addListener({ 86 | if (it.isSuccess) { 87 | // was able to flush out data, start to read the next chunk 88 | clientToProxyConnection.resumeRead() 89 | } else { 90 | exceptionOccurAndDisconnect(it.cause()) 91 | } 92 | }) 93 | } 94 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/ReverseProxyException.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core 2 | 3 | import com.bbz.network.reverseproxy.core.misc.ErrorCode 4 | 5 | class ReverseProxyException(val errorCode: ErrorCode, message: String) : RuntimeException(message) { 6 | constructor(errorCode: ErrorCode) : this(errorCode, "") 7 | } 8 | 9 | fun main(args: Array) { 10 | ReverseProxyException(ErrorCode.BACKEND_SERVER_NOT_FOUND,"abcd") 11 | ReverseProxyException(ErrorCode.BACKEND_SERVER_NOT_FOUND) 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/ReverseProxyInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core 2 | 3 | import io.netty.channel.ChannelInitializer 4 | import io.netty.channel.socket.SocketChannel 5 | import io.netty.handler.codec.http.HttpServerCodec 6 | import io.netty.handler.timeout.IdleStateHandler 7 | 8 | class ReverseProxyInitializer(private val proxyServer: DefaultReverseProxyServer): ChannelInitializer() { 9 | 10 | public override fun initChannel(ch: SocketChannel) { 11 | ch.pipeline().addLast("codec", HttpServerCodec()) 12 | ch.pipeline().addLast( 13 | "idle", 14 | IdleStateHandler(0, 0, proxyServer.getIdleConnectionTimeout())) 15 | // ch.pipeline().addLast(LoggingHandler (LogLevel.INFO)) 16 | 17 | ch.pipeline().addLast("handler", ClientToProxyConnection(proxyServer )) 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/concurrent/CategorizedThreadFactory.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.concurrent 2 | 3 | import org.slf4j.LoggerFactory 4 | import java.util.concurrent.ThreadFactory 5 | import java.util.concurrent.atomic.AtomicInteger 6 | 7 | @Suppress("PrivatePropertyName") 8 | /** 9 | * @param name the user-supplied name of this proxy 10 | * @param category the type of threads this factory is creating (acceptor, proxy worker) 11 | * @param uniqueServerGroupId a unique number for the server group creating this concurrent factory, to differentiate multiple proxy instances with the same name 12 | */ 13 | class CategorizedThreadFactory(private val name: String, private val category: String, private val uniqueServerGroupId: Int) : ThreadFactory { 14 | companion object { 15 | 16 | private val log = LoggerFactory.getLogger(CategorizedThreadFactory::class.java) 17 | private val UNCAUGHT_EXCEPTION_HANDLER: (Thread, Throwable) -> Unit = 18 | { t, e -> log.error("Uncaught throwable in concurrent: {}", t.name, e) } 19 | 20 | } 21 | 22 | private val threadCount = AtomicInteger(0) 23 | 24 | /** 25 | * Exception handler for proxy threads. Logs the name of the concurrent and the exception that was caught. 26 | */ 27 | override fun newThread(r: Runnable): Thread { 28 | val t = Thread(r, name + "-" + uniqueServerGroupId + "-" + category + "-" + threadCount.getAndIncrement()) 29 | 30 | t.setUncaughtExceptionHandler(UNCAUGHT_EXCEPTION_HANDLER) 31 | 32 | return t 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/concurrent/ProxyThreadPools.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.concurrent 2 | 3 | import io.netty.channel.EventLoopGroup 4 | import io.netty.channel.nio.NioEventLoopGroup 5 | import java.nio.channels.spi.SelectorProvider 6 | import java.util.concurrent.ConcurrentHashMap 7 | 8 | class ProxyThreadPools(selectorProvider: SelectorProvider, incomingAcceptorThreads: Int, incomingWorkerThreads: Int, serverGroupName: String, serverGroupId: Int) { 9 | 10 | /** 11 | * These [EventLoopGroup]s accept incoming connections to the 12 | * proxies. A different EventLoopGroup is used for each 13 | * TransportProtocol, since these have to be configured differently. 14 | */ 15 | val acceptorPool: NioEventLoopGroup = NioEventLoopGroup(incomingAcceptorThreads, CategorizedThreadFactory(serverGroupName, "ClientToProxyAcceptor", serverGroupId), selectorProvider) 16 | 17 | /** 18 | * These [EventLoopGroup]s process incoming requests to the 19 | * proxies. A different EventLoopGroup is used for each 20 | * TransportProtocol, since these have to be configured differently. 21 | */ 22 | val workerPool: NioEventLoopGroup = NioEventLoopGroup(incomingWorkerThreads, CategorizedThreadFactory(serverGroupName, "ReverseProxyWorker", serverGroupId), selectorProvider) 23 | 24 | 25 | init { 26 | workerPool.setIoRatio(90) 27 | } 28 | 29 | 30 | /** 31 | * Returns all event loops (acceptor and worker concurrent pools) in this pool. 32 | */ 33 | fun getAllEventLoops(): List { 34 | return listOf(acceptorPool, workerPool) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/concurrent/ServerGroup.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.concurrent 2 | 3 | import com.bbz.network.reverseproxy.ReverseProxyServer 4 | import io.netty.channel.EventLoopGroup 5 | import org.slf4j.LoggerFactory 6 | import java.nio.channels.spi.SelectorProvider 7 | import java.util.concurrent.TimeUnit 8 | import java.util.concurrent.atomic.AtomicBoolean 9 | import java.util.concurrent.atomic.AtomicInteger 10 | 11 | class ServerGroup(name: String, threadPoolConfiguration: ThreadPoolConfiguration) { 12 | companion object { 13 | 14 | private val log = LoggerFactory.getLogger(ServerGroup::class.java) 15 | 16 | } 17 | 18 | 19 | /** 20 | * Global counter for the [.serverGroupId]. 21 | */ 22 | private val serverGroupCount = AtomicInteger(0) 23 | 24 | 25 | /** 26 | * The ID of this server group. Forms part of the name of each concurrent created for this server group. Useful for 27 | * differentiating threads when multiple proxy instances are running. 28 | */ 29 | private val serverGroupId: Int 30 | 31 | private val threadPools: ProxyThreadPools 32 | 33 | /** 34 | * True when this ServerGroup is stopped. 35 | */ 36 | private val stopped = AtomicBoolean(false) 37 | 38 | /** 39 | * Creates a new ServerGroup instance for a proxy. Threads created for this ServerGroup will have the specified 40 | * ServerGroup name in the Thread name. This constructor does not actually initialize any concurrent pools; instead, 41 | * concurrent pools for specific transport protocols are lazily initialized as needed. 42 | * 43 | */ 44 | init { 45 | 46 | this.serverGroupId = serverGroupCount.getAndIncrement() 47 | 48 | threadPools = ProxyThreadPools(SelectorProvider.provider(), 49 | threadPoolConfiguration.getAcceptorThreadsNum(), 50 | threadPoolConfiguration.getWorkerThreadsNum(), 51 | name, 52 | serverGroupId) 53 | 54 | } 55 | 56 | 57 | /** 58 | * List of all servers registered to use this ServerGroup. Any access to this list should be synchronized using the 59 | * [.serverRegisterLock]. 60 | */ 61 | private val registeredServers: MutableList = ArrayList(1) 62 | 63 | /** 64 | * Lock controlling access to the [.registerProxyServer] and [.unregisterProxyServer] 65 | * methods. 66 | */ 67 | private val serverRegisterLock = Any() 68 | 69 | /** 70 | * Registers the specified proxy server as a consumer of this server group. The server group will not be shut down 71 | * until the proxy unregisters itself. 72 | * 73 | // * @param proxyServer proxy server instance to register 74 | */ 75 | fun registerProxyServer(proxyServer: ReverseProxyServer) { 76 | synchronized(serverRegisterLock) { 77 | registeredServers.add(proxyServer) 78 | } 79 | } 80 | 81 | /** 82 | * Unregisters the specified proxy server from this server group. If this was the last registered proxy server, the 83 | * server group will be shut down. 84 | * 85 | * @param proxyServer proxy server instance to unregister 86 | * @param graceful when true, the server group shutdown (if necessary) will be graceful 87 | */ 88 | fun unregisteProxyServer(proxyServer: ReverseProxyServer, graceful: Boolean) { 89 | synchronized(serverRegisterLock) { 90 | val wasRegistered = registeredServers.remove(proxyServer) 91 | if (!wasRegistered) { 92 | log.warn("Attempted to unregister proxy server from ServerGroup that it was not registered with. Was the proxy unregistered twice?") 93 | return 94 | } 95 | 96 | if (registeredServers.isEmpty()) { 97 | log.debug("Proxy server unregistered from ServerGroup. No proxy servers remain registered, so shutting down ServerGroup.") 98 | 99 | shutdown(graceful) 100 | } else { 101 | log.debug("Proxy server unregistered from ServerGroup. Not shutting down ServerGroup ({} proxy servers remain registered).", registeredServers.size) 102 | } 103 | } 104 | } 105 | 106 | /** 107 | * Shuts down all event loops owned by this server group. 108 | * 109 | * @param graceful when true, event loops will "gracefully" terminate, waiting for submitted tasks to finish 110 | */ 111 | private fun shutdown(graceful: Boolean) { 112 | if (!stopped.compareAndSet(false, true)) { 113 | log.info("Shutdown requested, but ServerGroup is already stopped. Doing nothing.") 114 | 115 | return 116 | } 117 | 118 | log.info("Shutting down server group event loops " + if (graceful) "(graceful)" else "(non-graceful)") 119 | 120 | // loop through all event loops managed by this server group. this includes acceptor and worker event loops 121 | // for both TCP and UDP transport protocols. 122 | val allEventLoopGroups = threadPools.getAllEventLoops() 123 | 124 | for (group in allEventLoopGroups) { 125 | if (graceful) { 126 | group.shutdownGracefully() 127 | } else { 128 | group.shutdownGracefully(0, 0, TimeUnit.SECONDS) 129 | } 130 | } 131 | 132 | if (graceful) { 133 | for (group in allEventLoopGroups) { 134 | try { 135 | group.awaitTermination(60, TimeUnit.SECONDS) 136 | } catch (e: InterruptedException) { 137 | Thread.currentThread().interrupt() 138 | 139 | log.warn("Interrupted while shutting down event loop") 140 | } 141 | } 142 | } 143 | 144 | log.debug("Done shutting down server group") 145 | } 146 | 147 | /** 148 | * Retrieves the client-to-proxy acceptor concurrent pool for the specified protocol 149 | * 150 | * @return the client-to-proxy acceptor concurrent pool 151 | */ 152 | fun getAcceptorPool(): EventLoopGroup { 153 | return threadPools.acceptorPool 154 | // return getThreadPoolsForProtocol(protocol).getAcceptorPool() 155 | } 156 | 157 | /** 158 | * Retrieves the client-to-proxy worker pool for the specified protocol 159 | * 160 | * @return the client-to-proxy worker concurrent pool 161 | */ 162 | fun getWorkerPool(): EventLoopGroup { 163 | return threadPools.workerPool 164 | } 165 | 166 | 167 | /** 168 | * @return true if this ServerGroup has already been stopped 169 | */ 170 | fun isStopped(): Boolean { 171 | return stopped.get() 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/concurrent/ThreadPoolConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.concurrent 2 | 3 | import com.bbz.network.reverseproxy.config.DefaultThreadPoolConfig 4 | 5 | class ThreadPoolConfiguration { 6 | private var acceptorThreadsNum = DefaultThreadPoolConfig.ACCEPTOR_THREAD_NUM 7 | private var workerThreadsNum = DefaultThreadPoolConfig.WORKER_THREAD_NUM 8 | 9 | fun workerThreadsNum(): Int { 10 | return workerThreadsNum 11 | } 12 | 13 | /** 14 | * Set the number of client-to-proxy worker threads to create. Worker threads perform the actual processing of 15 | * client requests. The default value is [DefaultThreadPoolConfig.WORKER_THREAD_NUM]. 16 | * 17 | * @param workThreadsNum number of client-to-proxy worker threads to create 18 | * @return this concurrent pool configuration instance, for chaining 19 | */ 20 | fun withWorkerThreadsNum(workThreadsNum: Int): ThreadPoolConfiguration { 21 | this.workerThreadsNum = workThreadsNum 22 | return this 23 | } 24 | 25 | fun getWorkerThreadsNum():Int { 26 | return workerThreadsNum 27 | } 28 | 29 | /** 30 | * Set the number of acceptor threads to create. Acceptor threads accept HTTP connections from the client and queue 31 | * them for processing by client-to-proxy worker threads. The default value is 32 | * [DefaultThreadPoolConfig.ACCEPTOR_THREAD_NUM]. 33 | * 34 | * @param acceptorThreadsNum number of acceptor threads to create 35 | * @return this concurrent pool configuration instance, for chaining 36 | */ 37 | fun withAcceptorThreadsNum(acceptorThreadsNum: Int): ThreadPoolConfiguration { 38 | this.acceptorThreadsNum = acceptorThreadsNum 39 | return this 40 | } 41 | 42 | 43 | fun getAcceptorThreadsNum():Int { 44 | return acceptorThreadsNum 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/filter/HttpFilter.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.filter 2 | 3 | import io.netty.channel.ChannelHandlerContext 4 | import io.netty.handler.codec.http.HttpObject 5 | import io.netty.handler.codec.http.HttpResponse 6 | 7 | interface HttpFilter { 8 | 9 | /** 10 | * 如果返回HttpResponse不为null,则直接返回客户端HttpResponse, 11 | * 流程结束 12 | */ 13 | fun clientToProxyRequest(httpObject: HttpObject): HttpResponse? 14 | 15 | /** 16 | * 客户端连接上来之后调用,可实现ip黑名单等功能 17 | * 如果返回HttpResponse不为null,则直接返回客户端HttpResponse 18 | * 流程结束 19 | */ 20 | fun clientToProxyConnected(ctx:ChannelHandlerContext): HttpResponse? 21 | 22 | 23 | fun proxyToClientResponse(httpObject: HttpObject): HttpResponse? 24 | /** 25 | * 初始化filter的相关设置 26 | */ 27 | fun init() 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/filter/HttpFilterAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.filter 2 | 3 | import io.netty.channel.ChannelHandlerContext 4 | import io.netty.handler.codec.http.HttpObject 5 | import io.netty.handler.codec.http.HttpResponse 6 | 7 | open class HttpFilterAdapter : HttpFilter { 8 | override fun proxyToClientResponse(httpObject: HttpObject): HttpResponse? { 9 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 10 | } 11 | 12 | override fun init() { 13 | } 14 | 15 | override fun clientToProxyConnected(ctx: ChannelHandlerContext): HttpResponse? { 16 | return null 17 | } 18 | 19 | override fun clientToProxyRequest(httpObject: HttpObject): HttpResponse? { 20 | return null 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/filter/impl/BlackListFilter.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.filter.impl 2 | 3 | import com.bbz.network.reverseproxy.core.DefaultReverseProxyServer 4 | import com.bbz.network.reverseproxy.core.filter.HttpFilterAdapter 5 | import com.bbz.network.reverseproxy.route.RoutePolicy 6 | import com.bbz.network.reverseproxy.utils.DataConverter 7 | import com.bbz.network.reverseproxy.utils.JsonUtils 8 | import com.bbz.network.reverseproxy.utils.ProxyUtils 9 | import io.netty.channel.Channel 10 | import io.netty.channel.ChannelHandlerContext 11 | import io.netty.handler.codec.http.HttpRequest 12 | import io.netty.handler.codec.http.HttpResponse 13 | import io.netty.handler.codec.http.HttpResponseStatus 14 | import io.netty.handler.codec.http.HttpVersion 15 | import java.net.InetSocketAddress 16 | 17 | class BlackListFilter : HttpFilterAdapter() { 18 | /** 19 | * 被屏蔽ip地址列表,ip转换为Int型 20 | */ 21 | internal var blackList = HashSet() 22 | 23 | override fun clientToProxyConnected(ctx: ChannelHandlerContext): HttpResponse? { 24 | val bytes = (ctx.channel().remoteAddress() as InetSocketAddress).address.address 25 | val ip = DataConverter.toInt(bytes) 26 | return if (blackList.contains(ip)) { 27 | ProxyUtils.createFullHttpResponse(HttpVersion.HTTP_1_0, HttpResponseStatus.BAD_GATEWAY, "IP禁止") 28 | } else { 29 | null 30 | } 31 | } 32 | 33 | 34 | override fun init() { 35 | val config = JsonUtils("resources/ip_blacklist.json").json.getJSONArray("blacklist") 36 | for (ip in config) { 37 | blackList.add(DataConverter.ipToInt(ip.toString())) 38 | } 39 | } 40 | } 41 | 42 | fun main(args: Array) { 43 | 44 | 45 | val bootstrap = DefaultReverseProxyServer.bootstrap() 46 | .withRoutePolice(object : RoutePolicy { 47 | override fun getBackendServerAddress(request: HttpRequest, channel: Channel): InetSocketAddress? { 48 | 49 | return when (request.uri()) { 50 | "user" -> InetSocketAddress("user.api.com", 80) 51 | "prouduct" -> InetSocketAddress("product.api.com", 80) 52 | else -> InetSocketAddress("else.api.com", 80) 53 | } 54 | } 55 | }) 56 | .withPort(8000) 57 | .withHttpFilter(BlackListFilter()) 58 | bootstrap.start() 59 | 60 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/misc/ConnectionState.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.misc 2 | 3 | enum class ConnectionState { 4 | /** 5 | * 远程服务器连接成功 6 | */ 7 | ESTABLISHED, 8 | /** 9 | * Connection attempting to connect. 10 | */ 11 | CONNECTING, 12 | 13 | /** 14 | * In the middle of doing an SSL handshake. 15 | */ 16 | HANDSHAKING, 17 | 18 | /** 19 | * In the process of negotiating an HTTP CONNECT from the client. 20 | */ 21 | NEGOTIATING_CONNECT, 22 | 23 | /** 24 | * When forwarding a CONNECT to a chained proxy, we await the CONNECTION_OK 25 | * message from the proxy. 26 | */ 27 | AWAITING_CONNECT_OK, 28 | 29 | /** 30 | * Connected but waiting for proxy authentication. 31 | */ 32 | AWAITING_PROXY_AUTHENTICATION, 33 | 34 | /** 35 | * Connected and awaiting initial message (e.g. HttpRequest or 36 | * HttpResponse). 37 | */ 38 | AWAITING_INITIAL, 39 | 40 | 41 | /** 42 | * Connected and awaiting HttpContent chunk. 43 | */ 44 | AWAITING_CHUNK, 45 | 46 | /** 47 | * We've asked the client to disconnect, but it hasn't yet. 48 | */ 49 | // DISCONNECT_REQUESTED, 50 | 51 | /** 52 | * Disconnected 53 | */ 54 | BACKEND_SERVER_DISCONNECTED; 55 | 56 | // private final boolean partOfConnectionFlow; 57 | // 58 | // ConnectionState(boolean partOfConnectionFlow) { 59 | // this.partOfConnectionFlow = partOfConnectionFlow; 60 | // } 61 | 62 | // ConnectionState() { 63 | // this(false); 64 | // } 65 | 66 | /** 67 | * Indicates whether this ConnectionState corresponds to a step in a 68 | * {@link ConnectionFlow}. This is useful to distinguish so that we know 69 | * whether or not we're in the process of establishing a connection. 70 | * 71 | * @return true if part of connection flow, otherwise false 72 | */ 73 | // public fun isPartOfConnectionFlow():Boolean { 74 | // return partOfConnectionFlow 75 | // } 76 | 77 | /** 78 | * Indicates whether this ConnectionState is no longer waiting for messages and is either in the process of disconnecting 79 | * or is already disconnected. 80 | * 81 | * @return true if the connection state is {@link #DISCONNECT_REQUESTED} or {@link #BACKEND_SERVER_DISCONNECTED}, otherwise false 82 | */ 83 | // fun isDisconnectingOrDisconnected():Boolean { 84 | // return this == DISCONNECT_REQUESTED || this == BACKEND_SERVER_DISCONNECTED; 85 | // } 86 | // 87 | // fun isPartOfConnectionFlow(): Boolean { 88 | // return partOfConnectionFlow 89 | // 90 | // } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/misc/ErrorCode.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.misc 2 | 3 | enum class ErrorCode(val code: Int, var msg: String) { 4 | 5 | 6 | SUCCESS(0, "success"), 7 | 8 | SYSTEM_ERROR(1, "system error"), 9 | DECODE_FAILURE(999, "DECODE_FAILURE"), 10 | 11 | BACKEND_SERVER_NOT_FOUND(1000, "BACKEND_SERVER_NOT_FOUND"), 12 | 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/thread/ServerGroup.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.thread 2 | 3 | import com.bbz.network.reverseproxy.ReverseProxyServer 4 | import com.bbz.network.reverseproxy.core.concurrent.ProxyThreadPools 5 | import io.netty.channel.EventLoopGroup 6 | import org.slf4j.LoggerFactory 7 | import java.nio.channels.spi.SelectorProvider 8 | import java.util.concurrent.TimeUnit 9 | import java.util.concurrent.atomic.AtomicBoolean 10 | import java.util.concurrent.atomic.AtomicInteger 11 | 12 | class ServerGroup(name: String, threadPoolConfiguration: ThreadPoolConfiguration) { 13 | companion object { 14 | 15 | private val log = LoggerFactory.getLogger(ServerGroup::class.java) 16 | 17 | } 18 | 19 | 20 | /** 21 | * Global counter for the [.serverGroupId]. 22 | */ 23 | private val serverGroupCount = AtomicInteger(0) 24 | 25 | 26 | /** 27 | * The ID of this server group. Forms part of the name of each thread created for this server group. Useful for 28 | * differentiating threads when multiple proxy instances are running. 29 | */ 30 | private val serverGroupId: Int 31 | 32 | private val threadPools: ProxyThreadPools 33 | 34 | /** 35 | * True when this ServerGroup is stopped. 36 | */ 37 | private val stopped = AtomicBoolean(false) 38 | 39 | /** 40 | * Creates a new ServerGroup instance for a proxy. Threads created for this ServerGroup will have the specified 41 | * ServerGroup name in the Thread name. This constructor does not actually initialize any thread pools; instead, 42 | * thread pools for specific transport protocols are lazily initialized as needed. 43 | * 44 | */ 45 | init { 46 | 47 | this.serverGroupId = serverGroupCount.getAndIncrement() 48 | 49 | threadPools = ProxyThreadPools(SelectorProvider.provider(), 50 | threadPoolConfiguration.getAcceptorThreadsNum(), 51 | threadPoolConfiguration.getWorkerThreadsNum(), 52 | name, 53 | serverGroupId) 54 | 55 | } 56 | 57 | 58 | /** 59 | * List of all servers registered to use this ServerGroup. Any access to this list should be synchronized using the 60 | * [.serverRegisterLock]. 61 | */ 62 | private val registeredServers: MutableList = ArrayList(1) 63 | 64 | /** 65 | * Lock controlling access to the [.registerProxyServer] and [.unregisterProxyServer] 66 | * methods. 67 | */ 68 | private val serverRegisterLock = Any() 69 | 70 | /** 71 | * Registers the specified proxy server as a consumer of this server group. The server group will not be shut down 72 | * until the proxy unregisters itself. 73 | * 74 | // * @param proxyServer proxy server instance to register 75 | */ 76 | fun registerProxyServer(proxyServer: ReverseProxyServer) { 77 | synchronized(serverRegisterLock) { 78 | registeredServers.add(proxyServer) 79 | } 80 | } 81 | 82 | /** 83 | * Unregisters the specified proxy server from this server group. If this was the last registered proxy server, the 84 | * server group will be shut down. 85 | * 86 | * @param proxyServer proxy server instance to unregister 87 | * @param graceful when true, the server group shutdown (if necessary) will be graceful 88 | */ 89 | fun unregisteProxyServer(proxyServer: ReverseProxyServer, graceful: Boolean) { 90 | synchronized(serverRegisterLock) { 91 | val wasRegistered = registeredServers.remove(proxyServer) 92 | if (!wasRegistered) { 93 | log.warn("Attempted to unregister proxy server from ServerGroup that it was not registered with. Was the proxy unregistered twice?") 94 | return 95 | } 96 | 97 | if (registeredServers.isEmpty()) { 98 | log.debug("Proxy server unregistered from ServerGroup. No proxy servers remain registered, so shutting down ServerGroup.") 99 | 100 | shutdown(graceful) 101 | } else { 102 | log.debug("Proxy server unregistered from ServerGroup. Not shutting down ServerGroup ({} proxy servers remain registered).", registeredServers.size) 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * Shuts down all event loops owned by this server group. 109 | * 110 | * @param graceful when true, event loops will "gracefully" terminate, waiting for submitted tasks to finish 111 | */ 112 | private fun shutdown(graceful: Boolean) { 113 | if (!stopped.compareAndSet(false, true)) { 114 | log.info("Shutdown requested, but ServerGroup is already stopped. Doing nothing.") 115 | 116 | return 117 | } 118 | 119 | log.info("Shutting down server group event loops " + if (graceful) "(graceful)" else "(non-graceful)") 120 | 121 | // loop through all event loops managed by this server group. this includes acceptor and worker event loops 122 | // for both TCP and UDP transport protocols. 123 | val allEventLoopGroups = threadPools.getAllEventLoops() 124 | 125 | for (group in allEventLoopGroups) { 126 | if (graceful) { 127 | group.shutdownGracefully() 128 | } else { 129 | group.shutdownGracefully(0, 0, TimeUnit.SECONDS) 130 | } 131 | } 132 | 133 | if (graceful) { 134 | for (group in allEventLoopGroups) { 135 | try { 136 | group.awaitTermination(60, TimeUnit.SECONDS) 137 | } catch (e: InterruptedException) { 138 | Thread.currentThread().interrupt() 139 | 140 | log.warn("Interrupted while shutting down event loop") 141 | } 142 | } 143 | } 144 | 145 | log.debug("Done shutting down server group") 146 | } 147 | 148 | /** 149 | * Retrieves the client-to-proxy acceptor thread pool for the specified protocol 150 | * 151 | * @return the client-to-proxy acceptor thread pool 152 | */ 153 | fun getAcceptorPool(): EventLoopGroup { 154 | return threadPools.acceptorPool 155 | // return getThreadPoolsForProtocol(protocol).getAcceptorPool() 156 | } 157 | 158 | /** 159 | * Retrieves the client-to-proxy worker pool for the specified protocol 160 | * 161 | * @return the client-to-proxy worker thread pool 162 | */ 163 | fun getWorkerPool(): EventLoopGroup { 164 | return threadPools.workerPool 165 | } 166 | 167 | 168 | /** 169 | * @return true if this ServerGroup has already been stopped 170 | */ 171 | fun isStopped(): Boolean { 172 | return stopped.get() 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/core/thread/ThreadPoolConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.thread 2 | 3 | import com.bbz.network.reverseproxy.config.DefaultThreadPoolConfig 4 | 5 | class ThreadPoolConfiguration { 6 | private var acceptorThreadsNum = DefaultThreadPoolConfig.ACCEPTOR_THREAD_NUM 7 | private var workerThreadsNum = DefaultThreadPoolConfig.WORKER_THREAD_NUM 8 | 9 | fun workerThreadsNum(): Int { 10 | return workerThreadsNum 11 | } 12 | 13 | /** 14 | * Set the number of client-to-proxy worker threads to create. Worker threads perform the actual processing of 15 | * client requests. The default value is [DefaultThreadPoolConfig.WORKER_THREAD_NUM]. 16 | * 17 | * @param workThreadsNum number of client-to-proxy worker threads to create 18 | * @return this thread pool configuration instance, for chaining 19 | */ 20 | fun withWorkerThreadsNum(workThreadsNum: Int): ThreadPoolConfiguration { 21 | this.workerThreadsNum = workThreadsNum 22 | return this 23 | } 24 | 25 | fun getWorkerThreadsNum():Int { 26 | return workerThreadsNum 27 | } 28 | 29 | /** 30 | * Set the number of acceptor threads to create. Acceptor threads accept HTTP connections from the client and queue 31 | * them for processing by client-to-proxy worker threads. The default value is 32 | * [DefaultThreadPoolConfig.ACCEPTOR_THREAD_NUM]. 33 | * 34 | * @param acceptorThreadsNum number of acceptor threads to create 35 | * @return this thread pool configuration instance, for chaining 36 | */ 37 | fun withAcceptorThreadsNum(acceptorThreadsNum: Int): ThreadPoolConfiguration { 38 | this.acceptorThreadsNum = acceptorThreadsNum 39 | return this 40 | } 41 | 42 | 43 | fun getAcceptorThreadsNum():Int { 44 | return acceptorThreadsNum 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/pojo/BackendServer.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.pojo 2 | 3 | import com.alibaba.fastjson.JSONObject 4 | import java.net.InetSocketAddress 5 | 6 | open class BackendServer( 7 | /** 8 | * ip地址 9 | */ 10 | val ip: String, 11 | 12 | val port: Int, 13 | /** 14 | * 权重 15 | */ 16 | val weight: Int, 17 | val down:Boolean 18 | 19 | ) { 20 | val address: InetSocketAddress = InetSocketAddress(ip, port) 21 | var fails = 0 22 | 23 | companion object { 24 | fun create(json: JSONObject):BackendServer { 25 | return BackendServer(json.getString("ip"), 26 | json.getIntValue("port"), 27 | json.getIntValue("weight"), 28 | json.getBoolean("down")) 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/route/RoutePolicy.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.route 2 | 3 | import io.netty.channel.Channel 4 | import io.netty.handler.codec.http.HttpRequest 5 | import java.net.InetSocketAddress 6 | 7 | interface RoutePolicy { 8 | fun getBackendServerAddress(request: HttpRequest, channel: Channel): InetSocketAddress? 9 | 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/route/impl/IpHashPolicy.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.route.impl 2 | 3 | import com.alibaba.fastjson.JSONObject 4 | import com.bbz.network.reverseproxy.pojo.BackendServer 5 | import com.bbz.network.reverseproxy.route.RoutePolicy 6 | import com.bbz.network.reverseproxy.utils.JsonUtils 7 | import io.netty.channel.Channel 8 | import io.netty.handler.codec.http.HttpRequest 9 | import java.net.InetSocketAddress 10 | 11 | @Suppress("unused") 12 | /** 13 | * ip hash 策略 14 | * 来自nginx 15 | * https://github.com/nginx/nginx/blob/release-1.13.10/src/http/modules/ngx_http_upstream_ip_hash_module.c 16 | * 注意: 17 | * 在IPV4的情况下 ,为了性能仅取ip地址的前三段进行hash,这样测试的时候同内网的ip很可能会被hash到同一台服务器上 18 | * 19 | * IPV6,则需要重新处理,同样可以参考nginx代码 20 | */ 21 | class IpHashPolicy : RoutePolicy { 22 | 23 | private val backendServer = arrayListOf() 24 | 25 | init { 26 | val config = JsonUtils("resources/backend_server.json") 27 | val servers = config.json.getJSONArray("backend_servers") 28 | for (server in servers) { 29 | backendServer.add(BackendServer.create(server as JSONObject)) 30 | } 31 | } 32 | 33 | override fun getBackendServerAddress(request: HttpRequest, channel: Channel): InetSocketAddress { 34 | val ip = (channel.remoteAddress() as InetSocketAddress) 35 | val index = hash(ip) 36 | return backendServer[index].address 37 | } 38 | 39 | /** 40 | * nginx的hash算法 41 | */ 42 | private fun hash(ip:InetSocketAddress):Int{ 43 | val address = ip.address.address 44 | var hash = 89 45 | repeat(3) { 46 | hash = (hash * 113 + address[it]) % 6271 47 | } 48 | hash %= backendServer.size 49 | return hash 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/route/impl/RoundRobinPolicy.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.route.impl 2 | 3 | import com.alibaba.fastjson.JSONObject 4 | import com.bbz.network.reverseproxy.pojo.BackendServer 5 | import com.bbz.network.reverseproxy.route.RoutePolicy 6 | import com.bbz.network.reverseproxy.utils.JsonUtils 7 | import io.netty.channel.Channel 8 | import io.netty.handler.codec.http.HttpRequest 9 | import org.slf4j.LoggerFactory 10 | import java.net.InetSocketAddress 11 | 12 | /** 13 | * 轮训算法,来自nginx 14 | * https://github.com/nginx/nginx/blob/release-1.13.10/src/http/ngx_http_upstream_round_robin.c 15 | */ 16 | 17 | private class RoundRobinBackendServer(ip: String, 18 | port: Int, 19 | weight: Int, 20 | down: Boolean) : BackendServer(ip, port, weight, down) { 21 | 22 | 23 | var accessed = 0L /* 最近一次成功访问的时间点 */ 24 | // val checked: Long, /* 用于检查是否超过了"一段时间" */ 25 | // 26 | //ngx_uint_t max_fails; /* "一段时间内",最大的失败次数,固定值 */ 27 | //time_t fail_timeout; /* "一段时间"的值,固定值 */ 28 | 29 | var currentWeight = 0//当前权重 30 | var effectiveWeight = weight //有效权重 31 | var conns = 0 //当前连接数 32 | 33 | companion object { 34 | 35 | fun create(json: JSONObject): RoundRobinBackendServer { 36 | return RoundRobinBackendServer(json.getString("ip"), 37 | json.getIntValue("port"), 38 | json.getIntValue("weight"), 39 | json.getBoolean("down")) 40 | } 41 | } 42 | 43 | } 44 | 45 | class RoundRobinPolicy : RoutePolicy { 46 | private val backendServer = arrayListOf() 47 | 48 | companion object { 49 | private val log = LoggerFactory.getLogger(RoundRobinPolicy::class.java) 50 | 51 | } 52 | 53 | init { 54 | val config = JsonUtils("resources/backend_server.json") 55 | val servers = config.json.getJSONArray("backend_servers") 56 | for (server in servers) { 57 | backendServer.add(RoundRobinBackendServer.create(server as JSONObject)) 58 | } 59 | } 60 | 61 | override fun getBackendServerAddress(request: HttpRequest, channel: Channel): InetSocketAddress? { 62 | return roundRobinPolicy()?.address 63 | } 64 | 65 | /** 66 | * nginx的round robin算法 67 | */ 68 | private fun roundRobinPolicy(): RoundRobinBackendServer? { 69 | // peer->current_weight += peer->effecitve_weight。 70 | // 71 | // 同时累加所有peer的effective_weight,保存为total。 72 | var best: RoundRobinBackendServer? = null 73 | var total = 0 74 | for (peer in backendServer) { 75 | if (peer.down) { 76 | continue 77 | } 78 | peer.currentWeight += peer.effectiveWeight 79 | total += peer.effectiveWeight 80 | 81 | if (peer.effectiveWeight < peer.weight) { 82 | peer.effectiveWeight++ 83 | } 84 | if (best == null || peer.currentWeight > best.currentWeight) { 85 | best = peer 86 | } 87 | } 88 | if (best == null) { 89 | return null 90 | } 91 | 92 | best.currentWeight -= total 93 | best.accessed = System.currentTimeMillis() 94 | return best 95 | } 96 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/utils/DataConverter.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.utils 2 | 3 | object DataConverter { 4 | // 有符号 5 | 6 | // fun convertTwoUnSignInt(byteArray: ByteArray): Int = 7 | // (byteArray[3].toInt() shl 24) or (byteArray[2].toInt() and 0xFF) or (byteArray[1].toInt() shl 8) or (byteArray[0].toInt() and 0xFF) 8 | fun toByteArray(source: Int): ByteArray { 9 | var target = byteArrayOf(0, 0, 0, 0) 10 | var index = 0 11 | while (index < 4) { 12 | target[index] = (source.shr(8 * index) and 0xff).toByte() 13 | index++ 14 | } 15 | return target 16 | } 17 | 18 | /** 19 | * 数组byte转int数据 20 | */ 21 | fun toInt(source: ByteArray): Int { 22 | return source[3].toInt() shl 24 or (source[2].toInt() and 0xFF shl 16) or (source[1].toInt() and 0xFF shl 8) or (source[0].toInt() and 0xFF) 23 | } 24 | 25 | /** 26 | * 这个方案先凑合,以后再调整 27 | */ 28 | fun toLong(byteArray: ByteArray): Long { 29 | return toInt(byteArray) + Math.abs(Int.MIN_VALUE.toLong()) * 2 30 | } 31 | 32 | 33 | /** 34 | * 把点分格式的ip转换为4字节Int,注意使用了reversed方法 35 | * 把192.168.1.1转换为byte数组的时候,从人的角度192是高位,并且在数组中的index为0, 36 | * 但从toInt函数的角度,高位在数组中的index应该是3,因此reversed一下 37 | */ 38 | fun ipToInt(ip: String): Int { 39 | return toInt(ip.split(".").reversed().map { it.toInt().toByte() }.toByteArray()) 40 | } 41 | 42 | /** 43 | * 把4字节Int转换为点分格式的ip 44 | */ 45 | fun intToIp(ip: Int): String { 46 | val bytes = toByteArray(ip) 47 | var builder = StringBuilder() 48 | for (byte in bytes) { 49 | if (byte < 0) { 50 | builder.append(byte + 256) 51 | } else { 52 | builder.append(byte) 53 | } 54 | builder.append(".") 55 | } 56 | return builder.substring(0, builder.length - 1) 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/utils/JsonUtils.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.utils 2 | 3 | import com.alibaba.fastjson.JSON 4 | import com.alibaba.fastjson.JSONObject 5 | import java.nio.file.Files 6 | import java.nio.file.Paths 7 | 8 | class JsonUtils(path: String) { 9 | val json: JSONObject 10 | 11 | init { 12 | val content = String(Files.readAllBytes(Paths.get(path))) 13 | json = JSON.parseObject(content) 14 | // println(json) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/utils/PropertiesUtil.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.utils 2 | 3 | import java.io.FileInputStream 4 | import java.util.* 5 | 6 | class PropertiesUtil( path: String) { 7 | 8 | private val pps = Properties() 9 | 10 | init { 11 | var fileInputStream = FileInputStream(path) 12 | pps.load(fileInputStream) 13 | fileInputStream.close() 14 | 15 | } 16 | 17 | fun getInt(key: String, defaultValue: Int? = null): Int? { 18 | var value = pps.getProperty(key) 19 | if (value != null) { 20 | return value.toInt() 21 | }else{ 22 | return defaultValue 23 | } 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/bbz/network/reverseproxy/utils/ProxyUtils.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused", "MemberVisibilityCanBePrivate") 2 | 3 | package com.bbz.network.reverseproxy.utils 4 | 5 | import io.netty.buffer.ByteBuf 6 | import io.netty.buffer.Unpooled 7 | import io.netty.handler.codec.http.* 8 | import org.apache.commons.lang3.StringUtils 9 | import org.slf4j.LoggerFactory 10 | import java.io.IOException 11 | import java.net.InetAddress 12 | import java.nio.charset.StandardCharsets 13 | import java.util.regex.Pattern 14 | 15 | object ProxyUtils { 16 | private val LOG = LoggerFactory.getLogger(ProxyUtils::class.java) 17 | private val HTTP_PREFIX = Pattern.compile("^https?://.*", Pattern.CASE_INSENSITIVE) 18 | fun getHostName(): String? { 19 | try { 20 | return InetAddress.getLocalHost().hostName 21 | } catch (e: IOException) { 22 | LOG.debug("Ignored exception", e) 23 | } catch (e: RuntimeException) { 24 | // An exception here must not stop the proxy. Android could throw a 25 | // runtime exception, since it not allows network access in the main 26 | // process. 27 | LOG.debug("Ignored exception", e) 28 | } 29 | 30 | LOG.info("Could not lookup localhost") 31 | return null 32 | } 33 | 34 | fun isLastChunk(chunk: HttpObject): Boolean { 35 | return chunk is LastHttpContent 36 | 37 | } 38 | fun isChunked(httpRequest: HttpObject): Boolean { 39 | return !isLastChunk(httpRequest) 40 | 41 | } 42 | fun createFullHttpResponse(httpVersion: HttpVersion, 43 | status: HttpResponseStatus, 44 | body: String): FullHttpResponse { 45 | val bytes = body.toByteArray(StandardCharsets.UTF_8) 46 | val content = Unpooled.copiedBuffer(bytes) 47 | 48 | return createFullHttpResponse(httpVersion, status, "text/html; charset=utf-8", content, bytes.size) 49 | } 50 | 51 | /** 52 | * Creates a new [FullHttpResponse] with the specified body. 53 | * 54 | * @param httpVersion HTTP version of the response 55 | * @param status HTTP status code 56 | * @param contentType the Content-Type of the body 57 | * @param body body to include in the FullHttpResponse; if null 58 | * @param contentLength number of bytes to send in the Content-Length header; should equal the number of bytes in the ByteBuf 59 | * @return new http response object 60 | */ 61 | private fun createFullHttpResponse(httpVersion: HttpVersion, 62 | status: HttpResponseStatus, 63 | contentType: String?, 64 | body: ByteBuf?, 65 | contentLength: Int): FullHttpResponse { 66 | val response: DefaultFullHttpResponse 67 | 68 | if (body != null) { 69 | response = DefaultFullHttpResponse(httpVersion, status, body) 70 | response.headers().set(HttpHeaderNames.CONTENT_LENGTH, contentLength) 71 | response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType) 72 | } else { 73 | response = DefaultFullHttpResponse(httpVersion, status) 74 | } 75 | 76 | return response 77 | } 78 | 79 | fun parseHostAndPort(httpRequest: HttpRequest): String { 80 | return parseHostAndPort(httpRequest.uri()) 81 | } 82 | 83 | /** 84 | * Parses the host and port an HTTP request is being sent to. 85 | * 86 | * @param uri 87 | * The URI. 88 | * @return The host and port string. 89 | */ 90 | fun parseHostAndPort(uri: String): String { 91 | val tempUri: String = if (!HTTP_PREFIX.matcher(uri).matches()) { 92 | // Browsers particularly seem to send requests in this form when 93 | // they use CONNECT. 94 | uri 95 | } else { 96 | // We can't just take a substring from a hard-coded index because it 97 | // could be either http or https. 98 | StringUtils.substringAfter(uri, "://") 99 | 100 | 101 | } 102 | val hostAndPort: String 103 | hostAndPort = if (tempUri.contains("/")) { 104 | tempUri.substring(0, tempUri.indexOf("/")) 105 | } else { 106 | tempUri 107 | } 108 | return hostAndPort 109 | } 110 | 111 | 112 | 113 | fun copyMutableResponse(original: HttpResponse): HttpResponse { 114 | val copy: HttpResponse? 115 | copy = if (original is DefaultFullHttpResponse) { 116 | val content = original.content() 117 | DefaultFullHttpResponse(original.protocolVersion(), 118 | original.status(), content) 119 | } else { 120 | DefaultHttpResponse(original.protocolVersion(), 121 | original.status()) 122 | } 123 | val headerNames = original.headers().names() 124 | for (name in headerNames) { 125 | val values = original.headers().getAll(name) 126 | copy.headers().set(name, values) 127 | } 128 | return copy 129 | } 130 | } -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %date %level [%thread] %logger{10} [%line] %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/java/com/bbz/network/reverseproxy/core/AbstractProxyTest.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core 2 | 3 | import com.bbz.network.reverseproxy.ReverseProxyServer 4 | import com.bbz.network.reverseproxy.ReverseProxyServerBootstrap 5 | import org.junit.Before 6 | 7 | abstract class AbstractProxyTest { 8 | protected var proxyServer: ReverseProxyServer? = null 9 | @Before 10 | fun runSetUp() { 11 | setUp() 12 | } 13 | 14 | abstract fun setUp() 15 | 16 | protected fun bootstrapProxy(): ReverseProxyServerBootstrap { 17 | return DefaultReverseProxyServer.bootstrap() 18 | // .bootstrapFromFile("./littleproxy.properties") 19 | .withPort(0) 20 | // .withRoutePolice(IpHashPolicy()) 21 | .withConnectTimeout(3000) 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/java/com/bbz/network/reverseproxy/core/ClientToProxyConnectedFilterTest.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core 2 | 3 | import com.bbz.network.reverseproxy.core.filter.HttpFilterAdapter 4 | import com.bbz.network.reverseproxy.utils.DataConverter 5 | import com.bbz.network.reverseproxy.utils.ProxyUtils 6 | import io.netty.channel.ChannelHandlerContext 7 | import io.netty.handler.codec.http.HttpResponse 8 | import io.netty.handler.codec.http.HttpResponseStatus 9 | import io.netty.handler.codec.http.HttpVersion 10 | import org.apache.http.client.methods.HttpGet 11 | import org.apache.http.impl.client.HttpClients 12 | import org.apache.http.util.EntityUtils 13 | import org.junit.Test 14 | import java.net.InetSocketAddress 15 | 16 | 17 | class ClientToProxyConnectedFilterTest : AbstractProxyTest() { 18 | override fun setUp() { 19 | val bootstrapProxy = super.bootstrapProxy() 20 | bootstrapProxy.withHttpFilter(object : HttpFilterAdapter() { 21 | /** 22 | * 被屏蔽ip地址列表 23 | */ 24 | private var blackList = HashSet() 25 | 26 | override fun clientToProxyConnected(ctx: ChannelHandlerContext): HttpResponse? { 27 | println(ctx.channel().remoteAddress()) 28 | val bytes = (ctx.channel().remoteAddress() as InetSocketAddress).address.address 29 | val ip = DataConverter.toInt(bytes) 30 | return if (blackList.contains(ip)) { 31 | ProxyUtils.createFullHttpResponse(HttpVersion.HTTP_1_0, HttpResponseStatus.BAD_GATEWAY, "IP禁止") 32 | } else { 33 | null 34 | } 35 | } 36 | 37 | override fun init() { 38 | val blackListArray: Array = arrayOf("192.168.1.1", "224.156.78.12", "1.2.3.4") 39 | blackList = blackListArray.map { DataConverter.ipToInt(it) }.toHashSet() 40 | 41 | } 42 | }) 43 | proxyServer = bootstrapProxy.start() 44 | } 45 | 46 | @Test 47 | fun test() { 48 | val blackListArray: Array = arrayOf("192.168.134.156", "224.156.78.12", "1.2.3.4") 49 | blackListArray.map { 50 | val list = it.split(".").map { it.toInt().toByte() }.toByteArray() 51 | println("$it:${DataConverter.toInt(list)}") 52 | } 53 | 54 | val i = -1668896576 55 | var toByteArray = DataConverter.toByteArray(i) 56 | for (b in toByteArray) { 57 | print(b) 58 | if (b < 0) { 59 | print("[${b + 256}],") 60 | } else { 61 | print(",") 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * 1、测试http filter 68 | */ 69 | @Test 70 | fun filterTest() { 71 | val httpclient = HttpClients.createDefault() 72 | val httpGet = HttpGet("http://localhost:${proxyServer!!.getListenAddress().port}/") 73 | val response = httpclient.execute(httpGet) 74 | response.use { response1 -> 75 | println(response1.statusLine) 76 | val entity1 = response1.entity 77 | // do something useful with the response body 78 | // and ensure it is fully consumed 79 | println(EntityUtils.toString(response1.entity)) 80 | EntityUtils.consume(entity1) 81 | } 82 | Thread.sleep(1000000) 83 | } 84 | 85 | @Test 86 | fun testIntConvertIp() { 87 | for (it in Int.MIN_VALUE..Int.MAX_VALUE) { 88 | if (it % 1000000 == 0) { 89 | println("$it done ") 90 | } 91 | val toByteArray = DataConverter.toByteArray(it) 92 | var toInt = DataConverter.toInt(toByteArray) 93 | if (it != toInt) { 94 | throw Exception() 95 | } 96 | } 97 | println("all done!") 98 | } 99 | 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/test/java/com/bbz/network/reverseproxy/core/ClientToProxyConnectionTest.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core 2 | 3 | import com.bbz.network.reverseproxy.utils.SocketClientUtil 4 | import io.netty.bootstrap.Bootstrap 5 | import io.netty.buffer.ByteBuf 6 | import io.netty.buffer.Unpooled 7 | import io.netty.channel.* 8 | import io.netty.channel.nio.NioEventLoopGroup 9 | import io.netty.channel.socket.SocketChannel 10 | import io.netty.channel.socket.nio.NioSocketChannel 11 | import org.junit.Test 12 | import java.util.concurrent.atomic.AtomicInteger 13 | 14 | 15 | class ClientToProxyConnectionTest : AbstractProxyTest() { 16 | val count = AtomicInteger(0) 17 | override fun setUp() { 18 | proxyServer = super.bootstrapProxy().start() 19 | } 20 | 21 | 22 | /** 23 | * 1、测试http请求解析失败的情况(模拟浏览器乱发请求包) 24 | */ 25 | @Test 26 | fun decodeFailueTest() { 27 | 28 | var eventLoopGroup = NioEventLoopGroup() 29 | repeat(10000) { 30 | var buffer = Unpooled.copiedBuffer("反倒是 / http 1.1\r\n\r\n".toByteArray()) 31 | connectAndWrite(buffer, eventLoopGroup) 32 | } 33 | Thread.sleep(10000000) 34 | } 35 | 36 | @Test 37 | fun decodeFailueTest1() { 38 | val socketToProxyServer = SocketClientUtil.getSocketToProxyServer(proxyServer!!) 39 | repeat(5) { 40 | SocketClientUtil.writeStringToSocket("abcddfsdfsdfsdfdsfse\r", socketToProxyServer) 41 | } 42 | val string = SocketClientUtil.readStringFromSocket(socketToProxyServer) 43 | println(string) 44 | 45 | } 46 | 47 | /** 48 | * 测试没有找到后台服务器地址的情况,考虑如下情况: 49 | * netty自带的http解码器一次性会解析两个部分出来: 50 | * 一个是http request,一个是http content(如果没有则是io.netty.handler.codec.http.LastHttpContent[empty content]) 51 | * 如果在获取http request之后解析backend server address失败,即使调用了close(), 52 | * 系统还是会调用channelRead(),这时如果没手动释放msg的话,会造成内存泄漏 53 | * 54 | * 55 | * 测试方法: 56 | * 1、删除backend_server.json配置文件中所有的后台服务器配置信息 57 | * 2、在Launcher.kt中设置ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID) 58 | * 3、java -Xms100m -Xmx128m -jar ReverseProxy-1.0-SNAPSHOT.jar >xx.log启动 59 | * 4、tail -f xx.log |grep leak 60 | * 3、ab -n 1000000 -c 100 -p 'post.txt' -T 'application/x-www-form-urlencoded' http://localhost:8000/ 进行压测 61 | * 62 | * post.txt内容:cid=4&status=1 63 | * 64 | * TODO 自动化测试 65 | * 66 | */ 67 | @Test 68 | fun noBackendServerAddress() { 69 | 70 | } 71 | 72 | /** 73 | * 当后端服务器连接失败的时候 74 | * 关于内存泄漏需要考虑2种情况 75 | * 1、先执行channelRead()中的http content中的情况,然后再执行serverConnectionFailed() 76 | * 2、先执行serverConnectionFailed(),然后执行channelRead()中的http content中的情况 77 | * 78 | * 测试方法: 79 | * 1、修改backend_server.json配置文件内容 80 | * 2、第一种情况好测试,直接curl即可,第二种则需要手动发送http请求,具体如下: 81 | * telnet localhost:8000 82 | * POST /post HTTP/1.1 83 | * Content-Length: 2 84 | * 85 | * 12 86 | * 87 | * 把最后一排的"12"延迟发送即可先触发先执行serverConnectionFailed(),再触发然后执行channelRead()方法 88 | * 目前触发了serverConnectionFailed()之后就直接触发channelInactive()了,没有执行到channelRead()方法 89 | */ 90 | @Test 91 | fun connectFailed() { 92 | 93 | } 94 | 95 | 96 | /** 97 | * 后端服务器500错误,考虑内存泄漏 98 | * 测试方法: 99 | * 需要手动发送http请求,具体如下: 100 | * telnet localhost:8000 101 | * POST /post HTTP/1.1 102 | * Content-Length: 2 103 | * 如果后端是nginx 就会产生500 错误 104 | */ 105 | @Test 106 | fun backendServer500Error() { 107 | 108 | } 109 | 110 | private fun connectAndWrite(buffer: ByteBuf, eventLoopGroup: NioEventLoopGroup) { 111 | val b = Bootstrap() 112 | b.group(eventLoopGroup) 113 | .channel(NioSocketChannel::class.java) 114 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000) 115 | .handler(object : ChannelInitializer() { 116 | override fun initChannel(ch: SocketChannel) { 117 | ch.pipeline().addLast(object : ChannelInboundHandlerAdapter() { 118 | override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { 119 | cause.printStackTrace() 120 | } 121 | 122 | override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { 123 | val buf = msg as ByteBuf 124 | try { 125 | // println("${count.getAndIncrement()}:${buf.toString(Charset.defaultCharset())}") 126 | println("${count.getAndIncrement()}") 127 | ctx.close() 128 | } finally { 129 | buf.release() 130 | } 131 | } 132 | }) 133 | } 134 | }) 135 | val address = proxyServer!!.getListenAddress() 136 | b.connect(address).addListener(object : ChannelFutureListener { 137 | override fun operationComplete(future: ChannelFuture) { 138 | if (future.isSuccess) { 139 | val channel = future.channel() 140 | channel.writeAndFlush(buffer) 141 | } 142 | } 143 | 144 | }) 145 | } 146 | 147 | } -------------------------------------------------------------------------------- /src/test/java/com/bbz/network/reverseproxy/core/HttpFilterTest.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core 2 | 3 | import com.bbz.network.reverseproxy.core.filter.HttpFilterAdapter 4 | import io.netty.handler.codec.http.HttpObject 5 | import io.netty.handler.codec.http.HttpRequest 6 | import io.netty.handler.codec.http.HttpResponse 7 | import org.apache.http.client.methods.HttpGet 8 | import org.apache.http.impl.client.HttpClients 9 | import org.apache.http.util.EntityUtils 10 | import org.junit.Test 11 | 12 | 13 | class HttpFilterTest : AbstractProxyTest() { 14 | override fun setUp() { 15 | val bootstrapProxy = super.bootstrapProxy() 16 | bootstrapProxy.withHttpFilter(object : HttpFilterAdapter() { 17 | override fun clientToProxyRequest(httpObject: HttpObject): HttpResponse? { 18 | if (httpObject is HttpRequest) { 19 | httpObject.uri = "/test" 20 | } 21 | return null 22 | } 23 | }) 24 | proxyServer = bootstrapProxy.start() 25 | } 26 | 27 | 28 | /** 29 | * 1、测试http filter 30 | */ 31 | @Test 32 | fun filterTest() { 33 | val httpclient = HttpClients.createDefault() 34 | val httpGet = HttpGet("http://localhost:${proxyServer!!.getListenAddress().port}/") 35 | val response1 = httpclient.execute(httpGet) 36 | // The underlying HTTP connection is still held by the response object 37 | // to allow the response content to be streamed directly from the network socket. 38 | // In order to ensure correct deallocation of system resources 39 | // the user MUST call CloseableHttpResponse#close() from a finally clause. 40 | // Please note that if response content is not fully consumed the underlying 41 | // connection cannot be safely re-used and will be shut down and discarded 42 | // by the connection manager. 43 | response1.use { response1 -> 44 | println(response1.statusLine) 45 | val entity1 = response1.entity 46 | // do something useful with the response body 47 | // and ensure it is fully consumed 48 | println(EntityUtils.toString(response1.entity)) 49 | EntityUtils.consume(entity1) 50 | } 51 | } 52 | 53 | @Test 54 | fun clientToProxyConnectedTest() { 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /src/test/java/com/bbz/network/reverseproxy/core/ProxyToClientResponseFilterTest.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core 2 | 3 | import com.bbz.network.reverseproxy.core.filter.HttpFilterAdapter 4 | import com.bbz.network.reverseproxy.utils.DataConverter 5 | import com.bbz.network.reverseproxy.utils.ProxyUtils 6 | import io.netty.handler.codec.http.HttpObject 7 | import io.netty.handler.codec.http.HttpResponse 8 | import io.netty.handler.codec.http.HttpResponseStatus 9 | import io.netty.handler.codec.http.HttpVersion 10 | import org.apache.http.client.methods.HttpGet 11 | import org.apache.http.impl.client.HttpClients 12 | import org.apache.http.util.EntityUtils 13 | import org.junit.Test 14 | 15 | 16 | class ProxyToClientResponseFilterTest : AbstractProxyTest() { 17 | override fun setUp() { 18 | val bootstrapProxy = super.bootstrapProxy() 19 | bootstrapProxy.withHttpFilter(object : HttpFilterAdapter() { 20 | override fun proxyToClientResponse(httpObject: HttpObject): HttpResponse? { 21 | if (httpObject is HttpResponse) { 22 | httpObject.headers().add("server","liulaoye") 23 | return ProxyUtils.createFullHttpResponse(HttpVersion.HTTP_1_0, HttpResponseStatus.BAD_GATEWAY, "不允许访问") 24 | 25 | // println(httpObject) 26 | } 27 | return null 28 | 29 | } 30 | }) 31 | proxyServer = bootstrapProxy.start() 32 | } 33 | 34 | /** 35 | * 1、测试http filter 36 | */ 37 | @Test 38 | fun filterTest() { 39 | val httpclient = HttpClients.createDefault() 40 | val httpGet = HttpGet("http://localhost:${proxyServer!!.getListenAddress().port}/") 41 | val response = httpclient.execute(httpGet) 42 | response.use { response1 -> 43 | println(response1.statusLine) 44 | val entity1 = response1.entity 45 | // do something useful with the response body 46 | // and ensure it is fully consumed 47 | println(EntityUtils.toString(response1.entity)) 48 | EntityUtils.consume(entity1) 49 | } 50 | Thread.sleep(1000000) 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/test/java/com/bbz/network/reverseproxy/core/filter/impl/BlackListFilterTest.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.core.filter.impl 2 | 3 | import com.bbz.network.reverseproxy.utils.DataConverter 4 | import com.bbz.network.reverseproxy.utils.JsonUtils 5 | import io.netty.buffer.ByteBufUtil 6 | import io.netty.buffer.Unpooled 7 | import org.junit.Test 8 | import kotlin.experimental.and 9 | import kotlin.experimental.xor 10 | import kotlin.test.assertEquals 11 | 12 | class BlackListFilterTest { 13 | 14 | @Test 15 | fun init() { 16 | var blackListFilter = BlackListFilter() 17 | blackListFilter.init() 18 | val config = readConfig() 19 | for (ip in config) { 20 | var temp = DataConverter.ipToInt(ip) 21 | println("$ip($temp)") 22 | assertEquals(blackListFilter.blackList.contains(temp), true) 23 | } 24 | 25 | 26 | } 27 | 28 | /** 29 | * read config and convert to hashset 30 | */ 31 | private fun readConfig(): HashSet { 32 | val config = JsonUtils("resources/ip_blacklist.json") 33 | val blackList = config.json.getJSONArray("blacklist") 34 | return blackList.map { it.toString() }.toHashSet() 35 | 36 | } 37 | 38 | @Test 39 | fun d() { 40 | var str = "192.168.1.123" 41 | // str.split(".").map { println(it.toInt().toByte().toString(2)) } 42 | var byteArray = str.split(".").map { it.toInt().toByte() }.toByteArray() 43 | println("$str::${DataConverter.toInt(byteArray)}::${ByteBufUtil.hexDump(byteArray)}") 44 | 45 | str = "192.168.1.124" 46 | byteArray = str.split(".").map { it.toInt().toByte() }.toByteArray() 47 | println("$str::${DataConverter.toInt(byteArray)}::${ByteBufUtil.hexDump(byteArray)}") 48 | 49 | str = "192.168.1.125" 50 | byteArray = str.split(".").map { it.toInt().toByte() }.toByteArray() 51 | println("$str::${DataConverter.toInt(byteArray)}::${ByteBufUtil.hexDump(byteArray)}") 52 | 53 | str = "192.168.1.126" 54 | byteArray = str.split(".").map { it.toInt().toByte() }.toByteArray() 55 | println("$str::${DataConverter.toInt(byteArray)}::${ByteBufUtil.hexDump(byteArray)}") 56 | 57 | str = "192.168.1.127" 58 | byteArray = str.split(".").map { it.toInt().toByte() }.toByteArray() 59 | println("$str::${DataConverter.toInt(byteArray)}::${ByteBufUtil.hexDump(byteArray)}") 60 | 61 | str = "192.168.1.128" 62 | byteArray = str.split(".").map { it.toInt().toByte() }.toByteArray() 63 | println("$str::${DataConverter.toInt(byteArray)}::${ByteBufUtil.hexDump(byteArray)}") 64 | 65 | str = "192.168.1.133" 66 | byteArray = str.split(".").map { it.toInt().toByte() }.toByteArray() 67 | println("$str::${DataConverter.toInt(byteArray)}::${ByteBufUtil.hexDump(byteArray)}") 68 | 69 | 70 | byteArray = DataConverter.toByteArray(Int.MAX_VALUE) 71 | println("${Int.MAX_VALUE}::${DataConverter.toInt(byteArray)}::${ByteBufUtil.hexDump(byteArray)}") 72 | 73 | str = "192.168.1.128" 74 | byteArray = str.split(".").map { it.toInt().toByte() }.toByteArray() 75 | 76 | println(DataConverter.toInt(byteArray) + Math.abs(Int.MIN_VALUE.toLong())*2) 77 | println("${DataConverter.toLong(byteArray)}") 78 | 79 | 80 | } 81 | } -------------------------------------------------------------------------------- /src/test/java/com/bbz/network/reverseproxy/route/impl/IpHashPolicyTest.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.route.impl 2 | 3 | import org.junit.Test 4 | import java.io.FileInputStream 5 | import java.net.InetSocketAddress 6 | import java.util.* 7 | 8 | class IpHashPolicyTest { 9 | 10 | @Test 11 | fun getUrl() { 12 | val count = 6 13 | println("192.168.1.20:${"192.168.1.20".hashCode()}:${"192.168.1.20".hashCode() % count}") 14 | println("192.168.1.21:${"192.168.1.21".hashCode()}:${"192.168.1.21".hashCode() % count}") 15 | println("192.168.1.22:${"192.168.1.22".hashCode()}:${"192.168.1.22".hashCode() % count}") 16 | println("192.168.1.23:${"192.168.1.23".hashCode()}:${"192.168.1.23".hashCode() % count}") 17 | println("192.168.1.24:${"192.168.1.24".hashCode()}:${"192.168.1.24".hashCode() % count}") 18 | println("192.168.1.25:${"192.168.1.25".hashCode()}:${"192.168.1.25".hashCode() % count}") 19 | println("192.168.1.26:${"192.168.1.26".hashCode()}:${"192.168.1.26".hashCode() % count}") 20 | println("192.168.1.28:${"192.168.1.27".hashCode()}:${"192.168.1.27".hashCode() % count}") 21 | println("192.168.1.28:${"192.168.1.28".hashCode()}:${"192.168.1.28".hashCode() % count}") 22 | println("192.168.1.29:${"192.168.1.29".hashCode()}:${"192.168.1.29".hashCode() % count}") 23 | println("192.168.1.30:${"192.168.1.30".hashCode()}:${"192.168.1.30".hashCode() % count}") 24 | println("221.23.45.124:${"221.23.45.12".hashCode()}:${"221.23.45.124".hashCode() % count}") 25 | 26 | var address = InetSocketAddress("221.23.45.12", 8000) 27 | var address1 = InetSocketAddress("221.23.45.12", 8080) 28 | println(address.hashCode()) 29 | println(address1.hashCode()) 30 | println(address.hostName) 31 | println(address.hostString) 32 | println(address.address.hostAddress) 33 | 34 | 35 | } 36 | 37 | /** 38 | * 测试nginx的iphash算法 39 | */ 40 | @Test 41 | fun testNginxIPHash() { 42 | val pps = Properties() 43 | pps.load(FileInputStream("resources/backend_server.properties")) 44 | 45 | // pps.get() 46 | 47 | val per = 0.5f //阈值,后端server命中个数与平均值偏离超过该比例则输出相关信息 48 | var random = Random() 49 | val peerNumber = 100//随机产生后端server节点数 50 | 51 | // val peerNumber = random.nextInt(6271) + 1//随机产生后端server节点数 52 | var result = IntArray(peerNumber) 53 | 54 | 55 | val totalNum = 1000000 //进行hash的总次数 56 | // int total_num_temp = total_num; 57 | repeat(totalNum) { 58 | val ipFiledCount = 4 59 | var ip = IntArray(ipFiledCount) 60 | repeat(ipFiledCount) { 61 | ip[it] = random.nextInt(255)//随机生成四个数作为ip地址前四段 62 | } 63 | // print(ip.map { it }) 64 | // print(" ") 65 | var hash = 89 66 | repeat(3) { 67 | hash = (hash * 113 + ip[it]) % 6271 68 | } 69 | hash %= peerNumber 70 | result[hash]++//统计hash值命中 71 | // println(hash) 72 | 73 | } 74 | val avg = totalNum / peerNumber 75 | val max = (avg.toFloat() * (1 + per)).toInt() 76 | val min = (avg.toFloat() * (1 - per)).toInt() 77 | repeat(peerNumber) { 78 | // if (result[it] > max || result[it] < min){ 79 | // println("$it:${result[it]}") 80 | // } 81 | println("$it:${result[it]}") 82 | 83 | 84 | } 85 | println("avg:$avg\tmin:$min\tmax:$max") 86 | 87 | 88 | } 89 | } -------------------------------------------------------------------------------- /src/test/java/com/bbz/network/reverseproxy/route/impl/RoundRobinPolicyTest.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.route.impl 2 | 3 | import io.netty.buffer.ByteBufAllocator 4 | import io.netty.channel.* 5 | import io.netty.channel.epoll.EpollServerSocketChannel 6 | import io.netty.handler.codec.http.DefaultHttpRequest 7 | import io.netty.handler.codec.http.HttpMethod 8 | import io.netty.handler.codec.http.HttpVersion 9 | import io.netty.util.Attribute 10 | import io.netty.util.AttributeKey 11 | import io.netty.util.concurrent.EventExecutor 12 | import org.junit.Test 13 | import java.net.SocketAddress 14 | 15 | class RoundRobinPolicyTest { 16 | 17 | @Test 18 | fun getUrl() { 19 | 20 | // var roundRobinPolicy = RoundRobinPolicy() 21 | // var request = DefaultHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.CONNECT, "") 22 | // var epollServerSocketChannel = EpollServerSocketChannel() 23 | // val channel: Channel = epollServerSocketChannel 24 | // repeat(7) { 25 | // val url = roundRobinPolicy.getBackendServerAddress(request,null) 26 | // 27 | // println(url) 28 | // } 29 | } 30 | } -------------------------------------------------------------------------------- /src/test/java/com/bbz/network/reverseproxy/utils/DataConverterTest.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.utils 2 | 3 | import io.netty.buffer.ByteBufUtil 4 | import io.netty.buffer.Unpooled 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | 8 | class DataConverterTest { 9 | 10 | @Test 11 | fun ipToInt() { 12 | val ip = "192.168.123.45" 13 | var result = DataConverter.ipToInt(ip) 14 | println(result) 15 | assertEquals(result, 763078848) 16 | } 17 | 18 | @Test 19 | fun intToIp() { 20 | val ip = 763078848 21 | var result = DataConverter.intToIp(ip) 22 | println(result) 23 | assertEquals(result, "192.168.123.45") 24 | 25 | } 26 | 27 | @Test 28 | fun toByteArray() { 29 | val i = -2 30 | var bytes = DataConverter.toByteArray(i) 31 | bytes.map { println(it) } 32 | var i1 = DataConverter.toInt(bytes) 33 | assertEquals(i, i1) 34 | } 35 | 36 | @Test 37 | fun toInt() { 38 | val ip = "192.168.123.45" 39 | var bytes = ip.split(".").reversed().map { it.toInt().toByte() }.toByteArray() 40 | println(ByteBufUtil.hexDump(bytes)) 41 | println(DataConverter.toLong(bytes)) 42 | println(DataConverter.toInt(bytes)) 43 | 44 | println(DataConverter.ipToInt(ip)) 45 | } 46 | 47 | 48 | } -------------------------------------------------------------------------------- /src/test/java/com/bbz/network/reverseproxy/utils/PropertiesUtilTest.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.utils 2 | 3 | import org.junit.Assert.* 4 | 5 | import org.junit.Test 6 | 7 | class PropertiesUtilTest { 8 | 9 | @Test 10 | fun getInt() { 11 | var propertiesUtil = PropertiesUtil("resources/backend_server.properties") 12 | var value = propertiesUtil.getInt("port") 13 | println(value) 14 | value = propertiesUtil.getInt("port",40) 15 | println(value) 16 | 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /src/test/java/com/bbz/network/reverseproxy/utils/SocketClientUtil.kt: -------------------------------------------------------------------------------- 1 | package com.bbz.network.reverseproxy.utils 2 | 3 | import com.bbz.network.reverseproxy.ReverseProxyServer 4 | import java.io.EOFException 5 | import java.io.IOException 6 | import java.net.InetSocketAddress 7 | import java.net.Socket 8 | import java.net.SocketException 9 | import java.net.SocketTimeoutException 10 | import java.nio.charset.Charset 11 | 12 | object SocketClientUtil { 13 | 14 | @Throws(IOException::class) 15 | 16 | fun writeStringToSocket(msg: String, socket: Socket) { 17 | val out = socket.getOutputStream() 18 | out.write(msg.toByteArray(Charset.forName("UTF-8"))) 19 | out.flush() 20 | } 21 | 22 | /** 23 | * Reads all available data from the socket and returns a String containing that content, interpreted in the 24 | * UTF-8 charset. 25 | * 26 | * @param socket socket to read UTF-8 bytes from 27 | * @return String containing the contents of whatever was read from the socket 28 | * @throws EOFException if the socket has been closed 29 | */ 30 | @Throws(IOException::class) 31 | fun readStringFromSocket(socket: Socket): String { 32 | val inputStream = socket.getInputStream() 33 | val bytes = ByteArray(10000) 34 | val bytesRead = inputStream.read(bytes) 35 | if (bytesRead == -1) { 36 | throw EOFException("Unable to read from socket. The socket is closed.") 37 | } 38 | 39 | return String(bytes, 0, bytesRead, Charset.forName("UTF-8")) 40 | } 41 | 42 | /** 43 | * Determines if the socket can be written to. This method tests the writability of the socket by writing to the socket, 44 | * so it should only be used immediately before closing the socket. 45 | * 46 | * @param socket socket to test 47 | * @return true if the socket is open and can be written to, otherwise false 48 | * @throws IOException 49 | */ 50 | @Throws(IOException::class) 51 | fun isSocketReadyToWrite(socket: Socket): Boolean { 52 | val out = socket.getOutputStream() 53 | try { 54 | repeat(500) { 55 | out.write(0) 56 | out.flush() 57 | } 58 | } catch (e: SocketException) { 59 | return false 60 | } 61 | 62 | return true 63 | } 64 | 65 | /** 66 | * Determines if the socket can be read from. This method tests the readability of the socket by attempting to read 67 | * a byte from the socket. If successful, the byte will be lost, so this method should only be called immediately 68 | * before closing the socket. 69 | * 70 | * @param socket socket to test 71 | * @return true if the socket is open and can be read from, otherwise false 72 | * @throws IOException 73 | */ 74 | fun isSocketReadyToRead(socket: Socket): Boolean { 75 | val inputStream = socket.getInputStream() 76 | return try { 77 | val readByte = inputStream.read() 78 | 79 | // we just lost that byte but it doesn't really matter for testing purposes 80 | readByte != -1 81 | } catch (e: SocketException) { 82 | // the socket couldn't be read, perhaps because the connection was reset or some other error. it cannot be read. 83 | false 84 | } catch (e: SocketTimeoutException) { 85 | // the read timed out, which means the socket is still connected but there's no data on it 86 | true 87 | } 88 | 89 | } 90 | 91 | /** 92 | * Opens a socket to the specified proxy server with a 3s timeout. The socket should be closed after it has been used. 93 | * 94 | * @param proxyServer proxy server to open the socket to 95 | * @return the new socket 96 | * @throws IOException 97 | */ 98 | @Throws(IOException::class) 99 | fun getSocketToProxyServer(proxyServer: ReverseProxyServer): Socket { 100 | val socket = Socket() 101 | socket.connect(InetSocketAddress("localhost", proxyServer.getListenAddress().port), 1000) 102 | socket.soTimeout = 300000 103 | return socket 104 | } 105 | } 106 | --------------------------------------------------------------------------------