前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Microsoft/thrifty vs facebook/swift: TTransportException:Buffer doesn't

Microsoft/thrifty vs facebook/swift: TTransportException:Buffer doesn't

作者头像
10km
发布2019-05-25 21:46:58
5470
发布2019-05-25 21:46:58
举报
文章被收录于专栏:10km的专栏10km的专栏

版权声明:本文为博主原创文章,转载请注明源地址。 https://cloud.tencent.com/developer/article/1433550

基于thrift的RPC系统中,如果service端是基于facebook的swift开源框架实现的,而client是基于Microsoft的thrifty开源框架实现的,那么在client向service端发送请求时,service端就可能会抛出本文标题所说的异常。

结论

经过层层溯源,找到问题的原因:swift和thrift的在底层的默认通讯协议都是使用相同的二进制数据格式,也是100%支持thrift框架的,但它们默认的报文格式却不一样,swift的实现二进制协议的org.apache.thrift.protocol.TBinaryProtocol类默认将消息名(RPC调用方法名,字符串类型)为报文的首字段,

而thrifty的实现二进制协议的com.microsoft.thrifty.protocol.BinaryProtocol类默认将通讯协议版本号(version,32位整数)为报文的首字段。

因为协议报文的格式不同,导致服务端收到client端数据后因为解析错误而抛出异常。

这么说还是太抽象,那么我们就一层层分析原因。

问题溯源

报文接收

下面是抛出异常的的代码位置com.facebook.nifty.core.TNiftyTransport

代码语言:javascript
复制
    @Override
    public int readAll(byte[] bytes, int offset, int length) throws TTransportException {
        if (read(bytes, offset, length) < length) {
            throw new TTransportException("Buffer doesn't have enough bytes to read");
        }
        return length;
    }

com.facebook.nifty.core.TNiftyTransport.readAll方法是在被org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin方法调用时抛出异常的。下面是readMessageBegin方法的实现代码,可以看出,swift在解析报文协议时,首先就是读取32位整数来判断协议版本号(高16位为版本号,低8位为消息类型):

代码语言:javascript
复制
	public TMessage readMessageBegin() throws TException {
		int size = this.readI32();
		if (size < 0) {
			int version = size & -65536;
			if (version != -2147418112) {
				throw new TProtocolException(4, "Bad version in readMessageBegin");
			}
			return new TMessage(this.readString(), (byte) (size & 255), this.readI32());
		}
		if (this.strictRead_) {
			throw new TProtocolException(4, "Missing version in readMessageBegin, old client?");
		}
		return new TMessage(this.readStringBody(size), this.readByte(), this.readI32());
	}

报文发送

再来看看org.apache.thrift.protocol.TBinaryProtocolwriteMessageBegin方法实现:

当成员变量strictWrite_true时,协议报文首先写入一个32位整数(高16位为版本号,低8位为消息类型),与readMessageBegin方法要求的顺序一致。

成员变量strictWrite_false时,最先写入的是消息名(字符串),这种情况下,接收端收到报文解析肯定会抛出异常的。

代码语言:javascript
复制
	public void writeMessageBegin(TMessage message) throws TException {
		if (this.strictWrite_) {
			int version = -2147418112 | message.type;
			this.writeI32(version);
			this.writeString(message.name);
			this.writeI32(message.seqid);
		} else {
			this.writeString(message.name);
			this.writeByte(message.type);
			this.writeI32(message.seqid);
		}
	}

再来看com.microsoft.thrifty.protocol.BinaryProtocol的writeMessageBegin方法实现,与swift的实现逻辑是一样的,也有一个成员变量strictWrite来控制报文头的格式。

代码语言:javascript
复制
    @Override
    public void writeMessageBegin(String name, byte typeId, int seqId) throws IOException {
        if (strictWrite) {
            int version = VERSION_1 | (typeId & 0xFF);
            writeI32(version);
            writeString(name);
            writeI32(seqId);
        } else {
            writeString(name);
            writeByte(typeId);
            writeI32(seqId);
        }
    }

报文格式控制

从这里看,既然swift和thrifty对报文格式的控制逻辑是一样的,那么问题就出在这个控制报文头的格式的成员变量strictWrite上了。

进一步的分析,可以发现com.microsoft.thrifty.protocol.BinaryProtocolstrictWrite恒为false,而且没有提供外部修改其值的方法,而org.apache.thrift.protocol.TBinaryProtocolstrictWrite_的值则可以由构造方法传入。用于创建org.apache.thrift.protocol.TBinaryProtocol实例的工厂类org.apache.thrift.protocol.TBinaryProtocol.Factory的默认构造方法则将strictWrite_置为true

下面是org.apache.thrift.protocol.TBinaryProtocol.Factory类的实现代码:

代码语言:javascript
复制
	public static class Factory implements TProtocolFactory {
		protected boolean strictRead_ = false;
		protected boolean strictWrite_ = true;

		public Factory() {
			this(false, true);
		}

		public Factory(boolean strictRead, boolean strictWrite) {
			this.strictRead_ = strictRead;
			this.strictWrite_ = strictWrite;
		}

		public TProtocol getProtocol(TTransport trans) {
			return new TBinaryProtocol(trans, this.strictRead_, this.strictWrite_);
		}
	}

而在swift的服务端实现代码com.facebook.swift.service.ThriftServer中对binary协议使用的正是strictWrite_trueTBinaryProtocol实例。

下面是com.facebook.swift.service.ThriftServer类中DEFAULT_PROTOCOL_FACTORIES常量的定义。

代码语言:javascript
复制
    public static final ImmutableMap<String,TDuplexProtocolFactory> DEFAULT_PROTOCOL_FACTORIES = ImmutableMap.of(
            "binary", TDuplexProtocolFactory.fromSingleFactory(new TBinaryProtocol.Factory()),
            "compact", TDuplexProtocolFactory.fromSingleFactory(new TCompactProtocol.Factory())
    );

好了,到这里真相大白了,就是swift的service端和thrifty的client端的binary协议默认报文格式不一致。

解决方法

service端和client端修改一端就可以了。在我的项目中,因为基于swift的service端和client端先完成,为了要支持android平台才基于thrifty设计了新的android client端。所以为了保持系统兼容性,我只能修改android client端。

但是com.microsoft.thrifty.protocol.BinaryProtocol没有为修改私有成员变量strictWrite提供方法,所以我只能使用java反射(reflection)机制强制修改成员变量。实现如下:

代码语言:javascript
复制
SocketTransport transport = 
        new SocketTransport.Builder(hostAndPort.getHost(),hostAndPort.getPort())
            .connectTimeout((int) connectTimeout)
            .readTimeout((int) readTimeout).build();
transport.connect();
Protocol protocol = new BinaryProtocol(transport);
/* force set private field 'strictWrite' to true */
Field field = BinaryProtocol.class.getDeclaredField("strictWrite");
field.setAccessible(true);
field.set(protocol, true);

即在创建com.microsoft.thrifty.protocol.BinaryProtocol实例后立即将strictWrite字段值为true。再次运行测试程序,则问题解决。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年01月08日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 结论
  • 问题溯源
    • 报文接收
      • 报文发送
        • 报文格式控制
        • 解决方法
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档