tokio_scgi/client.rs
1#![deny(warnings)]
2
3use bytes::{BufMut, BytesMut};
4use std::io;
5use tokio_util::codec::{Decoder, Encoder};
6
7const NUL: u8 = b'\0';
8
9/// A parsed SCGI request header with key/value header data, and/or bytes from the raw request body.
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub enum SCGIRequest {
12 /// The Vec contains the headers. The BytesMut optionally contains raw byte data from
13 /// the request body, which may be followed by additional `BodyFragment`s in later calls.
14 /// The `Content-Length` header, required by SCGI, can be used to detect whether to wait for
15 /// additional `BodyFragment`s.
16 Request(Vec<(String, String)>, BytesMut),
17
18 /// Additional body fragment(s), used for streaming fragmented request body data. These should
19 /// only be relevant in cases where the leading `Request` value doesn't contain all of the body.
20 BodyFragment(BytesMut),
21}
22
23/// A `Codec` implementation that creates SCGI requests for SCGI clients like web servers.
24/// The Encoder accepts `SCGIRequest` objects containing header/body request data and encodes them for
25/// sending to an SCGI server. The Decoder passes through the raw response returned by the SCGI server.
26#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct SCGICodec {}
28
29impl SCGICodec {
30 /// Returns a client `SCGICodec` for creating SCGI-format requests for use by SCGI clients
31 /// like web servers.
32 pub fn new() -> SCGICodec {
33 SCGICodec {}
34 }
35}
36
37/// Passes through any response data as-is. To be handled by the requesting client.
38impl Decoder for SCGICodec {
39 type Item = BytesMut;
40 type Error = io::Error;
41
42 fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<BytesMut>, io::Error> {
43 // Forward content (HTTP response, typically?) as-is
44 Ok(Some(buf.split_to(buf.len())))
45 }
46}
47
48/// Creates and produces SCGI requests. Invoke once with `Request`, followed by zero or more calls
49/// with `BodyFragment`.
50impl Encoder<SCGIRequest> for SCGICodec {
51 type Error = io::Error;
52
53 fn encode(&mut self, data: SCGIRequest, buf: &mut BytesMut) -> Result<(), io::Error> {
54 match data {
55 SCGIRequest::Request(env_map, body) => {
56 // Calculate size needed for header netstring
57 let mut sum_header_size: usize = 0;
58 for (k, v) in &env_map {
59 // While we're iterating over the keys/values, do some basic validation per the
60 // SCGI protocol spec.
61 if k.len() == 0 {
62 return Err(io::Error::new(
63 io::ErrorKind::InvalidInput,
64 format!("Keys in request header cannot be empty"),
65 ));
66 }
67 if k.as_bytes().contains(&NUL) || v.as_bytes().contains(&NUL) {
68 return Err(io::Error::new(
69 io::ErrorKind::InvalidInput,
70 format!("Keys/values in request header cannot contain NUL character"),
71 ));
72 }
73 // Include 2 x NUL in size:
74 sum_header_size += k.len() + 1/*NUL*/ + v.len() + 1/*NUL*/;
75 }
76 let netstring_size_str = sum_header_size.to_string();
77 // Include ':' and ',' in buffer, not included in netstring size:
78 buf.reserve(
79 netstring_size_str.len() + 1/*:*/ + sum_header_size + 1/*,*/ + body.len(),
80 );
81
82 // Insert the header content into the reserved buffer.
83 buf.put_slice(netstring_size_str.as_bytes());
84 buf.put_u8(b':');
85 for (k, v) in &env_map {
86 buf.put(k.as_bytes());
87 buf.put_u8(NUL);
88 buf.put(v.as_bytes());
89 buf.put_u8(NUL);
90 }
91 buf.put_u8(b',');
92
93 // Add any body content after the header
94 buf.put(body);
95 }
96 SCGIRequest::BodyFragment(fragment) => {
97 // Forward content as-is
98 buf.reserve(fragment.len());
99 buf.put(fragment);
100 }
101 }
102 Ok(())
103 }
104}