2

如何在Rust中开发一个电子邮件服务器软件?

 1 year ago
source link: https://www.jdon.com/62581
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

如何在Rust中开发一个电子邮件服务器软件?


1、设置新项目:

# create a new Rust binary application
cargo new rust-axum-email-server

navigate to the project directory 
cd rust-axum-email-server

fire up the server 
cargo watch -x run

2、准备依赖包:
在项目的根目录下打开一个新的命令行解释器,然后执行以下命令:

install dependencies
cargo add axum tokio -F tokio/full lettre serde -F serde/derive dotenv

通过参数-F <crate>/<feature>. 下载拉入以下依赖:

  • axum,我们的 Web 框架
  • tokio的全部功能, Rust 的异步运行时
  • lettre crate,Rust 的邮件程序库
  • serde的派生特性 ,一个用于解析 JSON 的 crate,
  • dotenv用于在开发中解析环境变量

此时,我们的应用程序清单 ( Cargo.toml) 将如下所示:

[package]
name = "rust-axum-email-server"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

<p class="indent">[dependencies]
axum = "0.5.16"
dotenv = "0.15.0"
lettre = "0.10.1"
serde = { version = "1.0.144", features = ["derive"] }
tokio = { version = "1.21.1", features = ["full"] }

3、编码
切换导航到src/main.rs内容并将其替换为以下列表:

use axum::{response::Html, routing::get, Router};
use std::net::SocketAddr;

#[tokio::main]
async fn main() {
    // build our application with a route
    let app = Router::new().route("/", get(handler));

    // run it
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    println!("listening on http://{}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn handler() -> Html<&'static str> {
    Html("<h1>Hello, World!</h1>")
}

这段是axum hello-word example的摘录。

我们首先从 axum 导入所需的模块以及SocketAddr从 Rust 标准库导入的模块,这SocketAddr是一个 IP 地址构建器。我们正在使用它来构造 localhost IP 地址,如下所示:

  let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

最后,我们将地址传递给我们的 Axum 服务器实例:

...
  axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();

Axum 使用“handler”来表示我们通常在Node 的 Expressjs的controller等框架中类似功能。
控制器(或handler)本质上是接受和解析我们的 HTTP 请求并处理它们以返回 HTTP 响应的函数或方法。

async fn handler() -> Html<&'static str> {
    Html("<h1>Hello, World!</h1>")
}

我们的服务器已将一个处理程序安装到基本路由,如下所示:

...
   // build our application with a route
    let app = Router::new().route("/", get(handler));

...

一旦您在浏览器中访问https://127.0.0.1:3000,这个路由就会打印出“Hello World”。

让我们继续创建一个处理程序来发送我们的电子邮件。就在我们这样做之前,我们需要创建一个.env文件来保存我们的环境变量。

create a .env file
touch .env

填充 . env包含以下字段及其对应值的文件:

the SMTP username, typically the full email address
SMTP_USER=

the SMTP password
SMTP_PASSWORD=

SMTP host
SMTP_HOST=

现在,让我们到 src/main.rs 创建我们的处理程序,一旦完成,我们将把处理程序挂载到路由中。
要做到这一点,请将 src/main.rs 的内容替换为下面的列表,注意注释和添加的片段:

use axum::{
    response::{Html, IntoResponse},
    routing::{get, post},
    Json, Router,
};
use dotenv::dotenv; // import the dotenv crate for parsing the `.env file`
use serde::{Deserialize, Serialize};
use std::env; //for getting fields from the environment
use std::net::SocketAddr; // import serde for parsing our struct to and from Json
                          //import the email library needed modules
use lettre::transport::smtp::authentication::Credentials;
use lettre::{Message, SmtpTransport, Transport};

/// define a structure that maps to the format of our HTTP request body
/// derive the Debug trait, this will allow, printing the struct in stdout
/// derive the Serializing trait, this will allow building up JSON
/// derive the Deserializing trait
#[derive(Debug, Serialize, Deserialize)]
struct EmailPayload {
    fullname: String,
    email: String,
    message: String,
}

//mount the tokio runtime to allow our main function to support asynchronous execution
#[tokio::main]
async fn main() {
    dotenv().ok();
    // build our application with a route
    let app = Router::new()
        .route("/", get(handler))
        //mount the handle to a path, using the HTTP POST verb
        .route("/send-email", post(dispatch_email));

    // run it
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    println!("listening on http://{}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn handler() -> Html<&'static str> {
    Html("<h1>Hello, World!</h1>")
}

/// define an email handler, the handler will take the user's email address and the message body
/// the parsed payload will be fed into the `lettre` library and finally, a response will be returned
async fn dispatch_email(Json(payload): Json<EmailPayload>) -> impl IntoResponse {
    // println!("{:#?}", payload);
    //destructure the HTTP request body
    let EmailPayload {
        email,
        message,
        fullname,
    } = &payload;

    //contruct emil config
    let from_address = String::from("You <[email protected]>");
    let to_address = format!("{fullname} <{email}>");
    let reply_to = String::from("You <[email protected]>");
    let email_subject = "Axum Rust tutorial";

    let email = Message::builder()
        .from(from_address.parse().unwrap())
        .reply_to(reply_to.parse().unwrap())
        .to(to_address.parse().unwrap())
        .subject(email_subject)
        .body(String::from(message))
        .unwrap();

    let creds = Credentials::new(
        env::var("SMTP_USERNAME").expect("SMTP Username not specified "),
        env::var("SMTP_PASSWORD").expect("SMTP Password not specified"),
    );

    // Open a remote connection to SMTP server
    let mailer = SmtpTransport::relay(&env::var("SMTP_HOST").expect("SMTP Host not specified"))
        .unwrap()
        .credentials(creds)
        .build();

    // Send the email
    match mailer.send(&email) {
        Ok(_) => println!("Email sent successfully!"),
        Err(e) => panic!("Could not send email: {:?}", e),
    }
}

解释

  • 我首先从每个 crate 导入所需的模块
...
use dotenv::dotenv; // import the dotenv crate for parsing the `.env file`
use serde::{Deserialize, Serialize};
use std::env; //for getting fields from the environment
use std::net::SocketAddr; // import serde for parsing our struct to and from Json
                          //import the email library needed modules
use lettre::transport::smtp::authentication::Credentials;
use lettre::{Message, SmtpTransport, Transport};
...
  • 我定义了一个包含 HTTP 有效负载的数据结构,这是Axum Extractor所要求的,我将在以下部分中讨论它。
...
#[derive(Debug, Serialize, Deserialize)]
struct EmailPayload {
    fullname: String,
    email: String,
    message: String,
}

...
  • 我初始化了dotenv crate以允许在开发中解析环境变量,然后我使用Axum的post方法将路由处理程序安装到/send-email路由上,处理服务器接受的请求。
...
    dotenv().ok();
    // build our application with a route
    let app = Router::new()
        .route("/", get(handler))
        //mount the handle to a path, using the HTTP POST verb
        .route("/send-email", post(dispatch_email));
...

总结部分基本上是,插入我们的环境变量,解析我们的 HTTP 请求负载并发送电子邮件,并在控制台中打印响应。

测试
对于测试,您可以使用任何您熟悉的 HTTP 客户端,最常见的是Curl和 Postman
但是,我发现Thunder Client使用起来更方便,因为它是一个 VS Code 扩展。这意味着我可以在舒适的情况下做任何事情。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK