Skip to content

Commit

Permalink
feat: Support serving web and api over https locally (#140)
Browse files Browse the repository at this point in the history
* feat(core): allows to specify protocol

* feat: add useHttps config entry

* feat(proxy): supports serving traffic over https

ref: #4

* refactor: makes useSsl flags type boolean

accepted these suggestions
- #140 (comment)
- #140 (comment)
- #140 (comment)

* style: modifies test case and error mesasges

- #140 (comment)
- #140 (comment)

* refactor: uses `ssl` insted of `useHttps` for making it suitable as a CLI option argument

* fix: defines ssl related environment variables to types definitions

* feat: adds ssl options to CLI flags

* refactor: changes the timing when validating ssl related args

* feat: changes in behavior when SSL cert or key are not specified

* style: modifies cli options description messages

- #140 (comment)
- #140 (comment)
  • Loading branch information
yamachu authored Mar 26, 2021
1 parent f26ac47 commit 56896dd
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 9 deletions.
9 changes: 9 additions & 0 deletions src/cli/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ export async function start(startContext: string, options: SWACLIConfig) {
}
}

if (options.ssl) {
if (options.sslCert === undefined || options.sslKey === undefined) {
logger.error(`SSL Key or SSL Cert are required when using HTTPS`, true);
}
}

// set env vars for current command
const envVarsObj = {
SWA_CLI_DEBUG: options.verbose,
Expand All @@ -94,6 +100,9 @@ export async function start(startContext: string, options: SWACLIConfig) {
SWA_CLI_HOST: options.host,
SWA_CLI_PORT: `${options.port}`,
SWA_WORKFLOW_FILES: userConfig?.files?.join(","),
SWA_CLI_APP_SSL: `${options.ssl}`,
SWA_CLI_APP_SSL_CERT: options.sslCert,
SWA_CLI_APP_SSL_KEY: options.sslKey,
};

if (options.verbose?.includes("silly")) {
Expand Down
3 changes: 3 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import { start } from "./commands/start";
.option("--host <host>", "set the cli host address", DEFAULT_CONFIG.host)
.option<number>("--port <port>", "set the cli port", parsePort, DEFAULT_CONFIG.port)
.option("--build", "build the app and API before starting the emulator", false)
.option("--ssl", "serving the app and API over HTTPS", DEFAULT_CONFIG.ssl)
.option("--ssl-cert <sslCertLocation>", "SSL certificate to use for serving HTTPS", DEFAULT_CONFIG.sslCert)
.option("--ssl-key <sslKeyLocation>", "SSL key to use for serving HTTPS", DEFAULT_CONFIG.sslKey)
.action(async (context: string = `.${path.sep}`, options: any) => {
options = {
...options,
Expand Down
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ export const DEFAULT_CONFIG: SWACLIConfig = {
host: "0.0.0.0",
apiPort: 7071,
apiPrefix: "api",
ssl: false,
appLocation: `.${path.sep}`,
appArtifactLocation: `.${path.sep}`,
sslCert: undefined,
sslKey: undefined,
appBuildCommand: "npm run build --if-present",
apiBuildCommand: "npm run build --if-present",
swaConfigFilename: "staticwebapp.config.json",
Expand Down
5 changes: 5 additions & 0 deletions src/core/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -770,5 +770,10 @@ jobs:
expect(address("127.0.0.1", "4200")).toBe("http://127.0.0.1:4200");
expect(address("[::1]", "4200")).toBe("http://[::1]:4200");
});

it("should accept protocol both HTTP and HTTPS protocols", () => {
expect(address("127.0.0.1", "4200", "http")).toBe("http://127.0.0.1:4200");
expect(address("127.0.0.1", "4200", "https")).toBe("https://127.0.0.1:4200");
});
});
});
3 changes: 1 addition & 2 deletions src/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,7 @@ export async function findSWAConfigFile(folder: string) {
return null;
}

export const address = (host: string, port: number | string | undefined) => {
const protocol = `http`;
export const address = (host: string, port: number | string | undefined, protocol = `http`) => {
if (!host) {
throw new Error(`Host value is not set`);
}
Expand Down
31 changes: 24 additions & 7 deletions src/proxy/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from "fs";
import chalk from "chalk";
import internalIp from "internal-ip";
import http from "http";
import https from "https";
import httpProxy from "http-proxy";
import net from "net";
import path from "path";
Expand All @@ -18,8 +19,11 @@ const SWA_CLI_PORT = parseInt((process.env.SWA_CLI_PORT || DEFAULT_CONFIG.port)
const SWA_CLI_API_URI = address(SWA_CLI_HOST, process.env.SWA_CLI_API_PORT);
const SWA_CLI_APP_LOCATION = (process.env.SWA_CLI_APP_LOCATION || DEFAULT_CONFIG.appLocation) as string;
const SWA_CLI_APP_ARTIFACT_LOCATION = (process.env.SWA_CLI_APP_ARTIFACT_LOCATION || DEFAULT_CONFIG.appArtifactLocation) as string;
const SWA_CLI_APP_SSL = process.env.SWA_CLI_APP_SSL === "true" || DEFAULT_CONFIG.ssl === true;
const SWA_CLI_APP_SSL_KEY = process.env.SWA_CLI_APP_SSL_KEY as string;
const SWA_CLI_APP_SSL_CERT = process.env.SWA_CLI_APP_SSL_CERT as string;

const PROTOCOL = `http://`;
const PROTOCOL = SWA_CLI_APP_SSL ? `https` : `http`;

const proxyApi = httpProxy.createProxyServer({ autoRewrite: true });
const proxyApp = httpProxy.createProxyServer({ autoRewrite: true });
Expand All @@ -34,14 +38,21 @@ if (SWA_WORKFLOW_CONFIG_FILE) {
logger.info(`\nFound workflow file:\n ${chalk.green(SWA_WORKFLOW_CONFIG_FILE)}`);
}

const httpsServerOptions: Pick<https.ServerOptions, "cert" | "key"> | null = SWA_CLI_APP_SSL
? {
cert: fs.readFileSync(SWA_CLI_APP_SSL_CERT, "utf8"),
key: fs.readFileSync(SWA_CLI_APP_SSL_KEY, "utf8"),
}
: null;

const SWA_PUBLIC_DIR = path.resolve(__dirname, "..", "public");

const logRequest = (req: http.IncomingMessage, target: string | null = null, statusCode: number | null = null) => {
if (process.env.SWA_CLI_BEBUG?.includes("req") === false) {
return;
}

const host = target || `${PROTOCOL}${req.headers.host}`;
const host = target || `${PROTOCOL}://${req.headers.host}`;
const url = req.url?.startsWith("/") ? req.url : `/${req.url}`;

if (statusCode) {
Expand Down Expand Up @@ -150,7 +161,7 @@ const requestHandler = (userConfig: SWAConfigFile | null) =>
res.statusCode = 404;
serve(SWA_PUBLIC_DIR, req, res);

logRequest(req, PROTOCOL + req.headers.host + req.url, 404);
logRequest(req, PROTOCOL + "://" + req.headers.host + req.url, 404);
}

// proxy AUTH request to AUTH emulator
Expand Down Expand Up @@ -258,8 +269,8 @@ const requestHandler = (userConfig: SWAConfigFile | null) =>
// prettier-ignore
logger.log(
`\nAvailable on:\n` +
` ${chalk.green(address(`${localIpAdress}`, SWA_CLI_PORT))}\n` +
` ${chalk.green(address(SWA_CLI_HOST, SWA_CLI_PORT))}\n\n` +
` ${chalk.green(address(`${localIpAdress}`, SWA_CLI_PORT, PROTOCOL))}\n` +
` ${chalk.green(address(SWA_CLI_HOST, SWA_CLI_PORT, PROTOCOL))}\n\n` +
`Azure Static Web Apps emulator started. Press CTRL+C to exit.\n\n`
);

Expand All @@ -272,11 +283,17 @@ const requestHandler = (userConfig: SWAConfigFile | null) =>
};

// load user custom rules if running in local mode (non-dev server)
let userConfig = null;
let userConfig: SWAConfigFile | null = null;
if (!isStaticDevServer) {
userConfig = await handleUserConfig(SWA_CLI_APP_LOCATION);
}
const server = http.createServer(requestHandler(userConfig));
const createServer = () => {
if (SWA_CLI_APP_SSL && httpsServerOptions !== null) {
return https.createServer(httpsServerOptions, requestHandler(userConfig));
}
return http.createServer(requestHandler(userConfig));
};
const server = createServer();
server.listen(SWA_CLI_PORT, SWA_CLI_HOST, onServerStart);
server.listen(SWA_CLI_PORT, localIpAdress);
})();
6 changes: 6 additions & 0 deletions src/swa.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ declare global {
SWA_CLI_HOST: string;
SWA_CLI_PORT: string;
SWA_WORKFLOW_FILE: string;
SWA_CLI_APP_SSL: boolean;
SWA_CLI_APP_SSL_KEY: string;
SWA_CLI_APP_SSL_CERT: string;
}
}
}
Expand Down Expand Up @@ -56,7 +59,10 @@ declare type SWACLIConfig = GithubActionWorkflow & {
port?: number;
host?: string;
apiPort?: number;
ssl?: boolean;
apiPrefix?: "api";
sslCert?: string;
sslKey?: string;
swaConfigFilename?: "staticwebapp.config.json";
swaConfigFilenameLegacy?: "routes.json";
app?: string;
Expand Down

0 comments on commit 56896dd

Please sign in to comment.