
Connect.pudu
Pudu116 lines4.9 KB
1/** @Transport.Connect.Module — connections opened directly or through a proxy */2module PuduLangHttpClient.Transport.Connect34import Std.Bytes as ByteSeq5import Std.Http.Message as Message6import PuduLangHttpClient.Constants.Defaults as Defaults7import PuduLangHttpClient.Constants.Messages as Messages8import PuduLangHttpClient.Domain.Expiry as Expiry9import PuduLangHttpClient.Domain.Uri as Uri10import PuduLangHttpClient as HttpClient11import PuduLangHttpClient.Proxy as Proxy12import PuduLangHttpClient.Transport.Wire as Wire13import PuduLangHttpClient.Utils.Template as Template1415/** @Transport.Connect.Route — where a request's connection goes and how it is shared */16export type Route = { key: Str, target: Uri.Endpoint, via: Option[Proxy.Proxy] }171819export fn routeTo(target: &Uri.Endpoint, proxy: &Option[Proxy.Proxy]) -> Route {20 match proxy {21 case Some(found) => {22 if Proxy.bypasses(&found, target.host) {23 Route{key: "direct " + Uri.origin(target), target: *target, via: None}24 } else if target.secure {25 Route{key: "tunnel " + found.address + " " + Uri.origin(target), target: *target, via: Some(found)}26 } else {27 Route{key: "proxy " + found.address, target: *target, via: Some(found)}28 }29 }30 case None => Route{key: "direct " + Uri.origin(target), target: *target, via: None}31 }32}333435export fn isProxied(route: &Route) -> Bool {36 match route.via {37 case Some(_) => !route.target.secure38 case None => false39 }40}41424344export fn open(route: &Route, connectTimeout: Int, deadline: Option[Int]) -> Result[Wire.Link, Wire.Fault] {45 let connecting = if connectTimeout == Defaults.INFINITE { deadline } else { Expiry.earliest(deadline, Some(clock() + connectTimeout)) }46 let outcome = match route.via {47 case None => dialSecured(&route.target, connecting)48 case Some(proxy) => through(&proxy, &route.target, connecting)49 }50 match outcome {51 case Err(Wire.Stalled) => {52 if Wire.budget(deadline) == None { Err(Wire.Stalled) } else { Err(Wire.Broken(HttpClient.Connection(Template.fill(Messages.STALLED, &["connecting"])))) }53 }54 case other => other55 }56}575859fn dialSecured(target: &Uri.Endpoint, deadline: Option[Int]) -> Result[Wire.Link, Wire.Fault] {60 let link = Wire.dial(target.host, target.port, deadline) ?61 if target.secure { Wire.secure(link, target.host, deadline) } else { Ok(link) }62}63646566fn through(proxy: &Proxy.Proxy, target: &Uri.Endpoint, deadline: Option[Int]) -> Result[Wire.Link, Wire.Fault] {67 let place = match Uri.endpointOf(proxy.address) {68 case Some(found) => found69 case None => { return Err(Wire.Broken(HttpClient.ProxyTunnel(proxy.address))) }70 }71 let link = Wire.dial(place.host, place.port, deadline) ?72 if !target.secure { return Ok(link) }73 let authority = target.host + ":" + show(target.port)74 var head = "CONNECT " + authority + " HTTP/1.1\r\nhost: " + authority + "\r\n"75 if let Some(credentials) = Proxy.authorization(proxy) { head = head + "proxy-authorization: " + credentials + "\r\n" }76 if let Err(fault) = Wire.write(&link, &(head + "\r\n").toBytes(), deadline) {77 Wire.close(&link)78 return Err(fault)79 }80 match tunnelAnswer(&link, deadline) {81 case Ok(code) => {82 if code >= 200 && code < 300 { Wire.secure(link, target.host, deadline) } else {83 Wire.close(&link)84 Err(Wire.Broken(HttpClient.ProxyTunnel("status " + show(code))))85 }86 }87 case Err(fault) => {88 Wire.close(&link)89 Err(fault)90 }91 }92}939495fn tunnelAnswer(link: &Wire.Link, deadline: Option[Int]) -> Result[Int, Wire.Fault] {96 var pending = ByteSeq.empty()97 loop {98 let end = pending.indexOf("\r\n\r\n".toBytes())99 if end >= 0 {100 let text = match ByteSeq.toText(&pending.take(end + 4)) {101 case Ok(found) => found102 case Err(_) => { return Err(Wire.Broken(HttpClient.ProxyTunnel("the answer is not text"))) }103 }104 return match Message.parseResponse(text) {105 case Ok(parsed) => Ok(parsed.status.code)106 case Err(problem) => Err(Wire.Broken(HttpClient.ProxyTunnel(Message.explain(&problem))))107 }108 }109 if pending.length() > Defaults.MAX_RESPONSE_HEADERS { return Err(Wire.Broken(HttpClient.ProxyTunnel("the answer is too long"))) }110 match Wire.read(link, deadline) ? {111 case Some(piece) => { pending = pending.concat(piece) }112 case None => { return Err(Wire.Broken(HttpClient.ProxyTunnel("the proxy closed the connection"))) }113 }114 }115}116