program code for Hands-On Microservices with Rust译 记录和配置微服务(第三章)

微服务在现实世界中起作用,这是动态的。 为了有用,它们必须是可配置的,以便您可以更改地址或端口以绑定服务器的套接字。 通常,您需要设置令牌,秘密和其他微服务的地址。 即使您已正确配置它们,您的微服务也可能会失败。 在这种情况下,您需要能够使用服务器的日志。

在本章中,我们将学习以下技能:

  • 如何使用日志包进行日志记录
  • 如何使用clap crate读取命令行参数
  • 如何使用dotenv crate读取环境变量
  • 如何声明和使用配置文件

技术要求

本章介绍如何将日志记录添加到服务并解析命令行参数或配置微服务所需的环境变量。 除Rust编译器版本1.31或更高版本之外,您不需要任何特殊软件。 使用生锈工具安装它。

您可以在GitHub上找到本章示例的代码:
https://github.com/PacktPublishing/Hands-On-Microservices-with-Rust-2018/tree/master/Chapter3.

将记录添加到微服务

如果微服务没有记录它执行的操作,我们就无法使用或调试它。 在本节中,我们将开始使用我们的微服务进行日志记录,以了解其中的内容。 我们将创建一个生成随机值的微服务,并将记录器连接到微服务以记录它执行的操作。 之后,我们将使用环境变量配置日志记录。

随机值生成微服务

为了讨论这些更高级的主题,我们需要一个微服务架构,它比生成hello消息更有用。 我们将创建一个用于生成随机值的微服务应用程序。 这很容易实现,并将为我们提供足够的机会来使用日志记录和配置。

但是,我们不会从头开始; 让我们从前一章中获取示例并为其添加依赖项:

[dependencies]
hyper = "0.12"
rand = "0.5"

rand crate提供了在Rust中生成随机值所需的实用程序。 在main.rs文件中导入必要的类型:

use hyper::{Body, Response, Server};
use hyper::rt::Future;
use hyper::service::service_fn_ok;

在service_fn_ok函数中添加两行来处理传入的请求:

fn main() {
    let addr = ([127, 0, 0, 1], 8080).into();
    let builder = Server::bind(&addr);
    let server = builder.serve(|| {
        service_fn_ok(|_| {
            let random_byte = rand::random::<u8>();
            Response::new(Body::from(random_byte.to_string()))
        })
    });
    let server = server.map_err(drop);
    hyper::rt::run(server);
}

要了解有关上述代码的更多信息,请参阅前一章,我们在其中探讨了超级包。

如您所见,我们在提供给service_fn_ok函数的闭包中添加了两行。 第一行生成一个随机字节,其中包含rand crate的随机函数。 我们在rand :: random :: ()调用的type参数中设置生成的类型。 现在,u8是无符号字节整数。

在第二行中,我们只是将生成的字节转换为字符串并将其作为响应的主体返回。 尝试运行代码来测试它:
在这里插入图片描述

从上面的屏幕截图中,您可以看到该服务已成功返回生成的随机值。

日志create

记录是记录程序活动的过程。 日志可以是指定格式的文本流,可以打印到控制台或写入文件。 Rust有一个基于日志包的伟大的日志生态系统。 值得注意的是,日志包含的宏没有真正的记录器实现。 这使您有机会根据您的需要使用不同的记录器。 在本节中,我们将开始使用微服务中的日志包来了解日志记录级别的工作方式以及如何设置所需的日志级别。

记录器

某些包装箱中包含的实际记录器实现如下:

  • env_logger
  • simple_logger
  • simplelog
  • pretty_env_logger
  • stderrlog
  • flexi_logger
  • log4rs
  • fern

在这些记录器实现之间进行选择可能很困难。 我建议你在crates.io上探索它们,以了解它们的不同之处。 最受欢迎的是env_logger,这是我们将要使用的。 env_logger读取RUST_LOG环境变量以配置日志记录并将日志打印到stderr。 还有pretty_env_logger包,它建立在env_logger之上,并以紧凑和多彩的格式打印日志。 两者都使用相同的环境变量进行配置。

stderr是三个标准的stream-stdin之一,你的程序用控制台读取输入数据; stdout,程序发送输出数据; 和stderr,其特殊目的是显示有关使用应用程序的错误或其他信息。 记录器经常使用stderr来避免 影响输出数据。 例如,假设您有一个解码输入流的工具。 您希望该工具仅将解码数据发送到输出流。 该计划将如何告知您遇到的任何问题? 在这种情况下,我们可以使用stderr流,它作为输出流,但不污染stdout? 有一个stderr流作为输出流,但不会污染stdout。

将记录器添加到Cargo.toml文件的依赖项列表中:

[dependencies]
log = "0.4"
pretty_env_logger = "0.2"
hyper = "0.12"rand = "0.5"

然后将这些类型添加到main.rs文件中:

use hyper::{Body, Response, Server};
use hyper::rt::Future;
use hyper::service::service_fn_ok;
use log::{debug, info, trace};

日志级别

正如我们之前讨论的那样,使用日志包,我们需要导入以下日志记录宏。 我们可以使用以下内容:

这些是按照他们打印的信息的重要性排序的,带有痕迹! 是最不重要和错误! 是最重要的:

  • trace!:用于打印有关任何关键活动的详细信息。 它允许Web服务器跟踪任何传入的数据块。
  • debug!:用于较不详细的消息,例如传入的服务器请求。 它对调试很有用。
  • info!:用于重要信息,例如运行时或服务器配置。 它很少用于图书馆板条箱。
  • warn!:通知用户非严重错误,例如客户端是否使用了损坏的cookie,或者必要的微服务是否暂时不可用,而缓存数据则用于响应。
  • error!:提供关于严重错误的警报。 这在数据库连接断开时使用。

我们直接从日志包中导入了必要的宏。

记录消息

没有代码的上下文数据,记录是没有用的。 每个日志记录宏都需要一条可以包含位置参数的文本消息。 例如,看一下println!宏:

debug!("Trying to bind server to address: {}", addr);

上述代码适用于实现Display trait的类型。 就像在println! 宏,您可以使用{:?}格式化程序添加实现Debug特征的类型。 使用[derive(Debug)]为代码中的所有类型派生Debug特性并为整个crate设置#deny(missing_debug_implementati ns)]属性很有用。

自定义消息级别

级别在日志记录过程中起着重要作用。 它们用于按优先级过滤记录。 如果设置记录器的信息级别,它将跳过所有调试和跟踪记录。 显然,您需要更详细的日志记录以进行调试,并且需要更简洁的日志记录才能在生产中使用服务器。

在内部,日志包的每个宏都使用日志! 宏,它有一个参数来设置级别:

log!(Level::Error, "Error information: {}", error);

它需要Level枚举的实例,它具有以下变体 - Trace,Debug,Info,Warn和Error。

检查日志记录已启用

有时,日志记录可能需要大量资源。 在这种情况下,您可以使用log_enabled! 用于检查是否已启用某个日志记录级别的宏:

if log_enabled!(Debug) {
    let data = get_data_which_requires_resources();
    debug!("expensive data: {}", data);
}

自己的目标

每个日志记录都有一个目标。 典型的日志记录如下所示:
在这里插入图片描述

日志记录包括日志记录级别,时间(此输出中未显示),目标和消息。 您可以将目标视为命名空间。 如果未指定目标,则日志包使用module_path! 宏来设置一个。 我们可以使用目标来检测发生错误或警告的模块,或者使用它来按名称过滤记录。 我们将在下一节中看到如何按环境变量设置过滤。

使用日志记录

我们现在可以将记录添加到我们的微服务中。 在以下示例中,我们将打印有关套接字地址,传入请求和生成的随机值的信息:

fn main() {    
 logger::init();   
   info!("Rand Microservice - v0.1.0");    
    trace!("Starting...");    
     let addr = ([127, 0, 0, 1], 8080).into();     
     debug!("Trying to bind server to address: {}", addr);   
       let builder = Server::bind(&addr);    
        trace!("Creating service handler...");   
          let server = builder.serve(|| {        
          									 service_fn_ok(|req| {           
          									 		  trace!("Incoming request is: {:?}", req);           
          									 		    let random_byte = rand::random::<u8>();            
          									 		     debug!("Generated value is: {}", random_byte);           
          									 		       Response::new(Body::from(random_byte.to_string()))         })
          									 		          });   
          									 		            info!("Used address: {}", server.local_addr());  
          									 		               let server = server.map_err(drop);    
          									 		                debug!("Run!");   
          									 		                  hyper::rt::run(server); }

使用日志记录非常简单。 我们可以使用宏来打印套接字的地址以及有关请求和响应的信息。

使用变量配置记录器

您可以使用一些环境变量来配置记录器。 我们来看看每个变量。

RUST_LOG

编译这个例子。 要使用激活的记录器运行它,您必须设置RUST_LOG环境变量。 env_logger包读取它并使用此变量中的过滤器配置记录器。 必须使用相应的日志记录级别配置记录器实例。

您可以在货运命令之前临时设置RUST_LOG:

RUST_LOG=trace cargo run

但是,这也会打印很多货物工具和编译器记录,因为Rust编译器还使用日志包来记录。 您可以使用按名称过滤来排除除程序之外的所有记录。 您只需要使用目标名称的一部分,如下所示:

RUST_LOG=random_service=trace,warn cargo run

RUST_LOG变量的这个值按警告级别过滤所有记录,并使用以random_service前缀开头的目标的跟踪级别。

RUST_LOG_STYLE

RUST_LOG_STYLE变量设置打印记录的样式。 它有三种变体:

  • auto:尝试使用样式字符
  • always:始终使用样式字符
  • never:关闭样式字符

请参阅以下示例:

如果您将stderr输出重定向到文件,或者如果要使用grep或awk提取具有特殊模式的值,我建议您使用never值。

将RUST_LOG变量更改为您自己的变量

如果您发布自己的产品,则可能需要将RUST_LOG的名称和RUST_LOG_STYLE变量更改为您自己的名称。 env_logger的新版本包含init_from_env特殊功能来解决此问题。 这需要一个参数 - 一个Env对象的实例。 看看下面的代码:

let env = env_logger::Env::new()    .filter("OWN_LOG_VAR")    .write_style("OWN_LOG_STYLE_VAR");
env_logger::init_from_env(env);

它创建一个Env实例并设置OWN_LOG_VAR变量以配置日志记录,并设置OWN_LOG_STYLE_VAR变量以控制日志的样式。 创建env对象时,我们将它用作env_logger crate的init_from_env函数调用的参数。

读取环境变量

在前面的示例中,我们使用RUST_LOG环境变量的值来设置用于记录的过滤参数。 我们也可以使用其他环境变量来为我们的服务器设置参数。 在以下示例中,我们将使用ADDRESS环境变量来设置要绑定的套接字的地址。

标准库

std :: env标准模块中有足够的函数来处理环境变量。它包含用于读取外部值的var函数。此函数返回一个Result,如果该变量存在,则返回该变量的String值;如果该变量不存在,则返回VarError错误。将env模块的导入添加到main.rs文件中:

use std::env;

我们需要替换以下行:

let addr = ([127, 0, 0, 1], 8080).into();

将其替换为以下内容:

let addr = env::var("ADDRESS")    .unwrap_or_else(|_| "127.0.0.1:8080".into())    .parse()    .expect("can't parse ADDRESS variable");

此新代码读取ADDRESS值。如果此值不存在,我们将不会让代码引起恐慌。相反,我们将使用unwrap_or_else方法调用将其替换为默认值“127.0.0.1:8080”。由于var函数返回一个String,我们还必须使用into方法调用将’static str转换为String实例。

如果我们无法解析地址,我们将在except方法调用中引发恐慌。

您的服务器现在将使用addr变量,该变量从ADDRESS环境变量或默认值中获取值。

环境变量是配置应用程序的简单方法。托管或云平台和Docker容器也广泛支持它们。

请记住,主机的系统管理员可以看到所有敏感数据。 在Linux中,系统管理员只需使用cat / proc /pidof random-service-with-env / environ` |即可读取此数据。 tr’\ 0’’\ n’命令。 这意味着将比特币钱包的密钥设置为环境变量并不是一个好主意。

使用.env文件

设置许多环境变量非常耗时。我们可以使用配置文件来简化这一过程,我们将在本章末尾进一步探讨。但是,在crates或dependencies使用环境变量的情况下,不能使用配置文件。

为了简化这个过程,我们可以使用dotenv crate。这用于从文件设置环境变量。这种做法作为The Twelve-Factor App方法(https://12factor.net/)的一部分出现。

十二因素应用程序方法是构建软件即服务(SaaS)应用程序以实现以下三个目标的方法:

  • 声明格式的配置
  • 操作系统和云的最大可移植性
  • 持续部署和扩展

此方法鼓励您使用环境变量来配置应用程序。十二因素应用程序方法不需要磁盘空间进行配置,并且它非常便携,这意味着所有操作系统都支持环境变量。

使用dotenv create

dotenv crate允许您在名为.env的文件中设置环境变量,并使用传统方式设置的变量将它们连接起来。 您无需手动读取此文件。 您需要做的就是添加依赖项并调用crate的初始化方法。

将此包添加到依赖项列表中:

dotenv = "0.13"

将以下导入添加到上一个示例的main.rs文件中以使用dotenv crate:

use dotenv::dotenv;
use std::env;

使用dotenv函数对其进行初始化,该函数将尝试查找.env文件。 它将返回一个带有此文件路径的Result。 如果找不到文件,则调用Result的ok方法忽略它。

将变量添加到.env文件中

.env文件包含环境变量的名称和值对。 对于我们的服务,我们将设置RUST_LOG,RUST_BACKTRACE和ADDRESS变量:

RUST_LOG=debug
RUST_BACKTRACE=1
ADDRESS=0.0.0.0:1234

如您所见,我们将记录器的所有目标设置为调试级别,因为货物不使用dotenv,因此跳过这些设置。

RUST_BACKTRACE变量设置标志,以便在出现紧急情况时打印应用程序的回溯。

将此文件存储在运行该应用程序的工作目录中。 您可以拥有多个文件,并将它们用于不同的配置。 此文件格式也与Docker兼容,可用于将变量设置为容器。

我建议您将.env文件添加到.gitignore以防止泄漏敏感或本地数据。 这意味着与您的项目合作的每个用户或开发人员都有自己的环境,并且需要他们自己的.env文件版本。

解析命令行参数

环境变量对于与容器一起使用很有用。 如果从控制台使用应用程序,或者希望避免名称与其他变量冲突,则可以使用命令行参数。 这是开发人员为程序设置参数的更常规方式。

您还可以使用env模块获取命令行参数。 它包含args函数,它返回一个Args对象。 此对象不是数组或向量,但它是可迭代的,您可以使用for循环处理所有命令行参数:

for arg in env::args() {
    // Interpret the arg here
}

在简单的情况下,这种变体可能派上用场。 但是,要解析具有复杂规则的参数,必须使用命令行参数解析器。 拍手箱中包含了一个很好的实施方案。

使用 clap crate

要使用clap crate来解析参数,您必须构建一个解析器并将其用于参数。 要构建解析器,首先要创建App类型的实例。 要使用它,请添加所有必需的导入。

添加依赖项

向Cargo.toml添加依赖项:

clap = "2.32"

此包提供了有用的宏来添加有关程序的元信息。 这些如下:

  • crate_name!:返回包的名称
  • crate_version!:返回包的版本
  • crate_authors!:返回作者列表
  • crate_description!:提供箱子的描述

这些宏的所有信息都来自Cargo.toml文件。

导入必要的类型。 我们需要两种类型,分别是App和Arg,以及前面提到的宏:

use clap::{crate_authors, crate_description, crate_name, crate_version, Arg, App};

构建解析器

构建解析器的过程非常简单。 您将创建一个App实例,并使用Arg实例提供此类型。 该应用程序还具有可用于设置有关应用程序的信息的方法。 将以下代码添加到我们服务器的main函数中:

let matches = App::new(crate_name!())         .version(crate_version!())         .author(crate_authors!())         .about(crate_description!())         .arg(Arg::with_name("address")              .short("a")              .long("address")              .value_name("ADDRESS")              .help("Sets an address")              .takes_value(true))         .arg(Arg::with_name("config")              .short("c")              .long("config")              .value_name("FILE")       .help("Sets a custom config file")              .takes_value(true))        .get_matches();

首先,我们使用一个需要crate名称的新方法创建一个App实例。我们使用crate_name提供此功能!宏。之后,我们使用版本,作者和about方法使用相应的宏设置此数据。我们可以链接这些方法调用,因为每个方法都使用并返回更新的App对象。当我们设置有关应用程序的元信息时,我们必须使用arg方法声明支持的参数。

要添加参数,我们必须使用with_name方法创建一个Arg实例,提供名称,并使用chaining-of-methods调用设置额外参数。我们可以用short方法设置一个简短形式的参数,用long方法设置long形式。您可以使用value_name方法为生成的文档设置值的名称。您可以使用帮助方法提供参数的描述。 takes_value方法用于指示此参数需要值。还有一个必需的方法来指示需要一个选项,但我们在这里没有使用它。所有选项在我们的服务器中是可选的我们使用这些方法添加了–address参数来设置我们将用于绑定服务器的套接字的地址。它还支持参数的简短形式。我们稍后会读到这个值。

服务器将支持–config参数来设置配置文件。我们已将此参数添加到构建器,但我们将在本章的下一部分中使用它。

在我们创建构建器之后,我们调用get_matches方法。这将使用std :: env :: args_os读取参数并返回一个ArgMatches实例,我们可以使用它来获取命令行参数的值。我们将它分配给匹配局部变量。

我们应该在任何日志记录调用之前添加get_matches方法,因为它还会打印帮助消息。 我们应该避免使用帮助描述打印日志。

读取参数

要读取参数,ArgMatches包含value_of方法,您可以在其中添加参数的名称。 在这种情况下,使用常量来避免拼写错误很方便。 解压缩–address参数,如果不存在,则检查ADDRESS环境变量。 这意味着命令行参数的优先级高于环境变量,您可以使用命令行参数覆盖.env文件中的参数:

let addr = matches.value_of("address")    .map(|s| s.to_owned())    .or(env::var("ADDRESS").ok())    .unwrap_or_else(|| "127.0.0.1:8080".into())    .parse()    .expect("can't parse ADDRESS variable");

在此代码中,我们已将所有提供的字符串引用与&str类型转换为实心String对象。 如果您希望稍后在代码中使用该对象,或者您需要将其移动到其他位置,这将非常有用。

用法

在应用程序中使用clap crate时,可以使用命令行参数进行调整。 拍手箱添加了一个–help参数,用户可以用它来打印有关所有参数的信息。 此描述由包装箱自动生成,如以下示例所示:

$ ./target/debug/random-service-with-args --helprandom-service-with-env 0.1.0Your NameRust MicroserviceUSAGE:    random-service-with-env [OPTIONS]FLAGS:    -h, --help       Prints help information    -V, --version    Prints version informationOPTIONS:
-a, --address <ADDRESS>    Sets an address    -c, --config <FILE>        Sets a custom config file

我们的应用程序成功打印了使用信息:它为我们提供了所有标志,选项和用法变体。 如果需要添加自己的帮助描述,可以使用App实例的help方法将任何字符串设置为帮助消息。
如果使用cargo run命令,还可以在 - 参数后设置命令行参数。 这意味着它会停止读取run命令并将所有剩余的参数传递给正在运行的应用程序:

$ cargo run -- --help

您现在可以使用带有值的–address参数启动服务器并设置地址:

$ cargo run -- --address 0.0.0.0:2345

服务器已启动并打印到控制台:

Finished dev [unoptimized + debuginfo] target(s) in 0.10s                                                                                             Running `target/debug/random-service-with-args --address '0.0.0.0:2345'` INFO 2018-07-26T04:23:52Z: random_service_with_env: Rand Microservi
ce - v0.1.0DEBUG 2018-07-26T04:23:52Z: random_service_with_env: Trying to bind server to address: 0.0.0.0:2345 INFO 2018-07-26T04:23:52Z: random_service_with_env: Used address: 0.0.0.0:2345DEBUG 2018-07-26T04:23:52Z: random_service_with_env: Run!DEBUG 2018-07-26T04:23:52Z: tokio_reactor::background: starting background reactor

如何添加子命令

一些流行的应用程序,如货物和码头工具,使用子命令在单个二进制文件中提供多个命令。 我们还可以使用clap crate支持子命令。 微服务可能有两个命令:一个用于运行服务器,另一个用于为HTTP cookie生成密码。 看看下面的代码:

let matches = App::new("Server with keys")    .setting(AppSettings::SubcommandRequiredElseHelp)
    .subcommand(SubCommand::with_name("run")
        .about("run the server")
        .arg(Arg::with_name("address")
            .short("a")            .long("address")            .takes_value(true)
            .help("address of the server"))    .subcommand(SubCommand::with_name("key")
        .about("generates a secret key for cookies")))    .get_matches();

在这里,我们使用了两种方法。 设置方法会调整构建器,您可以使用AppSettings枚举的变体对其进行设置。 如果没有提供子命令,SubcommandRequiredElseHelp方法要求我们使用子命令或打印帮助消息。 要添加子命令,我们将子命令方法与我们使用with_name方法创建的SubCommand实例一起使用。 子命令实例也有
设置有关子命令的元信息的方法,就像我们使用App实例一样。 子命令也可以接受参数。

在上面的例子中,我们添加了两个子命令 - run,运行服务器和key,以生成机密。 您可以在启动应用程序时使用这些:

$ cargo run -- run --address 0.0.0.0:2345

我们有两个运行参数,因为货物有一个同名的命令。

从文件中读取配置

环境变量和命令行参数对于为单次运行添加临时更改参数很有用。 它们是配置服务器以使用配置文件的更方便的方法。 这种方法不符合The Twelve-Factor App方法,但在需要设置长参数时非常有用。

有许多格式可用于配置文件。 流行的包括TOML,YAML和JSON。 我们将使用TOML,因为它广泛用于Rust编程语言。

添加TOML配置

TOML文件格式在toml crate中实现。 它以前使用过时的rustc-serialize包,但最后几个版本使用了serde crate进行序列化和反序列化。 我们将同时使用toml和serde crates。

添加依赖项

我们实际上不仅需要serde crate,还需要serde_derive箱子。 两个板条箱都以各种序列化格式提供序列化结构。 将所有三个包添加到Cargo.toml中的依赖项列表中:

serde = "1.0"serde_derive = "1.0"toml = "0.4"

main.rs文件中的完整导入列表包含以下内容:

use clap::{crate_authors, crate_description, crate_name, crate_version, Arg, App};
use dotenv::dotenv;
use hyper::{Body, Response, Server};
use hyper::rt::Future;use hyper::service::service_fn_ok;
use log::{debug, info, trace, warn};
use serde_derive::Deserialize;
use std::env;
use std::io::{self, Read};
use std::fs::File;
use std::net::SocketAddr;

如您所见,我们尚未在此处导入serde箱。 我们不会直接在代码中使用它,因为必须使用serde_derive包。 我们从serde_derive箱中导入了所有宏,因为serde crate包含Serialize和Deserialize特征,serde_derive帮助我们为我们的结构派生这些。
微服务通常需要在与客户端交互时序列化和反序列化数据。 我们将在下一章中讨论这个主题。

声明用于配置的Struct

我们现在已经导入了所有必需的依赖项,并且可以声明我们的配置文件结构。 将Config结构添加到您的代码中:

#[derive(Deserialize)]struct Config {    address: SocketAddr,}

此结构只包含一个带有地址的字段。 您可以添加更多,但请记住,所有字段都必须实现Deserialize特征。 serde crate已经有标准库类型的实现。 对于我们的类型,我们必须使用serde_derive包的宏来派生反序列化的实现。

一切都准备好让我们从文件中读取配置。

读取配置文件

我们的服务器希望在当前工作文件夹中找到名为microservice.toml的配置文件。 要读取配置并将其转换为Config结构,我们需要查找并读取该文件(如果存在)。 将以下代码添加到服务器的main函数:

let config = File::open("microservice.toml")    .and_then(|mut file| {        let mut buffer = String::new();        file.read_to_string(&mut buffer)?;        Ok(buffer)    })    .and_then(|buffer| {        toml::from_str::<Config>(&buffer)            .map_err(|err| io::Error::new(io::ErrorKind::Other, err))    })
 .map_err(|err| {        warn!("Can't read config file: {}", err);    })    .ok();

上面的代码是一个以File实例开头的方法调用链。我们使用open方法打开文件并提供名称microservice.toml。该调用返回一个Result,我们将在链中处理该Result。在处理结束时,我们将使用ok方法将其转换为选项,并忽略在解析配置文件期间发生的任何错误。这是因为我们的服务还支持环境变量和命令行参数,并且具有未设置参数的默认值。

文件准备好后,我们将尝试将其转换为String。我们创建了一个空字符串,称为缓冲区,并使用File实例的read_to_string方法将所有数据移动到缓冲区中。这是一种同步操作。它适用于读取配置,但不应将其用于读取要发送到客户端的文件,因为它将锁定服务器的运行时直到读取文件。

在我们读取缓冲区变量之后,我们将尝试将其作为TOML文件解析为Config结构。 toml crate在crate的根命名空间中有一个from_str方法。它期望反序列化的类型参数和输入字符串。我们使用Config结构作为输出类型,使用缓冲区作为输入。但是有一个问题:文件使用io :: Error进行错误,但from_str使用toml :: de:Error作为错误类型。我们可以将第二种类型转换为io :: Error,使其与调用链兼容。
链的倒数第二部分是map_err方法调用。 我们使用它将配置文件中的任何错误写入日志。 如您所见,我们使用了警告级别。 配置文件的问题并不重要,但了解它们很重要,因为它们会影响配置。 这使得microservices.toml文件可选。

按优先级加入所有值

我们的服务器有四个地址设置源:

  • 配置文件
  • 环境变量
  • 命令行参数
  • 默认值

我们必须按此顺序加入这些。 使用一组选项实现此操作很简单,如果选项不包含任何内容,则使用或方法设置值。 使用以下代码从所有来源获取地址值:

let addr = matches.value_of("address")    .map(|s| s.to_owned())    .or(env::var("ADDRESS").ok())    .and_then(|addr| addr.parse().ok()).or(config.map(|config| config.address))    .or_else(|| Some(([127, 0, 0, 1], 8080).into()))    .unwrap();

首先,此代码从–address命令行参数中获取值。 如果它不包含任何值,则代码会尝试从ADDRESS环境变量中获取值。 之后,我们尝试将文本值解析为套接字地址。 如果所有这些步骤都失败了,我们可以尝试从我们从microservice.toml读取的Config实例中获取一个值。 如果用户未设置该值,我们将使用默认地址值。 在之前的地址解析代码中,我们还从字符串中解析了默认值。 在此代码中,我们使用元组来构造SocketAddr实例。 由于我们保证获得一个值,我们打开选项来提取它。

创建和使用配置文件

我们现在可以创建配置文件并运行服务器。 在项目的根文件夹中创建microservice.toml文件,并向其添加以下行:

address = "0.0.0.0:9876"

编译并启动服务,您将看到它已绑定到该地址:

在这里插入图片描述

摘要

在本章中,我们向服务器添加了日志记录,并学习了如何激活记录器并为其设置过滤器。 之后,我们将不灵活的服务器转换为可配置的微服务,可以读取不同来源的设置 - 配置文件,环境变量和命令行参数。 我们熟悉了The Twelve-Factor App方法,并使用了dotenv crate,它帮助我们从文件中读取环境变量。 我们还使用了clap crate来添加命令行解析器。 最后,我们触及了serde crate,它将我们引入了序列化的世界。

在下一章中,我们将学习如何使用serde crate来满足微服务的需求:反序列化请求并将响应序列化为某种格式,如JSON,CBOR,BSON,MessagePack等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值