在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

运行结果如下所示:
Facebook最新Libra币开发指南---接口服务器开发-LMLPHP
使用浏览器访问如下地址: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 = &param[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发送命令,以及怎样解析命令的返回结果。

08-09 08:10