- Используйте программу блокчейн multichain для соединения двух капель Digital Ocean.
- Установка Drupal 8 на одну ноду
- Приложение Angular 5 на другом узле
- Обновления Drupal будут распространяться в реальном времени на Angular через блокчейн.
- Это часть 2, где я загружаю выходные данные блокчейна в Angular.
- Часть 1: https://medium.com/@blockbinder/blockchain-in-action-a-working-example-part-1-writing-drupal-output-to-blockchain-efb2ff1df73
- Это вторая часть рабочей демонстрации интеграции веб-приложений с блокчейном. Эта демонстрация является примером содержимого Drupal, записываемого в блокчейн и отображаемого в реальном времени в приложении Angular 5. В этой части мы будем использовать веб-сокеты, Angular и multichain для отображения потока данных, поступающих из блокчейна, в реальном времени.
Демо:
- Показатель:
- Введение
- Шаг 1. Напишите в Steam
- Шаг 2: подготовьте Angular
- Вывод
Chainfrog действительно счастливы и взволнованы тем интересом, который мы проявляем к нашей деятельности в области блокчейнов в последнее время. После разработки запатентованного инструмента синхронизации базы данных для безопасного обмена данными через блокчейн, мы получили самый убедительный отклик - это желание увидеть под капотом. Поскольку я не могу передать наш IP-адрес, я удалил наш инструмент из середины и получил полную демонстрацию использования блокчейна в качестве протокола веб-коммуникации. Если вы хотите немного узнать о том, как и почему, не стесняйтесь вернуться к моим предыдущим блогам:
Если вы хотите заполучить сочный код, читайте дальше. Это руководство позволит вам начать работу с инструментами с открытым исходным кодом за несколько часов.
Шаг 1. Подключите демон multichain и напишите в Steam
На предыдущем шаге мы настроили два наших облачных узла и подключили их к одной и той же цепочке блоков. Теперь нам нужен способ подключить вывод нашей цепочки блоков к нашему веб-приложению. Есть много способов сделать это (например, мы могли бы использовать drush для обновления Drupal, или мы могли бы использовать обратный прокси и отправлять данные через http-запрос), но в этой демонстрации я решил использовать веб-сокеты и каналы. Причина этого в том, что мы можем подписать наше приложение Angular на веб-сокет, а затем получать обновление «в реальном времени» каждый раз, когда блок публикуется в цепочке блоков. Есть много способов снять шкуру с кошки, и я выбрала более интересный, а не обычный!
Итак, первый шаг для нас - подключить демон. В параметрах выполнения нескольких цепочек https://www.multichain.com/developers/runtime-parameters/ мы можем увидеть команду blocknotify, команду, выполняемую каждый раз, когда новый блок публикуется в цепочке блоков.
blocknotify
Выполните эту команду, когда новый блок добавляется в конец текущей цепочки.%s
в параметрах команды будет заменен хешем блока.
Итак, план состоит в том, чтобы просто записать хэш блока в FIFO в Linux, а затем подписаться на этот файл через наш обратный прокси-сервер ExpressJS.
# login to node 2 cd /var/www/medium-demo-angular mkdir server cd server mkfifo blocks.fifo # Write block hashes to fifo when initalize daemon multichaind [email protected]:7891 -blocknotify="echo '%s' > /var/www/medium-demo-angular/server/blocks.fifo" -daemon #Check this is working nano fifo.sh
fifo.sh
#!/bin/bash pipe=/var/www/medium-demo-angular/server/blocks.fifo trap "rm -f $pipe" EXIT while true do if read line <$pipe; then echo $line fi done echo "Reader exiting" #exit
Теперь мы можем запустить скрипт!
sh fifo.sh
Теперь, если мы просто добавим контент на https://medium-demo-drupal.blockbinder.com (или на ваш узел 1), терминал будет подписан на FIFO, и вы должны получить результат, как показано ниже. Хеши из нашего блокчейна!
cd /var/www/medium-demo-angular mkdir server cd server # Setup express server nano package.json
package.json
{ "name": "auth", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": {}, "dependencies": { "body-parser": "^1.18.2", "express": "^4.16.2", "ws": "^5.0.0" } }
Установить пакеты
npm install node server.js
Настроить экспресс-сервер
const express = require('express'), bodyParser = require('body-parser'); const http = require('http'); const url = require('url'); const WebSocket = require('ws'); const app = express(); app.use(bodyParser.json()) var exec = require('child_process').exec; const server = http.createServer(app); const wss = new WebSocket.Server({ server }); var fs = require('fs') wss.broadcast = function broadcast(msg) { console.log(msg); wss.clients.forEach(function each(client) { client.send(msg); }); }; wss.on('connection', function connection(ws, req) { const location = url.parse(req.url, true); // You might use location.query.access_token to authenticate or share sessions // or req.headers.cookie (see http://stackoverflow.com/a/16395220/151312) console.log('Server is connected'); // PIPE LISTENER const fd = fs.openSync('/var/www/medium-demo-angular/server/blocks.fifo', 'r+') const stream = fs.createReadStream(null, {fd}) stream.on('data', data => { function puts(error, stdout, stderr) { sys.puts(stdout) } exec("multichain-cli medium-demo-blockchain liststreamitems root", function(error, stdout, stderr) { if (!error) { // things worked! // console.log(stdout) wss.broadcast(stdout); } else { console.log(stderr) // things failed :( } }); }) ws.on('close', function(code, reason) { console.log(code); console.log(reason); }); }); app.get("/retrieve-database" , function (request, response) { function puts(error, stdout, stderr) { sys.puts(stdout) } exec("multichain-cli medium-demo-blockchain liststreamitems root", function(error, stdout, stderr) { if (!error) { // things worked! console.log(stdout) response.send(stdout) } else { console.log(error) // things failed :( } }); }); server.listen(3500, function listening() { console.log('Listening on %d', server.address().port); });
Поздравляем, теперь у нас есть обратный прокси-сервер ExpressJS, открывающий веб-сокет, который подписан на канал! Круто, не правда ли (не самый эффективный метод, но определенно самый увлекательный). Теперь нам нужно подписать Angular на веб-сокет, чтобы обеспечить обновление HTML в реальном времени.
Шаг 2 - Подключите Angular к экспресс-серверу.
Теперь нам просто нужен способ подключения Angular к экспресс-серверу. Есть несколько файлов, которые вам нужно будет отредактировать на стороне Angular:
app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { HttpModule } from '@angular/http'; import { HttpClientModule } from '@angular/common/http'; // Material inputs import { MatInputModule, MatCheckboxModule, MatToolbarModule } from '@angular/material'; import { MatIconModule } from '@angular/material'; import { MatListModule } from '@angular/material'; import { MatCardModule } from '@angular/material'; import { MatButtonModule } from '@angular/material'; import { MatGridListModule } from '@angular/material'; import { MatMenuModule } from '@angular/material'; import { MatSidenavModule } from '@angular/material'; import { MatChipsModule } from '@angular/material'; import { MatRadioModule, MatPaginatorModule } from '@angular/material'; import { MatStepperModule } from '@angular/material/stepper'; import { MatSelectModule } from '@angular/material/select'; import { MatTabsModule } from '@angular/material/tabs'; import { MatTableModule } from '@angular/material/table'; import { DataService } from './data.service'; import { WebSocketService } from './websocket.service'; import { AppComponent } from './app.component'; import { HeaderComponent } from './blocks/header/header.component'; import { FooterComponent } from './blocks/footer/footer.component'; import { FlexLayoutModule } from '@angular/flex-layout'; @NgModule({ declarations: [ AppComponent, HeaderComponent, FooterComponent ], imports: [ BrowserModule, FormsModule, ReactiveFormsModule, MatIconModule, MatListModule, MatCardModule, MatInputModule, MatCheckboxModule, MatButtonModule, MatGridListModule, MatChipsModule, MatMenuModule, MatSidenavModule, MatSelectModule, MatRadioModule, MatStepperModule, MatPaginatorModule, MatToolbarModule, MatTabsModule, MatTableModule, HttpModule, HttpClientModule, BrowserAnimationsModule, FlexLayoutModule ], providers: [DataService, WebSocketService], bootstrap: [AppComponent] }) export class AppModule { }
app.component.ts
import { Component, ViewChild, ElementRef, OnInit, AfterViewInit } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { WebSocketSubject } from 'rxjs/observable/dom/WebSocketSubject'; import { ActivatedRoute, Router, NavigationEnd } from '@angular/router'; // <-- do not forget to import import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MatPaginator, MatTableDataSource } from '@angular/material'; import { DataService } from './data.service'; import { WebSocketService } from './websocket.service'; import { trigger,style,transition,animate,keyframes,query,stagger,state } from '@angular/animations'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], providers: [DataService, WebSocketService], animations: [ trigger('onOffTrigger', [ state('off', style({ backgroundColor: '#E5E7E9', transform: 'scale(1)' })), state('on', style({ backgroundColor: 'green', transform: 'scale(1.1)' })), transition('off => on', animate('.6s 100ms ease-in')), transition('on => off', animate('.7s 100ms ease-out')) ])] }) export class AppComponent { title = 'app'; addProduct: FormGroup; @ViewChild(MatPaginator) paginator: MatPaginator; // Data for table displayedColumns = ['title','body','date']; dataSource = new MatTableDataSource(); array1: any = []; array2: any = []; constructor( private _formBuilder: FormBuilder, public dataService : DataService, private wsService: WebSocketService ) { this.wsService.createObservableSocket('wss://angular.blockbinder.com/api/') .subscribe(message => { console.log(message); this.dataService.tableData().subscribe(updatedData => { this.array2 = updatedData['data']; var length = this.array1.length; var length2 = this.array2.length; var lengthdifference = length2 - length; for(let key in this.array2){ this.array2[key].active = 'off'; } this.dataSource = new MatTableDataSource(this.array2); if(lengthdifference > 0){ this.array2[0].active = 'on'; } setTimeout(()=>{ //<<<--- using ()=> syntax this.array2[0].active = 'off'; },3000); }) }, err => console.log(err), () => console.log('stream complete') ); } ngAfterViewInit() { this.dataService.tableData().subscribe(response => { this.array1 = response['data']; for(let key in this.array1){ this.array1[key].active = 'off'; } this.dataSource = new MatTableDataSource(this.array1); }) } }
app.component.html
<app-header></app-header> <mat-sidenav-container> <mat-sidenav #sidenav mode="side" class="sidebar" [fixedInViewport]="false" [fixedTopGap]="0" [fixedBottomGap]="0"> <div class="sidebar__container fullwidth"> </div> </mat-sidenav> <mat-sidenav-content> <div class="container-fluid primary"> <div class="container__inner" id="top"> <section class="main"> <h1>Welcome to our Medium demo, here you can see Drupal data appearing in real time in an Angular table! The fun bit -- it was transferred via Blockchain</h1> <div fxLayout fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="30px" fxLayoutGap.xs="0" > <div fxFlex> <mat-table fxFlex #table [dataSource]="dataSource"> <!-- Value Column --> <ng-container matColumnDef="title"> <mat-header-cell *matHeaderCellDef>Title</mat-header-cell> <mat-cell *matCellDef="let element"> {{element.data.json.title}} </mat-cell> </ng-container> <!-- Value Column --> <ng-container matColumnDef="body"> <mat-header-cell *matHeaderCellDef>body</mat-header-cell> <mat-cell *matCellDef="let element"> {{element.data.json.body}} </mat-cell> </ng-container> <!-- Value Column --> <ng-container matColumnDef="date"> <mat-header-cell *matHeaderCellDef>date</mat-header-cell> <mat-cell *matCellDef="let element"> {{element.blocktime}} </mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row [@onOffTrigger]="row.active" *matRowDef="let row; columns: displayedColumns;"></mat-row> </mat-table> </div> </div> </section> </div> </div> </mat-sidenav-content> </mat-sidenav-container> <app-footer></app-footer>
package.json
{ "name": "blockbinder-shop-api", "version": "0.0.0", "license": "MIT", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build --prod", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" }, "private": true, "dependencies": { "@angular/animations": "^5.2.0", "@angular/cdk": "^5.1.0", "@angular/common": "^5.2.0", "@angular/compiler": "^5.2.0", "@angular/core": "^5.2.0", "@angular/forms": "^5.2.0", "@angular/http": "^5.2.0", "@angular/material": "^5.1.0", "@angular/platform-browser": "^5.2.2", "@angular/platform-browser-dynamic": "^5.2.2", "@angular/router": "^5.2.0", "@angular/flex-layout": "git+https://github.com/angular/flex-layout-builds.git", "core-js": "^2.4.1", "rxjs": "^5.5.6", "zone.js": "^0.8.19" }, "devDependencies": { "@angular/cli": "1.6.6", "@angular/compiler-cli": "^5.2.0", "@angular/language-service": "^5.2.0", "@types/jasmine": "~2.8.3", "@types/jasminewd2": "~2.0.2", "@types/node": "~6.0.60", "codelyzer": "^4.0.1", "jasmine-core": "~2.8.0", "jasmine-spec-reporter": "~4.2.1", "karma": "~2.0.0", "karma-chrome-launcher": "~2.2.0", "karma-coverage-istanbul-reporter": "^1.2.1", "karma-jasmine": "~1.1.0", "karma-jasmine-html-reporter": "^0.2.2", "protractor": "~5.1.2", "ts-node": "~4.1.0", "tslint": "~5.9.1", "typescript": "~2.5.3" } }
websocket.service.ts
import { Observable } from 'rxjs/Rx'; export class WebSocketService { ws: WebSocket; createObservableSocket(url:string): Observable<string>{ this.ws = new WebSocket(url); return new Observable(observer => { this.ws.onmessage = (event) => observer.next(event.data); this.ws.onerror = (event) => observer.error(event); this.ws.onclose = (event) => observer.complete(); }); } }
data.service.ts
import { Injectable, Inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/Rx'; import 'rxjs/add/operator/toPromise'; class promise {}; @Injectable() export class DataService { host: string = 'https://angular.blockbinder.com/api/'; constructor(private http: HttpClient) {} tableData() : Observable<promise> { return this.http.get( this.host+'retrieve-database' ); } }
Как и в случае с Angular, часто зависимости обновляются, поэтому вам нужно будет внести несколько изменений в свое приложение, чтобы обеспечить безопасную сборку / запуск. Если вам нужен полный код, включая стили и т. Д., Для справки, я поместил его в github здесь: https://github.com/Ejb503/medium-demo-angular
Теперь у нас есть настройка Angular и подписка на блоки, которые выходят из цепочки блоков, давайте создадим приложение.
# load node 2 cd /var/www/medium-demo-angular ng build
Вывод
В этой демонстрации из двух частей мы настроили два веб-приложения, которые обмениваются данными через блокчейн. У нас есть приложение Drupal на одном конце, и по мере добавления контента оно отображается в Angular 5 на другом конце. Хотя это был пример a- ›b через блокчейн, мы также можем расширить его для более полезных приложений. a- ›b, c, d, например, когда несколько узлов обновляют свои данные при обновлении источника. Это также может быть двусторонним способом, поэтому изменения из приложения Angular возвращаются в Drupal.
Это всего лишь простой прототип и пример использования блокчейна для синхронизации веб-приложений! Пожалуйста, прочтите другие блоги, чтобы узнать больше о дебатах о том, «почему» вы выбрали именно это!
Ответ на COVID-19 Blockchain - ›
В эти трудные времена мы опубликовали систему POC для распределенной системы блокчейн, чтобы обеспечить потоки данных и реализовать систему светофора, следите за блогами здесь: https://medium.com/@ed_burton/a-blockchain-covid- система ответов poc-889d9b74786e