Netty源码学习-HTTP-tunnel


Netty关于HTTP tunnel的说明:
http://docs.jboss.org/netty/3.2/api/org/jboss/netty/channel/socket/http/package-summary.html#package_description

这个说明有点太简略了

一个完整的例子在这里:
https://github.com/bylijinnan/nettyLearn/tree/master/ljn-netty3-httptunnel

示例里的具体流程是这样:
1.web工程(以下称为HttpServer)启动时,注册HttpTunnelingServlet并通过Spring启动Server(以下称为ServerNetty):
//web.xml
	<servlet>
		<servlet-name>NettyTunnelingServlet</servlet-name>
		 <servlet-class>org.jboss.netty.channel.socket.http.HttpTunnelingServlet</servlet-class>
		<init-param>
			<param-name>endpoint</param-name>
			<param-value>local:myLocalServer</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>NettyTunnelingServlet</servlet-name>
		<url-pattern>/netty-tunnel</url-pattern>
	</servlet-mapping>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
//applicationContext.xml,bean初始化完成后,调用start方法
<bean id="com.ljn.netty3.tunnelserver.LocalEchoServerRegistration"
		class="com.ljn.netty3.tunnelserver.LocalEchoServerRegistration" 
		init-method="start" destroy-method="stop"
		/>
		
		
//LocalEchoServerRegistration.java的start方法启动ServerNetty(这里ServerNetty的作用是echo):
public class LocalEchoServerRegistration {
    public void start() {
        ServerBootstrap serverBootstrap = new ServerBootstrap(factory);
        EchoServerHandler handler = new EchoServerHandler();
        serverBootstrap.getPipeline().addLast("handler", handler);
        serverChannel = serverBootstrap.bind(new LocalAddress("myLocalServer"));
    }
}


2.ClientNetty向HttpServer发起一个HTTP连接(URL为http://localhost:8088/netty3tunnelserver/netty-tunnel)
该连接被HttpServer端的HttpTunnelingServlet捕获

3.HttpTunnelingServlet做以下两个操作:
a.开启一个Client并连接到ServerNetty,把HttpRequest的数据发给ServerNetty:
b.读取ServerNetty的响应数据,通过HttpResponse转发给ClientNetty
HttpTunnelingServlet相当于一个“代理”:
对于ClientNetty来说,它是“Server”;对于ServerNetty来说,它是“Client”:

class HttpTunnelingServlet ...{
    protected void service(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
			
		//只支持POST
        if (!"POST".equalsIgnoreCase(req.getMethod())) {
            if (logger.isWarnEnabled()) {
                logger.warn("Unallowed method: " + req.getMethod());
            }
            res.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
            return;
        }

        final ChannelPipeline pipeline = Channels.pipeline();
        final ServletOutputStream out = res.getOutputStream();
		
		//messageReceived时,把从“ServerNetty”中接收到的数据写到HttpResponse
        final OutboundConnectionHandler handler = new OutboundConnectionHandler(out);
        pipeline.addLast("handler", handler);

        Channel channel = channelFactory.newChannel(pipeline);
		
		//向“ServerNetty”发起连接
        ChannelFuture future = channel.connect(remoteAddress).awaitUninterruptibly();

        ChannelFuture lastWriteFuture = null;
        try {
            res.setStatus(HttpServletResponse.SC_OK);
            res.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream");
            res.setHeader(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, HttpHeaders.Values.BINARY);

            //先发送响应头
            out.flush();

			/*从HttpRequest中读取数据并发送给“ServerNetty”
			不理解为什么要用PushbackInputStream(通常用在“parse data”:先读取InputStream前面一些字节
			来决定如何解析这个InputStream)
			*/
            PushbackInputStream in =
                    new PushbackInputStream(req.getInputStream());
            while (channel.isConnected()) {
                ChannelBuffer buffer;
                try {
                    buffer = read(in);
                } catch (EOFException e) {
                    break;
                }
                if (buffer == null) {
                    break;
                }
                lastWriteFuture = channel.write(buffer);
            }
        } 
    }
}


回过头来看看ClientNetty如何发HttpRequest:

class HttpTunnelingClientExample ...{
        ClientBootstrap b = new ClientBootstrap(
                new HttpTunnelingClientSocketChannelFactory(
                        new OioClientSocketChannelFactory(Executors.newCachedThreadPool())));

        b.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() throws Exception {
                return Channels.pipeline(
                        new StringDecoder(),
                        new StringEncoder(),
                        new LoggingHandler(InternalLogLevel.INFO));
            }
        });

        // Set additional options required by the HTTP tunneling transport.
        b.setOption("serverName", uri.getHost());
        b.setOption("serverPath", uri.getRawPath());

        // Make the connection attempt.
        ChannelFuture channelFuture = b.connect(
                new InetSocketAddress(uri.getHost(), uri.getPort()));
        channelFuture.awaitUninterruptibly();

        // Read commands from the stdin.
        System.out.println("Enter text ('quit' to exit)");
        ChannelFuture lastWriteFuture = null;
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        for (; ;) {
            String line = in.readLine();
            if (line == null || "quit".equalsIgnoreCase(line)) {
                break;
            }

            // Sends the received line to the server.
            lastWriteFuture = channelFuture.getChannel().write(line);
        }
}


ClientNetty做的事情很简单:启动ClientBootstrap并connect,然后读取System.in的输入并发送
ClientNetty只是一行一行地发送字符串,那是如何发送HttpRequest呢(POST)?

答案在HttpTunnelingClientSocketChannel

ClientNetty在connect时,触发CONNECTED的ChannelStateEvent并被HttpTunnelingClientSocketPipelineSink捕获,
最后调用HttpTunnelingClientSocketChannel的connectReal方法。在connect成功后,发送HttpRequest:

 void connectReal(final SocketAddress remoteAddress, final ChannelFuture future) {
                final String serverName = config.getServerName();
                final int serverPort = ((InetSocketAddress) remoteAddress).getPort();
                final String serverPath = config.getServerPath();

                if (f.isSuccess()) {
					...something about SSL
                    // Send the HTTP request.
                    final HttpRequest req = new DefaultHttpRequest(
                            HttpVersion.HTTP_1_1, HttpMethod.POST, serverPath);
                    if (serverName != null) {
                        req.setHeader(HttpHeaders.Names.HOST, serverName);
                    }
                    req.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream");
                    req.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
                    req.setHeader(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, HttpHeaders.Values.BINARY);
                    req.setHeader(HttpHeaders.Names.USER_AGENT, HttpTunnelingClientSocketChannel.class.getName());

					realChannel.write(req);
					requestHeaderWritten = true;
					future.setSuccess();
					fireChannelConnected(virtualChannel, remoteAddress);
                } 
}


可以看到,发送的HttpRequest是POST,且采用chunk方式
它与一般浏览器的HttpRequest还不太一样,浏览器把请求发过去就结束了
但它会一直等用户输入,直到用户输入“quit”退出才断开http连接
因此,在NettyClient里通过System.in输入的每一行,都是一个chunk也就不奇怪了:

//HttpTunnelingClientSocketChannel的writeReal方法
void writeReal(final ChannelBuffer a, final ChannelFuture future) {
        final int size = a.readableBytes();
        final ChannelFuture f;

        if (size == 0) {
            f = realChannel.write(ChannelBuffers.EMPTY_BUFFER);
        } else {
            f = realChannel.write(new DefaultHttpChunk(a));
        }
		...
    }



参考:
PushbackInputStream: http://tutorials.jenkov.com/java-io/pushbackinputstream.html
HTTP tunnel的含义: http://www.360doc.com/content/11/0512/21/4478545_116303972.shtml

你可能感兴趣的:(java,netty)