Hyperledger Fabric Rest API服务开发教程【含源码】
Hyperledger Fabric Rest API服務(wù)開(kāi)發(fā)教程【含源碼】
Hyperledger Fabric 提供了軟件開(kāi)發(fā)包/SDK以幫助開(kāi)發(fā)者訪問(wèn)fabric網(wǎng)絡(luò) 和部署在網(wǎng)絡(luò)上的鏈碼,但是Hyperledger Fabric官方?jīng)]有提供簡(jiǎn)單易用的REST API訪問(wèn)接口,在這個(gè)教程里我們將學(xué)習(xí)如何利用Hyperledger Fabric的SDK 來(lái)開(kāi)發(fā)REST API服務(wù)器。
1、系統(tǒng)結(jié)構(gòu)概述
相關(guān)推薦:H..Fabric Java 開(kāi)發(fā)教程?|?H..Fabric Nodejs開(kāi)發(fā)教程
整個(gè)系統(tǒng)包含兩個(gè)物理節(jié)點(diǎn):
- Fabric節(jié)點(diǎn):運(yùn)行Fabric示例中的First Network,并且實(shí)例化了Fabcar鏈碼
- API服務(wù)器節(jié)點(diǎn):運(yùn)行REST API Server代碼供外部訪問(wèn)
下面是部署在AWS上的兩個(gè)節(jié)點(diǎn)實(shí)例的情況:
首先參考官方文檔安裝hyperledger fabric。
然后運(yùn)行腳本fabcar/startFabric.sh:
| 1 2 | cd fabric-samples/fabcar ./startFabric.sh |
上述腳本運(yùn)行之后,我們就得到一個(gè)正常運(yùn)轉(zhuǎn)的Hyperledger Fabric網(wǎng)絡(luò)(著名的演示網(wǎng)絡(luò)First Network),包含2個(gè)機(jī)構(gòu)/4個(gè)對(duì)等節(jié)點(diǎn), 通道為mychannel,鏈碼Fabcar安裝在全部4個(gè)對(duì)等節(jié)點(diǎn)上并且在mychannel上激活。賬本中有10條車(chē)輛記錄,這是調(diào)用 合約的initLedger方法的結(jié)果。
現(xiàn)在我們?yōu)镽EST API Server準(zhǔn)備身份標(biāo)識(shí)數(shù)據(jù)。使用fabcar/javascript創(chuàng)建一個(gè)用戶標(biāo)識(shí)user1,我們將在REST API Server 中使用這個(gè)身份標(biāo)識(shí):
| 1 2 3 4 5 | cd javascript npm install node enrollAdmin.js node registerUser.js ls wallet/user1 |
運(yùn)行結(jié)果如下:
現(xiàn)在Rest API Server需要的東西都備齊了:
- org1的連接配置文件:first-network/connection-org1.json
- Node.js包文件:fabcar/package.json
- User1身份錢(qián)包:fabcar/javascript/wallet/user1/
后面我們會(huì)把這些數(shù)據(jù)文件拷貝到Rest API Server。
2、Rest API Server設(shè)計(jì)
我們使用ExressJS來(lái)開(kāi)發(fā)API服務(wù),利用query.js和invoke.js 中的代碼實(shí)現(xiàn)與fabric交互的邏輯。API設(shè)計(jì)如下:
- GET /api/queryallcars:返回全部車(chē)輛記錄
- GET /api/query/CarID:返回指定ID的車(chē)輛記錄
- POST /api/addcar/:添加一條新的車(chē)輛記錄
- PUT /api/changeowner/CarID:修改指定ID的車(chē)輛記錄
3、Rest API Server代碼實(shí)現(xiàn)
apiserver.js代碼如下:
express
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | var bodyParser = require('body-parser');var app = express(); app.use(bodyParser.json());// Setting for Hyperledger Fabric const { FileSystemWallet, Gateway } = require('fabric-network'); const path = require('path'); const ccpPath = path.resolve(__dirname, '.', 'connection-org1.json');app.get('/api/queryallcars', async function (req, res) {try {// Create a new file system based wallet for managing identities.const walletPath = path.join(process.cwd(), 'wallet');const wallet = new FileSystemWallet(walletPath);console.log(`Wallet path: ${walletPath}`);// Check to see if we've already enrolled the user.const userExists = await wallet.exists('user1');if (!userExists) {console.log('An identity for the user "user1" does not exist in the wallet');console.log('Run the registerUser.js application before retrying');return;}// Create a new gateway for connecting to our peer node.const gateway = new Gateway();await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: false } });// Get the network (channel) our contract is deployed to.const network = await gateway.getNetwork('mychannel');// Get the contract from the network.const contract = network.getContract('fabcar');// Evaluate the specified transaction.// queryCar transaction - requires 1 argument, ex: ('queryCar', 'CAR4')// queryAllCars transaction - requires no arguments, ex: ('queryAllCars')const result = await contract.evaluateTransaction('queryAllCars');console.log(`Transaction has been evaluated, result is: ${result.toString()}`);res.status(200).json({response: result.toString()});} catch (error) {console.error(`Failed to evaluate transaction: ${error}`);res.status(500).json({error: error});process.exit(1);} });app.get('/api/query/:car_index', async function (req, res) {try {// Create a new file system based wallet for managing identities.const walletPath = path.join(process.cwd(), 'wallet');const wallet = new FileSystemWallet(walletPath);console.log(`Wallet path: ${walletPath}`);// Check to see if we've already enrolled the user.const userExists = await wallet.exists('user1');if (!userExists) {console.log('An identity for the user "user1" does not exist in the wallet');console.log('Run the registerUser.js application before retrying');return;}// Create a new gateway for connecting to our peer node.const gateway = new Gateway();await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: false } });// Get the network (channel) our contract is deployed to.const network = await gateway.getNetwork('mychannel');// Get the contract from the network.const contract = network.getContract('fabcar');// Evaluate the specified transaction.// queryCar transaction - requires 1 argument, ex: ('queryCar', 'CAR4')// queryAllCars transaction - requires no arguments, ex: ('queryAllCars')const result = await contract.evaluateTransaction('queryCar', req.params.car_index);console.log(`Transaction has been evaluated, result is: ${result.toString()}`);res.status(200).json({response: result.toString()});} catch (error) {console.error(`Failed to evaluate transaction: ${error}`);res.status(500).json({error: error});process.exit(1);} });app.post('/api/addcar/', async function (req, res) {try {// Create a new file system based wallet for managing identities.const walletPath = path.join(process.cwd(), 'wallet');const wallet = new FileSystemWallet(walletPath);console.log(`Wallet path: ${walletPath}`);// Check to see if we've already enrolled the user.const userExists = await wallet.exists('user1');if (!userExists) {console.log('An identity for the user "user1" does not exist in the wallet');console.log('Run the registerUser.js application before retrying');return;}// Create a new gateway for connecting to our peer node.const gateway = new Gateway();await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: false } });// Get the network (channel) our contract is deployed to.const network = await gateway.getNetwork('mychannel');// Get the contract from the network.const contract = network.getContract('fabcar');// Submit the specified transaction.// createCar transaction - requires 5 argument, ex: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom')// changeCarOwner transaction - requires 2 args , ex: ('changeCarOwner', 'CAR10', 'Dave')await contract.submitTransaction('createCar', req.body.carid, req.body.make, req.body.model, req.body.colour, req.body.owner);console.log('Transaction has been submitted');res.send('Transaction has been submitted');// Disconnect from the gateway.await gateway.disconnect();} catch (error) {console.error(`Failed to submit transaction: ${error}`);process.exit(1);} })app.put('/api/changeowner/:car_index', async function (req, res) {try {// Create a new file system based wallet for managing identities.const walletPath = path.join(process.cwd(), 'wallet');const wallet = new FileSystemWallet(walletPath);console.log(`Wallet path: ${walletPath}`);// Check to see if we've already enrolled the user.const userExists = await wallet.exists('user1');if (!userExists) {console.log('An identity for the user "user1" does not exist in the wallet');console.log('Run the registerUser.js application before retrying');return;}// Create a new gateway for connecting to our peer node.const gateway = new Gateway();await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: false } });// Get the network (channel) our contract is deployed to.const network = await gateway.getNetwork('mychannel');// Get the contract from the network.const contract = network.getContract('fabcar');// Submit the specified transaction.// createCar transaction - requires 5 argument, ex: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom')// changeCarOwner transaction - requires 2 args , ex: ('changeCarOwner', 'CAR10', 'Dave')await contract.submitTransaction('changeCarOwner', req.params.car_index, req.body.owner);console.log('Transaction has been submitted');res.send('Transaction has been submitted');// Disconnect from the gateway.await gateway.disconnect();} catch (error) {console.error(`Failed to submit transaction: ${error}`);process.exit(1);} })app.listen(8080); |
代碼中對(duì)原來(lái)fabcar的query.js和invoke.js修改如下:
- ccpPath修改為當(dāng)前目錄,因?yàn)槲覀円褂猛宦窂较碌倪B接配置文件connection-org1.json
- 在gateway.connect調(diào)用中,修改選項(xiàng)discovery.asLocalhost為false
4、Rest API Server的連接配置文件
API服務(wù)依賴于連接配置文件來(lái)正確連接fabric網(wǎng)絡(luò)。文件 connection-org1.json 可以直接從 fabric網(wǎng)絡(luò)中獲取:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | {"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": "-----BEGIN CERTIFICATE-----\nMIICVjCCAf2gAwIBAgIQEB1sDT11gzTv0/N4cIGoEjAKBggqhkjOPQQDA jB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGF tcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTA5MDQwMjQzMDBaFw0yOTA5MDEwMjQz\nMDBaMHYxCzAJB gNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tM R8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAEoN0qd5hM2SDfvGzNjTCXuQqyk+X K4VISa16/y9iXBPpa0onyAXJuv7T0\noPf+mh3T7/g8uYtV2bwTpT2XFO3Q6KNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud\nJQQWMBQGCCsGA QUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud\nDgQiBCCalpyChmrLtpgOll6TVmlMOO/2iiyI2PadNPsIYx51mTAKBggqh kjOPQQD\nAgNHADBEAiBLNoAYWe9LvoxxBxl3sUM64kl7rx6dI3JU+dJG6FRxWgIgCu1ONEyp\nfux9lZWr6gcrIdsn/8fQuWiOIbAgq0HSr60 =\n-----END CERTIFICATE-----\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": "-----BEGIN CERTIFICATE-----\nMIICVjCCAf2gAwIBAgIQEB1sDT11gzTv0/N4cIGoEjAKBggqhkjOPQQDA jB2MQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGF tcGxlLmNvbTEfMB0GA1UEAxMWdGxz\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTA5MDQwMjQzMDBaFw0yOTA5MDEwMjQz\nMDBaMHYxCzAJB gNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tM R8wHQYD\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAEoN0qd5hM2SDfvGzNjTCXuQqyk+X K4VISa16/y9iXBPpa0onyAXJuv7T0\noPf+mh3T7/g8uYtV2bwTpT2XFO3Q6KNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud\nJQQWMBQGCCsGA QUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud\nDgQiBCCalpyChmrLtpgOll6TVmlMOO/2iiyI2PadNPsIYx51mTAKBggqh kjOPQQD\nAgNHADBEAiBLNoAYWe9LvoxxBxl3sUM64kl7rx6dI3JU+dJG6FRxWgIgCu1ONEyp\nfux9lZWr6gcrIdsn/8fQuWiOIbAgq0HSr60 =\n-----END CERTIFICATE-----\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": "-----BEGIN CERTIFICATE-----\nMIICUTCCAfegAwIBAgIQSiMHm4n9QvhD6wltAHkZPTAKBggqhkjOPQQDA jBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGF tcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0xOTA5MDQwMjQzMDBaFw0yOTA5MDEwMjQzMDBa\nMHMxCzAJBgNVB AYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwG gYDVQQD\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\nz93lOhLJG93uJQgnh93QcPPal5NQXQnAutF KYkun/eMHMe23wNPd0aJhnXdCjWF8\nMRHVAjtPn4NVCJYiTzSAnaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\nCCsGAQUFBwMCB ggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDK\naDhLwl3RBO6eKgHh4lHJovIyDJO3jTNb1ix1W86bFjAKBggqhkjOPQQDA gNIADBF\nAiEA8KTKkjQwb1TduTWWkmsLmKdxrlE6/H7CfsdeGE+onewCIHJ1S0nLhbWYv+G9\nTbAFlNCpqr0AQefaRT3ghdURrlbo\n----- END CERTIFICATE-----\n"},"httpOptions": {"verify": false}}} } |
當(dāng)fabcar/startFabric.sh執(zhí)行時(shí),我們可以交叉檢查證書(shū)的傳播是否正確。 peer0.org1和 peer1.org1 的證書(shū)是org1的 TLS root CA 證書(shū)簽名的。
注意所有的節(jié)點(diǎn)都以localhost引用,我們稍后會(huì)將其修改為Fabric Node的 公開(kāi)IP地址。
5、用戶身份標(biāo)識(shí)
我們已經(jīng)在Fabric節(jié)點(diǎn)上生成了一個(gè)用戶標(biāo)識(shí)user1并保存在wallet目錄中, 我們可以看到有三個(gè)對(duì)應(yīng)的文件:私鑰、公鑰和證書(shū)對(duì)象:
稍后我們會(huì)把這些文件拷貝到Rest API Server上。
6、安裝Rest API Server節(jié)點(diǎn)
1、首先在Rest API Server節(jié)點(diǎn)上安裝npm、node:
| 1 2 3 4 5 6 | sudo apt-get update sudo apt install curl curl -sL https://deb.nodesource.com/setup_8.x | sudo bash - sudo apt install -y nodejs sudo apt-get install build-essentialnode -v npm -v |
驗(yàn)證結(jié)果如下:
2、然后在Rest API Server上創(chuàng)建一個(gè)目錄:
| 1 2 | mkdir apiserver cd apiserver |
3、接下來(lái)將下面的文件從Fabric節(jié)點(diǎn)拷貝到Rest API Server節(jié)點(diǎn)。我們 利用loccalhost在兩個(gè)EC2實(shí)例間拷貝:
| 1 2 3 4 5 6 7 | # localhost (update your own IP of the two servers) # temp is an empty directory cd temp scp -i ~/Downloads/aws.pem ubuntu@[Fabric-Node-IP]:/home/ubuntu/fabric-samples/first-network/connection-org1.json . scp -i ~/Downloads/aws.pem ubuntu@[Fabric-Node-IP]:/home/ubuntu/fabric-samples/fabcar/javascript/package.json . scp -r -i ~/Downloads/aws.pem ubuntu@[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/ |
運(yùn)行結(jié)果如下:
4、可以看到現(xiàn)在所有的文件都拷貝到Rest API Server了,為了保持一致,我們將user1/改名為wallet/user1/:
| 1 2 3 | cd apiserver mkdir wallet mv user1 wallet/user1 |
運(yùn)行結(jié)果如下:
5、現(xiàn)在在Rest API Server上創(chuàng)建上面的apiserver.js文件。
6、修改連接配置文件connection-org1.json 中的fabric節(jié)點(diǎn)的ip地址:
| 1 | sed -i 's/localhost/[Fabric-Node-IP]/g' connection-org1.json |
運(yùn)行結(jié)果如下:
7、在/etc/hosts中增加條目以便可以正確解析fabric節(jié)點(diǎn)的IP:
| 1 2 3 4 5 6 | 127.0.0.1 localhost [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 |
運(yùn)行結(jié)果如下:
8、安裝必要的依賴包:
| 1 2 | npm install npm install express body-parser --save |
9、萬(wàn)事俱備,啟動(dòng)Rest API Server:
| 1 | node apiserver.js |
7、訪問(wèn)API
我們的API服務(wù)在8080端口監(jiān)聽(tīng),在下面的示例中,我們使用curl來(lái) 演示如何訪問(wèn)。
1、查詢所有車(chē)輛記錄
| 1 | curl http://[API-Server-Node-IP]:8080/api/queryallcars |
運(yùn)行結(jié)果如下:
2、添加新的車(chē)輛記錄并查詢
| 1 2 3 | curl -d '{"carid":"CAR12","make":"Honda","model":"Accord","colour":"black","owner":"Tom"}' -H "Content-Type: application/json" -X POST http://[API-Server-Node-IP]:8080/api/addcarcurl http://[API-Server-Node-IP]:8080/api/query/CAR12 |
運(yùn)行結(jié)果如下:
3、修改車(chē)輛所有者并再次查詢
| 1 2 3 4 5 | curl http://[API-Server-Node-IP]:8080/api/query/CAR4curl -d '{"owner":"KC"}' -H "Content-Type: application/json" -X PUT http://[API-Server-Node-IP]:8080/api/changeowner/CAR4curl http://[API-Server-Node-IP]:8080/api/query/CAR4 |
運(yùn)行結(jié)果如下:
我們也可以用postman得到同樣的結(jié)果:
原文鏈接:An Implementation of API Server for Hyperledger Fabric Network
總結(jié)
以上是生活随笔為你收集整理的Hyperledger Fabric Rest API服务开发教程【含源码】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: hyperledger-simple-a
- 下一篇: Ubuntu 18.04上进行Hyper