import { ClientResponse } from "./server.ts";import { assert } from "./vendor/https/deno.land/std/testing/asserts.ts";import { defer } from "./promises.ts";import { readResponse, writeRequest } from "./serveio.ts";import { BufReader, BufWriter } from "./vendor/https/deno.land/std/io/bufio.ts";import Conn = Deno.Conn;import Reader = Deno.Reader;import DialOptions = Deno.DialOptions;
export interface HttpAgent { send(opts: HttpAgentSendOptions): Promise<ClientResponse>;
conn: Conn;}
export class ConnectionClosedError extends Error {}
export type HttpAgentOptions = { cancel?: Promise<void>; timeout?: number; };
export type HttpAgentSendOptions = { path: string; method: string; headers?: Headers; body?: string | Uint8Array | Reader;};
const kPortMap = { "http:": 80, "https:": 443};
export function createAgent( baseUrl: string, opts: HttpAgentOptions = {}): HttpAgent { let connected = false; let connecting = false; let _conn: Conn; let connectDeferred = defer(); let bufReader: BufReader; let bufWriter: BufWriter; const url = new URL(baseUrl); assert( url.protocol === "http:" || url.protocol === "https:", `scheme must be http or https: ${url.protocol}` ); let port = url.port || kPortMap[url.protocol]; assert(port !== void 0, `unexpected protocol: ${url.protocol}`); const connect = async () => { if (connected) return; if (connecting) return connectDeferred.promise; connecting = true; const opts: DialOptions = { port: parseInt(port), transport: "tcp" }; if (url.hostname) { opts.hostname = url.hostname; } if (url.protocol === "http:") { _conn = await Deno.dial(opts); } else { _conn = await Deno.dialTLS(opts); } bufReader = new BufReader(_conn); bufWriter = new BufWriter(_conn); connected = true; connecting = false; connectDeferred.resolve(); }; let prevResponse: ClientResponse; let sending = false; async function send( sendOptions: HttpAgentSendOptions ): Promise<ClientResponse> { if (sending) { throw new Error("It is not able to send http request concurrently"); } sending = true; if (!connected) { await connect(); } const { path, method, headers, body } = sendOptions; const destUrl = new URL(path, url); try { if (prevResponse) { await prevResponse.finalize(); } await writeRequest(bufWriter, { url: destUrl.toString(), method, headers, body }); const res = await readResponse(bufReader, opts); return (prevResponse = Object.assign(res, { bufWriter, bufReader, conn: _conn })); } catch (e) { if (e === Deno.EOF) { throw new ConnectionClosedError(); } else { throw new Error(`${e}`); } } finally { sending = false; } } return { send, get conn() { return _conn; } };}