版权声明:本文为博主原创文章,转载请注明源地址。 https://cloud.tencent.com/developer/article/1433450
《facebook/swift:构建thrift http server(1)》
《facebook/swift:构建thrift http server(2)–HttpServerCodec》
接续前面的文章
测试
在上一篇文章中我已经通过替换frameCodec
为HttpServerCodec
将ThriftServer
改造为可以接收HTTP响应的netty server。完成代码修改后,赶紧用浏览器测试一下:
test_js.html代码如下:
代码语言:javascript复制<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Thrift Javascript Test</title>
<script src="build/js/thrift.js" type="text/javascript" charset="utf-8"></script>
<script src="gen-js/IFaceLog_types.js" type="text/javascript" charset="utf-8"></script>
<script src="gen-js/IFaceLog.js" type="text/javascript" charset="utf-8"></script>
<!-- jQuery -->
<script src="https://cdn.bootcss.com/jquery/1.10.0/jquery.min.js"></script>
<script>
// 连接facelog XHR服务
var transport = new Thrift.Transport("http://localhost:26412");
var protocol = new Thrift.Protocol(transport);
var client = new IFaceLogClient(protocol);
</script>
</head>
<body>
<h1 id="qunit-header">Thrift Javascript Test: </h1>
<script>
// 调用version接口方法
client.version((res)=>{
$('.hint-version').append('version:' res )
</script>
<div>
RESPONSE:
<p class="hint-version"></p>
</div>
</body>
</html>
打开测试页面后,发出XHR请求后,服务端却没有响应,也没有报错。测试页面一直处于pending状态。
通过在服务端添加断点,拦截到了前端发出的第一个http请求,找到了原因,如下图:
通过拦截到的这个已经解码成DefaultHttpRequest
对象的HTTP请求,至少可以判断替换的HttpServerCodec
编解码器已经生效了,但这第一个HTTP请求居然不是浏览器端发出的POST
,而是我不认识的OPTIONS
,何解?这就引出了XHR请求的另一个问题CORS
CORS跨域问题
对于HTTP协议,我们最熟悉的最常用HTTP 请求是POST,GET这些,OPTIONS
是啥,没见过,唉学艺不精啊。
赶紧去百度查,原来是跨域资源共享(CORS) 。
关于CORS,参见 《HTTP访问控制(CORS)》 《HTTP的请求方法OPTIONS》 《http请求,多一次OPTIONS请求(CORS跨域)》
看了上面这些文章,我大概明白了,因为test_js.html网页文件与http://localhost:26412
不同源,所以Preflighted Requests(预检请求)机制被激活。
Preflighted Requests是CORS中一种透明服务器验证机制。预检请求首先需要向另外一个域名的资源发送一个 HTTP OPTIONS 请求头,其目的就是为了判断实际发送的请求是否是安全的。
而我的netty http server并没有响应CORS的机制,所以响应这个OPTIONS请求。
CorsHandler
那么现在的问题变成了如何响应OPTIONS请求?或者说是如何解决CORS跨域问题?
下面这篇文章提供了思路,
《Netty HTTP 请求允许跨域》
但是我还是觉得好麻烦,要自己根据CORS的原理写代码。就没有现成的解决方案嘛?
有,io.netty.handler.codec.http.cors.CorsHandler这个ChannelHandler
接口实例就是用于响应CORS请求的。
更详细的说明参见io.netty.handler.codec.http.cors
关于CorsHandler
的使用示例也找到了,只要CorsHandler
添加到ChannelPipeline中,就可以了:
《Java Code Examples for io.netty.handler.codec.http.cors.CorsHandler》
看到这个代码,好激动,不用写太多代码就能解决问题。
然而再深入了解,发现问题不简单:netty的版本问题
Netty3CorsHandler
io.netty.handler.codec.http.cors.CorsHandler
是netty 4.0以后的版本提供的类,在netty 3中并不存在。而facebook/swift
框架是基于netty3的,更重要的是netty3和netty4并不兼容(package都不一样了),所以不能升级facebook/swift
框架依赖的netty版本,也不可以简单的将CorsHandler
的相关源码复制出来使用。
看到吃不到,好恼火,要把netty4的CorsHandler
实现移植到netty3,需要对netty4和netty3都非常了解才行。这方面我是小白,完全不能胜任啊。
就没有现成的解决方案么?我又在google上一通找,居然让我找到了这个
org.elasticsearch.http.netty3.cors.Netty3CorsHandler,天助我也,原来有人已经实现了。
这个Netty3CorsHandler
已经实现了对OPTIONS请求的响应,已经比较完备,只是用JAVA 8写的,我改成了适合JDK1.7编译的版本,
参见我修改的JDK 1.7版本:org.jboss.netty3.handler.codec.http.cors
改造NettyServerTransport
解决CORS问题的Netty3CorsHandler
现在有了,但怎么将Netty3CorsHandler
添加到NettyServerTransport
的ChannelPipeline
中呢?
facebook/swift/swift-service的com.facebook.swift.service.ThriftServer类的作用是将thrift服务实例(封装为NiftyProcessor
接口实例,thrift实现类为ThriftServiceProcessor
)进一步封装为一个可以启动的Netty server。
下面是eclipse中以大纲模式看到的ThriftServer
类的全貌,可以看到关键的NettyServerTransport
实例作为私有成员常量transport
,且并没有提供可供外部读取的方法。
看到这些,我们发现ThriftServer
并没有向外部提供可以获取成员transport
,也就无法通过常规方法获取NettyServerTransport
实例。
同样在NettyServerTransport
类中负责定义ChannelPipeline
的pipelineFactory
也是一个私有成员,没有提供外部访问的方法。如下图:
现在问题来了,如何在外部修改ChannelPipeline
的ChannelHandler
队列呢?
我不想把ThriftServer
和NettyServerTransport
类重写一遍,那样加增加日后的维护工作量,我的办法就是用java反射(reflect)技术把NettyServerTransport
的pipelineFactory
的实例偷出来,修改后再把它放回去。
/**
* 反射获取{@code object}的私有成员
* @param object
* @param name
* @return 成员对象
*/
@SuppressWarnings("unchecked")
public static <T> T valueOfField(Object object,String name){
try {
Field field = checkNotNull(object,"object is null").getClass().getDeclaredField(checkNotNull(name,"name is null"));
field.setAccessible(true);
return (T) field.get(object);
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
/**
* 添加CORS handler
*/
protected void addCorsHandlerIfHttp(){
if(HTTP_TRANSPORT.equals(thriftServerConfig.getTransportName())){
try {
// 反射获取私有的成员NettyServerTransport
final NettyServerTransport nettyServerTransport = valueOfField(thriftServer, "transport");
// 反射获取私有的成员ChannelPipelineFactory
Field pipelineFactory = NettyServerTransport.class.getDeclaredField("pipelineFactory");
{
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true); //Field 的 modifiers 是私有的
modifiersField.setInt(pipelineFactory, pipelineFactory.getModifiers() & ~Modifier.FINAL);
}
pipelineFactory.setAccessible(true);
final ChannelPipelineFactory channelPipelineFactory = (ChannelPipelineFactory) pipelineFactory.get(nettyServerTransport);
// 设置CORS 参数
final Netty3CorsConfig corsConfig = Netty3CorsConfigBuilder.forAnyOrigin()
.allowedRequestMethods(POST,GET,OPTIONS)
.allowedRequestHeaders("Origin","Content-Type","Accept","application","x-requested-with")
.build();
ChannelPipelineFactory factoryWithCORS = new ChannelPipelineFactory(){
@Override
public ChannelPipeline getPipeline() throws Exception {
// 修改 ChannelPipeline,在frameCodec后(顺序)增加CORS handler
ChannelPipeline cp = channelPipelineFactory.getPipeline();
cp.addAfter("frameCodec", "cors", new Netty3CorsHandler(corsConfig));
return cp;
}};
// 修改nettyServerTransport的私有常量pipelineFactory
pipelineFactory.set(nettyServerTransport, factoryWithCORS);
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
}
修改完成后再次测试,已经可以正常响应OPTION请求了。
然而真正的POST请求还是没有响应
看来问题还是没有彻底解决,下一篇文章继续。
《facebook/swift:构建thrift http server(4)–ThriftXHRDecoder,ThriftXHREncoder》