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.Locale;
38 import java.util.Set;
39
40 import org.apache.hc.core5.http.ClassicHttpRequest;
41 import org.apache.hc.core5.http.ClassicHttpResponse;
42 import org.apache.hc.core5.http.ConnectionClosedException;
43 import org.apache.hc.core5.http.ExceptionListener;
44 import org.apache.hc.core5.http.Header;
45 import org.apache.hc.core5.http.HttpConnection;
46 import org.apache.hc.core5.http.HttpException;
47 import org.apache.hc.core5.http.HttpHeaders;
48 import org.apache.hc.core5.http.HttpHost;
49 import org.apache.hc.core5.http.HttpRequest;
50 import org.apache.hc.core5.http.HttpResponse;
51 import org.apache.hc.core5.http.impl.Http1StreamListener;
52 import org.apache.hc.core5.http.impl.bootstrap.HttpRequester;
53 import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
54 import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap;
55 import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
56 import org.apache.hc.core5.http.io.HttpRequestHandler;
57 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
58 import org.apache.hc.core5.http.protocol.HttpContext;
59 import org.apache.hc.core5.http.protocol.HttpCoreContext;
60 import org.apache.hc.core5.io.CloseMode;
61 import org.apache.hc.core5.pool.ConnPoolListener;
62 import org.apache.hc.core5.pool.ConnPoolStats;
63 import org.apache.hc.core5.pool.PoolStats;
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 @Override
182 public void run() {
183 server.close(CloseMode.GRACEFUL);
184 requester.close(CloseMode.GRACEFUL);
185 }
186 });
187
188 System.out.println("Listening on port " + port);
189 server.awaitTermination(TimeValue.MAX_VALUE);
190 }
191
192 private final static Set<String> HOP_BY_HOP = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
193 HttpHeaders.HOST.toLowerCase(Locale.ROOT),
194 HttpHeaders.CONTENT_LENGTH.toLowerCase(Locale.ROOT),
195 HttpHeaders.TRANSFER_ENCODING.toLowerCase(Locale.ROOT),
196 HttpHeaders.CONNECTION.toLowerCase(Locale.ROOT),
197 HttpHeaders.KEEP_ALIVE.toLowerCase(Locale.ROOT),
198 HttpHeaders.PROXY_AUTHENTICATE.toLowerCase(Locale.ROOT),
199 HttpHeaders.TE.toLowerCase(Locale.ROOT),
200 HttpHeaders.TRAILER.toLowerCase(Locale.ROOT),
201 HttpHeaders.UPGRADE.toLowerCase(Locale.ROOT))));
202
203
204 static class ProxyHandler implements HttpRequestHandler {
205
206 private final HttpHost targetHost;
207 private final HttpRequester requester;
208
209 public ProxyHandler(
210 final HttpHost targetHost,
211 final HttpRequester requester) {
212 super();
213 this.targetHost = targetHost;
214 this.requester = requester;
215 }
216
217 @Override
218 public void handle(
219 final ClassicHttpRequest incomingRequest,
220 final ClassicHttpResponse outgoingResponse,
221 final HttpContext serverContext) throws HttpException, IOException {
222
223 final HttpCoreContext clientContext = HttpCoreContext.create();
224 final ClassicHttpRequest outgoingRequest = new BasicClassicHttpRequest(
225 incomingRequest.getMethod(),
226 targetHost,
227 incomingRequest.getPath());
228 for (final Iterator<Header> it = incomingRequest.headerIterator(); it.hasNext(); ) {
229 final Header header = it.next();
230 if (!HOP_BY_HOP.contains(header.getName().toLowerCase(Locale.ROOT))) {
231 outgoingRequest.addHeader(header);
232 }
233 }
234 outgoingRequest.setEntity(incomingRequest.getEntity());
235 final ClassicHttpResponse incomingResponse = requester.execute(
236 targetHost, outgoingRequest, Timeout.ofMinutes(1), clientContext);
237 outgoingResponse.setCode(incomingResponse.getCode());
238 for (final Iterator<Header> it = incomingResponse.headerIterator(); it.hasNext(); ) {
239 final Header header = it.next();
240 if (!HOP_BY_HOP.contains(header.getName().toLowerCase(Locale.ROOT))) {
241 outgoingResponse.addHeader(header);
242 }
243 }
244 outgoingResponse.setEntity(incomingResponse.getEntity());
245 }
246 }
247
248 }