Skip to content

Commit

Permalink
Typescript functionsProxy (#1037)
Browse files Browse the repository at this point in the history
  • Loading branch information
bkendall committed Dec 4, 2018
1 parent 6322ffb commit 40afb70
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 114 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,25 +105,29 @@
"@types/chai": "^4.1.6",
"@types/chai-as-promised": "^7.1.0",
"@types/cli-color": "^0.3.29",
"@types/express": "^4.16.0",
"@types/glob": "^7.1.1",
"@types/lodash": "^4.14.118",
"@types/mocha": "^5.2.5",
"@types/nock": "^9.3.0",
"@types/node": "^10.12.0",
"@types/request": "^2.48.1",
"@types/sinon": "^5.0.5",
"@types/supertest": "^2.0.6",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"coveralls": "^3.0.1",
"eslint": "^5.7.0",
"eslint-plugin-prettier": "^3.0.0",
"express": "^4.16.4",
"mocha": "^5.0.5",
"nock": "^9.3.3",
"nyc": "^11.9.0",
"nyc": "^13.1.0",
"prettier": "1.14.3",
"sinon": "^6.3.4",
"sinon-chai": "^3.2.0",
"source-map-support": "^0.5.9",
"supertest": "^3.3.0",
"ts-node": "^7.0.1",
"tslint": "^5.11.0",
"tslint-no-unused-expression-chai": "^0.1.4",
Expand Down
112 changes: 0 additions & 112 deletions src/hosting/functionsProxy.js

This file was deleted.

125 changes: 125 additions & 0 deletions src/hosting/functionsProxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { capitalize, includes } from "lodash";
import { Request, RequestHandler, Response } from "express";
import * as request from "request";

import * as getProjectId from "../getProjectId";
import * as logger from "../logger";

export interface FunctionsProxyOptions {
port: number;
project?: string;
targets: string[];
}

export interface FunctionProxyRewrite {
function: string;
}

const REQUIRED_VARY_VALUES = ["Accept-Encoding", "Authorization", "Cookie"];

function makeVary(vary?: string): string {
if (!vary) {
return "Accept-Encoding, Authorization, Cookie";
}

const varies = vary.split(/, ?/).map((v) => {
return v
.split("-")
.map((part) => capitalize(part))
.join("-");
});

REQUIRED_VARY_VALUES.forEach((requiredVary) => {
if (!includes(varies, requiredVary)) {
varies.push(requiredVary);
}
});

return varies.join(", ");
}

/**
* Returns a function which, given a FunctionProxyRewrite, returns a Promise
* that resolves with a middleware-like function that proxies the request to a
* hosted or live function.
*/
export default function(
options: FunctionsProxyOptions
): (r: FunctionProxyRewrite) => Promise<RequestHandler> {
return async (rewrite: FunctionProxyRewrite) => {
let url = `https://us-central1-${getProjectId(options, false)}.cloudfunctions.net/${
rewrite.function
}`;
let destLabel = "live";
if (includes(options.targets, "functions")) {
destLabel = "local";
url = `http://localhost:${options.port + 1}/${getProjectId(options, false)}/us-central1/${
rewrite.function
}`;
}
return await ((req: Request, res: Response, next: () => void): any => {
logger.info(`[hosting] Rewriting ${req.url} to ${destLabel} function ${rewrite.function}`);
// Extract the __session cookie from headers to forward it to the functions
// cookie is not a string[].
const cookie = (req.headers.cookie as string) || "";
const sessionCookie = cookie.split(/; ?/).find((c: string) => {
return c.trim().indexOf("__session=") === 0;
});

const proxied = request({
method: req.method,
qs: req.query,
url: url + req.url,
headers: {
"X-Forwarded-Host": req.headers.host,
"X-Original-Url": req.url,
Pragma: "no-cache",
"Cache-Control": "no-cache, no-store",
// forward the parsed __session cookie if any
Cookie: sessionCookie,
},
followRedirect: false,
timeout: 60000,
});

req.pipe(proxied);

// err here is `any` in order to check `.code`
proxied.on("error", (err: any) => {
if (err.code === "ETIMEDOUT" || err.code === "ESOCKETTIMEDOUT") {
res.statusCode = 504;
return res.end("Timed out waiting for function to respond.");
}

res.statusCode = 500;
res.end(
`An internal error occurred while connecting to Cloud Function "${rewrite.function}"`
);
});

proxied.on("response", (response) => {
if (response.statusCode === 404) {
// x-cascade is not a string[].
const cascade = response.headers["x-cascade"] as string;
if (cascade && cascade.toUpperCase() === "PASS") {
return next();
}
}

// default to private cache
if (!response.headers["cache-control"]) {
response.headers["cache-control"] = "private";
}

// don't allow cookies to be set on non-private cached responses
if (response.headers["cache-control"].indexOf("private") < 0) {
delete response.headers["set-cookie"];
}

response.headers.vary = makeVary(response.headers.vary);

proxied.pipe(res);
});
});
};
}
2 changes: 1 addition & 1 deletion src/serve/hosting.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ var utils = require("../utils");
var detectProjectRoot = require("../detectProjectRoot");
var implicitInit = require("../hosting/implicitInit");
var initMiddleware = require("../hosting/initMiddleware");
var functionsProxy = require("../hosting/functionsProxy");
var functionsProxy = require("../hosting/functionsProxy").default;
var normalizedHostingConfigs = require("../hosting/normalizedHostingConfigs");

var MAX_PORT_ATTEMPTS = 10;
Expand Down
Loading

0 comments on commit 40afb70

Please sign in to comment.