TypeScript: parsing a raw HTTP request into a typed object
An HTTP/1.1 request is plain text. The first line is the request line with the method, the target path, and the protocol version. Header lines follow, each as name: value, and a blank line ends the headers. Lines are separated by carriage return plus line feed (CRLF), and the body, if any, comes after the blank line.
TypeScript
interface HttpRequest {
method: string;
path: string;
version: string;
headers: Record<string, string>;
body: string;
}
function parse(raw: string): HttpRequest {
const split = raw.indexOf("\r\n\r\n");
const head = split === -1 ? raw : raw.slice(0, split);
const body = split === -1 ? "" : raw.slice(split + 4);
const [requestLine, ...headerLines] = head.split("\r\n");
const [method, path, version] = requestLine.split(" ");
const headers: Record<string, string> = {};
for (const line of headerLines) {
const colon = line.indexOf(":");
headers[line.slice(0, colon).trim().toLowerCase()] = line.slice(colon + 1).trim();
}
return { method, path, version, headers, body };
}
const raw = "POST /search?q=tcp&page=2 HTTP/1.1\r\nHost: example.com\r\nContent-Length: 5\r\n\r\nhello";
const request = parse(raw);
console.log(request.method + " " + request.path + " " + request.version);
console.log("host: " + request.headers["host"]);
const url = new URL(request.path, "http://" + request.headers["host"]);
console.log("q=" + url.searchParams.get("q") + " page=" + url.searchParams.get("page"));
console.log("body (" + request.headers["content-length"] + " bytes): " + request.body);