如何利用Hyperledger Fabric的SDK来开发REST API服务器
如何利用Hyperledger Fabric的SDK来开发REST API服务器
这篇文章主要为大家展示了“如何利用Hyperledger Fabric的SDK来开发REST API服务器”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何利用Hyperledger Fabric的SDK来开发REST API服务器”这篇文章吧。
1、系统结构概述
整个系统包含两个物理节点:
Fabric节点:运行Fabric示例中的First Network,并且实例化了Fabcar链码
API服务器节点:运行REST API Server代码供外部访问
下面是部署在AWS上的两个节点实例的情况:
首先参考官方文档安装hyperledger fabric。
然后运行脚本fabcar/startFabric.sh
:
cdfabric-samples/fabcar./startFabric.sh
上述脚本运行之后,我们就得到一个正常运转的Hyperledger Fabric网络(著名的演示网络First Network),包含2个机构/4个对等节点,通道为mychannel,链码Fabcar安装在全部4个对等节点上并且在mychannel上激活。账本中有10条车辆记录,这是调用合约的initLedger
方法的结果。
现在我们为REST API Server准备身份标识数据。使用fabcar/javascript创建一个用户标识user1,我们将在REST API Server中使用这个身份标识:
cdjavascriptnpminstallnodeenrollAdmin.jsnoderegisterUser.jslswallet/user1
运行结果如下:
现在Rest API Server需要的东西都备齐了:
org1的连接配置文件:first-network/connection-org1.json
Node.js包文件:fabcar/package.json
User1身份钱包:fabcar/javascript/wallet/user1/
后面我们会把这些数据文件拷贝到Rest API Server。
2、Rest API Server设计
我们使用ExressJS来开发API服务,利用query.js和invoke.js中的代码实现与fabric交互的逻辑。API设计如下:
GET /api/queryallcars:返回全部车辆记录
GET /api/query/CarID:返回指定ID的车辆记录
POST /api/addcar/:添加一条新的车辆记录
PUT /api/changeowner/CarID:修改指定ID的车辆记录
3、Rest API Server代码实现
apiserver.js代码如下:
varbodyParser=require('body-parser');varapp=express();app.use(bodyParser.json());//SettingforHyperledgerFabricconst{FileSystemWallet,Gateway}=require('fabric-network');constpath=require('path');constccpPath=path.resolve(__dirname,'.','connection-org1.json');app.get('/api/queryallcars',asyncfunction(req,res){try{//Createanewfilesystembasedwalletformanagingidentities.constwalletPath=path.join(process.cwd(),'wallet');constwallet=newFileSystemWallet(walletPath);console.log(`Walletpath:${walletPath}`);//Checktoseeifwe'vealreadyenrolledtheuser.constuserExists=awaitwallet.exists('user1');if(!userExists){console.log('Anidentityfortheuser"user1"doesnotexistinthewallet');console.log('RuntheregisterUser.jsapplicationbeforeretrying');return;}//Createanewgatewayforconnectingtoourpeernode.constgateway=newGateway();awaitgateway.connect(ccpPath,{wallet,identity:'user1',discovery:{enabled:true,asLocalhost:false}});//Getthenetwork(channel)ourcontractisdeployedto.constnetwork=awaitgateway.getNetwork('mychannel');//Getthecontractfromthenetwork.constcontract=network.getContract('fabcar');//Evaluatethespecifiedtransaction.//queryCartransaction-requires1argument,ex:('queryCar','CAR4')//queryAllCarstransaction-requiresnoarguments,ex:('queryAllCars')constresult=awaitcontract.evaluateTransaction('queryAllCars');console.log(`Transactionhasbeenevaluated,resultis:${result.toString()}`);res.status(200).json({response:result.toString()});}catch(error){console.error(`Failedtoevaluatetransaction:${error}`);res.status(500).json({error:error});process.exit(1);}});app.get('/api/query/:car_index',asyncfunction(req,res){try{//Createanewfilesystembasedwalletformanagingidentities.constwalletPath=path.join(process.cwd(),'wallet');constwallet=newFileSystemWallet(walletPath);console.log(`Walletpath:${walletPath}`);//Checktoseeifwe'vealreadyenrolledtheuser.constuserExists=awaitwallet.exists('user1');if(!userExists){console.log('Anidentityfortheuser"user1"doesnotexistinthewallet');console.log('RuntheregisterUser.jsapplicationbeforeretrying');return;}//Createanewgatewayforconnectingtoourpeernode.constgateway=newGateway();awaitgateway.connect(ccpPath,{wallet,identity:'user1',discovery:{enabled:true,asLocalhost:false}});//Getthenetwork(channel)ourcontractisdeployedto.constnetwork=awaitgateway.getNetwork('mychannel');//Getthecontractfromthenetwork.constcontract=network.getContract('fabcar');//Evaluatethespecifiedtransaction.//queryCartransaction-requires1argument,ex:('queryCar','CAR4')//queryAllCarstransaction-requiresnoarguments,ex:('queryAllCars')constresult=awaitcontract.evaluateTransaction('queryCar',req.params.car_index);console.log(`Transactionhasbeenevaluated,resultis:${result.toString()}`);res.status(200).json({response:result.toString()});}catch(error){console.error(`Failedtoevaluatetransaction:${error}`);res.status(500).json({error:error});process.exit(1);}});app.post('/api/addcar/',asyncfunction(req,res){try{//Createanewfilesystembasedwalletformanagingidentities.constwalletPath=path.join(process.cwd(),'wallet');constwallet=newFileSystemWallet(walletPath);console.log(`Walletpath:${walletPath}`);//Checktoseeifwe'vealreadyenrolledtheuser.constuserExists=awaitwallet.exists('user1');if(!userExists){console.log('Anidentityfortheuser"user1"doesnotexistinthewallet');console.log('RuntheregisterUser.jsapplicationbeforeretrying');return;}//Createanewgatewayforconnectingtoourpeernode.constgateway=newGateway();awaitgateway.connect(ccpPath,{wallet,identity:'user1',discovery:{enabled:true,asLocalhost:false}});//Getthenetwork(channel)ourcontractisdeployedto.constnetwork=awaitgateway.getNetwork('mychannel');//Getthecontractfromthenetwork.constcontract=network.getContract('fabcar');//Submitthespecifiedtransaction.//createCartransaction-requires5argument,ex:('createCar','CAR12','Honda','Accord','Black','Tom')//changeCarOwnertransaction-requires2args,ex:('changeCarOwner','CAR10','Dave')awaitcontract.submitTransaction('createCar',req.body.carid,req.body.make,req.body.model,req.body.colour,req.body.owner);console.log('Transactionhasbeensubmitted');res.send('Transactionhasbeensubmitted');//Disconnectfromthegateway.awaitgateway.disconnect();}catch(error){console.error(`Failedtosubmittransaction:${error}`);process.exit(1);}})app.put('/api/changeowner/:car_index',asyncfunction(req,res){try{//Createanewfilesystembasedwalletformanagingidentities.constwalletPath=path.join(process.cwd(),'wallet');constwallet=newFileSystemWallet(walletPath);console.log(`Walletpath:${walletPath}`);//Checktoseeifwe'vealreadyenrolledtheuser.constuserExists=awaitwallet.exists('user1');if(!userExists){console.log('Anidentityfortheuser"user1"doesnotexistinthewallet');console.log('RuntheregisterUser.jsapplicationbeforeretrying');return;}//Createanewgatewayforconnectingtoourpeernode.constgateway=newGateway();awaitgateway.connect(ccpPath,{wallet,identity:'user1',discovery:{enabled:true,asLocalhost:false}});//Getthenetwork(channel)ourcontractisdeployedto.constnetwork=awaitgateway.getNetwork('mychannel');//Getthecontractfromthenetwork.constcontract=network.getContract('fabcar');//Submitthespecifiedtransaction.//createCartransaction-requires5argument,ex:('createCar','CAR12','Honda','Accord','Black','Tom')//changeCarOwnertransaction-requires2args,ex:('changeCarOwner','CAR10','Dave')awaitcontract.submitTransaction('changeCarOwner',req.params.car_index,req.body.owner);console.log('Transactionhasbeensubmitted');res.send('Transactionhasbeensubmitted');//Disconnectfromthegateway.awaitgateway.disconnect();}catch(error){console.error(`Failedtosubmittransaction:${error}`);process.exit(1);}})app.listen(8080);
代码中对原来fabcar的query.js和invoke.js修改如下:
ccpPath修改为当前目录,因为我们要使用同一路径下的连接配置文件connection-org1.json
在gateway.connect调用中,修改选项discovery.asLocalhost为false
4、Rest API Server的连接配置文件
API服务依赖于连接配置文件来正确连接fabric网络。文件 connection-org1.json 可以直接从 fabric网络中获取:
{"name":"first-network-org1","version":"1.0.0","client":{"organization":"Org1","connection":{"timeout":{"peer":{"endorser":"300"}}}},"organizations":{"Org1":{"mspid":"Org1MSP","peers":["peer0.org1.example.com","peer1.org1.example.com"],"certificateAuthorities":["ca.org1.example.com"]}},"peers":{"peer0.org1.example.com":{"url":"grpcs://localhost:7051","tlsCACerts":{"pem":"-----BEGINCERTIFICATE-----\nMIICVjCCAf2gAwIBAgIQEB1sDT11gzTv0/N4cIGoEjAKBggqhkjOPQQDAjB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTA5MDQwMjQzMDBaFw0yOTA5MDEwMjQz\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAEoN0qd5hM2SDfvGzNjTCXuQqyk+XK4VISa16/y9iXBPpa0onyAXJuv7T0\noPf+mh4T7/g8uYtV2bwTpT2XFO3Q6KNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud\nJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud\nDgQiBCCalpyChmrLtpgOll6TVmlMOO/2iiyI2PadNPsIYx51mTAKBggqhkjOPQQD\nAgNHADBEAiBLNoAYWe9LvoxxBxl3sUM64kl7rx6dI3JU+dJG6FRxWgIgCu1ONEyp\nfux9lZWr6gcrIdsn/8fQuWiOIbAgq0HSr60=\n-----ENDCERTIFICATE-----\n"},"grpcOptions":{"ssl-target-name-override":"peer0.org1.example.com","hostnameOverride":"peer0.org1.example.com"}},"peer1.org1.example.com":{"url":"grpcs://localhost:8051","tlsCACerts":{"pem":"-----BEGINCERTIFICATE-----\nMIICVjCCAf2gAwIBAgIQEB1sDT11gzTv0/N4cIGoEjAKBggqhkjOPQQDAjB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTA5MDQwMjQzMDBaFw0yOTA5MDEwMjQz\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAEoN0qd5hM2SDfvGzNjTCXuQqyk+XK4VISa16/y9iXBPpa0onyAXJuv7T0\noPf+mh4T7/g8uYtV2bwTpT2XFO3Q6KNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud\nJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud\nDgQiBCCalpyChmrLtpgOll6TVmlMOO/2iiyI2PadNPsIYx51mTAKBggqhkjOPQQD\nAgNHADBEAiBLNoAYWe9LvoxxBxl3sUM64kl7rx6dI3JU+dJG6FRxWgIgCu1ONEyp\nfux9lZWr6gcrIdsn/8fQuWiOIbAgq0HSr60=\n-----ENDCERTIFICATE-----\n"},"grpcOptions":{"ssl-target-name-override":"peer1.org1.example.com","hostnameOverride":"peer1.org1.example.com"}}},"certificateAuthorities":{"ca.org1.example.com":{"url":"https://localhost:7054","caName":"ca-org1","tlsCACerts":{"pem":"-----BEGINCERTIFICATE-----\nMIICUTCCAfegAwIBAgIQSiMHm4n9QvhD6wltAHkZPTAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0xOTA5MDQwMjQzMDBaFw0yOTA5MDEwMjQzMDBa\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nz93lOhLJG93uJQgnh93QcPPal5NQXQnAutFKYkun/eMHMe23wNPd0aJhnXdCjWF8\nMRHVAjtPn4NVCJYiTzSAnaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDK\naDhLwl3RBO6eKgHh5lHJovIyDJO3jTNb1ix1W86bFjAKBggqhkjOPQQDAgNIADBF\nAiEA8KTKkjQwb1TduTWWkmsLmKdxrlE6/H7CfsdeGE+onewCIHJ1S0nLhbWYv+G9\nTbAFlNCpqr0AQefaRT3ghdURrlbo\n-----ENDCERTIFICATE-----\n"},"httpOptions":{"verify":false}}}}
当fabcar/startFabric.sh执行时,我们可以交叉检查证书的传播是否正确。 peer0.org1和 peer1.org1 的证书是org1的 TLS root CA 证书签名的。
注意所有的节点都以localhost引用,我们稍后会将其修改为Fabric Node的公开IP地址。
5、用户身份标识
我们已经在Fabric节点上生成了一个用户标识user1并保存在wallet目录中,我们可以看到有三个对应的文件:私钥、公钥和证书对象:
稍后我们会把这些文件拷贝到Rest API Server上。
6、安装Rest API Server节点
1、首先在Rest API Server节点上安装npm、node:
sudoapt-getupdatesudoaptinstallcurlcurl-sLhttps://deb.nodesource.com/setup_8.x|sudobash-sudoaptinstall-ynodejssudoapt-getinstallbuild-essentialnode-vnpm-v
验证结果如下:
2、然后在Rest API Server上创建一个目录:
mkdirapiservercdapiserver
3、接下来将下面的文件从Fabric节点拷贝到Rest API Server节点。我们 利用loccalhost在两个EC2实例间拷贝:
#localhost(updateyourownIPofthetwoservers)#tempisanemptydirectorycdtempscp-i~/Downloads/aws.pemubuntu@[Fabric-Node-IP]:/home/ubuntu/fabric-samples/first-network/connection-org1.json.scp-i~/Downloads/aws.pemubuntu@[Fabric-Node-IP]:/home/ubuntu/fabric-samples/fabcar/javascript/package.json.scp-r-i~/Downloads/aws.pemubuntu@[Fabric-Node-IP]:/home/ubuntu/fabric-samples/fabcar/javascript/wallet/user1/.scp-r-i~/Downloads/aws.pem*ubuntu@[API-Server-Node-IP]:/home/ubuntu/apiserver/
运行结果如下:
4、可以看到现在所有的文件都拷贝到Rest API Server了,为了保持一致,我们将user1/改名为wallet/user1/:
cdapiservermkdirwalletmvuser1wallet/user1
运行结果如下:
5、现在在Rest API Server上创建上面的apiserver.js文件。
6、修改连接配置文件connection-org1.json 中的fabric节点的ip地址:
sed-i's/localhost/[Fabric-Node-IP]/g'connection-org1.json
运行结果如下:
7、在/etc/hosts中增加条目以便可以正确解析fabric节点的IP:
127.0.0.1localhost[Fabric-Node-IP]orderer.example.com[Fabric-Node-IP]peer0.org1.example.com[Fabric-Node-IP]peer1.org1.example.com[Fabric-Node-IP]peer0.org2.example.com[Fabric-Node-IP]peer1.org2.example.com
运行结果如下:
8、安装必要的依赖包:
npminstallnpminstallexpressbody-parser--save
9、万事俱备,启动Rest API Server:
nodeapiserver.js
7、访问API
我们的API服务在8080端口监听,在下面的示例中,我们使用curl来 演示如何访问。
1、查询所有车辆记录
curlhttp://[API-Server-Node-IP]:8080/api/queryallcars
运行结果如下:
2、添加新的车辆记录并查询
curl-d'{"carid":"CAR12","make":"Honda","model":"Accord","colour":"black","owner":"Tom"}'-H"Content-Type:application/json"-XPOSThttp://[API-Server-Node-IP]:8080/api/addcarcurlhttp://[API-Server-Node-IP]:8080/api/query/CAR12
运行结果如下:
3、修改车辆所有者并再次查询
curlhttp://[API-Server-Node-IP]:8080/api/query/CAR4curl-d'{"owner":"KC"}'-H"Content-Type:application/json"-XPUThttp://[API-Server-Node-IP]:8080/api/changeowner/CAR4curlhttp://[API-Server-Node-IP]:8080/api/query/CAR4
运行结果如下:
我们也可以用postman得到同样的结果:
以上是“如何利用Hyperledger Fabric的SDK来开发REST API服务器”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注恰卡编程网行业资讯频道!