1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.http.examples;
29
30 import java.io.IOException;
31 import java.net.SocketException;
32 import java.net.SocketTimeoutException;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.Set;
38
39 import org.apache.hc.core5.http.ClassicHttpRequest;
40 import org.apache.hc.core5.http.ClassicHttpResponse;
41 import org.apache.hc.core5.http.ConnectionClosedException;
42 import org.apache.hc.core5.http.ExceptionListener;
43 import org.apache.hc.core5.http.Header;
44 import org.apache.hc.core5.http.HttpConnection;
45 import org.apache.hc.core5.http.HttpException;
46 import org.apache.hc.core5.http.HttpHeaders;
47 import org.apache.hc.core5.http.HttpHost;
48 import org.apache.hc.core5.http.HttpRequest;
49 import org.apache.hc.core5.http.HttpResponse;
50 import org.apache.hc.core5.http.impl.Http1StreamListener;
51 import org.apache.hc.core5.http.impl.bootstrap.HttpRequester;
52 import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
53 import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap;
54 import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
55 import org.apache.hc.core5.http.io.HttpRequestHandler;
56 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
57 import org.apache.hc.core5.http.protocol.HttpContext;
58 import org.apache.hc.core5.http.protocol.HttpCoreContext;
59 import org.apache.hc.core5.io.CloseMode;
60 import org.apache.hc.core5.pool.ConnPoolListener;
61 import org.apache.hc.core5.pool.ConnPoolStats;
62 import org.apache.hc.core5.pool.PoolStats;
63 import org.apache.hc.core5.util.TextUtils;
64 import org.apache.hc.core5.util.TimeValue;
65 import org.apache.hc.core5.util.Timeout;
66
67
68
69
70 public class ClassicReverseProxyExample {
71
72 public static void main(final String[] args) throws Exception {
73 if (args.length < 1) {
74 System.out.println("Usage: <hostname[:port]> [listener port]");
75 System.exit(1);
76 }
77 final HttpHost targetHost = HttpHost.create(args[0]);
78 int port = 8080;
79 if (args.length > 1) {
80 port = Integer.parseInt(args[1]);
81 }
82
83 System.out.println("Reverse proxy to " + targetHost);
84
85 final HttpRequester requester = RequesterBootstrap.bootstrap()
86 .setStreamListener(new Http1StreamListener() {
87
88 @Override
89 public void onRequestHead(final HttpConnection connection, final HttpRequest request) {
90 System.out.println("[proxy->origin] " + Thread.currentThread() + " " +
91 request.getMethod() + " " + request.getRequestUri());
92 }
93
94 @Override
95 public void onResponseHead(final HttpConnection connection, final HttpResponse response) {
96 System.out.println("[proxy<-origin] " + Thread.currentThread() + " status " + response.getCode());
97 }
98
99 @Override
100 public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) {
101 System.out.println("[proxy<-origin] " + Thread.currentThread() + " exchange completed; " +
102 "connection " + (keepAlive ? "kept alive" : "cannot be kept alive"));
103 }
104
105 })
106 .setConnPoolListener(new ConnPoolListener<HttpHost>() {
107
108 @Override
109 public void onLease(final HttpHost route, final ConnPoolStats<HttpHost> connPoolStats) {
110 final StringBuilder buf = new StringBuilder();
111 buf.append("[proxy->origin] ").append(Thread.currentThread()).append(" connection leased ").append(route);
112 System.out.println(buf);
113 }
114
115 @Override
116 public void onRelease(final HttpHost route, final ConnPoolStats<HttpHost> connPoolStats) {
117 final StringBuilder buf = new StringBuilder();
118 buf.append("[proxy->origin] ").append(Thread.currentThread()).append(" connection released ").append(route);
119 final PoolStats totals = connPoolStats.getTotalStats();
120 buf.append("; total kept alive: ").append(totals.getAvailable()).append("; ");
121 buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable());
122 buf.append(" of ").append(totals.getMax());
123 System.out.println(buf);
124 }
125
126 })
127 .create();
128
129 final HttpServer server = ServerBootstrap.bootstrap()
130 .setListenerPort(port)
131 .setStreamListener(new Http1StreamListener() {
132
133 @Override
134 public void onRequestHead(final HttpConnection connection, final HttpRequest request) {
135 System.out.println("[client->proxy] " + Thread.currentThread() + " " +
136 request.getMethod() + " " + request.getRequestUri());
137 }
138
139 @Override
140 public void onResponseHead(final HttpConnection connection, final HttpResponse response) {
141 System.out.println("[client<-proxy] " + Thread.currentThread() + " status " + response.getCode());
142 }
143
144 @Override
145 public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) {
146 System.out.println("[client<-proxy] " + Thread.currentThread() + " exchange completed; " +
147 "connection " + (keepAlive ? "kept alive" : "cannot be kept alive"));
148 }
149
150 })
151 .setExceptionListener(new ExceptionListener() {
152
153 @Override
154 public void onError(final Exception ex) {
155 if (ex instanceof SocketException) {
156 System.out.println("[client->proxy] " + Thread.currentThread() + " " + ex.getMessage());
157 } else {
158 System.out.println("[client->proxy] " + Thread.currentThread() + " " + ex.getMessage());
159 ex.printStackTrace(System.out);
160 }
161 }
162
163 @Override
164 public void onError(final HttpConnection connection, final Exception ex) {
165 if (ex instanceof SocketTimeoutException) {
166 System.out.println("[client->proxy] " + Thread.currentThread() + " time out");
167 } else if (ex instanceof SocketException || ex instanceof ConnectionClosedException) {
168 System.out.println("[client->proxy] " + Thread.currentThread() + " " + ex.getMessage());
169 } else {
170 System.out.println("[client->proxy] " + Thread.currentThread() + " " + ex.getMessage());
171 ex.printStackTrace(System.out);
172 }
173 }
174
175 })
176 .register("*", new ProxyHandler(targetHost, requester))
177 .create();
178
179 server.start();
180 Runtime.getRuntime().addShutdownHook(new Thread(() -> {
181 server.close(CloseMode.GRACEFUL);
182 requester.close(CloseMode.GRACEFUL);
183 }));
184
185 System.out.println("Listening on port " + port);
186 server.awaitTermination(TimeValue.MAX_VALUE);
187 }
188
189 private final static Set<String> HOP_BY_HOP = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
190 TextUtils.toLowerCase(HttpHeaders.HOST),
191 TextUtils.toLowerCase(HttpHeaders.CONTENT_LENGTH),
192 TextUtils.toLowerCase(HttpHeaders.TRANSFER_ENCODING),
193 TextUtils.toLowerCase(HttpHeaders.CONNECTION),
194 TextUtils.toLowerCase(HttpHeaders.KEEP_ALIVE),
195 TextUtils.toLowerCase(HttpHeaders.PROXY_AUTHENTICATE),
196 TextUtils.toLowerCase(HttpHeaders.TE),
197 TextUtils.toLowerCase(HttpHeaders.TRAILER),
198 TextUtils.toLowerCase(HttpHeaders.UPGRADE))));
199
200
201 static class ProxyHandler implements HttpRequestHandler {
202
203 private final HttpHost targetHost;
204 private final HttpRequester requester;
205
206 public ProxyHandler(
207 final HttpHost targetHost,
208 final HttpRequester requester) {
209 super();
210 this.targetHost = targetHost;
211 this.requester = requester;
212 }
213
214 @Override
215 public void handle(
216 final ClassicHttpRequest incomingRequest,
217 final ClassicHttpResponse outgoingResponse,
218 final HttpContext serverContext) throws HttpException, IOException {
219
220 final HttpCoreContext clientContext = HttpCoreContext.create();
221 final ClassicHttpRequest outgoingRequest = new BasicClassicHttpRequest(
222 incomingRequest.getMethod(),
223 targetHost,
224 incomingRequest.getPath());
225 for (final Iterator<Header> it = incomingRequest.headerIterator(); it.hasNext(); ) {
226 final Header header = it.next();
227 if (!HOP_BY_HOP.contains(TextUtils.toLowerCase(header.getName()))) {
228 outgoingRequest.addHeader(header);
229 }
230 }
231 outgoingRequest.setEntity(incomingRequest.getEntity());
232 final ClassicHttpResponse incomingResponse = requester.execute(
233 targetHost, outgoingRequest, Timeout.ofMinutes(1), clientContext);
234 outgoingResponse.setCode(incomingResponse.getCode());
235 for (final Iterator<Header> it = incomingResponse.headerIterator(); it.hasNext(); ) {
236 final Header header = it.next();
237 if (!HOP_BY_HOP.contains(TextUtils.toLowerCase(header.getName()))) {
238 outgoingResponse.addHeader(header);
239 }
240 }
241 outgoingResponse.setEntity(incomingResponse.getEntity());
242 }
243 }
244
245 }