-
Notifications
You must be signed in to change notification settings - Fork 2
/
http_client.rs
90 lines (74 loc) · 3.09 KB
/
http_client.rs
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
use std::net::{SocketAddr, SocketAddrV4};
use std::{env, io, mem, str};
use a10::net::socket;
use a10::{AsyncFd, Ring, SubmissionQueue};
mod runtime;
fn main() -> io::Result<()> {
// Create a new I/O uring.
let mut ring = Ring::new(2)?;
let mut host = env::args()
.nth(1)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "missing host"))?;
if !host.contains(':') {
// Add port 80 for `ToSocketAddrs`.
let insert_idx = host.find('/').unwrap_or(host.len());
host.insert_str(insert_idx, ":80");
}
let addr_host = host.split_once('/').map(|(h, _)| h).unwrap_or(&host);
// Get an IPv4 address for the domain (using blocking I/O).
let address = std::net::ToSocketAddrs::to_socket_addrs(&addr_host)?
.filter(SocketAddr::is_ipv4)
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "failed to lookup ip"))?;
let address = match address {
SocketAddr::V4(address) => address,
SocketAddr::V6(_) => unreachable!(),
};
// Create our future that makes the request.
let request_future = request(ring.submission_queue().clone(), &host, address);
// Use our fake runtime to poll the future, this basically polls the future
// and the `a10::Ring` in a loop.
let response = runtime::block_on(&mut ring, request_future)?;
// We'll print the response (using ol' fashioned blocking I/O).
let response = str::from_utf8(&response).map_err(|err| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("response doesn't contain UTF-8: {err}"),
)
})?;
println!("{response}");
Ok(())
}
/// Make a HTTP GET request to `address`.
async fn request(sq: SubmissionQueue, host: &str, address: SocketAddrV4) -> io::Result<Vec<u8>> {
// Create a new TCP, IPv4 socket.
let domain = libc::AF_INET;
let r#type = libc::SOCK_STREAM | libc::SOCK_CLOEXEC;
let protocol = 0;
let flags = 0;
let socket: AsyncFd = socket(sq, domain, r#type, protocol, flags).await?;
// Connect.
let addr = to_sockaddr_storage(address);
socket.connect(addr).await?;
// Send a HTTP GET / request to the socket.
let host = host.split_once(':').map(|(h, _)| h).unwrap_or(host);
let version = env!("CARGO_PKG_VERSION");
let request = format!("GET / HTTP/1.1\r\nHost: {host}\r\nUser-Agent: A10-example/{version}\r\nAccept: */*\r\n\r\n");
socket.send(request, 0).await?;
// Receiving the response.
let recv_buf = socket.recv(Vec::with_capacity(8192), 0).await?;
// We'll explicitly close the socket, although that happens for us when we
// drop the socket. In other words, this is not needed.
socket.close().await?;
Ok(recv_buf)
}
fn to_sockaddr_storage(addr: SocketAddrV4) -> libc::sockaddr_in {
// SAFETY: a `sockaddr_in` of all zeros is valid.
let mut storage: libc::sockaddr_in = unsafe { mem::zeroed() };
storage.sin_family = libc::AF_INET as _;
storage.sin_port = addr.port().to_be();
storage.sin_addr = libc::in_addr {
s_addr: u32::from_ne_bytes(addr.ip().octets()),
};
storage
}