(目前有点乱,先贴上来,等以后有时间在整理吧。这个问题一直想拿出来分享,还有两个博客,都是相关的,一点点发出来)
最近要在fabric网络和外部添加一层load balance,然后使用node的grpcs调用nginx,再转发到peer或者orderer。但是一直显示code 14 connect failed
。log信息少的可怜。所以索性就过一遍代码。找找区别,顺便打打log。
版本
fabric-client和grpc的版本
$ npm ls | grep fabric
+-- [email protected]
+-- [email protected]
$ npm ls | grep grpc
`-- [email protected]
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()
构建通道。
- 这个类非常重要,它是构建grpc对象的核心。主要就是针对url判断protocol,如果是grpcs则会使用
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。
ALPN
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\
详细的解决方案可以看另外一个博客。
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之上
gRPC
其是谷歌开发的一种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的移植