Skip to content

Commit

Permalink
feat(cli): http2 switch
Browse files Browse the repository at this point in the history
  • Loading branch information
Mastercuber committed Dec 9, 2023
1 parent 187216b commit 129a3f8
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 22 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ When the keystore is password protected also set `passphrase`.
You can also provide an inline cert and key instead of reading from the filesystem. In this case, they should start with `--`.
### `http2`
- Type: Boolean
- Default: `false`
HTTP-Versions 1 and 2 will be used when enabled; otherwise only HTTP 1 is used.
### `showURL`
- Default: `true` (force disabled on a test environment)
Expand Down
6 changes: 6 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ export function getArgs() {
description:
"Comma seperated list of domains and IPs, the autogenerated certificate should be valid for (https: true)",
},
http2: {
type: "boolean",
description: "Enable serving HTTP Protocol Version 2 requests",
required: false,
},
publicURL: {
type: "string",
description: "Displayed public URL (used for QR code)",
Expand Down Expand Up @@ -167,5 +172,6 @@ export function parseArgs(args: ParsedListhenArgs): Partial<ListenOptions> {
: undefined,
}
: false,
http2: args.http2,
};
}
49 changes: 30 additions & 19 deletions src/listen.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { createServer, IncomingMessage, ServerResponse } from "node:http";
import {
createSecureServer,
createServer as createHttpServer,
IncomingMessage,
ServerResponse,
} from "node:http";
import { createServer as createHttpsServer } from "node:https";
import {
createSecureServer as createHttps2Server,
createServer as createHttp2Server,
Http2ServerRequest,
Http2ServerResponse,
} from "node:http2";
Expand All @@ -10,27 +16,27 @@ import { getPort } from "get-port-please";
import addShutdown from "http-shutdown";
import consola from "consola";
import { defu } from "defu";
import { ColorName, getColor, colors } from "consola/utils";
import { ColorName, colors, getColor } from "consola/utils";
import { renderUnicodeCompact as renderQRCode } from "uqr";
import type { Tunnel } from "untun";
import { open } from "./lib/open";
import type {
ListenOptions,
Listener,
ShowURLOptions,
GetURLOptions,
HTTPSOptions,
Listener,
ListenOptions,
ListenURL,
GetURLOptions,
Server,
ShowURLOptions,
} from "./types";
import {
formatURL,
getNetworkInterfaces,
isLocalhost,
isAnyhost,
getPublicURL,
generateURL,
getDefaultHost,
getNetworkInterfaces,
getPublicURL,
isAnyhost,
isLocalhost,
validateHostname,
} from "./_utils";
import { resolveCertificate } from "./_cert";
Expand Down Expand Up @@ -71,6 +77,7 @@ export async function listen(
const listhenOptions = defu<ListenOptions, ListenOptions[]>(_options, {
name: "",
https: false,
http2: false,
port: process.env.PORT || 3000,
hostname: _hostname ?? getDefaultHost(_public),
showURL: true,
Expand Down Expand Up @@ -133,20 +140,24 @@ export async function listen(
let _addr: AddressInfo;
if (httpsOptions) {
https = await resolveCertificate(httpsOptions);
server = createSecureServer(
{
...https,
allowHTTP1: true,
},
handle as RequestListenerHttp2,
);
server = listhenOptions.http2
? createHttps2Server(
{
...https,
allowHTTP1: true,
},
handle as RequestListenerHttp2,
)
: createHttpsServer(https, handle as RequestListenerHttp1x);
addShutdown(server);
// @ts-ignore
await promisify(server.listen.bind(server))(port, listhenOptions.hostname);
_addr = server.address() as AddressInfo;
listhenOptions.port = _addr.port;
} else {
server = createServer(handle as RequestListenerHttp1x);
server = listhenOptions.http2
? createHttp2Server(handle as RequestListenerHttp2)
: createHttpServer(handle as RequestListenerHttp1x);
addShutdown(server);
// @ts-ignore
await promisify(server.listen.bind(server))(port, listhenOptions.hostname);
Expand Down
5 changes: 3 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Server as HttpServer } from "node:http";
import type { Server as HttpsServer } from "node:https";
import type { Http2SecureServer } from "node:http2";
import type { Http2Server, Http2SecureServer } from "node:http2";
import type { AddressInfo } from "node:net";
import type { GetPortInput } from "get-port-please";

export type Server = HttpServer | HttpsServer | Http2SecureServer;
export type Server = HttpServer | HttpsServer | Http2Server | Http2SecureServer;

export interface Certificate {
key: string;
Expand All @@ -29,6 +29,7 @@ export interface ListenOptions {
baseURL: string;
open: boolean;
https: boolean | HTTPSOptions;
http2: boolean;
clipboard: boolean;
isTest: boolean;
isProd: boolean;
Expand Down
15 changes: 14 additions & 1 deletion test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,21 @@ describe("listhen", () => {
});

// see https://http2.github.io/faq/#does-http2-require-encryption
test("listen (http2)", async () => {
test("listen (http2): http1 client", async () => {
listener = await listen(handle);
expect(listener.url.startsWith("http://")).toBeTruthy();

const response = (await sendRequest(listener.url, false)) as string;
expect(JSON.parse(response)).toEqual({
path: "/",
httpVersion: "1.1",
});
});
test("listhen (http2): http2 client", async () => {
listener = await listen(handle);
expect(listener.url.startsWith("http://")).toBeTruthy();

// Protocol HTTP 0.9 is used in firefox
await expect(sendHttp2Request(listener.url)).rejects.toThrowError(
"Protocol error",
);
Expand All @@ -118,6 +130,7 @@ describe("listhen", () => {
test("listen (http2)", async () => {
listener = await listen(handle, {
https: true,
http2: true,
});
expect(listener.url.startsWith("https:")).toBeTruthy();

Expand Down

0 comments on commit 129a3f8

Please sign in to comment.