View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
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   * Example of embedded HTTP/1.1 reverse proxy using classic I/O.
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 }