
Wire.pudu
Pudu131 lines4.3 KB
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)15161718export 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}252627export 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}394041export 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}565758export 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}787980export 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}102103104export 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}110111112fn 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}120121122fn 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