Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-httpclient

Named HTTP clients for Pudu: a client factory, delegating handlers, pooled keep-alive connections, handler lifetimes, logging, and resilience

0.1.1Apache-2.01

InstallClose

Wire.pudu

Pudu131 lines4.3 KB

GitHub ↗
1/** @Transport.Wire.Module — one open connection, plain or secured, bounded by a deadline */2module PuduLangHttpClient.Transport.Wire34import Std.Net as Net5import Std.Tls as Tls6import PuduLangHttpClient.Constants.Defaults as Defaults7import PuduLangHttpClient.Domain.Expiry as Expiry8import PuduLangHttpClient as HttpClient910/** @Transport.Wire.Link — an open connection */11export type Link = Plain(Net.Connection) | Secured(Tls.Secured)1213/** @Transport.Wire.Fault — why an operation on a connection did not finish */14export type Fault = Stalled | Ended | Broken(HttpClient.Failure)1516/// Milliseconds an operation may wait under a deadline, `Defaults.INFINITE` for none; `None` once17/// the deadline has passed.18export fn budget(deadline: Option[Int]) -> Option[Int] {19  match Expiry.remaining(deadline, clock()) {20    case Some(0) => None21    case Some(left) => Some(left)22    case None => Some(Defaults.INFINITE)23  }24}2526/// A plain connection to a host and port.27export fn dial(host: Str, port: Int, deadline: Option[Int]) -> Result[Link, Fault] {28  let millis = budget(deadline)29  match millis {30    case None => Err(Stalled)31    case Some(left) => {32      match Net.connectWithin(host, port, left) {33        case Ok(connection) => Ok(Plain(connection))34        case Err(problem) => Err(plainFault(&problem))35      }36    }37  }38}3940/// A plain connection secured for a host whose certificate must verify.41export fn secure(link: Link, host: Str, deadline: Option[Int]) -> Result[Link, Fault] {42  match (link, budget(deadline)) {43    case (Plain(connection), Some(left)) => {44      match Tls.upgradeWithin(&connection, host, left) {45        case Ok(secured) => Ok(Secured(secured))46        case Err(problem) => Err(securedFault(&problem))47      }48    }49    case (Plain(connection), None) => {50      let _closed = Net.close(&connection)51      Err(Stalled)52    }53    case (secured, _) => Ok(secured)54  }55}5657/// Writes every byte.58export fn write(link: &Link, payload: &Bytes, deadline: Option[Int]) -> Result[(), Fault] {59  let left = match budget(deadline) {60    case Some(found) => found61    case None => { return Err(Stalled) }62  }63  match link {64    case Plain(connection) => {65      match Net.sendWithin(&connection, payload, left) {66        case Ok(_) => Ok(())67        case Err(problem) => Err(plainFault(&problem))68      }69    }70    case Secured(connection) => {71      match Tls.sendWithin(&connection, payload, left) {72        case Ok(_) => Ok(())73        case Err(problem) => Err(securedFault(&problem))74      }75    }76  }77}7879/// The next bytes that arrive, or `None` once the other side closed.80export fn read(link: &Link, deadline: Option[Int]) -> Result[Option[Bytes], Fault] {81  let left = match budget(deadline) {82    case Some(found) => found83    case None => { return Err(Stalled) }84  }85  match link {86    case Plain(connection) => {87      match Net.receiveWithin(&connection, Defaults.READ_CHUNK, left) {88        case Ok(piece) => Ok(piece)89        case Err(Net.Closed) => Ok(None)90        case Err(problem) => Err(plainFault(&problem))91      }92    }93    case Secured(connection) => {94      match Tls.receiveWithin(&connection, Defaults.READ_CHUNK, left) {95        case Ok(piece) => Ok(piece)96        case Err(Tls.Closed) => Ok(None)97        case Err(problem) => Err(securedFault(&problem))98      }99    }100  }101}102103/// Closes the connection, ignoring a failure: a connection being given up on has nothing left to say.104export fn close(link: &Link) -> () {105  match link {106    case Plain(connection) => { let _closed = Net.close(&connection) }107    case Secured(connection) => { let _closed = Tls.closeWithin(&connection, 1000) }108  }109}110111/// The failure a plain socket's error stands for.112fn plainFault(problem: &Net.NetError) -> Fault {113  match problem {114    case Net.NetOperationTimedOut => Stalled115    case Net.Closed => Ended116    case Net.Unresolved(reason) => Broken(HttpClient.NameResolution(reason))117    case other => Broken(HttpClient.Connection(show(other)))118  }119}120121/// The failure a secured socket's error stands for.122fn securedFault(problem: &Tls.TlsError) -> Fault {123  match problem {124    case Tls.TlsOperationTimedOut => Stalled125    case Tls.Closed => Ended126    case Tls.Unresolved(reason) => Broken(HttpClient.NameResolution(reason))127    case Tls.Refused(reason) => Broken(HttpClient.Connection(reason))128    case other => Broken(HttpClient.SecureConnection(show(other)))129  }130}131