首頁>技術>

Hyperledger Fabric 提供了軟體開發包/SDK以幫助開發者訪問fabric網路 和部署在網路上的鏈碼,但是Hyperledger Fabric官方沒有提供簡單易用的REST API訪問介面,在這個教程裡我們將學習如何利用Hyperledger Fabric的SDK 來開發REST API伺服器。

1、系統結構概述

相關推薦:H..Fabric Java 開發教程 | H..Fabric Nodejs開發教程

整個系統包含兩個物理節點:

Fabric節點:執行Fabric示例中的First Network,並且例項化了Fabcar鏈碼API伺服器節點:執行REST API Server程式碼供外部訪問

下面是部署在AWS上的兩個節點例項的情況:

首先參考官方文件安裝hyperledger fabric。

然後執行指令碼fabcar/startFabric.sh:

cd fabric-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 中使用這個身份標識:

cd javascriptnpm installnode enrollAdmin.jsnode registerUser.jsls wallet/user1

執行結果如下:

現在Rest API Server需要的東西都備齊了:

org1的連線配置檔案:first-network/connection-org1.jsonNode.js包檔案:fabcar/package.jsonUser1身份錢包: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程式碼如下:

var bodyParser = require('body-parser');var app = express();app.use(bodyParser.json());// Setting for Hyperledger Fabricconst { 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);

程式碼中對原來fabcar的query.js和invoke.js修改如下:

ccpPath修改為當前目錄,因為我們要使用同一路徑下的連線配置檔案connection-org1.json在gateway.connect呼叫中,修改選項discovery.asLocalhost為false4、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": "-----BEGIN CERTIFICATE-----\\nMIICVjCCAf2gAwIBAgIQEB1sDT11gzTv0/N4cIGoEjAKBggqhkjOPQQDAjB2MQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTA5MDQwMjQzMDBaFw0yOTA5MDEwMjQz\\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\\nAQcDQgAEoN0qd5hM2SDfvGzNjTCXuQqyk+XK4VISa16/y9iXBPpa0onyAXJuv7T0\\noPf+mh3T7/g8uYtV2bwTpT2XFO3Q6KNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud\\nJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud\\nDgQiBCCalpyChmrLtpgOll6TVmlMOO/2iiyI2PadNPsIYx51mTAKBggqhkjOPQQD\\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/N4cIGoEjAKBggqhkjOPQQDAjB2MQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz\\nY2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xOTA5MDQwMjQzMDBaFw0yOTA5MDEwMjQz\\nMDBaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\\nEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD\\nVQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\\nAQcDQgAEoN0qd5hM2SDfvGzNjTCXuQqyk+XK4VISa16/y9iXBPpa0onyAXJuv7T0\\noPf+mh3T7/g8uYtV2bwTpT2XFO3Q6KNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1Ud\\nJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1Ud\\nDgQiBCCalpyChmrLtpgOll6TVmlMOO/2iiyI2PadNPsIYx51mTAKBggqhkjOPQQD\\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-----\\nMIICUTCCAfegAwIBAgIQSiMHm4n9QvhD6wltAHkZPTAKBggqhkjOPQQDAjBzMQsw\\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\\nb3JnMS5leGFtcGxlLmNvbTAeFw0xOTA5MDQwMjQzMDBaFw0yOTA5MDEwMjQzMDBa\\nMHMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\\nYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMRwwGgYDVQQD\\nExNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\\nz93lOhLJG93uJQgnh93QcPPal5NQXQnAutFKYkun/eMHMe23wNPd0aJhnXdCjWF8\\nMRHVAjtPn4NVCJYiTzSAnaNtMGswDgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQG\\nCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCDK\\naDhLwl3RBO6eKgHh4lHJovIyDJO3jTNb1ix1W86bFjAKBggqhkjOPQQDAgNIADBF\\nAiEA8KTKkjQwb1TduTWWkmsLmKdxrlE6/H7CfsdeGE+onewCIHJ1S0nLhbWYv+G9\\nTbAFlNCpqr0AQefaRT3ghdURrlbo\\n-----END CERTIFICATE-----\\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:

驗證結果如下:

2、然後在Rest API Server上建立一個目錄:

mkdir apiservercd apiserver

3、接下來將下面的檔案從Fabric節點拷貝到Rest API Server節點。我們 利用loccalhost在兩個EC2例項間拷貝:

# localhost (update your own IP of the two servers)# temp is an empty directorycd tempscp -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/

執行結果如下:

4、可以看到現在所有的檔案都拷貝到Rest API Server了,為了保持一致,我們將user1/改名為wallet/user1/:

cd apiservermkdir walletmv user1 wallet/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.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

執行結果如下:

8、安裝必要的依賴包:

npm installnpm install express body-parser --save

9、萬事俱備,啟動Rest API Server:

node apiserver.js
7、訪問API

我們的API服務在8080埠監聽,在下面的示例中,我們使用curl來 演示如何訪問。

1、查詢所有車輛記錄

curl http://[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" -X POST http://[API-Server-Node-IP]:8080/api/addcarcurl http://[API-Server-Node-IP]:8080/api/query/CAR12

執行結果如下:

3、修改車輛所有者並再次查詢

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

執行結果如下:

我們也可以用postman得到同樣的結果:


匯智網原文連結:http://blog.hubwiz.com/2020/01/06/hyperledger-api-server-impl/

  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Web測試和App測試有什麼區別