4 个版本 (重大更改)
0.8.0 | 2023 年 7 月 19 日 |
---|---|
0.7.0 | 2022 年 6 月 9 日 |
0.6.0 | 2022 年 5 月 18 日 |
0.5.0 | 2022 年 5 月 17 日 |
#471 在 网络编程 中
56KB
636 行
dumb-cgi
一个充足、几乎无依赖的 Rust CGI 库。
该库的目的是允许服务器端 CGI 程序轻松解析请求(特别是“multipart/formdata”请求)并生成响应,而无需引入许多功能齐全的 crate。 dumb_cgi
不会尝试(或至少一开始不会尝试)成为资源高效的;其主要目标是简单易用。它做出的某些权衡包括
-
它进行了大量的复制并创建了大量的小分配。这使得它更容易使用(和编写),但这也带来了性能和资源使用的代价。
-
它强制将所有环境变量和标题名称和值进行有损转换到 UTF-8(以便可以将它们存储为
String
)。规范应保证标题名称是有效的 UTF-8,但如果你需要任何其他三个(标题值、环境变量名称或环境变量值)是某种无法正确、有意义地转换为 UTF-8 的东西,那么这个 crate 对你的用例来说就太简单了。 -
它不尝试解析格式不正确或几乎格式正确的请求,甚至可能为了简化实现而错误处理一些不常见的边缘情况。例如,multipart 主体部分中的标题和空白行必须以 "\r\n" 结尾(这是严格符合规范的),尽管其他许多 HTTP 实现仍然会识别普通的 "\n" 行结束符。这简化了实现。
-
它的预期用例是仅限服务器端 CGI 程序。它支持 读取 请求,但不创建请求,支持 写入 响应,但不读取响应,并且仅支持与读取、解析和响应对 CGI 请求直接相关的 HTTP-verse 部分。
用法
为了说明读取和响应请求,以下是一个示例程序,它读取一个请求,记录其信息,然后返回一个简单的“成功”响应。如果任何 .unwrap()
或 .expect()
发生崩溃,则Web服务器将仅返回通用的500响应。
对于日志记录,我们将使用来自 log
日志接口和 simplelog
日志crate的功能(如果你使用 log
功能编译 dumb-cgi
,这两个都将成为依赖)。
use dumb_cgi::{Request, EmptyResponse, Query, Body};
use simplelog::{WriteLogger, LevelFilter, Config};
fn main() {
// Open the log file.
WriteLogger::init(
LevelFilter::max(),
Config::default(),
std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("dumb_example.log")
.unwrap()
).unwrap();
// Gather info about the CGI request, including reading any body
// (if present) from stdin.
let request = Request::new().unwrap();
// Log method request origin information.
log::trace!(
"Rec'd {} request from {} on port {}:",
request.var("METHOD").unwrap_or("none"),
request.var("REMOTE_ADDR").unwrap_or("nowhere"),
request.var("SERVER_PORT").unwrap_or("-1")
);
// Log all the headers.
//
// The `Request::header()` method will return individual header values
// (if present); the `Request::headers()` method will return an
// iterator over all `(name, value)` header pairs.
log::trace!(" Request headers:");
for (name, value) in request.headers() {
log::trace!(" {}: {}", name, value);
}
// If there's a query string, log info about it.
//
// The `Request::query()` method returns a reference to a
// `dumb_cgi::Query` enum.
match request.query() {
Query::None => {
log::trace!(" No query string.");
},
Query::Some(form) => {
// If this variant is returned, then the query string was
// parseable as `&`-separated `name=value` pairs, and the
// contained `form` value is a `HashMap<String, String>`.
log::trace!(" Form data from query string:");
for (name, value) in form.iter() {
log::trace!(" {}={}", name, value);
}
},
Query::Err(e) => {
// If this variant is returned, there was an error attempting
// to parse the `QUERY_STRING` environment variable as a series
// of `&`-separated `name=value` pairs.
// `dumb_cgi::Error`s have a public `.details` member that is a
// string with information about the error.
log::trace!(" Error parsing query string: {}", &e.details);
// You can still access the value of `QUERY_STRING` directly:
log::trace!(
" Raw QUERY_STRING value: {}",
request.var("QUERY_STRING").unwrap()
);
},
}
// If there's a body, log info about it.
//
// The `Request::body()` method returns a reference to a
// `dumb_cgi::Body` enum.
match request.body() {
Body::None => {
log::trace!(" No body.");
},
Body::Some(bytes) => {
// Most valid bodies of properly-formed requests will return
// this variant; `bytes` will be an `&[u8]`.
log::trace!(" {} bytes of body.", bytes.len());
},
Body::Multipart(parts) => {
// If the request has a properly-formed `Content-type` header
// indicating `multipart/form-data`, and the body of the request
// is also properly formed, this variant will be returned.
//
// The contained `parts` is a vector of `dumb_cgi::MultipartPart`
// structs, one per part.
log::trace!(" Multipart body with {} part(s).", parts.len());
},
Body::Err(e) => {
// This variant will be returned if there is an error reading
// the body.
log::trace!(" Error reading body: {}", &e.details);
},
}
// And we'll just put a blank line here in the log to separate
// info about separate requests.
log::trace!("");
// Now that we've read and logged all the information we want from our
// request, it's time to generate and send a response.
//
// Responses can be created with the builder pattern, starting with
// an `EmptyResponse` (which has no body). In order to send a response
// with a body, we need to call `EmptyResponse::with_content_type()`,
// which turns our `EmptyResponse` into a `FullResponse`, which takes
// a body.
// Takes the HTTP response code.
let response = EmptyResponse::new(200)
// Headers can be added any time.
.with_header("Cache-Control", "no-store")
// Now we can add a body.
.with_content_type("text/plain")
// A body can be added this way; `FullResponse` also implements
// `std::io::Write` for writing to the response body.
.with_body("Success. Your request has been logged.")
// Again, headers can be added any time.
.with_header("Request-Status", "logged");
// `FullResponse::respond()` consumes the response value and writes the
// response to stdout.
response.respond().unwrap();
}
显然,更多详细信息可以在文档中找到。
待办事项
这可能实际上是这个crate的最终形式(除了错误修复或可用性调整之外)。我有一些改进的想法,但它们都将这个项目危险地推向了“不那么笨”的领域。另一方面,我不太喜欢同时维护 dumb_cgi
和 not_quite_as_dumb_cgi
的想法,所以这很可能只是功能蔓延和长出瑕疵,直到我对它感到厌恶。
注释
-
v 0.3.0: 移除了对
lua-patterns
的依赖,因为尽管我喜欢这个想法,它也在其他项目中工作得很好,但它总是崩溃。现在dumb_cgi
仅依赖于log
日志接口(以及如果你实际上想进行一些日志记录并启用log
功能的话,还有simplelog
)。 -
v 0.4.0: 添加了显式的查询字符串解析。
-
v 0.5.0: 添加了响应类型和功能。
-
v 0.6.0: 修改了
Error
类型和处理。 -
v 0.7.0:
log
现在也是一个可选依赖。 -
v 0.8.0 实现了
std::error::Error
用于dumb_cgi::Error
;修复了README.md
中的一个拼写错误。
依赖项
~0–7.5MB
~43K SLoC