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