View Javadoc

    1   /*
    2    * Copyright 2012 The Netty Project
    3    *
    4    * The Netty Project licenses this file to you under the Apache License,
    5    * version 2.0 (the "License"); you may not use this file except in compliance
    6    * with the License. You may obtain a copy of the License at:
    7    *
    8    *   http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    13   * License for the specific language governing permissions and limitations
    14   * under the License.
    15   */
    16  package org.jboss.netty.channel.socket.http;
    17
    18  import static org.jboss.netty.channel.Channels.*;
    19
    20  import java.net.InetSocketAddress;
    21  import java.net.SocketAddress;
    22  import java.nio.channels.NotYetConnectedException;
    23
    24  import javax.net.ssl.SSLContext;
    25  import javax.net.ssl.SSLEngine;
    26
    27  import org.jboss.netty.buffer.ChannelBuffer;
    28  import org.jboss.netty.buffer.ChannelBuffers;
    29  import org.jboss.netty.channel.AbstractChannel;
    30  import org.jboss.netty.channel.ChannelException;
    31  import org.jboss.netty.channel.ChannelFactory;
    32  import org.jboss.netty.channel.ChannelFuture;
    33  import org.jboss.netty.channel.ChannelFutureListener;
    34  import org.jboss.netty.channel.ChannelHandlerContext;
    35  import org.jboss.netty.channel.ChannelPipeline;
    36  import org.jboss.netty.channel.ChannelSink;
    37  import org.jboss.netty.channel.ChannelStateEvent;
    38  import org.jboss.netty.channel.DefaultChannelPipeline;
    39  import org.jboss.netty.channel.ExceptionEvent;
    40  import org.jboss.netty.channel.MessageEvent;
    41  import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
    42  import org.jboss.netty.channel.socket.ClientSocketChannelFactory;
    43  import org.jboss.netty.channel.socket.SocketChannel;
    44  import org.jboss.netty.handler.codec.http.DefaultHttpChunk;
    45  import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
    46  import org.jboss.netty.handler.codec.http.HttpChunk;
    47  import org.jboss.netty.handler.codec.http.HttpHeaders;
    48  import org.jboss.netty.handler.codec.http.HttpMethod;
    49  import org.jboss.netty.handler.codec.http.HttpRequest;
    50  import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
    51  import org.jboss.netty.handler.codec.http.HttpResponse;
    52  import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
    53  import org.jboss.netty.handler.codec.http.HttpResponseStatus;
    54  import org.jboss.netty.handler.codec.http.HttpVersion;
    55  import org.jboss.netty.handler.ssl.SslHandler;
    56
    57  /**
    58   */
    59  class HttpTunnelingClientSocketChannel extends AbstractChannel
    60          implements org.jboss.netty.channel.socket.SocketChannel {
    61
    62      final HttpTunnelingSocketChannelConfig config;
    63
    64      volatile boolean requestHeaderWritten;
    65
    66      final Object interestOpsLock = new Object();
    67
    68      final SocketChannel realChannel;
    69
    70      private final HttpTunnelingClientSocketChannel.ServletChannelHandler handler = new ServletChannelHandler();
    71
    72      HttpTunnelingClientSocketChannel(
    73              ChannelFactory factory,
    74              ChannelPipeline pipeline,
    75              ChannelSink sink, ClientSocketChannelFactory clientSocketChannelFactory) {
    76
    77          super(null, factory, pipeline, sink);
    78
    79          config = new HttpTunnelingSocketChannelConfig(this);
    80          DefaultChannelPipeline channelPipeline = new DefaultChannelPipeline();
    81          channelPipeline.addLast("decoder", new HttpResponseDecoder());
    82          channelPipeline.addLast("encoder", new HttpRequestEncoder());
    83          channelPipeline.addLast("handler", handler);
    84          realChannel = clientSocketChannelFactory.newChannel(channelPipeline);
    85
    86          fireChannelOpen(this);
    87      }
    88
    89      public HttpTunnelingSocketChannelConfig getConfig() {
    90          return config;
    91      }
    92
    93      public InetSocketAddress getLocalAddress() {
    94          return realChannel.getLocalAddress();
    95      }
    96
    97      public InetSocketAddress getRemoteAddress() {
    98          return realChannel.getRemoteAddress();
    99      }
    100
    101     public boolean isBound() {
    102         return realChannel.isBound();
    103     }
    104
    105     public boolean isConnected() {
    106         return realChannel.isConnected();
    107     }
    108
    109     @Override
    110     public int getInterestOps() {
    111         return realChannel.getInterestOps();
    112     }
    113
    114     @Override
    115     public boolean isWritable() {
    116         return realChannel.isWritable();
    117     }
    118
    119     @Override
    120     protected boolean setClosed() {
    121         return super.setClosed();
    122     }
    123
    124     @Override
    125     public ChannelFuture write(Object message, SocketAddress remoteAddress) {
    126         if (remoteAddress == null || remoteAddress.equals(getRemoteAddress())) {
    127             return super.write(message, null);
    128         } else {
    129             return getUnsupportedOperationFuture();
    130         }
    131     }
    132
    133     void bindReal(final SocketAddress localAddress, final ChannelFuture future) {
    134         realChannel.bind(localAddress).addListener(new ChannelFutureListener() {
    135             public void operationComplete(ChannelFuture f) {
    136                 if (f.isSuccess()) {
    137                     future.setSuccess();
    138                 } else {
    139                     future.setFailure(f.getCause());
    140                 }
    141             }
    142         });
    143     }
    144
    145     void connectReal(final SocketAddress remoteAddress, final ChannelFuture future) {
    146         final SocketChannel virtualChannel = this;
    147         realChannel.connect(remoteAddress).addListener(new ChannelFutureListener() {
    148             public void operationComplete(ChannelFuture f) {
    149                 final String serverName = config.getServerName();
    150                 final int serverPort = ((InetSocketAddress) remoteAddress).getPort();
    151                 final String serverPath = config.getServerPath();
    152
    153                 if (f.isSuccess()) {
    154                     // Configure SSL
    155                     SSLContext sslContext = config.getSslContext();
    156                     ChannelFuture sslHandshakeFuture = null;
    157                     if (sslContext != null) {
    158                         // Create a new SSLEngine from the specified SSLContext.
    159                         SSLEngine engine;
    160                         if (serverName != null) {
    161                             engine = sslContext.createSSLEngine(serverName, serverPort);
    162                         } else {
    163                             engine = sslContext.createSSLEngine();
    164                         }
    165
    166                         // Configure the SSLEngine.
    167                         engine.setUseClientMode(true);
    168                         engine.setEnableSessionCreation(config.isEnableSslSessionCreation());
    169                         String[] enabledCipherSuites = config.getEnabledSslCipherSuites();
    170                         if (enabledCipherSuites != null) {
    171                             engine.setEnabledCipherSuites(enabledCipherSuites);
    172                         }
    173                         String[] enabledProtocols = config.getEnabledSslProtocols();
    174                         if (enabledProtocols != null) {
    175                             engine.setEnabledProtocols(enabledProtocols);
    176                         }
    177
    178                         SslHandler sslHandler = new SslHandler(engine);
    179                         realChannel.getPipeline().addFirst("ssl", sslHandler);
    180                         sslHandshakeFuture = sslHandler.handshake();
    181                     }
    182
    183                     // Send the HTTP request.
    184                     final HttpRequest req = new DefaultHttpRequest(
    185                             HttpVersion.HTTP_1_1, HttpMethod.POST, serverPath);
    186                     if (serverName != null) {
    187                         req.setHeader(HttpHeaders.Names.HOST, serverName);
    188                     }
    189                     req.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream");
    190                     req.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
    191                     req.setHeader(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, HttpHeaders.Values.BINARY);
    192                     req.setHeader(HttpHeaders.Names.USER_AGENT, HttpTunnelingClientSocketChannel.class.getName());
    193
    194                     if (sslHandshakeFuture == null) {
    195                         realChannel.write(req);
    196                         requestHeaderWritten = true;
    197                         future.setSuccess();
    198                         fireChannelConnected(virtualChannel, remoteAddress);
    199                     } else {
    200                         sslHandshakeFuture.addListener(new ChannelFutureListener() {
    201                             public void operationComplete(ChannelFuture f) {
    202                                 if (f.isSuccess()) {
    203                                     realChannel.write(req);
    204                                     requestHeaderWritten = true;
    205                                     future.setSuccess();
    206                                     fireChannelConnected(virtualChannel, remoteAddress);
    207                                 } else {
    208                                     future.setFailure(f.getCause());
    209                                     fireExceptionCaught(virtualChannel, f.getCause());
    210                                 }
    211                             }
    212                         });
    213                     }
    214                 } else {
    215                     future.setFailure(f.getCause());
    216                     fireExceptionCaught(virtualChannel, f.getCause());
    217                 }
    218             }
    219         });
    220     }
    221
    222     void writeReal(final ChannelBuffer a, final ChannelFuture future) {
    223         if (!requestHeaderWritten) {
    224             throw new NotYetConnectedException();
    225         }
    226
    227         final int size = a.readableBytes();
    228         final ChannelFuture f;
    229
    230         if (size == 0) {
    231             f = realChannel.write(ChannelBuffers.EMPTY_BUFFER);
    232         } else {
    233             f = realChannel.write(new DefaultHttpChunk(a));
    234         }
    235
    236         f.addListener(new ChannelFutureListener() {
    237             public void operationComplete(ChannelFuture f) {
    238                 if (f.isSuccess()) {
    239                     future.setSuccess();
    240                     if (size != 0) {
    241                         fireWriteComplete(HttpTunnelingClientSocketChannel.this, size);
    242                     }
    243                 } else {
    244                     future.setFailure(f.getCause());
    245                 }
    246             }
    247         });
    248     }
    249
    250     private ChannelFuture writeLastChunk() {
    251         if (!requestHeaderWritten) {
    252             return failedFuture(this, new NotYetConnectedException());
    253         } else {
    254             return realChannel.write(HttpChunk.LAST_CHUNK);
    255         }
    256     }
    257
    258     void setInterestOpsReal(final int interestOps, final ChannelFuture future) {
    259         realChannel.setInterestOps(interestOps).addListener(new ChannelFutureListener() {
    260             public void operationComplete(ChannelFuture f) {
    261                 if (f.isSuccess()) {
    262                     future.setSuccess();
    263                 } else {
    264                     future.setFailure(f.getCause());
    265                 }
    266             }
    267         });
    268     }
    269
    270     void disconnectReal(final ChannelFuture future) {
    271         writeLastChunk().addListener(new ChannelFutureListener() {
    272             public void operationComplete(ChannelFuture f) {
    273                 realChannel.disconnect().addListener(new ChannelFutureListener() {
    274                     public void operationComplete(ChannelFuture f) {
    275                         if (f.isSuccess()) {
    276                             future.setSuccess();
    277                         } else {
    278                             future.setFailure(f.getCause());
    279                         }
    280                     }
    281                 });
    282             }
    283         });
    284     }
    285
    286     void unbindReal(final ChannelFuture future) {
    287         writeLastChunk().addListener(new ChannelFutureListener() {
    288             public void operationComplete(ChannelFuture f) {
    289                 realChannel.unbind().addListener(new ChannelFutureListener() {
    290                     public void operationComplete(ChannelFuture f) {
    291                         if (f.isSuccess()) {
    292                             future.setSuccess();
    293                         } else {
    294                             future.setFailure(f.getCause());
    295                         }
    296                     }
    297                 });
    298             }
    299         });
    300     }
    301
    302     void closeReal(final ChannelFuture future) {
    303         writeLastChunk().addListener(new ChannelFutureListener() {
    304             public void operationComplete(ChannelFuture f) {
    305                 realChannel.close().addListener(new ChannelFutureListener() {
    306                     public void operationComplete(ChannelFuture f) {
    307                         // Note: If 'future' refers to the closeFuture,
    308                         // setSuccess() and setFailure() do nothing.
    309                         // AbstractChannel.setClosed() should be called instead.
    310                         // (See AbstractChannel.ChannelCloseFuture)
    311
    312                         if (f.isSuccess()) {
    313                             future.setSuccess();
    314                         } else {
    315                             future.setFailure(f.getCause());
    316                         }
    317
    318                         // Notify the closeFuture.
    319                         setClosed();
    320                     }
    321                 });
    322             }
    323         });
    324     }
    325
    326     final class ServletChannelHandler extends SimpleChannelUpstreamHandler {
    327
    328         private volatile boolean readingChunks;
    329         final SocketChannel virtualChannel = HttpTunnelingClientSocketChannel.this;
    330
    331         @Override
    332         public void channelBound(ChannelHandlerContext ctx, ChannelStateEvent e)
    333                 throws Exception {
    334             fireChannelBound(virtualChannel, (SocketAddress) e.getValue());
    335         }
    336
    337         @Override
    338         public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
    339             if (!readingChunks) {
    340                 HttpResponse res = (HttpResponse) e.getMessage();
    341                 if (res.getStatus().getCode() != HttpResponseStatus.OK.getCode()) {
    342                     throw new ChannelException("Unexpected HTTP response status: " + res.getStatus());
    343                 }
    344
    345                 if (res.isChunked()) {
    346                     readingChunks = true;
    347                 } else {
    348                     ChannelBuffer content = res.getContent();
    349                     if (content.readable()) {
    350                         fireMessageReceived(HttpTunnelingClientSocketChannel.this, content);
    351                     }
    352                     // Reached to the end of response - close the request.
    353                     closeReal(succeededFuture(virtualChannel));
    354                 }
    355             } else {
    356                 HttpChunk chunk = (HttpChunk) e.getMessage();
    357                 if (!chunk.isLast()) {
    358                     fireMessageReceived(HttpTunnelingClientSocketChannel.this, chunk.getContent());
    359                 } else {
    360                     readingChunks = false;
    361                     // Reached to the end of response - close the request.
    362                     closeReal(succeededFuture(virtualChannel));
    363                 }
    364             }
    365         }
    366
    367         @Override
    368         public void channelInterestChanged(ChannelHandlerContext ctx,
    369                 ChannelStateEvent e) throws Exception {
    370             fireChannelInterestChanged(virtualChannel);
    371         }
    372
    373         @Override
    374         public void channelDisconnected(ChannelHandlerContext ctx,
    375                 ChannelStateEvent e) throws Exception {
    376             fireChannelDisconnected(virtualChannel);
    377         }
    378
    379         @Override
    380         public void channelUnbound(ChannelHandlerContext ctx,
    381                 ChannelStateEvent e) throws Exception {
    382             fireChannelUnbound(virtualChannel);
    383         }
    384
    385         @Override
    386         public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
    387                 throws Exception {
    388             fireChannelClosed(virtualChannel);
    389         }
    390
    391         @Override
    392         public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
    393             fireExceptionCaught(virtualChannel, e.getCause());
    394             realChannel.close();
    395         }
    396     }
    397 }