Netty源码学习-HTTP-tunnel

本文详细介绍了使用Netty实现HTTP隧道的过程,包括配置Web工程、启动Netty服务、Client发起连接、Server接收请求并响应的流程。同时,解释了HTTP隧道的含义,并提供了具体的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

这个说明有点太简略了

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

示例里的具体流程是这样:
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:[url]http://tutorials.jenkov.com/java-io/pushbackinputstream.html[/url]
HTTP tunnel的含义:[url]http://www.360doc.com/content/11/0512/21/4478545_116303972.shtml[/url]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值