在Libra Core中,官方提供了一个命令行工具,可以实现创建账户、挖矿和转账等基本操作,但是没有提供Restful接口,使我们想要开发的应用系统,将区块链逻辑移植到Libra Testnet上去。在本篇博文中,我们将利用Rust语言,将官方的命令行接口,改造成RESTful接口。由于我们只是临时改造,相信官方的RESTful接口很快就会出现,因此我们在这里仅使用最简实现,实现一个单线程的Web服务器来完成这一工作。
最简Web服务器
我们需要一个最简单的Web服务器,来接收客户端的请求,然后调用系统功能完成Libra Core中相关的区块链操作操作。我们先开发一个独立的应用,实现最基本的Web服务器功能,然后再将其集成到Libra Core的命令行工具中。
我们首先通过如下命令创建一个新工程:
cargo new libra_server --bin
我们创建一个名称为libra_server的工程,其为可执行文件形式。上面的命令会在当前目录下创建libra_server目录,并创建libra_server/src/main.rs文件,这个文件就是整个项目的主程序文件。
下面我们创建一个Hello World的Web服务器:
use std::io::prelude::*;
use std::net::TcpStream;
use std::net::TcpListener;
fn main() {
start_server();
}
fn start_server() {
println!("Libra Server v0.0.2 Starting up ...");
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
handle_connection(stream);
}
}
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 512];
stream.read(&mut buffer).unwrap();
println!("Request: {}", String::from_utf8_lossy(&buffer[..]));
let contents = "Hello World!";
let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", contents);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
我们直接编译运行程序:
cargo run
运行结果如下所示:
使用浏览器访问如下地址:http://127.0.0.1:7878/account_list/88 ,会显示Hello World信息,这就说明我们的Web服务器可以正常运行了。
下面我们在handle_connection方法中,求出cmd参数的值,根据cmd调用不同的处理函数,在这些函数中调用Libra Core的区块链服务,解析区块链服务的返回结果,最后再以Http响应的形式返回给客户端。
我们要做的第一件事就是求出QueryString,代码如下所示:
/**
* 获取请求中的Query String,规定参数以?cmd=开头
* @version v0.0.1 闫涛 2019.06.23
*/
fn get_query_string(request: &str) -> String {
let pos = request.find("?cmd=");
if pos <= Some(0) {
return "Has no parameters in request".to_string();
}
let end_pos = request.find(" HTTP/1.1");
return (&request[(pos.unwrap()+1)..end_pos.unwrap()]).to_string();
}
在这段代码中,我们首先找到QueryString开始位置,如果没找到则返回出错信息。接着我们找到结束信息,最后我们截取出子字符串作为QueryString返回。
在得到QueryString之后,我们需要找出参数cmd的值,这样我们才能根据cmd参数的值调用对应的命令处理函数,如下所示:
/**
* 获取请求cmd参数值
* @version v0.0.1 闫涛 2019.06.23
*/
fn get_cmd_param(query_string: String) -> String {
let params: Vec<_> = query_string.split("&").collect();
for param in params.iter() {
println!("item: {}!", param);
if param.find("cmd=") >= Some(0) {
let cmd = ¶m[4..];
return cmd.to_string();
}
}
return "".to_string();
}
接下来我们定义生成账户的命令处理函数,如下所示:
/**
* 生成账户命令处理函数
* @version v0.0.1 闫涛 2019.06.23
*/
fn handle_account_create(_params: Vec<&str>) -> String {
println!("生成新账户!");
let rst: String = String::from("create account: 0");
return rst;
}
实际中,这个函数需要调用Libra Core来创建账户,我们在这里先简单的返回一个字符串,在下一篇文章中我们再来具体讲怎么调用Libra Core服务以及解析响应结果。
接下来我们看handle_connection方法,这时这个方法的逻辑就变为接到一个请求后,首先得到QueryString,然后从QueryString得到cmd参数,然后根据cmd的值调用对应的命令处理函数,如下所示:
fn handle_connection(mut stream: TcpStream) {
let mut contents: String = String::from("Hello World!");
let mut buffer = [0; 1024];
// 获取请求信息
stream.read(&mut buffer).unwrap();
println!("Request: {}", String::from_utf8_lossy(&buffer[..]));
let request = String::from_utf8_lossy(&buffer[..]);
// 不处理请求网站图标请求
if request.find("GET /favicon.ico HTTP/1.1") >= Some(0) {
return ;
}
// 请出请求中的query string
let query_string = &get_query_string(&request);
println!("query_string:{}", query_string);
let cmd = get_cmd_param(query_string.to_string());
println!("接收到命令:cmd={}!", cmd);
let params: Vec<_> = query_string.split("&").collect();
if cmd.find("account_create")>=Some(0) {
contents = handle_account_create(params);
} else if cmd.find("account_list")>=Some(0) {
contents = handle_account_list(params);
}
let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", contents);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
我们可以按照这种方式,将下列命令先以占位符的形式写出来,我在这里就不重复贴代码了,在下一篇文章中,我们将对每个命令,学习怎样向Libra Core发送命令,以及怎样解析命令的返回结果。