pub type Descriptor<'f> = BorrowedFd<'f>; Handlers run on the polling thread, so a reply or a pull that finishes later runs on a worker: Request::detach hands the request over as a DetachedRequest, which reads and replies like a Request and alone has the three verbs below.
| Verb | Returns | Final when |
|---|---|---|
reply_parts(status, headers, parts) | None for bytes alone, else a Deferred | every part is sent |
reply_stream(status, headers) | a ReplyStream with append(parts) and end() | end() returns None or a Deferred |
body_chunk() | the bytes decoded since the last pull | an empty chunk once the body ended |
§ Serving a File
use std::sync::mpsc;
use ucall::*;
let (handed, received) = mpsc::channel::<DetachedRequest>();
let interface = Interface::new("0.0.0.0:8080").reply_directories(["/srv/videos"]);
let mut server = Server::new(&[interface])?;
server.get("/videos/{name}", move |request: Request| handed.send(request.detach()).unwrap())?;
std::thread::scope(|scope| {
scope.spawn(|| server.take_calls(0));
for detached in received {
let name = detached.path_parameter("name").unwrap_or_default().to_vec();
let path = PathRange { directory: 0, path: name.as_slice().into(), offset: 0, length: 0 };
let parts = [ReplyPart::Path(path)];
let replied = detached.reply_parts(HTTPStatus::OK, &[], &parts);
if let Ok(Some(deferred)) = replied {
let _ = deferred.wait(); // or: deferred.await
}
}
});reply_parts concatenates ReplyPart::Bytes, ReplyPart::Path and ReplyPart::File parts into one body whose Content-Length is measured from the files. ReplyPart::from turns a borrowed std::io::IoSlice into a bytes part, so vectored buffers from other I/O code map over directly.
A Path resolves beneath the interface’s reply_directories, refusing absolute paths, .. and links leading out, and the event loop opens, reads and closes it unbuffered.
A File borrows the caller’s descriptor, opened with O_DIRECT, for the Deferred’s lifetime, while bytes and paths are copied before the call returns, so they may be dropped right after it.
A file that is missing, unreadable or too short replaces the reply with 404, 416 or 500 before its head leaves, and the Deferred reports the cause. Deferred::status polls without blocking, Deferred::wait blocks until the reply is final, .await polls the status between other tasks, since the library has no wake-up, and dropping a Deferred waits for it, which is why only a detached request makes one.
§ Streaming a Reply
fn send_rows(detached: DetachedRequest, rows: &[String]) -> Result<()> {
let mut stream = detached.reply_stream(HTTPStatus::OK, &["content-type: text/csv".into()])?;
for row in rows {
stream.append(&[ReplyPart::Bytes(row.as_bytes().into())])?;
}
stream.end().map(|_| ())
}The stream is chunked unless a Content-Length header declares the length; append refuses with Status::WouldBlock, queueing nothing, while the peer lags behind what is queued; end answers None or a Deferred like reply_parts, and dropping an unended stream ends it.
§ Streaming an Upload
fn store(mut detached: DetachedRequest) -> Result<()> {
let mut received_bytes = 0usize;
loop {
let chunk = detached.body_chunk()?;
if chunk.is_empty() {
break;
}
received_bytes += chunk.len();
}
let total = received_bytes.to_string();
detached.reply_parts(HTTPStatus::OK, &[], &[ReplyPart::Bytes(total.as_bytes().into())]).map(|_| ())
}An endpoint registered through Server::route_with with BodyDelivery::Streamed runs its handler once the headers arrive; the handler hands request.detach() to a worker as above, and body_chunk blocks for the bytes decoded since the last pull, empty once the body ended.
On the polling thread, where nothing arrives while the handler runs, a pull with nothing decoded refuses with Status::WouldBlock instead of waiting.
API
Types
DescriptorA file descriptor borrowed for 'f, laid out as the C int the library reads.
StructFileRangeucall_file_range_t: the caller's O_DIRECT descriptor, borrowed until the reply is final.
pub struct FileRange<'f> Fields
file | Descriptor<'f> | The open file, borrowed and never closed by UCall. |
|---|---|---|
offset | u64 | The range's first byte. |
length | u64 | The range's byte count; zero sends through the end of the file. |
Implements
StructPathRangeucall_path_range_t: a file beneath one of the interface's reply directories.
pub struct PathRange<'b> Fields
directory | usize | The position of the directory among the interface's reply directories. |
|---|---|---|
path | BytesView<'b> | The file beneath it, relative, copied before the call returns. |
offset | u64 | The range's first byte. |
length | u64 | The range's byte count; zero sends through the end of the file. |
Implements
StructReplyStreamA reply whose head left and whose body is appended part by part, from any one thread at a time, until ReplyStream::end; dropping it ends it, waiting for its file parts to send.2
pub struct ReplyStream<'f> The descriptors its file parts borrow stay open for 'f, through the Deferred its end may
hand back.
Implements
Send, DropappendQueues parts on the body together, or none of them.
pub fn append(&mut self, parts: &[ReplyPart<'_, 'f>]) -> Result<()> Fails with Status::WouldBlock, queueing nothing, while the peer lags behind the parts
already queued; retry once it catches up.
endEnds the body, consuming the request whatever the outcome; None once final, and a Deferred while file or path parts still send.
pub fn end(self) -> Result<Option<Deferred<'f>>> A body short of its declared Content-Length fails with Status::Malformed and closes
the connection after the bytes already sent.