///|
struct Client {
base_url : @uri.Uri
api_key : Bytes?
uv : @uv.Loop
ssl_ctx : @openssl.SslCtx
}
///|
pub fn Client::new(base_url? : String, api_key? : String) -> Client raise {
let base_url = match base_url {
Some(base_url) => @encoding.encode(base_url, encoding=UTF8)
None =>
match @uv.os_getenv("OPENAI_API_BASE_URL") {
Some(base_url) => base_url
None => "https://api.openai.com/v1"
}
}
let base_url = @uri.Uri::parse(base_url)
let api_key = match api_key {
Some(api_key) => Some(@encoding.encode(api_key, encoding=UTF8))
None =>
match @uv.os_getenv("OPENAI_API_KEY") {
Some(api_key) => Some(api_key)
None => None
}
}
if @openssl.init_ssl(
@openssl.INIT_LOAD_SSL_STRINGS | @openssl.INIT_LOAD_CRYPTO_STRINGS,
) !=
1 {
fail("Failed to initialize OpenSSL")
}
let ssl_ctx = @openssl.SslCtx::new(@openssl.Tls::client_method())
ssl_ctx.set_default_verify_paths()
Client::{ base_url, api_key, uv: @uv.Loop::new(), ssl_ctx }
}
///|
fn Client::dns_resolve(
self : Client,
host : Bytes,
port : Bytes,
) -> @uv.Sockaddr raise {
let errors = []
let hints = @uv.AddrInfoHints::new()
let mut addr = None
let _ = self.uv.getaddrinfo(
infos => for info in infos {
addr = Some(info.addr())
},
error => errors.push(error),
host,
port,
hints~,
)
self.uv.run(Default)
for error in errors {
raise error
}
guard addr is Some(addr) else { fail("Failed to resolve address") }
addr
}
///|
fn Client::tcp_connect(
self : Client,
addr : @uv.Sockaddr,
) -> @uv.Tcp raise @uv.Errno {
let client = @uv.Tcp::new(self.uv)
let errors : Array[@uv.Errno] = []
client.connect(addr, () => (), error => errors.push(error)) |> ignore()
self.uv.run(Default)
for error in errors {
raise error
}
client
}
///|
fn Client::ssl_connect(
self : Client,
tcp : @uv.Tcp,
poll : @uv.Poll,
host : Bytes,
) -> @openssl.Ssl raise {
fn on_poll(
poll : @uv.Poll,
ssl : @openssl.Ssl,
errors : Array[Error],
) -> Unit {
match ssl.get_error(ssl.connect()) {
None => poll.stop() catch { error => errors.push(error) }
WantRead =>
poll.start(
@uv.PollEvent::readable(),
(_, _) => on_poll(poll, ssl, errors),
(_, error) => errors.push(error),
) catch {
error => errors.push(error)
}
WantWrite =>
poll.start(
@uv.PollEvent::writable(),
(_, _) => on_poll(poll, ssl, errors),
(_, error) => errors.push(error),
) catch {
error => errors.push(error)
}
error => errors.push(Failure("SSL connect error: \{error}"))
}
}
let tcp_os_sock = tcp.os_sock()
let ssl = @openssl.Ssl::new(self.ssl_ctx)
let _ = ssl.set_fd(tcp_os_sock.to_int())
let _ = ssl.set_tlsext_host_name(host)
let _ = ssl.set1_host(host)
match ssl.get_error(ssl.connect()) {
WantRead => {
let errors = []
poll.start(
@uv.PollEvent::readable(),
(_, _) => on_poll(poll, ssl, errors),
(_, error) => errors.push(error),
)
self.uv.run(Default)
for error in errors {
raise error
}
}
WantWrite => {
let errors = []
poll.start(
@uv.PollEvent::writable(),
(_, _) => on_poll(poll, ssl, errors),
(_, error) => errors.push(error),
)
self.uv.run(Default)
for error in errors {
raise error
}
}
error => fail("SSL connect error: \{error}")
}
ssl
}
///|
fn Client::ssl_write_all(
self : Client,
ssl : @openssl.Ssl,
data : @bytes.View,
poll : @uv.Poll,
) -> Unit raise {
fn on_poll(
poll : @uv.Poll,
ssl : @openssl.Ssl,
buf : @bytes.View,
errors : Array[Error],
) -> Unit {
let written = ssl.write(buf.data(), buf.start_offset(), buf.length())
match ssl.get_error(written) {
None if written == buf.length() =>
poll.stop() catch {
error => errors.push(error)
}
WantWrite | None => ()
error => {
errors.push(Failure("SSL write error: \{error}"))
poll.stop() catch {
error => errors.push(error)
}
}
}
}
let written = ssl.write(data.data(), data.start_offset(), data.length())
match ssl.get_error(written) {
None if written == data.length() => return
WantWrite => {
let errors = []
poll.start(
@uv.PollEvent::writable(),
(_, _) => on_poll(poll, ssl, data, errors),
(_, error) => errors.push(error),
)
self.uv.run(Default)
for error in errors {
raise error
}
}
WantRead => {
let errors = []
poll.start(
@uv.PollEvent::readable(),
(_, _) => on_poll(poll, ssl, data, errors),
(_, error) => errors.push(error),
)
self.uv.run(Default)
for error in errors {
raise error
}
}
error => fail("SSL write error: \{error}")
}
}
///|
fn Client::ssl_read_all(
self : Client,
ssl : @openssl.Ssl,
buffer : @buffer.T,
poll : @uv.Poll,
) -> Unit raise {
fn on_poll(
poll : @uv.Poll,
ssl : @openssl.Ssl,
buffer : @buffer.T,
errors : Array[Error],
) -> Unit {
let bytes = Bytes::make(4096, 0)
let read = ssl.read(bytes, 0, bytes.length())
match ssl.get_error(read) {
None => buffer.write_bytesview(bytes[:read])
ZeroReturn => poll.stop() catch { e => errors.push(e) }
WantRead =>
poll.start(
@uv.PollEvent::readable(),
(_, _) => on_poll(poll, ssl, buffer, errors),
(_, error) => errors.push(error),
) catch {
error => errors.push(error)
}
WantWrite =>
poll.start(
@uv.PollEvent::writable(),
(_, _) => on_poll(poll, ssl, buffer, errors),
(_, error) => errors.push(error),
) catch {
error => errors.push(error)
}
error => {
errors.push(Failure("SSL read error: \{error}"))
poll.stop() catch {
e => errors.push(e)
}
}
}
}
let errors = []
let bytes = Bytes::make(4096, 0)
let read = ssl.read(bytes, 0, bytes.length())
match ssl.get_error(read) {
None => buffer.write_bytesview(bytes[:read])
WantRead =>
poll.start(
@uv.PollEvent::readable(),
(_, _) => on_poll(poll, ssl, buffer, errors),
(_, error) => errors.push(error),
)
WantWrite =>
poll.start(
@uv.PollEvent::writable(),
(_, _) => on_poll(poll, ssl, buffer, errors),
(_, error) => errors.push(error),
)
error => {
errors.push(Failure("SSL read error: \{error}"))
poll.stop() catch {
error => errors.push(error)
}
}
}
self.uv.run(Default)
for error in errors {
raise error
}
}
///|
fn send_http_request(self : Client, request : Bytes) -> Bytes raise {
let host = match self.base_url.authority.unwrap().host {
IPv6Address(address) => address
RegName(name) => name
}
let host : Bytes = [..host]
let addr = self.dns_resolve(host, "443")
let tcp = self.tcp_connect(addr)
let poll = @uv.Poll::socket(self.uv, tcp.os_sock())
let ssl = self.ssl_connect(tcp, poll, host)
self.ssl_write_all(ssl, request, poll)
let buffer = @buffer.new()
self.ssl_read_all(ssl, buffer, poll)
buffer.contents()
}