참고서적: 이더리움을 활용한 블록체인 프로젝트 구축
이전글에서 스마트 컨트랙트 코딩과 배포, 백엔드 구축에 대해서 설명하겠습니다. 이번 내용을 따라하기 위해서는 이전 1, 2,
3번을 하셔야 합니다. 그러면 나머지 웹서비스를 위한 프론트 엔드 구축 및 동작 테스트를 해보겠습니다.
문서 소유권 스마트 컨트랙트 코딩스마트 컨트랙트 배포웹서비스 백엔드 구축- 웹서비스 프론트엔드 구축
- 동작 테스트
4. 웹서비스 프론트엔드 구축
프론트엔드라는 말은 서버단에서 돌아가는 프로그램이며, 사용자에게 직접적으로 드러나는 부분입니다. 사용자와 프론트에서 직접 만난다는 그런 개념으로요. 웹서버에 접속하면 무엇과 만나가 되나요? 바로 웹페이지 입니다. 웹페이지의 구성은 HTML, CSS, Javascript로 구성되어 있습니다. 웹페이지에서 버튼을 눌렀을 때, 이미지를 조작했을 때 등과 같이 어떤 웹페이지를 구성할 것이고, 사용자와 직접적으로 인터페이스 하는 부분을 구축해야 합니다.
전체 소스 코드는 아래 사이트에 있습니다.
https://github.com/PacktPublishing/Building-Blockchain-Projects/tree/master/Chapter04/Final
여기서는 주요 부분만 설명하겠습니다.
4.1 폴더 구조
폴더 구조를 살펴보면 다음과 같습니다.
- Chapter04
- app.js
- package.json
- public
- css
- files
- js
- main.js
- html
- index.html
- css
위 처럼 구조가 되어 있고, css, js 폴더 밑에서 여러 파일들이 있습니다만, 여기서는 설명을 생략하겠습니다. 여기서 살펴볼 부분은 바로 index.html와 main.js 입니다. 사용자가 웹서버에 접속했을 때 바로 index.html 페이지가 보이게 됩니다.
index.html 구성 화면은 다음과 같습니다.
- 업로드할 파일을 선택할 수 있는 버튼
- 파일의 소유자를 표시하는 문자 에디트 박스
- 스마트 컨트랙트의 내용을 변경하는 Submit 버튼
- 스마트 컨트랙트에 저장된 소유자를 읽어오는 Get Infor 버튼
- 생성된 트랜잭션 해쉬를 표시
4.2 index.html 코딩
위와 같은 화면을 구성하기 위해 아래와 같이 index.html 파일에 입력합니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 offset-md-3 text-xs-center">
<br>
<h3>Upload any file</h3>
<br>
<div>
<div class="form-group">
<label class="custom-file text-xs-left">
<input type="file" id="file" class="custom-file-input">
<span class="custom-file-control"></span>
</label>
</div>
<div class="form-group">
<label for="owner">Enter owner name</label>
<input type="text" class="form-control" id="owner">
</div>
<button onclick="submit()" class="btn btn-primary">Submit</button>
<button onclick="getInfo()" class="btn btn-primary">Get Info</button>
<br><br>
<div class="alert alert-info" role="alert" id="message">
You can either submit file's details and get information about it.
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 offset-md-3 text-xs-center">
<br>
<h3>Live Transactions Mined</h3>
<br>
<ol id="events_list">No Transaction Found</ol>
</div>
</div>
</div>
<script type="text/javascript" src="/js/sha1.min.js"></script>
<script type="text/javascript" src="/js/jquery.min.js"></script>
<script type="text/javascript" src="/js/socket.io.min.js"></script>
<script type="text/javascript" src="/js/main.js"></script>
</body>
</html>
위 html 코드는 단독으로 동작하지 않고, 화면 구성을 담당하는 css 파일과 버튼을 눌렀을 때 데이터를 처리하는 함수들은 javascript(js 폴더 밑의 main.js) 파일과 함께 동작합니다.
4.3 main.js 코딩
아래와 같이 main.js 파일을 생성하고 코드를 입력합니다.
function submit()
{
var file = document.getElementById("file").files[0];
if(file)
{
var owner = document.getElementById("owner").value;
if(owner == "")
{
alert("Please enter owner name");
}
else
{
var reader = new FileReader();
reader.onload = function (event) {
var hash = sha1(event.target.result);
$.get("/submit?hash=" + hash + "&owner=" + owner, function(data){
if(data == "Error")
{
$("#message").text("An error occured.");
}
else
{
$("#message").html("Transaction hash: " + data);
}
});
};
reader.readAsArrayBuffer(file);
}
}
else
{
alert("Please select a file");
}
}
function getInfo()
{
var file = document.getElementById("file").files[0];
if(file)
{
var reader = new FileReader();
reader.onload = function (event) {
var hash = sha1(event.target.result);
$.get("/getInfo?hash=" + hash, function(data){
if(data[0] == 0 && data[1] == "")
{
$("#message").html("File not found");
}
else
{
$("#message").html("Timestamp: " + data[0] + " Owner: " + data[1]);
}
});
};
reader.readAsArrayBuffer(file);
}
else
{
alert("Please select a file");
}
}
var socket = io("http://localhost:8080");
socket.on("connect", function () {
socket.on("message", function (msg) {
if($("#events_list").text() == "No Transaction Found")
{
$("#events_list").html("<li>Txn Hash: " + msg.transactionHash + "\nOwner: " + msg.args.owner + "\nFile Hash: " + msg.args.fileHash + "</li>");
}
else
{
$("#events_list").prepend("<li>Txn Hash: " + msg.transactionHash + "\nOwner: " + msg.args.owner + "\nFile Hash: " + msg.args.fileHash + "</li>");
}
});
});
사용자가 원하는 파일을 선택하고, 그 파일에 대한 소유자를 입력하고, "Submit" 버튼을 누르면 파일의 내용으로 sha1() 함수로 해쉬를 생성하고, 웹페이지 하단에 트랜잭션 해쉬를 표시하게 됩니다. 파일 내용 자체는 블락체인에 저장되지 않습니다. 대부분의 스마트 컨트랙트는 gas 사용을 최소로 하기 위해 데이터 용량이 큰 파일등은 블락체인에 저장하지 않습니다. 보통은 별도의 파일 저장 서버를 사용하게 됩니다.
다시 파일을 선택하고 "Get Info" 버튼을 누르면, 해당 파일의 소유자와 timestamp를 스마트 컨트랙트로 부터 읽어서 화면에 표시합니다. 만약 파일이 등록되어 있지 않다면 File Not Found 메시지를 표시하게 됩니다.
여기까지 작성하고, 나머지 부분은 프로젝트에 포함된 부분을 그대로 사용합니다.
5. 동작 테스트
이전 글에서 app.js을 작성했는데 이것을 실행시키기 위해서 의존 패키지 설치가 필요합니다. app.js가 있는 곳으로 이동한 후 아래와 같이 명령을 입력합니다.
$ npm install
문제가 없이 설치가 되면 같은 폴더 내에 node_modules
란 폴더가 보일 것입니다. 의존 패키지가 설치된 폴더입니다.
5.1 웹 서버 기동
자 그럼 이제 웹서버를 기동할 차례입니다. 아래와 같이 app.js가 있는 곳으로 이동하여 아래 명령을 입력합니다.
$ node app.js
에러가 없다면 웹서버가 자신의 pc에서 돌고 있는 것입니다.
5.2 웹 브라우저에서 웹 서버 접속
웹브라우저를 실행시켜 주소창에 localhost:8080
을 입력합니다. 그러면 아래와 같은 화면이 나옵니다.
5.3 Submit 클릭
먼저 파일을 선택합니다. 그리고 소유자(owner)를 적은 후에 "Submit" 버튼을 클릭합니다.
그러면 위 그림과 같이 웹페이지 하단에 트랜잭션 해쉬가 표시됩니다.
5.4 Get Info 클릭
위에서 선택한 파일을 다시 선택합니다. 그리고 "Get Info" 버튼을 누르면 해당 파일의 소유자와 timestamp가 화면에 표시되게 됩니다. 파일에 소유자 설정이 안되어 있다면 File Not Found 메시지가 표시됩니다.
5.5 마무리
이전글에서 스마트 컨트랙트 작성할 때, 아래와 같이 복수의 파일에 대한 소유자를 저장할 수 있게 했습니다.
struct FileDetails
{
uint timestamp;
string owner;
}
mapping (string => FileDetails) files;
아래 mapping 변수인 files를 보면 파일의 내용에 대한 해쉬값을 key로 하고 그것의 소유자와 timestamp를 포함하는 구조체를 value로 하고 있는 것을 볼 수 있습니다. 따라서 복수의 파일에 대해서 소유자를 설정할 수가 있는 것입니다.
지금까지 세편에 걸쳐 스마트 컨트랙트의 구동 및 web3.js를 통해 웹브라우저에서 인터페이스 하는 부분을 살펴봤습니다. 보시다시피 스마트 컨트랙트 구현 부분은 gas 소비때문에 최소한으로 구현하고, 대부분의 작업이 웹서버 구축 및 웹페이지 디자인에 몰려있는 것을 알 수 있습니다. 스마트 컨트랙트만 알아서만 블락체인 서비스를 할 수 없는 것이죠.
오늘의 실습: 파일을 저장하는 서버가 해킹당하거나 파일이 삭제되면 어떻게 될까요? 이런 상황에서 블락체인이 해줄 수 있는 것은 무엇이 있을까요?