# Files and Streams

> UCall for Rust

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

```rust
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

```rust
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

```rust
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.

`use ucall::FileRange;`

`use ucall::PathRange;`

`use ucall::ReplyStream;`

`use ucall::*;`

## Descriptor

A file descriptor borrowed for `'f`, laid out as the C `int` the library reads.

```rust
pub type Descriptor<'f> = BorrowedFd<'f>;
```

## FileRange

`ucall_file_range_t`: the caller's `O_DIRECT` descriptor, borrowed until the reply is final.

```rust
pub struct FileRange<'f>
```

- `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: `Clone`, `Copy`, [`Debug`](https://www.unum.cloud/docs/ucall/rust/serving.md#Debug)

## PathRange

`ucall_path_range_t`: a file beneath one of the interface's reply directories.

```rust
pub struct PathRange<'b>
```

- `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: `Clone`, `Copy`, [`Debug`](https://www.unum.cloud/docs/ucall/rust/serving.md#Debug)

## ReplyStream

A reply whose head left and whose body is appended part by part, from any one thread at a time, until [`ReplyStream::end`](#end); dropping it ends it, waiting for its file parts to send.

```rust
pub struct ReplyStream<'f>
```

The descriptors its file parts borrow stay open for `'f`, through the [`Deferred`](https://www.unum.cloud/docs/ucall/rust/errors.md#Deferred) its end may
hand back.

Implements: `Send`, `Drop`

## append

Queues `parts` on the body together, or none of them.

```rust
pub fn append(&mut self, parts: &[ReplyPart<'_, 'f>]) -> Result<()>
```

Fails with [`Status::WouldBlock`](https://www.unum.cloud/docs/ucall/rust/errors.md#WouldBlock), queueing nothing, while the peer lags behind the parts
already queued; retry once it catches up.

## end

Ends the body, consuming the request whatever the outcome; [`None`](https://www.unum.cloud/docs/ucall/rust/noise-keys.md#None) once final, and a [`Deferred`](https://www.unum.cloud/docs/ucall/rust/errors.md#Deferred) while file or path parts still send.

```rust
pub fn end(self) -> Result<Option<Deferred<'f>>>
```

A body short of its declared Content-Length fails with [`Status::Malformed`](https://www.unum.cloud/docs/ucall/rust/errors.md#Malformed) and closes
the connection after the bytes already sent.

