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.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   * 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             @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 }