~nickbp/tokio-scgi

ref: 7ae850993189639a89eda0570ce331873e2f8ed9 tokio-scgi/examples/client.rs -rw-r--r-- 3.2 KiB
7ae85099Nick Parker Switch examples to async/await 2 years 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
#![deny(warnings, rust_2018_idioms)]
#![feature(async_await)]

use bytes::{BufMut, BytesMut};
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_codec::Framed;
use tokio_scgi::client::{SCGICodec, SCGIRequest};

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 tx_scgi, rx_scgi) = Framed::new(conn, SCGICodec::new()).split();

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

    // Consume response
    // TODO support looping over multiple response calls?
    match rx_scgi.into_future().await {
        (None, _) => Err(Error::new(ErrorKind::Other, "No response received")),
        (Some(Err(e)), _) => Err(Error::new(
            ErrorKind::Other,
            format!("Error when waiting for query result: {}", e),
        )),
        (Some(Ok(response)), _) => {
            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)
            }
            Ok(())
        },
    }
}

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(content_str.to_vec());

    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)
}