~ubuntu-branches/ubuntu/trusty/httpcomponents-core/trusty

« back to all changes in this revision

Viewing changes to httpcore-nio/src/examples/org/apache/http/examples/nio/NHttpReverseProxy.java

  • Committer: Bazaar Package Importer
  • Author(s): David Paleino
  • Date: 2010-06-12 08:37:34 UTC
  • Revision ID: james.westby@ubuntu.com-20100612083734-1y8kp6qm4sjk60az
Tags: upstream-4.0.1
ImportĀ upstreamĀ versionĀ 4.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/httpcore/tags/4.0.1/httpcore-nio/src/examples/org/apache/http/examples/nio/NHttpReverseProxy.java $
 
3
 * $Revision: 744516 $
 
4
 * $Date: 2009-02-14 17:38:14 +0100 (Sat, 14 Feb 2009) $
 
5
 *
 
6
 * ====================================================================
 
7
 * Licensed to the Apache Software Foundation (ASF) under one
 
8
 * or more contributor license agreements.  See the NOTICE file
 
9
 * distributed with this work for additional information
 
10
 * regarding copyright ownership.  The ASF licenses this file
 
11
 * to you under the Apache License, Version 2.0 (the
 
12
 * "License"); you may not use this file except in compliance
 
13
 * with the License.  You may obtain a copy of the License at
 
14
 *
 
15
 *   http://www.apache.org/licenses/LICENSE-2.0
 
16
 *
 
17
 * Unless required by applicable law or agreed to in writing,
 
18
 * software distributed under the License is distributed on an
 
19
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 
20
 * KIND, either express or implied.  See the License for the
 
21
 * specific language governing permissions and limitations
 
22
 * under the License.
 
23
 * ====================================================================
 
24
 *
 
25
 * This software consists of voluntary contributions made by many
 
26
 * individuals on behalf of the Apache Software Foundation.  For more
 
27
 * information on the Apache Software Foundation, please see
 
28
 * <http://www.apache.org/>.
 
29
 *
 
30
 */
 
31
package org.apache.http.examples.nio;
 
32
 
 
33
import java.io.IOException;
 
34
import java.io.InterruptedIOException;
 
35
import java.net.InetSocketAddress;
 
36
import java.nio.ByteBuffer;
 
37
 
 
38
import org.apache.http.ConnectionReuseStrategy;
 
39
import org.apache.http.HttpConnection;
 
40
import org.apache.http.HttpEntityEnclosingRequest;
 
41
import org.apache.http.HttpException;
 
42
import org.apache.http.HttpHost;
 
43
import org.apache.http.HttpRequest;
 
44
import org.apache.http.HttpResponse;
 
45
import org.apache.http.HttpResponseFactory;
 
46
import org.apache.http.HttpStatus;
 
47
import org.apache.http.HttpVersion;
 
48
import org.apache.http.ProtocolVersion;
 
49
import org.apache.http.impl.DefaultConnectionReuseStrategy;
 
50
import org.apache.http.impl.DefaultHttpResponseFactory;
 
51
import org.apache.http.impl.nio.DefaultClientIOEventDispatch;
 
52
import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
 
53
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
 
54
import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
 
55
import org.apache.http.nio.ContentDecoder;
 
56
import org.apache.http.nio.ContentEncoder;
 
57
import org.apache.http.nio.IOControl;
 
58
import org.apache.http.nio.NHttpClientConnection;
 
59
import org.apache.http.nio.NHttpClientHandler;
 
60
import org.apache.http.nio.NHttpConnection;
 
61
import org.apache.http.nio.NHttpServerConnection;
 
62
import org.apache.http.nio.NHttpServiceHandler;
 
63
import org.apache.http.nio.reactor.ConnectingIOReactor;
 
64
import org.apache.http.nio.reactor.IOEventDispatch;
 
65
import org.apache.http.nio.reactor.ListeningIOReactor;
 
66
import org.apache.http.params.BasicHttpParams;
 
67
import org.apache.http.params.CoreConnectionPNames;
 
68
import org.apache.http.params.DefaultedHttpParams;
 
69
import org.apache.http.params.HttpParams;
 
70
import org.apache.http.params.CoreProtocolPNames;
 
71
import org.apache.http.protocol.BasicHttpProcessor;
 
72
import org.apache.http.protocol.HTTP;
 
73
import org.apache.http.protocol.HttpContext;
 
74
import org.apache.http.protocol.ExecutionContext;
 
75
import org.apache.http.protocol.HttpProcessor;
 
76
import org.apache.http.protocol.RequestConnControl;
 
77
import org.apache.http.protocol.RequestContent;
 
78
import org.apache.http.protocol.RequestExpectContinue;
 
79
import org.apache.http.protocol.RequestTargetHost;
 
80
import org.apache.http.protocol.RequestUserAgent;
 
81
import org.apache.http.protocol.ResponseConnControl;
 
82
import org.apache.http.protocol.ResponseContent;
 
83
import org.apache.http.protocol.ResponseDate;
 
84
import org.apache.http.protocol.ResponseServer;
 
85
 
 
86
/**
 
87
 * Rudimentary HTTP/1.1 reverse proxy based on the non-blocking I/O model.
 
88
 * <p>
 
89
 * Please note the purpose of this application is demonstrate the usage of HttpCore APIs.
 
90
 * It is NOT intended to demonstrate the most efficient way of building an HTTP reverse proxy. 
 
91
 * 
 
92
 *
 
93
 * @version $Revision: 744516 $
 
94
 */
 
95
public class NHttpReverseProxy {
 
96
 
 
97
    public static void main(String[] args) throws Exception {
 
98
        
 
99
        if (args.length < 1) {
 
100
            System.out.println("Usage: NHttpReverseProxy <hostname> [port]");
 
101
            System.exit(1);
 
102
        }
 
103
        String hostname = args[0];
 
104
        int port = 80;
 
105
        if (args.length > 1) {
 
106
            port = Integer.parseInt(args[1]);
 
107
        }
 
108
        
 
109
        // Target host
 
110
        HttpHost targetHost = new HttpHost(hostname, port); 
 
111
        
 
112
        HttpParams params = new BasicHttpParams();
 
113
        params
 
114
            .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 30000)
 
115
            .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
 
116
            .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
 
117
            .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
 
118
            .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "HttpComponents/1.1")
 
119
            .setParameter(CoreProtocolPNames.USER_AGENT, "HttpComponents/1.1");
 
120
 
 
121
        final ConnectingIOReactor connectingIOReactor = new DefaultConnectingIOReactor(
 
122
                1, params);
 
123
 
 
124
        final ListeningIOReactor listeningIOReactor = new DefaultListeningIOReactor(
 
125
                1, params);
 
126
        
 
127
        BasicHttpProcessor originServerProc = new BasicHttpProcessor();
 
128
        originServerProc.addInterceptor(new RequestContent());
 
129
        originServerProc.addInterceptor(new RequestTargetHost());
 
130
        originServerProc.addInterceptor(new RequestConnControl());
 
131
        originServerProc.addInterceptor(new RequestUserAgent());
 
132
        originServerProc.addInterceptor(new RequestExpectContinue());
 
133
        
 
134
        BasicHttpProcessor clientProxyProcessor = new BasicHttpProcessor();
 
135
        clientProxyProcessor.addInterceptor(new ResponseDate());
 
136
        clientProxyProcessor.addInterceptor(new ResponseServer());
 
137
        clientProxyProcessor.addInterceptor(new ResponseContent());
 
138
        clientProxyProcessor.addInterceptor(new ResponseConnControl());
 
139
        
 
140
        NHttpClientHandler connectingHandler = new ConnectingHandler(
 
141
                originServerProc,
 
142
                new DefaultConnectionReuseStrategy(),
 
143
                params);
 
144
 
 
145
        NHttpServiceHandler listeningHandler = new ListeningHandler(
 
146
                targetHost,
 
147
                connectingIOReactor,
 
148
                clientProxyProcessor, 
 
149
                new DefaultHttpResponseFactory(),
 
150
                new DefaultConnectionReuseStrategy(),
 
151
                params);
 
152
        
 
153
        final IOEventDispatch connectingEventDispatch = new DefaultClientIOEventDispatch(
 
154
                connectingHandler, params);
 
155
 
 
156
        final IOEventDispatch listeningEventDispatch = new DefaultServerIOEventDispatch(
 
157
                listeningHandler, params);
 
158
        
 
159
        Thread t = new Thread(new Runnable() {
 
160
            
 
161
            public void run() {
 
162
                try {
 
163
                    connectingIOReactor.execute(connectingEventDispatch);
 
164
                } catch (InterruptedIOException ex) {
 
165
                    System.err.println("Interrupted");
 
166
                } catch (IOException e) {
 
167
                    System.err.println("I/O error: " + e.getMessage());
 
168
                }
 
169
            }
 
170
            
 
171
        });
 
172
        t.start();
 
173
        
 
174
        try {
 
175
            listeningIOReactor.listen(new InetSocketAddress(8888));
 
176
            listeningIOReactor.execute(listeningEventDispatch);
 
177
        } catch (InterruptedIOException ex) {
 
178
            System.err.println("Interrupted");
 
179
        } catch (IOException e) {
 
180
            System.err.println("I/O error: " + e.getMessage());
 
181
        }
 
182
    }
 
183
 
 
184
    static class ListeningHandler implements NHttpServiceHandler {
 
185
 
 
186
        private final HttpHost targetHost;
 
187
        private final ConnectingIOReactor connectingIOReactor;    
 
188
        private final HttpProcessor httpProcessor;
 
189
        private final HttpResponseFactory responseFactory;
 
190
        private final ConnectionReuseStrategy connStrategy;
 
191
        private final HttpParams params;
 
192
        
 
193
        public ListeningHandler(
 
194
                final HttpHost targetHost,
 
195
                final ConnectingIOReactor connectingIOReactor,
 
196
                final HttpProcessor httpProcessor, 
 
197
                final HttpResponseFactory responseFactory,
 
198
                final ConnectionReuseStrategy connStrategy,
 
199
                final HttpParams params) {
 
200
            super();
 
201
            this.targetHost = targetHost;
 
202
            this.connectingIOReactor = connectingIOReactor;
 
203
            this.httpProcessor = httpProcessor;
 
204
            this.connStrategy = connStrategy;
 
205
            this.responseFactory = responseFactory;
 
206
            this.params = params;
 
207
        }
 
208
 
 
209
        public void connected(final NHttpServerConnection conn) {
 
210
            System.out.println(conn + " [client->proxy] conn open");
 
211
 
 
212
            ProxyTask proxyTask = new ProxyTask();
 
213
            
 
214
            synchronized (proxyTask) {
 
215
 
 
216
                // Initialize connection state
 
217
                proxyTask.setTarget(this.targetHost);
 
218
                proxyTask.setClientIOControl(conn);
 
219
                proxyTask.setClientState(ConnState.CONNECTED);
 
220
                
 
221
                HttpContext context = conn.getContext();
 
222
                context.setAttribute(ProxyTask.ATTRIB, proxyTask);
 
223
                
 
224
                InetSocketAddress address = new InetSocketAddress(
 
225
                        this.targetHost.getHostName(), 
 
226
                        this.targetHost.getPort()); 
 
227
                
 
228
                this.connectingIOReactor.connect(
 
229
                        address, 
 
230
                        null, 
 
231
                        proxyTask, 
 
232
                        null);            
 
233
            }
 
234
        }
 
235
 
 
236
        public void requestReceived(final NHttpServerConnection conn) {
 
237
            System.out.println(conn + " [client->proxy] request received");
 
238
 
 
239
            HttpContext context = conn.getContext();
 
240
            ProxyTask proxyTask = (ProxyTask) context.getAttribute(ProxyTask.ATTRIB);
 
241
 
 
242
            synchronized (proxyTask) {
 
243
                ConnState connState = proxyTask.getClientState();
 
244
                if (connState != ConnState.IDLE
 
245
                        && connState != ConnState.CONNECTED) {
 
246
                    throw new IllegalStateException("Illegal client connection state: " + connState);
 
247
                }
 
248
 
 
249
                try {
 
250
 
 
251
                    HttpRequest request = conn.getHttpRequest();
 
252
                    
 
253
                    System.out.println(conn + " [client->proxy] >> " + request.getRequestLine());
 
254
                    
 
255
                    ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
 
256
                    if (!ver.lessEquals(HttpVersion.HTTP_1_1)) {
 
257
                        // Downgrade protocol version if greater than HTTP/1.1 
 
258
                        ver = HttpVersion.HTTP_1_1;
 
259
                    }
 
260
                    
 
261
                    // Update connection state
 
262
                    proxyTask.setRequest(request);
 
263
                    proxyTask.setClientState(ConnState.REQUEST_RECEIVED);
 
264
                    
 
265
                    // See if the client expects a 100-Continue
 
266
                    if (request instanceof HttpEntityEnclosingRequest) {
 
267
                        if (((HttpEntityEnclosingRequest) request).expectContinue()) {
 
268
                            HttpResponse ack = this.responseFactory.newHttpResponse(
 
269
                                    ver, 
 
270
                                    HttpStatus.SC_CONTINUE, 
 
271
                                    context);
 
272
                            conn.submitResponse(ack);
 
273
                        }
 
274
                    } else {
 
275
                        // No request content expected. Suspend client input
 
276
                        conn.suspendInput();
 
277
                    }
 
278
                    
 
279
                    // If there is already a connection to the origin server
 
280
                    // make sure origin output is active
 
281
                    if (proxyTask.getOriginIOControl() != null) {
 
282
                        proxyTask.getOriginIOControl().requestOutput();
 
283
                    }
 
284
                    
 
285
                } catch (IOException ex) {
 
286
                    shutdownConnection(conn);
 
287
                } catch (HttpException ex) {
 
288
                    shutdownConnection(conn);
 
289
                }
 
290
            }
 
291
        }
 
292
 
 
293
        public void inputReady(final NHttpServerConnection conn, final ContentDecoder decoder) {
 
294
            System.out.println(conn + " [client->proxy] input ready");
 
295
 
 
296
            HttpContext context = conn.getContext();
 
297
            ProxyTask proxyTask = (ProxyTask) context.getAttribute(ProxyTask.ATTRIB);
 
298
 
 
299
            synchronized (proxyTask) {
 
300
                ConnState connState = proxyTask.getClientState();
 
301
                if (connState != ConnState.REQUEST_RECEIVED
 
302
                        && connState != ConnState.REQUEST_BODY_STREAM) {
 
303
                    throw new IllegalStateException("Illegal client connection state: " + connState);
 
304
                }
 
305
                
 
306
                try {
 
307
 
 
308
                    ByteBuffer dst = proxyTask.getInBuffer();
 
309
                    int bytesRead = decoder.read(dst);
 
310
                    System.out.println(conn + " [client->proxy] " + bytesRead + " bytes read");
 
311
                    System.out.println(conn + " [client->proxy] " + decoder);
 
312
                    if (!dst.hasRemaining()) {
 
313
                        // Input buffer is full. Suspend client input
 
314
                        // until the origin handler frees up some space in the buffer
 
315
                        conn.suspendInput();
 
316
                    }
 
317
                    // If there is some content in the input buffer make sure origin 
 
318
                    // output is active
 
319
                    if (dst.position() > 0) {
 
320
                        if (proxyTask.getOriginIOControl() != null) {
 
321
                            proxyTask.getOriginIOControl().requestOutput();
 
322
                        }
 
323
                    }
 
324
 
 
325
                    if (decoder.isCompleted()) {
 
326
                        System.out.println(conn + " [client->proxy] request body received");
 
327
                        // Update connection state
 
328
                        proxyTask.setClientState(ConnState.REQUEST_BODY_DONE);
 
329
                        // Suspend client input
 
330
                        conn.suspendInput();
 
331
                    } else {
 
332
                        proxyTask.setClientState(ConnState.REQUEST_BODY_STREAM);
 
333
                    }
 
334
                    
 
335
                } catch (IOException ex) {
 
336
                    shutdownConnection(conn);
 
337
                }
 
338
            }
 
339
        }
 
340
 
 
341
        public void responseReady(final NHttpServerConnection conn) {
 
342
            System.out.println(conn + " [client<-proxy] response ready");
 
343
 
 
344
            HttpContext context = conn.getContext();
 
345
            ProxyTask proxyTask = (ProxyTask) context.getAttribute(ProxyTask.ATTRIB);
 
346
 
 
347
            synchronized (proxyTask) {
 
348
                ConnState connState = proxyTask.getClientState();
 
349
                if (connState == ConnState.IDLE) {
 
350
                    // Response not available 
 
351
                    return;
 
352
                }
 
353
                if (connState != ConnState.REQUEST_RECEIVED
 
354
                        && connState != ConnState.REQUEST_BODY_DONE) {
 
355
                    throw new IllegalStateException("Illegal client connection state: " + connState);
 
356
                }
 
357
 
 
358
                try {
 
359
 
 
360
                    HttpRequest request = proxyTask.getRequest();
 
361
                    HttpResponse response = proxyTask.getResponse();
 
362
                    if (response == null) {
 
363
                        throw new IllegalStateException("HTTP request is null");
 
364
                    }
 
365
                    // Remove hop-by-hop headers
 
366
                    response.removeHeaders(HTTP.CONTENT_LEN);
 
367
                    response.removeHeaders(HTTP.TRANSFER_ENCODING);
 
368
                    response.removeHeaders(HTTP.CONN_DIRECTIVE);
 
369
                    response.removeHeaders("Keep-Alive");
 
370
                    response.removeHeaders("Proxy-Authenticate");
 
371
                    response.removeHeaders("Proxy-Authorization");
 
372
                    response.removeHeaders("TE");
 
373
                    response.removeHeaders("Trailers");
 
374
                    response.removeHeaders("Upgrade");
 
375
                    
 
376
                    response.setParams(
 
377
                            new DefaultedHttpParams(response.getParams(), this.params));
 
378
 
 
379
                    // Close client connection if the connection to the target 
 
380
                    // is no longer active / open
 
381
                    if (proxyTask.getOriginState().compareTo(ConnState.CLOSING) >= 0) {
 
382
                        response.addHeader(HTTP.CONN_DIRECTIVE, "Close");    
 
383
                    }
 
384
                    
 
385
                    // Pre-process HTTP request
 
386
                    context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
 
387
                    context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
 
388
                    this.httpProcessor.process(response, context);
 
389
                    
 
390
                    conn.submitResponse(response);
 
391
 
 
392
                    proxyTask.setClientState(ConnState.RESPONSE_SENT);
 
393
 
 
394
                    System.out.println(conn + " [client<-proxy] << " + response.getStatusLine());
 
395
                    
 
396
                    if (!canResponseHaveBody(request, response)) {
 
397
                        conn.resetInput();
 
398
                        if (!this.connStrategy.keepAlive(response, context)) {
 
399
                            System.out.println(conn + " [client<-proxy] close connection");
 
400
                            proxyTask.setClientState(ConnState.CLOSING);
 
401
                            conn.close();
 
402
                        } else {
 
403
                            // Reset connection state
 
404
                            proxyTask.reset();
 
405
                            conn.requestInput();
 
406
                            // Ready to deal with a new request
 
407
                        }
 
408
                    }
 
409
                    
 
410
                } catch (IOException ex) {
 
411
                    shutdownConnection(conn);
 
412
                } catch (HttpException ex) {
 
413
                    shutdownConnection(conn);
 
414
                }
 
415
            }
 
416
        }
 
417
        
 
418
        private boolean canResponseHaveBody(
 
419
                final HttpRequest request, final HttpResponse response) {
 
420
 
 
421
            if (request != null && "HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
 
422
                return false;
 
423
            }
 
424
            
 
425
            int status = response.getStatusLine().getStatusCode(); 
 
426
            return status >= HttpStatus.SC_OK 
 
427
                && status != HttpStatus.SC_NO_CONTENT 
 
428
                && status != HttpStatus.SC_NOT_MODIFIED
 
429
                && status != HttpStatus.SC_RESET_CONTENT; 
 
430
        }
 
431
        
 
432
        public void outputReady(final NHttpServerConnection conn, final ContentEncoder encoder) {
 
433
            System.out.println(conn + " [client<-proxy] output ready");
 
434
 
 
435
            HttpContext context = conn.getContext();
 
436
            ProxyTask proxyTask = (ProxyTask) context.getAttribute(ProxyTask.ATTRIB);
 
437
 
 
438
            synchronized (proxyTask) {
 
439
                ConnState connState = proxyTask.getClientState();
 
440
                if (connState != ConnState.RESPONSE_SENT
 
441
                        && connState != ConnState.RESPONSE_BODY_STREAM) {
 
442
                    throw new IllegalStateException("Illegal client connection state: " + connState);
 
443
                }
 
444
 
 
445
                HttpResponse response = proxyTask.getResponse();
 
446
                if (response == null) {
 
447
                    throw new IllegalStateException("HTTP request is null");
 
448
                }
 
449
                
 
450
                try {
 
451
 
 
452
                    ByteBuffer src = proxyTask.getOutBuffer();
 
453
                    src.flip();
 
454
                    int bytesWritten = encoder.write(src);
 
455
                    System.out.println(conn + " [client<-proxy] " + bytesWritten + " bytes written");
 
456
                    System.out.println(conn + " [client<-proxy] " + encoder);
 
457
                    src.compact();
 
458
 
 
459
                    if (src.position() == 0) {
 
460
 
 
461
                        if (proxyTask.getOriginState() == ConnState.RESPONSE_BODY_DONE) {
 
462
                            encoder.complete();
 
463
                        } else {
 
464
                            // Input output is empty. Wait until the origin handler 
 
465
                            // fills up the buffer
 
466
                            conn.suspendOutput();
 
467
                        }
 
468
                    }
 
469
 
 
470
                    // Update connection state
 
471
                    if (encoder.isCompleted()) {
 
472
                        System.out.println(conn + " [proxy] response body sent");
 
473
                        proxyTask.setClientState(ConnState.RESPONSE_BODY_DONE);
 
474
                        if (!this.connStrategy.keepAlive(response, context)) {
 
475
                            System.out.println(conn + " [client<-proxy] close connection");
 
476
                            proxyTask.setClientState(ConnState.CLOSING);
 
477
                            conn.close();
 
478
                        } else {
 
479
                            // Reset connection state
 
480
                            proxyTask.reset();
 
481
                            conn.requestInput();
 
482
                            // Ready to deal with a new request
 
483
                        }
 
484
                    } else {
 
485
                        proxyTask.setClientState(ConnState.RESPONSE_BODY_STREAM);
 
486
                        // Make sure origin input is active
 
487
                        proxyTask.getOriginIOControl().requestInput();
 
488
                    }
 
489
                    
 
490
                } catch (IOException ex) {
 
491
                    shutdownConnection(conn);
 
492
                } 
 
493
            }
 
494
        }
 
495
 
 
496
        public void closed(final NHttpServerConnection conn) {
 
497
            System.out.println(conn + " [client->proxy] conn closed");
 
498
            HttpContext context = conn.getContext();
 
499
            ProxyTask proxyTask = (ProxyTask) context.getAttribute(ProxyTask.ATTRIB);
 
500
 
 
501
            if (proxyTask != null) {
 
502
                synchronized (proxyTask) {
 
503
                    proxyTask.setClientState(ConnState.CLOSED);
 
504
                }
 
505
            }
 
506
        }
 
507
 
 
508
        public void exception(final NHttpServerConnection conn, final HttpException httpex) {
 
509
            System.out.println(conn + " [client->proxy] HTTP error: " + httpex.getMessage());
 
510
 
 
511
            if (conn.isResponseSubmitted()) {
 
512
                shutdownConnection(conn);
 
513
                return;
 
514
            }
 
515
            
 
516
            HttpContext context = conn.getContext();
 
517
 
 
518
            try {
 
519
                HttpResponse response = this.responseFactory.newHttpResponse(
 
520
                        HttpVersion.HTTP_1_0, HttpStatus.SC_BAD_REQUEST, context);
 
521
                response.setParams(
 
522
                        new DefaultedHttpParams(this.params, response.getParams()));
 
523
                response.addHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE);
 
524
                // Pre-process HTTP request
 
525
                context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
 
526
                context.setAttribute(ExecutionContext.HTTP_REQUEST, null);
 
527
                this.httpProcessor.process(response, context);
 
528
                
 
529
                conn.submitResponse(response);
 
530
 
 
531
                conn.close();
 
532
                
 
533
            } catch (IOException ex) {
 
534
                shutdownConnection(conn);
 
535
            } catch (HttpException ex) {
 
536
                shutdownConnection(conn);
 
537
            }
 
538
        }
 
539
 
 
540
        public void exception(final NHttpServerConnection conn, final IOException ex) {
 
541
            shutdownConnection(conn);
 
542
            System.out.println(conn + " [client->proxy] I/O error: " + ex.getMessage());
 
543
        }
 
544
        
 
545
        public void timeout(final NHttpServerConnection conn) {
 
546
            System.out.println(conn + " [client->proxy] timeout");
 
547
            closeConnection(conn);
 
548
        }
 
549
        
 
550
        private void shutdownConnection(final NHttpConnection conn) {
 
551
            try {
 
552
                conn.shutdown();
 
553
            } catch (IOException ignore) {
 
554
            }
 
555
        }
 
556
 
 
557
        private void closeConnection(final NHttpConnection conn) {
 
558
            try {
 
559
                conn.close();
 
560
            } catch (IOException ignore) {
 
561
            }
 
562
        }
 
563
 
 
564
    }
 
565
    
 
566
    static class ConnectingHandler implements NHttpClientHandler {
 
567
 
 
568
        private final HttpProcessor httpProcessor;
 
569
        private final ConnectionReuseStrategy connStrategy;
 
570
        private final HttpParams params;
 
571
        
 
572
        public ConnectingHandler(
 
573
                final HttpProcessor httpProcessor, 
 
574
                final ConnectionReuseStrategy connStrategy,
 
575
                final HttpParams params) {
 
576
            super();
 
577
            this.httpProcessor = httpProcessor;
 
578
            this.connStrategy = connStrategy;
 
579
            this.params = params;
 
580
        }
 
581
        
 
582
        public void connected(final NHttpClientConnection conn, final Object attachment) {
 
583
            System.out.println(conn + " [proxy->origin] conn open");
 
584
            
 
585
            // The shared state object is expected to be passed as an attachment
 
586
            ProxyTask proxyTask = (ProxyTask) attachment;
 
587
 
 
588
            synchronized (proxyTask) {
 
589
                ConnState connState = proxyTask.getOriginState();
 
590
                if (connState != ConnState.IDLE) {
 
591
                    throw new IllegalStateException("Illegal target connection state: " + connState);
 
592
                }
 
593
 
 
594
                // Set origin IO control handle
 
595
                proxyTask.setOriginIOControl(conn);
 
596
                // Store the state object in the context
 
597
                HttpContext context = conn.getContext();
 
598
                context.setAttribute(ProxyTask.ATTRIB, proxyTask);
 
599
                // Update connection state
 
600
                proxyTask.setOriginState(ConnState.CONNECTED);
 
601
                
 
602
                if (proxyTask.getRequest() != null) {
 
603
                    conn.requestOutput();
 
604
                }
 
605
            }
 
606
        }
 
607
 
 
608
        public void requestReady(final NHttpClientConnection conn) {
 
609
            System.out.println(conn + " [proxy->origin] request ready");
 
610
 
 
611
            HttpContext context = conn.getContext();
 
612
            ProxyTask proxyTask = (ProxyTask) context.getAttribute(ProxyTask.ATTRIB);
 
613
 
 
614
            synchronized (proxyTask) {
 
615
                ConnState connState = proxyTask.getOriginState();
 
616
                if (connState == ConnState.REQUEST_SENT 
 
617
                        || connState == ConnState.REQUEST_BODY_DONE) {
 
618
                    // Request sent but no response available yet
 
619
                    return;
 
620
                }
 
621
 
 
622
                if (connState != ConnState.IDLE
 
623
                        && connState != ConnState.CONNECTED) {
 
624
                    throw new IllegalStateException("Illegal target connection state: " + connState);
 
625
                }
 
626
 
 
627
                HttpRequest request = proxyTask.getRequest();
 
628
                if (request == null) {
 
629
                    throw new IllegalStateException("HTTP request is null");
 
630
                }
 
631
                
 
632
                // Remove hop-by-hop headers
 
633
                request.removeHeaders(HTTP.CONTENT_LEN);
 
634
                request.removeHeaders(HTTP.TRANSFER_ENCODING);
 
635
                request.removeHeaders(HTTP.CONN_DIRECTIVE);
 
636
                request.removeHeaders("Keep-Alive");
 
637
                request.removeHeaders("Proxy-Authenticate");
 
638
                request.removeHeaders("Proxy-Authorization");
 
639
                request.removeHeaders("TE");
 
640
                request.removeHeaders("Trailers");
 
641
                request.removeHeaders("Upgrade");
 
642
                // Remove host header
 
643
                request.removeHeaders(HTTP.TARGET_HOST);
 
644
                
 
645
                HttpHost targetHost = proxyTask.getTarget();
 
646
                
 
647
                try {
 
648
                    
 
649
                    request.setParams(
 
650
                            new DefaultedHttpParams(request.getParams(), this.params));
 
651
                    
 
652
                    // Pre-process HTTP request
 
653
                    context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
 
654
                    context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, targetHost);
 
655
 
 
656
                    this.httpProcessor.process(request, context);
 
657
                    // and send it to the origin server
 
658
                    conn.submitRequest(request);
 
659
                    // Update connection state
 
660
                    proxyTask.setOriginState(ConnState.REQUEST_SENT);
 
661
                    
 
662
                    System.out.println(conn + " [proxy->origin] >> " + request.getRequestLine().toString());
 
663
                    
 
664
                } catch (IOException ex) {
 
665
                    shutdownConnection(conn);
 
666
                } catch (HttpException ex) {
 
667
                    shutdownConnection(conn);
 
668
                }
 
669
                
 
670
            }
 
671
        }
 
672
 
 
673
        public void outputReady(final NHttpClientConnection conn, final ContentEncoder encoder) {
 
674
            System.out.println(conn + " [proxy->origin] output ready");
 
675
            
 
676
            HttpContext context = conn.getContext();
 
677
            ProxyTask proxyTask = (ProxyTask) context.getAttribute(ProxyTask.ATTRIB);
 
678
 
 
679
            synchronized (proxyTask) {
 
680
                ConnState connState = proxyTask.getOriginState();
 
681
                if (connState != ConnState.REQUEST_SENT
 
682
                        && connState != ConnState.REQUEST_BODY_STREAM) {
 
683
                    throw new IllegalStateException("Illegal target connection state: " + connState);
 
684
                }
 
685
                
 
686
                try {
 
687
                    
 
688
                    ByteBuffer src = proxyTask.getInBuffer();
 
689
                    src.flip();
 
690
                    int bytesWritten = encoder.write(src);
 
691
                    System.out.println(conn + " [proxy->origin] " + bytesWritten + " bytes written");
 
692
                    System.out.println(conn + " [proxy->origin] " + encoder);
 
693
                    src.compact();
 
694
                    
 
695
                    if (src.position() == 0) {
 
696
                        if (proxyTask.getClientState() == ConnState.REQUEST_BODY_DONE) {
 
697
                            encoder.complete();
 
698
                        } else {
 
699
                            // Input buffer is empty. Wait until the client fills up 
 
700
                            // the buffer
 
701
                            conn.suspendOutput();
 
702
                        }
 
703
                    }
 
704
                    // Update connection state
 
705
                    if (encoder.isCompleted()) {
 
706
                        System.out.println(conn + " [proxy->origin] request body sent");
 
707
                        proxyTask.setOriginState(ConnState.REQUEST_BODY_DONE);
 
708
                    } else {
 
709
                        proxyTask.setOriginState(ConnState.REQUEST_BODY_STREAM);
 
710
                        // Make sure client input is active
 
711
                        proxyTask.getClientIOControl().requestInput();
 
712
                    }
 
713
                    
 
714
                } catch (IOException ex) {
 
715
                    shutdownConnection(conn);
 
716
                }
 
717
            }
 
718
        }
 
719
 
 
720
        public void responseReceived(final NHttpClientConnection conn) {
 
721
            System.out.println(conn + " [proxy<-origin] response received");
 
722
            
 
723
            HttpContext context = conn.getContext();
 
724
            ProxyTask proxyTask = (ProxyTask) context.getAttribute(ProxyTask.ATTRIB);
 
725
 
 
726
            synchronized (proxyTask) {
 
727
                ConnState connState = proxyTask.getOriginState();
 
728
                if (connState != ConnState.REQUEST_SENT
 
729
                        && connState != ConnState.REQUEST_BODY_DONE) {
 
730
                    throw new IllegalStateException("Illegal target connection state: " + connState);
 
731
                }
 
732
 
 
733
                HttpResponse response = conn.getHttpResponse();
 
734
                HttpRequest request = proxyTask.getRequest();
 
735
 
 
736
                System.out.println(conn + " [proxy<-origin] << " + response.getStatusLine());
 
737
                
 
738
                int statusCode = response.getStatusLine().getStatusCode();
 
739
                if (statusCode < HttpStatus.SC_OK) {
 
740
                    // Ignore 1xx response
 
741
                    return;
 
742
                }
 
743
                try {
 
744
                
 
745
                    // Update connection state
 
746
                    proxyTask.setResponse(response);
 
747
                    proxyTask.setOriginState(ConnState.RESPONSE_RECEIVED);
 
748
                    
 
749
                    if (!canResponseHaveBody(request, response)) {
 
750
                        conn.resetInput();
 
751
                        if (!this.connStrategy.keepAlive(response, context)) {
 
752
                            System.out.println(conn + " [proxy<-origin] close connection");
 
753
                            proxyTask.setOriginState(ConnState.CLOSING);
 
754
                            conn.close();
 
755
                        }
 
756
                    }
 
757
                    // Make sure client output is active
 
758
                    proxyTask.getClientIOControl().requestOutput();
 
759
 
 
760
                } catch (IOException ex) {
 
761
                    shutdownConnection(conn);
 
762
                }
 
763
            }
 
764
 
 
765
        }
 
766
 
 
767
        private boolean canResponseHaveBody(
 
768
                final HttpRequest request, final HttpResponse response) {
 
769
 
 
770
            if (request != null && "HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
 
771
                return false;
 
772
            }
 
773
            
 
774
            int status = response.getStatusLine().getStatusCode(); 
 
775
            return status >= HttpStatus.SC_OK 
 
776
                && status != HttpStatus.SC_NO_CONTENT 
 
777
                && status != HttpStatus.SC_NOT_MODIFIED
 
778
                && status != HttpStatus.SC_RESET_CONTENT; 
 
779
        }
 
780
        
 
781
        public void inputReady(final NHttpClientConnection conn, final ContentDecoder decoder) {
 
782
            System.out.println(conn + " [proxy<-origin] input ready");
 
783
 
 
784
            HttpContext context = conn.getContext();
 
785
            ProxyTask proxyTask = (ProxyTask) context.getAttribute(ProxyTask.ATTRIB);
 
786
 
 
787
            synchronized (proxyTask) {
 
788
                ConnState connState = proxyTask.getOriginState();
 
789
                if (connState != ConnState.RESPONSE_RECEIVED
 
790
                        && connState != ConnState.RESPONSE_BODY_STREAM) {
 
791
                    throw new IllegalStateException("Illegal target connection state: " + connState);
 
792
                }
 
793
 
 
794
                HttpResponse response = proxyTask.getResponse();
 
795
                try {
 
796
                    
 
797
                    ByteBuffer dst = proxyTask.getOutBuffer();
 
798
                    int bytesRead = decoder.read(dst);
 
799
                    System.out.println(conn + " [proxy<-origin] " + bytesRead + " bytes read");
 
800
                    System.out.println(conn + " [proxy<-origin] " + decoder);
 
801
                    if (!dst.hasRemaining()) {
 
802
                        // Output buffer is full. Suspend origin input until 
 
803
                        // the client handler frees up some space in the buffer
 
804
                        conn.suspendInput();
 
805
                    }
 
806
                    // If there is some content in the buffer make sure client output 
 
807
                    // is active
 
808
                    if (dst.position() > 0) {
 
809
                        proxyTask.getClientIOControl().requestOutput();
 
810
                    }
 
811
                    
 
812
                    if (decoder.isCompleted()) {
 
813
                        System.out.println(conn + " [proxy<-origin] response body received");
 
814
                        proxyTask.setOriginState(ConnState.RESPONSE_BODY_DONE);
 
815
 
 
816
                        if (!this.connStrategy.keepAlive(response, context)) {
 
817
                            System.out.println(conn + " [proxy<-origin] close connection");
 
818
                            proxyTask.setOriginState(ConnState.CLOSING);
 
819
                            conn.close();
 
820
                        }
 
821
                    } else {
 
822
                        proxyTask.setOriginState(ConnState.RESPONSE_BODY_STREAM);
 
823
                    }
 
824
                    
 
825
                } catch (IOException ex) {
 
826
                    shutdownConnection(conn);
 
827
                }
 
828
            }
 
829
        }
 
830
 
 
831
        public void closed(final NHttpClientConnection conn) {
 
832
            System.out.println(conn + " [proxy->origin] conn closed");
 
833
            HttpContext context = conn.getContext();
 
834
            ProxyTask proxyTask = (ProxyTask) context.getAttribute(ProxyTask.ATTRIB);
 
835
 
 
836
            if (proxyTask != null) {
 
837
                synchronized (proxyTask) {
 
838
                    proxyTask.setOriginState(ConnState.CLOSED);
 
839
                }
 
840
            }
 
841
        }
 
842
 
 
843
        public void exception(final NHttpClientConnection conn, final HttpException ex) {
 
844
            shutdownConnection(conn);
 
845
            System.out.println(conn + " [proxy->origin] HTTP error: " + ex.getMessage());
 
846
        }
 
847
 
 
848
        public void exception(final NHttpClientConnection conn, final IOException ex) {
 
849
            shutdownConnection(conn);
 
850
            System.out.println(conn + " [proxy->origin] I/O error: " + ex.getMessage());
 
851
        }
 
852
        
 
853
        public void timeout(final NHttpClientConnection conn) {
 
854
            System.out.println(conn + " [proxy->origin] timeout");
 
855
            closeConnection(conn);
 
856
        }
 
857
     
 
858
        private void shutdownConnection(final HttpConnection conn) {
 
859
            try {
 
860
                conn.shutdown();
 
861
            } catch (IOException ignore) {
 
862
            }
 
863
        }
 
864
        
 
865
        private void closeConnection(final HttpConnection conn) {
 
866
            try {
 
867
                conn.shutdown();
 
868
            } catch (IOException ignore) {
 
869
            }
 
870
        }
 
871
 
 
872
    }    
 
873
    
 
874
    enum ConnState {
 
875
        IDLE,
 
876
        CONNECTED,
 
877
        REQUEST_RECEIVED,
 
878
        REQUEST_SENT,
 
879
        REQUEST_BODY_STREAM,
 
880
        REQUEST_BODY_DONE,
 
881
        RESPONSE_RECEIVED,
 
882
        RESPONSE_SENT,
 
883
        RESPONSE_BODY_STREAM,
 
884
        RESPONSE_BODY_DONE,
 
885
        CLOSING,
 
886
        CLOSED
 
887
    }
 
888
    
 
889
    static class ProxyTask {
 
890
        
 
891
        public static final String ATTRIB = "nhttp.proxy-task";
 
892
        
 
893
        private final ByteBuffer inBuffer;
 
894
        private final ByteBuffer outBuffer;
 
895
 
 
896
        private HttpHost target;
 
897
        
 
898
        private IOControl originIOControl;
 
899
        private IOControl clientIOControl;
 
900
        
 
901
        private ConnState originState;
 
902
        private ConnState clientState;
 
903
        
 
904
        private HttpRequest request;
 
905
        private HttpResponse response;
 
906
        
 
907
        public ProxyTask() {
 
908
            super();
 
909
            this.originState = ConnState.IDLE;
 
910
            this.clientState = ConnState.IDLE;
 
911
            this.inBuffer = ByteBuffer.allocateDirect(10240);
 
912
            this.outBuffer = ByteBuffer.allocateDirect(10240);
 
913
        }
 
914
 
 
915
        public ByteBuffer getInBuffer() {
 
916
            return this.inBuffer;
 
917
        }
 
918
 
 
919
        public ByteBuffer getOutBuffer() {
 
920
            return this.outBuffer;
 
921
        }
 
922
        
 
923
        public HttpHost getTarget() {
 
924
            return this.target;
 
925
        }
 
926
 
 
927
        public void setTarget(final HttpHost target) {
 
928
            this.target = target;
 
929
        }
 
930
 
 
931
        public HttpRequest getRequest() {
 
932
            return this.request;
 
933
        }
 
934
 
 
935
        public void setRequest(final HttpRequest request) {
 
936
            this.request = request;
 
937
        }
 
938
 
 
939
        public HttpResponse getResponse() {
 
940
            return this.response;
 
941
        }
 
942
 
 
943
        public void setResponse(final HttpResponse response) {
 
944
            this.response = response;
 
945
        }
 
946
 
 
947
        public IOControl getClientIOControl() {
 
948
            return this.clientIOControl;
 
949
        }
 
950
 
 
951
        public void setClientIOControl(final IOControl clientIOControl) {
 
952
            this.clientIOControl = clientIOControl;
 
953
        }
 
954
 
 
955
        public IOControl getOriginIOControl() {
 
956
            return this.originIOControl;
 
957
        }
 
958
 
 
959
        public void setOriginIOControl(final IOControl originIOControl) {
 
960
            this.originIOControl = originIOControl;
 
961
        }
 
962
        
 
963
        public ConnState getOriginState() {
 
964
            return this.originState;
 
965
        }
 
966
 
 
967
        public void setOriginState(final ConnState state) {
 
968
            this.originState = state;
 
969
        }
 
970
        
 
971
        public ConnState getClientState() {
 
972
            return this.clientState;
 
973
        }
 
974
 
 
975
        public void setClientState(final ConnState state) {
 
976
            this.clientState = state;
 
977
        }
 
978
 
 
979
        public void reset() {
 
980
            this.inBuffer.clear();
 
981
            this.outBuffer.clear();
 
982
            this.originState = ConnState.IDLE;
 
983
            this.clientState = ConnState.IDLE;
 
984
            this.request = null;
 
985
            this.response = null;
 
986
        }
 
987
        
 
988
        public void shutdown() {
 
989
            if (this.clientIOControl != null) {
 
990
                try {
 
991
                    this.clientIOControl.shutdown();
 
992
                } catch (IOException ignore) {
 
993
                }
 
994
            }
 
995
            if (this.originIOControl != null) {
 
996
                try {
 
997
                    this.originIOControl.shutdown();
 
998
                } catch (IOException ignore) {
 
999
                }
 
1000
            }
 
1001
        }
 
1002
 
 
1003
    }
 
1004
    
 
1005
}