~nickbp/tokio-scgi

ref: 7c1ebe92b12db79a080d21a2b7d37aec4a50ceb0 tokio-scgi/examples/client.rs -rw-r--r-- 3.8 KiB
7c1ebe92Nick Parker Bump version for cutting with updated dependencies 9 months ago
                                                                                
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#![deny(warnings)]

use bytes::{BufMut, BytesMut};
use futures::{SinkExt, StreamExt};
use std::env;
use std::io::{Error, ErrorKind};
use std::net::ToSocketAddrs;
use std::path::Path;
use tokio::net::{TcpStream, UnixStream};
use tokio::prelude::*;
use tokio_scgi::client::{SCGICodec, SCGIRequest};
use tokio_util::codec::Framed;

fn syntax() -> Error {
    println!(
        "Syntax: {} </path/to/unix.sock or tcp-host:1234>",
        env::args().nth(0).unwrap()
    );
    Error::new(ErrorKind::InvalidInput, "Missing required argument")
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    if env::args().len() <= 1 {
        return Err(syntax());
    }
    let endpoint = env::args().nth(1).unwrap();
    if endpoint.starts_with('-') {
        // Probably a commandline argument like '-h'/'--help', avoid parsing as a hostname
        return Err(syntax());
    }
    if endpoint.contains('/') {
        // Probably a path to a file, assume the argument is a unix socket
        let addr = Path::new(&endpoint);
        println!("Connecting to {}", addr.display());
        let mut conn = UnixStream::connect(&addr).await?;
        run_client(&mut conn).await
    } else {
        // Probably a TCP endpoint, try to resolve it in case it's a hostname
        let addr = endpoint
            .to_socket_addrs()
            .expect(format!("Invalid TCP endpoint '{}'", endpoint).as_str())
            .next()
            .unwrap();
        println!("Connecting to {}", addr);
        let mut conn = TcpStream::connect(&addr).await?;
        run_client(&mut conn).await
    }
}

/// Runs the client: Sends a request and prints the responses via the provided UDS or TCP connection.
async fn run_client<C>(conn: &mut C) -> Result<(), Error>
where
    C: AsyncRead + AsyncWrite + std::marker::Send + std::marker::Unpin + std::fmt::Debug,
{
    let mut framed = Framed::new(conn, SCGICodec::new());

    // Send request
    framed.send(build_request()).await?;

    // Consume response(s): loop until error or empty data returned
    loop {
        match framed.next().await {
            None => {
                // SCGI response not ready: loop for more rx data
                // Shouldn't happen for response data, but this is how it would work...
                println!("Response data is incomplete, resuming read");
            }
            Some(Err(e)) => {
                // RX error: return error and abort
                return Err(Error::new(
                    ErrorKind::Other,
                    format!("Error when waiting for response: {}", e),
                ));
            }
            Some(Ok(response)) => {
                // Got SCGI response: if empty, treat as end of response.
                if response.len() == 0 {
                    return Ok(());
                }
                // Otherwise 'handle' by printing content, then resume read for more
                match String::from_utf8(response.to_vec()) {
                    Ok(s) => println!("Got {} bytes:\n{}", response.len(), s),
                    Err(e) => println!(
                        "{} byte response is not UTF8 ({}):\n{:?}",
                        response.len(),
                        e,
                        response
                    ),
                }
            }
        }
    }
}

fn build_request() -> SCGIRequest {
    let content_str = b"{\"description\": \"my name is also bort <><><>\"}";
    let mut content = BytesMut::with_capacity(content_str.len());
    content.put_slice(content_str);

    let mut headers = Vec::new();
    headers.push(("Content-Length".to_string(), content_str.len().to_string()));
    headers.push(("SCGI".to_string(), "1".to_string()));
    headers.push(("Content-Type".to_string(), "application/json".to_string()));
    headers.push(("X-Username".to_string(), "bort".to_string()));

    SCGIRequest::Request(headers, content)
}