├── .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 |
9 |
10 |
11 |
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 |
--------------------------------------------------------------------------------