facebook/swift:构建thrift http server(3)--CORS跨域

本文接续前文,讲述构建Thrift HTTP Server的测试情况。测试时发现服务端对前端XHR请求无响应,引出CORS跨域问题。介绍了CORS预检请求机制,探讨如何解决该问题,提及Netty不同版本的CorsHandler,最后用反射技术修改NettyServerTransport,虽能响应OPTION请求,但POST请求仍未解决。

《facebook/swift:构建thrift http server(1)》
《facebook/swift:构建thrift http server(2)–HttpServerCodec》
接续前面的文章

测试

在上一篇文章中我已经通过替换frameCodecHttpServerCodecThriftServer改造为可以接收HTTP响应的netty server。完成代码修改后,赶紧用浏览器测试一下:
test_js.html代码如下:

<!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添加到NettyServerTransportChannelPipeline中呢?
facebook/swift/swift-servicecom.facebook.swift.service.ThriftServer类的作用是将thrift服务实例(封装为NiftyProcessor接口实例,thrift实现类为ThriftServiceProcessor)进一步封装为一个可以启动的Netty server。

下面是eclipse中以大纲模式看到的ThriftServer类的全貌,可以看到关键的NettyServerTransport实例作为私有成员常量transport,且并没有提供可供外部读取的方法。

在这里插入图片描述
看到这些,我们发现ThriftServer并没有向外部提供可以获取成员transport,也就无法通过常规方法获取NettyServerTransport实例。
同样在NettyServerTransport类中负责定义ChannelPipelinepipelineFactory也是一个私有成员,没有提供外部访问的方法。如下图:
在这里插入图片描述
现在问题来了,如何在外部修改ChannelPipelineChannelHandler队列呢?
我不想把ThriftServerNettyServerTransport类重写一遍,那样加增加日后的维护工作量,我的办法就是用java反射(reflect)技术把NettyServerTransportpipelineFactory的实例偷出来,修改后再把它放回去。

	/**
	 * 反射获取{@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》

<think>好的,我现在需要帮助用户确认Iceberg Hive Catalog配置中的URI格式和Hive仓库路径的正确设置。用户提到了几个可能的配置选项,我需要结合他们提供的hive-site.xml内容以及之前的回答来给出准确的建议。 首先,用户提供的hive-site.xml中有几个关键的property: 1. hive.metastore.uris的value是thrift://hw-cdh-1:9083,这应该是Hive Metastore的服务地址。用户需要确定在Iceberg的CREATE CATALOG语句中,uri参数是否应该使用这个具体的地址(hw-cdh-1)还是示例中的metastore-host。这里需要指出,实际配置应替换为真实的主机名或IP,但用户示例中的hw-cdh-1可能就是他们实际的主机名,因此应直接使用。 2. hive.metastore.warehouse.dir的value是/user/hive/warehouse,这是一个HDFS路径,但用户示例中给出的warehouse路径是hdfs://nn:8020/warehouse/path,而用户现在想确认应该使用hdfs://namenode:8020/user/hive/warehouse还是直接使用/user/hive/warehouse。这里需要说明,Hive的warehouse.dir配置可能已经包含了HDFS的协议和地址,但有时候也可能只是路径部分,需要根据实际情况补全完整的HDFS URL。例如,如果Hive的配置是相对路径,那么Iceberg中的warehouse参数需要明确指定HDFS的namenode地址和端口,如hdfs://hw-cdh-1:8020/user/hive/warehouse,但用户提供的hive-site.xml中的warehouse.dir是/user/hive/warehouse,可能没有指定HDFS的地址,因此需要结合集群的实际HDFS配置来确定完整的路径。 接下来,用户的问题涉及URI格式和仓库路径的正确配置。需要解释thrift URI的格式,确认是否需要使用具体的主机名,以及仓库路径是否需要完整的HDFS URL。 此外,用户引用了之前的回答中的内容,特别是关于hive.metastore.uris和warehouse的映射。之前的回答中示例使用了thrift://metastore-host:9083和hdfs://namenode:8020/user/hive/warehouse,但用户的实际配置是thrift://hw-cdh-1:9083和/user/hive/warehouse。需要指出用户需要将示例中的占位符(如metastore-host、namenode)替换为他们实际的环境配置,比如hw-cdh-1可能是他们的Metastore主机名,而HDFS的namenode地址可能是hdfs://hw-cdh-1:8020或其他地址。 另外,用户提到的引用[3]和[4]涉及到Hive Metastore的性能问题,但当前问题主要关注URI和路径配置,这部分可能暂时不需要处理,但需要注意在配置正确的情况下,避免因为路径或URI错误导致的连接问题,进而引发性能问题或元数据不一致。 总结用户的需求:确认在Iceberg的Hive Catalog配置中,uri参数应该使用thrift://hw-cdh-1:9083还是其他形式,以及warehouse路径是否需要完整的HDFS URL还是可以相对路径。需要结合用户的hive-site.xml配置和实际集群环境来给出建议,同时参考之前的回答,确保配置正确,避免常见的连接失败或路径权限问题。</think>--- ### **关键配置解析** #### **1. Thrift URI 配置** 在 `hive-site.xml` 中,`hive.metastore.uris` 的值为 `thrift://hw-cdh-1:9083`,这是 **Hive Metastore 的实际服务地址**。在 Iceberg Hive Catalog 的 `CREATE CATALOG` 语句中,`uri` 参数应与此保持一致: ```sql CREATE CATALOG hive_catalog WITH ( &#39;type&#39;=&#39;iceberg&#39;, &#39;catalog-type&#39;=&#39;hive&#39;, &#39;uri&#39;=&#39;thrift://hw-cdh-1:9083&#39;, -- 直接使用实际的主机名 hw-cdh-1 ... ); ``` - **`thrift://metastore-host:9083` 是示例占位符**,需替换为实际地址[^1][^3] - 若 `hive.metastore.uris` 包含多个地址(如高可用场景),需用逗号分隔,例如:`thrift://node1:9083,thrift://node2:9083` --- #### **2. Hive Warehouse 路径配置** `hive.metastore.warehouse.dir` 的值为 `/user/hive/warehouse`,但需根据 HDFS 实际地址补充 **完整的 URI 协议和 NameNode 信息**: ```sql CREATE CATALOG hive_catalog WITH ( ... &#39;warehouse&#39;=&#39;hdfs://hw-cdh-1:8020/user/hive/warehouse&#39;, -- 补全 HDFS 协议和 NameNode 地址 ... ); ``` - **`hdfs://namenode:8020` 是示例占位符**,需替换为实际 NameNode 地址(例如 `hdfs://hw-cdh-1:8020`) - **路径格式对比**: | 格式 | 示例 | 适用场景 | |-----|-----|---------| | 完整 HDFS URI | `hdfs://hw-cdh-1:8020/user/hive/warehouse` | 集群或非默认文件系统 | | 相对路径 | `/user/hive/warehouse` | 仅当 Hadoop 配置已指定默认文件系统(如 `fs.defaultFS`)时可用 | --- ### **验证配置正确性** 1. **检查 Metastore 连通性**: ```bash telnet hw-cdh-1 9083 # 确认 Thrift 端口可达 ``` 2. **验证 HDFS 路径权限**: ```bash hadoop fs -ls hdfs://hw-cdh-1:8020/user/hive/warehouse ``` 3. **测试 Iceberg 表创建**: ```sql USE CATALOG hive_catalog; CREATE TABLE test_db.sample (id INT) WITH (&#39;format-version&#39;=&#39;2&#39;); -- 检查表路径是否生成在正确位置 DESCRIBE EXTENDED test_db.sample; ``` --- ### **常见配置错误与修复** | 错误场景 | 表现 | 解决方案 | |---------|------|----------| | URI 主机名错误 | `Connection refused` 或超时 | 检查 `hive.metastore.uris` 与实际服务地址是否一致[^3] | | HDFS 路径权限不足 | `Permission denied` | 执行 `hadoop fs -chmod` 调整权限 | | 未指定完整 HDFS URI | 表路径指向本地文件系统 | 在 `warehouse` 参数中补全 `hdfs://namenode:port` 前缀 | --- ### **总结配置模板** 根据您的 `hive-site.xml` 配置,**最终 Iceberg Catalog 定义应为**: ```sql CREATE CATALOG hive_catalog WITH ( &#39;type&#39;=&#39;iceberg&#39;, &#39;catalog-type&#39;=&#39;hive&#39;, &#39;uri&#39;=&#39;thrift://hw-cdh-1:9083&#39;, -- 来自 hive.metastore.uris &#39;warehouse&#39;=&#39;hdfs://hw-cdh-1:8020/user/hive/warehouse&#39;, -- 补全 HDFS 地址和端口 &#39;clients&#39;=&#39;5&#39;, &#39;property-version&#39;=&#39;1&#39; ); ``` --- ### 相关问题 1. 如何诊断 Iceberg 与 Hive Metastore 的连接超时问题? 2. Iceberg 表存储路径与 Hive 默认路径不一致时如何处理? 3. 如何配置 Iceberg 使用 Kerberos 认证的 Hive Metastore?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值