Skip to content

Commit

Permalink
Rewrite http command to use the syscalls
Browse files Browse the repository at this point in the history
  • Loading branch information
vinc committed Jul 7, 2023
1 parent 00c87ed commit 249908b
Showing 1 changed file with 77 additions and 105 deletions.
182 changes: 77 additions & 105 deletions src/usr/http.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
use crate::{sys, usr};
use crate::api::console::Style;
use crate::api::clock;
use crate::api::process::ExitCode;
use crate::api::random;
use crate::api::syscall;
use alloc::string::{String, ToString};
use alloc::vec;
use core::str::{self, FromStr};
use smoltcp::iface::SocketSet;
use smoltcp::socket::tcp;
use smoltcp::time::Instant;
use smoltcp::wire::IpAddress;

#[derive(Debug)]
Expand All @@ -19,7 +14,6 @@ struct URL {
pub path: String,
}

enum SessionState { Connect, Request, Response }
enum ResponseState { Headers, Body }

impl URL {
Expand Down Expand Up @@ -103,9 +97,17 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {

let url = "http://".to_string() + host + path;
let url = URL::parse(&url).expect("invalid URL format");

let address = if url.host.ends_with(char::is_numeric) {
IpAddress::from_str(&url.host).expect("invalid address format")
let port = url.port;
let addr = if url.host.ends_with(char::is_numeric) {
match IpAddress::from_str(&url.host) {
Ok(ip_addr) => {
ip_addr
}
Err(_) => {
error!("Invalid address format");
return Err(ExitCode::UsageError);
}
}
} else {
match usr::host::resolve(&url.host) {
Ok(ip_addr) => {
Expand All @@ -118,117 +120,87 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
}
};

let mut session_state = SessionState::Connect;

if let Some((ref mut iface, ref mut device)) = *sys::net::NET.lock() {
let mut sockets = SocketSet::new(vec![]);
let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]);
let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
let tcp_handle = sockets.add(tcp_socket);
use alloc::format;
use crate::sys::fs::OpenFlag;
let addr = format!("{}", addr);
let flags = OpenFlag::Device as usize;
if let Some(handle) = syscall::open("/dev/net/tcp", flags) {
if syscall::connect(handle, &addr, port).is_err() {
error!("Could not connect to {}:{}", addr, port);
syscall::close(handle);
return Err(ExitCode::Failure);
}
let req = vec![
format!("GET {} HTTP/1.1\r\n", url.path),
format!("Host: {}\r\n", url.host),
format!("User-Agent: MOROS/{}\r\n", env!("CARGO_PKG_VERSION")),
format!("Connection: close\r\n"),
format!("\r\n"),
];
if is_verbose {
print!("{}", csi_verbose);
for line in &req {
print!("> {}", line);
}
print!("{}", csi_reset);
}
let req = req.join("");
syscall::write(handle, &req.as_bytes());

let mut last_received_at = clock::realtime();
let mut response_state = ResponseState::Headers;
loop {
if clock::realtime() - last_received_at > timeout {
error!("Timeout reached");
return Err(ExitCode::Failure);
}
if sys::console::end_of_text() || sys::console::end_of_transmission() {
eprintln!();
syscall::close(handle);
return Err(ExitCode::Failure);
}

let time = Instant::from_micros((clock::realtime() * 1000000.0) as i64);
iface.poll(time, device, &mut sockets);
let socket = sockets.get_mut::<tcp::Socket>(tcp_handle);
let cx = iface.context();

session_state = match session_state {
SessionState::Connect if !socket.is_active() => {
let local_port = 49152 + random::get_u16() % 16384;
if is_verbose {
print!("{}", csi_verbose);
println!("* Connecting to {}:{}", address, url.port);
print!("{}", csi_reset);
}
if socket.connect(cx, (address, url.port), local_port).is_err() {
error!("Could not connect to {}:{}", address, url.port);
return Err(ExitCode::Failure);
}
SessionState::Request
}
SessionState::Request if socket.may_send() => {
let http_get = "GET ".to_string() + &url.path + " HTTP/1.1\r\n";
let http_host = "Host: ".to_string() + &url.host + "\r\n";
let http_ua = "User-Agent: MOROS/".to_string() + env!("CARGO_PKG_VERSION") + "\r\n";
let http_connection = "Connection: close\r\n".to_string();
if is_verbose {
print!("{}", csi_verbose);
print!("> {}", http_get);
print!("> {}", http_host);
print!("> {}", http_ua);
print!("> {}", http_connection);
println!(">");
print!("{}", csi_reset);
}
socket.send_slice(http_get.as_ref()).expect("cannot send");
socket.send_slice(http_host.as_ref()).expect("cannot send");
socket.send_slice(http_ua.as_ref()).expect("cannot send");
socket.send_slice(http_connection.as_ref()).expect("cannot send");
socket.send_slice(b"\r\n").expect("cannot send");
SessionState::Response
let mut data = vec![0; 2048];
if let Some(n) = syscall::read(handle, &mut data) {
if n == 0 {
break;
}
SessionState::Response if socket.can_recv() => {
socket.recv(|data| {
last_received_at = clock::realtime();
let n = data.len();
let mut i = 0;
while i < n {
match response_state {
ResponseState::Headers => {
let mut j = i;
while j < n {
if data[j] == b'\n' {
break;
}
j += 1;
}
let line = String::from_utf8_lossy(&data[i..j]); // TODO: check i == j
if is_verbose {
if i == 0 {
print!("{}", csi_verbose);
}
println!("< {}", line);
}
if line.trim().is_empty() {
if is_verbose {
print!("{}", csi_reset);
}
response_state = ResponseState::Body;
}
i = j + 1;
}
ResponseState::Body => {
syscall::write(1, &data[i..n]);
data.resize(n, 0);
let mut i = 0;
while i < n {
match response_state {
ResponseState::Headers => {
let mut j = i;
while j < n {
if data[j] == b'\n' {
break;
}
j += 1;
}
let line = String::from_utf8_lossy(&data[i..j]); // TODO: check i == j
if is_verbose {
if i == 0 {
print!("{}", csi_verbose);
}
println!("< {}", line);
}
if line.trim().is_empty() {
if is_verbose {
print!("{}", csi_reset);
}
response_state = ResponseState::Body;
}
i = j + 1;
}
(data.len(), ())
}).unwrap();
SessionState::Response
}
SessionState::Response if !socket.may_recv() => {
break;
ResponseState::Body => {
// NOTE: The buffer may not be convertible to a UTF-8 string
// so we write it to STDOUT directly instead of using print.
syscall::write(1, &data[i..n]);
break;
}
}
}
_ => session_state
};

if let Some(wait_duration) = iface.poll_delay(time, &sockets) {
syscall::sleep((wait_duration.total_micros() as f64) / 1000000.0);
} else {
error!("Could not read from {}:{}", addr, port);
syscall::close(handle);
return Err(ExitCode::Failure);
}
}
syscall::close(handle);
Ok(())
} else {
Err(ExitCode::Failure)
Expand Down

0 comments on commit 249908b

Please sign in to comment.