Hyperledger fabric grpc 源码分析--- error:connection failed
(目前有点乱,先贴上来,等以后有时间在整理吧。这个问题一直想拿出来分享,还有两个博客,都是相关的,一点点发出来)
最近要在fabric网络和外部添加一层load balance,然后使用node的grpcs调用nginx,再转发到peer或者orderer。但是一直显示
code 14 connect failed
。log信息少的可怜。所以索性就过一遍代码。找找区别,顺便打打log。
fabric-client和grpc的版本
$ npm ls | grep fabric
+-- fabric-ca-client@1.0.8
+-- fabric-client@1.0.8
$ npm ls | grep grpc
`-- grpc@1.10.1
fabric版本:
这里面要注意,fabric-node-sdk这个版本强制要求grpc的版本要高于1.10.1。
fabric sdk node的grpc
然后我们就开始跟踪fabric进行一个request的全过程。
我们跟踪的方法时client.installChaincode()。
构建proposal
其内部针对request和chaincode的相关数据进行了封装,然后用userContext的secure进行签名。最后调用clientUtils.sendPeersProposal(peers, signed_proposal, timeout)
。
遍历peers的list,然后调用peer.sendProposal(proposal, timeout)
。
peer的构建和发起请求
SDK中Peer是继承了Remote类。
Remote类主要就是两件事情:
构造器针对grpc的各个参数进行配置,主要包括
ssl-target-name-override
:如果server是tls开启的状态,而且hostname的名字和tls证书的CN域名不同,那么就可以在这里指定CN的那个hostname。而且,这个选项更改了grpc的两个属性:
grpc.ssl_target_name_override
grpc.default_authority
这个参数就是针对server的证书进行验证。如果hostname和证书的签名是一致的,则这个参数并不需要。
pem
server的tls证书内容。
还有一些其他的grpc设置
grpc.max_receive_message_length
grpc.max_send_message_length
request-timeout
配置一个grpc的request超时时间
构建内置类-Endpoint
这个类非常重要,它是构建grpc对象的核心。主要就是针对url判断protocol,如果是grpcs则会使用this.creds = grpc.credentials.createSsl(pembuf)
构建一个ssl的通道;如果是grpc则使用grpc.credentials.createInsecure()
构建通道。
Peer发起的请求sendProposal
,直接调用grpc的方法等待response:self._endorserClient.processProposal(proposal, function(err, proposalResponse){}
。
这里要注意的是,其调用的grpc的simple RPC方法,发送一个请求,并且等待请求的response。不是array也不是stream的形式。
查看了下grpc的service的定义,果然,直接发送object:
service Endorser {
rpc ProcessProposal(SignedProposal) returns (ProposalResponse) {}
node Grpc调用
src中核心组件就是client.js
和credentials.js
。前者负责请求调用,后者负责channel的创建接口(前面提到的两个创建通道的方法定义于此)。我们重点看client.js。我们逐个函数(精力有限,先分析用到的)的分析:
(有一些需要后续补充的,EventEmitter-event调用,stream-流)
* createStatusError
:如果grpc返回的数据有error,则通过该函数解析,返回error
* ClientUnaryCall
绑定一个event-EventEmitter。
* _readsDone
当server发送消息完成之后,client会调用方法,并确认状态。默认为ok
* _receiveStatus
当从server收到任何status信息时候,调用。
* Client(address, credentials, options)
构造函数,创建一个channel。在创建一个grpc的client的时候就调用了。
* makeClientConstructor
在后面有一个导出函数,来具体根据需求创建不同类型的Client,其中request的类型
* Client.prototype.makeUnaryRequest
普通Grpc调用,创建请求。可以给出序列化和反序列化的方法,以及一些参数和回调函数。
* getCall(channel, method, options)
:这里面有些参数设置,然后将一个call返回-new grpc.Call(channel, method, deadline, host,parent, propagate_flags);
* hostname:server ip
* deadline:这个connection的timeout
* credential:如果这个client时grpcs(也就是含有cred),就会在call中将其进行设置-call.setCredentials(credentials)
同时,它有三个参数:
* channel
:就是实例化一个client的时候创建的channel。
* method
:grpc方法。这里是/protos.Endorser/ProcessProposal
* options
:一些参数:包括hostname、deadline和credential等。
找一下这个options的来源,其来源于makeUnaryRequest
。
首先,其会调用makeUnaryRequest,check各种参数。
然后,调用`var call = getCall(this.$channel, method, options);`获取需要的rpc方法。然后创建一个emitter-`new ClientUnaryCall(call)`。
之后,组装一个client_batch,call并将其发送给server,等待response。同时收到response后使用emitter将response的消息填充进metadata。
打了一圈log,在getCall中发现:
Hostname undefined
deadline Infinity
parent undefined
credentials undefined
Init unary Call
ClientUnaryCall {
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
call: Call { channel_: Channel {} } }
为毛线都是undefined,貌似发现了问题。
追踪之后发现其来源于makeUnaryRequest
的参数,回头check一下校验的代码逻辑。
首先,打印了这几个参数发现:
options:undefined,不应该。
metadata :在调用service的时候callback的function。
callback:undefined
argument:proposal的data
这边是校验的逻辑:
if (options instanceof Function) {
callback = options;
if (metadata instanceof Metadata) {
options = {};
} else {
options = metadata;
metadata = new Metadata();
} else if (metadata instanceof Function) {
callback = metadata;
metadata = new Metadata();
options = {};
if (!metadata) {
metadata = new Metadata();
if (!options) {
options = {};
if (!((metadata instanceof Metadata) &&
(options instanceof Object) &&
(callback instanceof Function))) {
throw new Error("Argument mismatch in makeUnaryRequest");
按照获取的数据来看,其走到了第二个分支:如果metadata是一个function,则callback赋值,options为空对象。
经过查看发现,Channel中应该包含了addr和credential的相关信息。所以在options的时候就取消了。这里可以继续digging。
Tips:一直忘记开启grpc的详细日志,在运行node的程序中使用该环境变量---GRPC_TRACE=all
和 GRPC_VERBOSITY=DEBUG
(因为这个是给C++内核用的,所以应该用export)
打开之后,发现有个问题:
Cannot check peer: missing selected ALPN property
貌似是有关ALPN的错误。server和client并不同时支持ALPN。
这里提一点,就是orderer的sendDeliver是用的stream,而不是普通GRPC。
openssl 1.0.2以上的版本支持了ALPN。
这个问题是client发起的ssl握手,然后服务端并没有将其APLN或者是NPN的版本发给客户端。
然后,这里提到,我们用GO的sdk(或者是peer的cli)进行调用,就能够连接,并且功能执行正常。
windows & linux
Grpc的ssl版本在windows和linux中使用的并不一样。
windows使用的BoringSSL, Linux使用的是OpenSSL。BoringSSL有可能不能处理证书中domin为ip的情况(还未测试)。
如果发生了一些SSL的错误,可以直接使用openssl或者bssl的命令行进行连接测试:
bssl s_client -connect 127.0.0.1:9110
openssl s_client -connect 127.0.0.1:9110 -showcerts
Tips:
BoringSSL已经将所有的ECC算法移除,除了P-256和P-384。同时其还有一些bug。如果是在找不到原因可以去github上看看issue。
Grpc-node上build的时候有一个配置,如果该主机不支持ALPN就会rebuild项目排除ALPN的支持。
'variables': {
'runtime%': 'node',
# Some Node installations use the system installation of OpenSSL, and on
# some systems, the system OpenSSL still does not have ALPN support. This
# will let users recompile gRPC to work without ALPN.
'grpc_alpn%': 'true',
# Indicates that the library should be built with gcov.
'grpc_gcov%': 'false',
# Indicates that the library should be built with compatibility for musl
# libc, so that it can run on Alpine Linux. This is only necessary if not
# building on Alpine Linux
'grpc_alpine%': 'false'
node 程序运行时可以添加环境变量:process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
,强制取消对server证书的授权验证。
Grpc编译(无ALPN的版本)
发现npm支持从源码进行安装的方法-grpc npm。所以我们选择先通过源码安装grpc然后在安装其他的组件。
从github中获取源码。
git clone https://github.com/grpc/grpc.git
时间会很久。
更改grpc的源码
参考之前提到的那个配置项。这里将其改为false
'grpc_alpn%': 'false',
3 npm 编译
npm install grpc --build-from-source
但是在windows可能会出现问题:node-grpc build on windows。如果在一开始(步骤比较靠前的地方)出现该错误:
Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
WINDOWS_BUILD_WARNING
"..\IMPORTANT: Due to https:\github.com\nodejs\node\issues\4932, to build this library on Windows, you must first remove C:\Users\jenkins\.node-gyp\4.4.0\include\node\openssl"
解决方法,就是把node-gyp的openssl删掉(如果存在着会发现有冲突),具体地址为:C:\Users\<username>\.node-gyp\<node_version>\include\node\openssl
详细的解决方案可以看另外一个博客。
fabric go server端grpc
(待补充)
http2和http1
HTTP/2(超文本传输协议第2版,最初命名为 HTTP 2.0),是HTTP协议的的第二个主要版本,使用于万维网。HTTP/2 是 HTTP 协议自 1999 年 HTTP 1.1 发布后的首个更新,主要基于 SPDY 协议。HTTP/2 标准于2015年5月以 RFC 7540 正式发表,HTTP/2协议规范 rfc。
为了实现 HTTP 工作组设定的性能目标,HTTP/2 引入了一个新的二进制分帧层,该层无法与之前的 HTTP/1.x 服务器和客户端向后兼容,因此协议的主版本提升到 HTTP/2。
http/2的优点
采用二进制格式传输数据,而非文本格式。二进制格式在协议的解析和优化扩展上带来更多的优势和可能。
对消息头进行压缩传输,能够节省消息头占用的网络的流量,而 HTTP 1.1 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源,头压缩能够很好的解决该问题。
多路复用,就是多个请求都是通过一个 TCP 连接并发完成, HTTP 1.1 虽然通过 pipeline 也能并发请求,但是多个请求之间的响应会被阻塞的,所以 pipeline 至今也没有被普及应用,而 HTTP/2 做到了真正的并发请求,同时流还支持优先级和流量控制。
服务器推送,服务端能够更快的把资源推送给客户端,例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求,当客户端需要的时候,它已经在客户端了。
h2c 和h2
h2c:HTTP/2协议,类型为clear text
h2:HTTP/2协议(加密),例如构建在SSL之上
其是谷歌开发的一种RPC协议。主要用于建立在跨语言调用、数据压缩的C/S链接上。gRPC是建立在HTTP/2之上进行连接的,不管是cleartext(h2c,未加密的数据)还是TLS-encrypted(h2)的数据。
一个gRPC的call,其实是实现了一个HTTP的POST请求,对body的数据进行了高效的编码(当然,肯定离不开谷歌的protobuf)。同样gRPC的response也使用了同样的编码以及HTTP的数据规则(比如说status code等)。
gRPC协议并不直接在HTTP/1.X之上传输。gRPC使用HTTP/2是为了能够支持多工(multiplexing)以及流式传输的特性(HTTP/2)。
ALPN和NPN
NPN: Next Protocol Negotiation
ALPN:Application Layer Protocol Negotiation,ALPN wiki。
这两个都是TLS的扩展组件。因为 https, SPDY and HTTP/2协议都直接连通了443端口,所以ALPN和NPN让应用层协议能够让应用层协议(plain http/1.1, SPDY or HTTP/2)转化,连通构建在SSL/TLS加密链接上的client和server。
SPDY使用NPN进行转化,HTTP2使用ALPN进行转化。其是建立在SSL/TLS的握手协议流程之上的。
NPN和ALPN都是在SSL/TLS建立链接中进行干预。ALPN会将client支持的应用层协议放在hello message中让server选择一个协议来建立安全链接。NPN则是server列举,client进行选择。
我们可以通过该网站HTTP/2 Test查看浏览器针对各个协议的支持情况。
我们也可以通过该命令行来查看是否支持APLN。
echo | openssl s_client -alpn h2 -connect yourdomain.com:443 | grep ALPN
//check the openssl verison
openssl verison
Tips: openssl版本一定要在1.0.2及其以上。
Nginx支持HTTP/2 (ALPN)
在Nginx上开启 HTTP/2 需要 Nginx 1.9.5 (或者是Nginx Plus R7)以上版本,并且需要 OpenSSL 版本在 1.0.2 以上。
因为 HTTP/2 不仅需要Web服务器还需要一个扩展支持,目前可以用的有 ALPN 和 NPN 两种(Chrome 已经移除了对 NPN 的支持)。只有 OpenSSL 1.0.2 以上版本才开始支持 ALPN 。
如果系统版本不支持或者openssl过低,则需要下载openssl的高版本source code,然后使用--with-openssl
显示的指定openssl library的源码位置,然后rebuild整个Nginx项目。
各个操作系统版本针对openssl以及ALPN的支持情况:
指的注意的是,nginx的一个端口不能绑定多个协议类型,比如说HTTP/1(文本)和ClearText类型的HTTP/2(二进制)绑定在同一个端口。建议如果针对clearText类型的数据,针对不同的协议版本绑定不同的监听端口。因为nginx需要实现设置该端口支持哪一个版本的协议。
针对gRPC,它主要使用HTTP/2当做传输层来使用。
所以当使用nginx来处理普通数据时,一定要小心,其可能有很多种情况。
有三种方法可以让一个HTTP server知道这个请求是http/2:
使用HTTP(原始文本)进行HTTP/2的升级
使用HTTPS(加密数据),然后利用ALPN或者NPN转化为TLS建立安全连接。然后进行HTTP的消息传输。
使用HTTP的原始文本,但是构建HTTP/2的链接(双方直接商议链接方案,事先约定好),直接使用HTTP/2。
nginx第一种不支持,并没有一种能使用HTTP/2链接来进行HTTP/1.1普通文本的数据传输(自动转化),除非事先声明,直接建立HTTP/2的链接。不能主动探测(自动识别,并使用HTTP/2进行连接)。当然第二种方案,例如GRPC的实现,也是可以的。GRPC会清楚的知道这个连接是否需要使用TLS并构建彼此的链接,也就是是否使用HTTP/2的协议。
但是GRPC并不是真正的HTTP。他只是使用了HTTP/2的the binary framing layer,构建一个流控制的、多通道的链接,来进行gRPC的消息的传输。它和Websocket实现HTTP TCP的链接来传输消息是一样的,但是其并不是HTTP,只是用了HTTP的语法定义(规则或者说协议规则)。让他们看起来像是HTTP协议的数据。
Tips:Nginx利用HTTP server监听gRPC的请求,同时使用grpc_pass来进行分发代理。
GRPC node
nginx with grpc
grpc-node for self-signed
grpcs ssl failed
node client to go server
node client to go server
windows and linux, BoringSSL in Grpc
fabric sdk guide for tls enabled
nginx grpc module
grpc status unstable
java grpc error 1
java grpc error 2
Java Grpc ALPN error
Support https with nginx
openssl howto
Libssl wiki
nodejs TLS create
http1.1, SPDY, http2
NPN and ALPN
TLS/SSL握手协议
Golang net http
OpenSSL到BoringSSL的移植