use std::io::{Read, Write};
use std::net::{SocketAddr, TcpStream};
use crate::http;
use crate::server::{self, Request};
use crate::store::{Outcome, Shared};
use crate::support::{log_request, plain};
pub(crate) fn get_first(shared: &Shared, stream: &mut TcpStream, req: &Request, path: &str) {
let request = forward_bytes(req);
for addr in &shared.members {
if let Some((status, response)) = relay_member(*addr, &request) {
log_request(shared, &req.method, path, Outcome::Status(status));
let _ = stream.write_all(&response);
return;
}
}
log_request(shared, &req.method, path, Outcome::Status(404));
let resp = if req.method == "HEAD" {
server::Resp {
status: 404,
extra_headers: Vec::new(),
content_length: b"not found\n".len(),
body: Vec::new(),
drip: shared.drip,
hide_length: false,
}
} else {
plain(404, b"not found\n", None)
};
let _ = server::write_response(stream, &resp);
}
pub(crate) fn refuse_writes(shared: &Shared, stream: &mut TcpStream, req: &Request, path: &str) {
log_request(shared, &req.method, path, Outcome::Status(405));
let mut resp = plain(405, b"", None);
resp.extra_headers.push(("Allow", "GET,HEAD".to_owned()));
let _ = server::write_response(stream, &resp);
}
fn relay_member(addr: SocketAddr, request: &[u8]) -> Option<(u16, Vec<u8>)> {
let mut member = TcpStream::connect(addr).ok()?;
let _ = member.set_read_timeout(Some(http::IO_TIMEOUT));
let _ = member.set_write_timeout(Some(http::IO_TIMEOUT));
member.write_all(request).ok()?;
let mut response = Vec::new();
member.read_to_end(&mut response).ok()?;
let status = status_of(&response)?;
(200..300).contains(&status).then_some((status, response))
}
fn forward_bytes(req: &Request) -> Vec<u8> {
let mut out = format!("{} {} HTTP/1.1\r\n", req.method, req.target).into_bytes();
for (name, value) in &req.headers {
let framing = name.eq_ignore_ascii_case("content-length")
|| name.eq_ignore_ascii_case("transfer-encoding");
if !framing {
out.extend_from_slice(format!("{name}: {value}\r\n").as_bytes());
}
}
out.extend_from_slice(format!("Content-Length: {}\r\n\r\n", req.body.len()).as_bytes());
out.extend_from_slice(&req.body);
out
}
fn status_of(response: &[u8]) -> Option<u16> {
let line = String::from_utf8_lossy(response.split(|b| *b == b'\n').next()?);
line.split_whitespace().nth(1)?.parse().ok()
}