Featured image of post 使用rust编写cloudflare workers程序

使用rust编写cloudflare workers程序

之前我已经尝试过使用javascript以及typescript编写一些workers程序,最近了解到cloudflare workers还支持rust编写程序并编译为wasm来运行,这样便可以用上rust完善的类型系统,于是我做了一些尝试并将过程记录下来。

Cloudflare Workers 提供了很方便的 serverless 形式的边缘计算服务,在一些情况下,使用 workers 程序来执行一些操作有着很大的优势,其中一个场景便是我们希望发送一些请求但是又不希望暴露自己服务器的地址,那么我们借助 workers 来实现这个需求。我们可以将请求发送到 cloudflare workers,然后再由 workers 对外进行请求并进行一些处理,这样避免了我们的服务器直接去对外部进行请求,通过cloudflare worker来增加了一些安全性。

之前我已经尝试过使用javascript以及typescript编写一些workers程序,最近了解到cloudflare workers还支持rust编写程序并编译为wasm来运行,这样便可以用上rust完善的类型系统,于是我做了一些尝试,编写了一个用于检查一个URL对应的资源的MIME类型的workers程序,并将过程记录下来。

我使用rust编写了两个cloudflare workers的简单小程序,在起步时可以做一些参考。

准备工作

关于如何创建一个使用rust的cloudflare workers程序,cloudflare提供了多篇文章,但是这几篇文章或多或少有一些问题(比如版本过时或者不够详细),初始化项目的方法有多种,如:

  • 写这篇文章的两天前,Cloudflare正好发布了一篇名为 hello world rust 的博客,提供了很好的指引, 也展示了如何在index.js中引用wasm模块。
  • worker-rs仓库 中的文档也提到了"How to start",不过按照这个仓库的 readme 来初始化的话,会出错,BUG Too many positional arguments provided #392
  • Template: worker-rust 的文档中提到了可用 npx wrangler generate project-name https://github.com/cloudflare/workers-sdk/templates/experimental/worker-rust 来初始化项目。

本文使用的是第三个方案来初始化项目,在项目初始化之后,我们便可以尝试使用 npx wrangler dev 来以开发模式运行我们的workers程序,这样我们便可以在本地进行调试。(这里有一个小插曲,我在mac上使用该命令的时候会卡在 Starting local server… 这一步上,使用最新版的wrangler才发现遇到了 getaddrinfo ENOTFOUND localhost 的错误,我向 /etc/hosts 里面加入 127.0.0.1 localhost 的记录后才正常工作)

npx wrangler dev

可以发现这个模板创建出来的项目在使用 npx wrangler dev 之后会编译wasm文件,并生成出workers需要的index.js文件以及在index.js内部引入wasm模块。

编写程序

接着开始编写我们程序的主体部分。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
use serde::{Deserialize, Serialize};
use worker::*;
#[derive(Deserialize)]
struct ContentTypeCheckRequest {
    url: String,
    expected_types: Option<Vec<String>>, // priority
    expected_main_types: Option<Vec<String>>,
}

#[derive(Serialize)]
struct ContentTypeCheckResponse {
    is_ok: bool,
    content_type: String,
    expected_types: Option<Vec<String>>,
    expected_main_types: Option<Vec<String>>,
    // error_message: Option<String>,
}

async fn get_content_type(url: &str) -> Result<String> {
    let request = Request::new_with_init(url, RequestInit::new().with_method(Method::Head))?;
    let resp = Fetch::Request(request).send().await?;
    let headers = resp.headers();
    let content_type = headers.get("content-type")?.unwrap_or_default();
    Ok(content_type)
}

#[event(fetch)]
async fn main(mut req: Request, _env: Env, _ctx: Context) -> Result<Response> {
    if let Method::Post = req.method() {
        let cr = match req.json::<ContentTypeCheckRequest>().await {
            Ok(r) => r,
            Err(e) => {
                return Response::error(format!("Failed to parse request: {}", e), 400);
            }
        };
        println!("Checking content type for {}", cr.url);
        let content_type = match get_content_type(&cr.url).await {
            Ok(ct) => ct,
            Err(e) => {
                return Response::error(format!("Failed to get content type: {}", e), 500);
            }
        };
        // check exact types first
        if let Some(ref types) = cr.expected_types {
            if !types.is_empty() {
                for t in types {
                    if &content_type == t {
                        return Response::from_json::<ContentTypeCheckResponse>(
                            &ContentTypeCheckResponse {
                                is_ok: true,
                                content_type,
                                expected_types: cr.expected_types,
                                expected_main_types: cr.expected_main_types,
                            },
                        );
                    }
                }
            }
        }
        // check main types
        if let Some(ref main_types) = cr.expected_main_types {
            if !main_types.is_empty() {
                let main_type = content_type.split('/').next().unwrap_or_default();
                for t in main_types {
                    if main_type == t {
                        return Response::from_json::<ContentTypeCheckResponse>(
                            &ContentTypeCheckResponse {
                                is_ok: true,
                                content_type,
                                expected_types: cr.expected_types,
                                expected_main_types: cr.expected_main_types,
                            },
                        );
                    }
                }
            }
        }

        Response::from_json::<ContentTypeCheckResponse>(&ContentTypeCheckResponse {
            is_ok: false,
            content_type,
            expected_types: cr.expected_types,
            expected_main_types: cr.expected_main_types,
        })
    } else {
        Response::error("Unsupported method.", 405)
    }
}

编写过程中,可以使用 npx wrangler dev,在本地运行一个workers服务来执行我们的程序进行调试。在编写完成后,可以使用 npx wrangler deploy 来发布我们的workers程序, 在我们通过浏览器进行 OAuth 认证之后,程序便会被发布到我们的workers服务上。

comments powered by Disqus
本站访客数:
使用 Hugo 构建
主题 StackJimmy 设计