Programming Language

Nocter

A self-contained systems language built around simplicity, encapsulation, and foolproof design.

/examples/async-http/exchange.nct

exchange.nct

see ./index.nct

use std/http.{Client, Request}
use std/io
use std/net.{IpAddress, Ipv4Address, SocketAddress, TcpListener}
use std/string.String
use std/task
use std/time.Duration
use std/url.Url
use std/vec.Vec

func loopback(): SocketAddress {
    return SocketAddress.new(IpAddress.from_ipv4(Ipv4Address.loopback()), 0)
}

noalloc func ends_with(bytes: &[u8], suffix: &[u8]): bool {
    if suffix.len() > bytes.len() { return false }
    let start = bytes.len() - suffix.len()
    var offset: usize = 0
    while offset < suffix.len() {
        if bytes[start + offset] != suffix[offset] { return false }
        offset += 1
    }
    return true
}

func serve_once(
    listener: TcpListener,
    timeout: Duration,
): async void! {
    var owner = move listener
    let accepted = await owner.accept_async_with_timeout(timeout)?
    var stream = move accepted.0
    var request: Vec<u8> = Vec.empty()
    var scratch: Vec<u8> = Vec [
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
        u8.truncate(0),
    ]
    while !ends_with(&request, "\r\n\r\nping".bytes()) {
        let received = await stream.read_async_with_timeout(&+scratch, timeout)?
        if received == 0 {
            return error.new("async-http.request", "client closed before its request was complete")
        }
        var offset: usize = 0
        while offset < received {
            request.push(scratch[offset])
            offset += 1
        }
        if request.len() > 4096 {
            return error.new("async-http.request", "local request exceeded the example limit")
        }
    }
    await stream.write_async_with_timeout(
        "HTTP/1.1 200 OK\r\nContent-Length: 4\r\nX-Example: local\r\n\r\npong".bytes(),
        timeout,
    )?
    return
}

func run(): async i32! {
    let timeout = Duration.from_seconds(1)
    var listener = TcpListener.bind(loopback())?
    let address = listener.local_address()?
    let port = address.port().to_string()
    let url_text = String.concat("http://127.0.0.1:", &port, "/echo")

    var request = Request.post(Url.parse(&url_text)?)?
    request.append_header_text("X-Example", "client")?
    request.set_text_body("ping")
    let client = Client.new()
    let response = client.send_async_with_timeout(move request, timeout)?
    let exchanged = await task.join(
        move response,
        serve_once(move listener, timeout),
    )
    let response_result = move exchanged.0
    let server_result = move exchanged.1
    var received = move response_result?
    move server_result?
    if received.status().code() != 200 {
        return error.new("async-http.status", "local server returned an unexpected status")
    }
    let _header = received.headers().first("x-example") otherwise {
        return error.new("async-http.header", "local response header was absent")
    }
    let body = await received.read_to_string_async_with_timeout(timeout)?
    io.print("async http: ")?
    io.println(&body)?
    return 0
}