Skip to content

Commit

Permalink
refactor: add web framework plugin types and script to fetch plugin t…
Browse files Browse the repository at this point in the history
…ypes
  • Loading branch information
kjin committed Dec 5, 2017
1 parent f1ca3a3 commit d5f73f7
Show file tree
Hide file tree
Showing 14 changed files with 412 additions and 216 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
node_modules
node_modules/
npm-debug.log
coverage
.DS_Store
.vscode
build/
*.tgz
src/plugins/types/*
!src/plugins/types/index.d.ts
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
"system-test": "ts-node -P ./scripts ./scripts npm-compile decrypt-service-account-key run-system-tests",
"changelog": "./bin/run-changelog.sh",
"check-install": "ts-node -P ./scripts ./scripts npm-compile check-install",
"get-plugin-types": "ts-node -P ./scripts ./scripts get-plugin-types",
"coverage": "ts-node -P ./scripts ./scripts npm-check npm-compile init-test-fixtures run-unit-tests-with-coverage report-coverage",
"bump": "./bin/run-bump.sh",
"check": "gts check",
"clean": "gts clean",
"compile-all": "tsc -p ./tsconfig.full.json",
"compile-strict": "tsc -p .",
"compile": "ts-node -P ./scripts ./scripts npm-compile-all npm-compile-strict",
"compile": "ts-node -P ./scripts ./scripts get-plugin-types npm-compile-all npm-compile-strict",
"fix": "gts fix",
"prepare": "ts-node -P ./scripts ./scripts npm-clean npm-compile"
},
Expand Down Expand Up @@ -50,6 +51,7 @@
"@types/extend": "^3.0.0",
"@types/glob": "^5.0.32",
"@types/is": "0.0.17",
"@types/methods": "^1.1.0",
"@types/mocha": "^2.2.44",
"@types/ncp": "^2.0.1",
"@types/node": "^8.0.53",
Expand Down
40 changes: 40 additions & 0 deletions scripts/get-plugin-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { flatten, globP, mkdirP, ncpP, readFileP, spawnP, tmpDirP, writeFileP } from './utils';

const TYPES_DIRECTORY = 'src/plugins/types';

async function mkdirSafeP(dir: string) {
try {
await mkdirP(dir);
return true;
} catch (e) {
if (e.code !== 'EEXIST') {
throw new Error(`Error creating directory ${dir}`);
}
return false;
}
}

export default async function() {
await mkdirSafeP(TYPES_DIRECTORY);

const indexTs = (await readFileP(`${TYPES_DIRECTORY}/index.d.ts`, 'utf8') as string)
.split('\n');
for (const line of indexTs) {
const matches = line.match(/^import \* as .* from '\.\/(.*)';\s*\/\/\s*(.*)@(.*)$/);
if (!matches) {
continue;
}
const [_0, packageName, name, version] = matches;
const installDir = `${TYPES_DIRECTORY}/${packageName}`;
if (await mkdirSafeP(installDir)) {
await spawnP('npm', ['init', '-y'], {
cwd: installDir
});
await spawnP('npm', ['install', `@types/${name}@${version}`], {
cwd: installDir
});
await writeFileP(`${installDir}/index.ts`,
`import * as _ from '${name}'; export = _;\n`, 'utf8');
}
}
}
4 changes: 4 additions & 0 deletions scripts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const [bin, script, ...steps] = process.argv;

import checkInstall from './check-install';
import decryptServiceAccountKey from './decrypt-service-account-key';
import getPluginTypes from './get-plugin-types';
import initTestFixtures from './init-test-fixtures';
import reportCoverage from './report-coverage';
import runTests from './run-tests';
Expand Down Expand Up @@ -33,6 +34,9 @@ async function run(steps: string[]) {
console.log('> Not decrypting service account key in PRs');
}
break;
case 'get-plugin-types':
await getPluginTypes();
break;
case 'init-test-fixtures':
await initTestFixtures();
break;
Expand Down
8 changes: 7 additions & 1 deletion scripts/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Stats, stat, readFile } from 'fs';
import { mkdir, Stats, stat, readFile, writeFile } from 'fs';
import * as glob from 'glob';
import { ncp } from 'ncp';
import * as path from 'path';
Expand All @@ -10,15 +10,21 @@ import * as tmp from 'tmp';
export const BUILD_DIRECTORY = 'build';

export const globP: (pattern: string) => Promise<string[]> = pify(glob);
export const mkdirP: (path: string) => Promise<void> = pify(mkdir);
export const ncpP: (src: string, dest: string) => Promise<void> = pify(ncp);
export const readFileP: (path: string, encoding?: string) => Promise<Buffer|string> = pify(readFile);
export const statP: (path: string) => Promise<Stats> = pify(stat);
export const tmpDirP: () => Promise<string> = pify(tmp.dir);
export const writeFileP: (path: string, data: any, encoding?: string) => Promise<void> = pify(writeFile);

export function nodule(nodule: string) {
return path.relative(BUILD_DIRECTORY, `node_modules/${nodule}`);
}

export function flatten<T>(arr: Array<Array<T>>): Array<T> {
return arr.reduce((acc, e) => acc.concat(e), []);
}

export function existsP(path: string): Promise<boolean> {
return statP(path).then(
() => Promise.resolve(true),
Expand Down
92 changes: 54 additions & 38 deletions src/plugins/plugin-connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,50 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';

var urlParse = require('url').parse;
// tslint:disable-next-line:no-reference
/// <reference path="../types.d.ts" />

var SUPPORTED_VERSIONS = '3.x';
import {IncomingMessage, ServerResponse} from 'http';
import * as shimmer from 'shimmer';
import {parse as urlParse} from 'url';

function createMiddleware(api) {
return function middleware(req, res, next) {
var options = {
name: urlParse(req.originalUrl).pathname,
import {PluginTypes} from '..';

import {connect_3} from './types';

type Connect3 = typeof connect_3;
// Connect docs note that routed requests have an originalUrl property.
// https://github.com/senchalabs/connect/tree/3.6.5#appuseroute-fn
type Request = IncomingMessage&{originalUrl?: string};

const SUPPORTED_VERSIONS = '3.x';

function getFirstHeader(req: IncomingMessage, key: string) {
let headerValue = req.headers[key];
if (headerValue && typeof headerValue !== 'string') {
headerValue = headerValue[0];
}
return headerValue;
}

function createMiddleware(api: PluginTypes.TraceAgent):
connect_3.NextHandleFunction {
return function middleware(req: Request, res, next) {
const options = {
name: req.originalUrl ? (urlParse(req.originalUrl).pathname || '') : '',
url: req.originalUrl,
traceContext: req.headers[api.constants.TRACE_CONTEXT_HEADER_NAME.toLowerCase()],
traceContext:
getFirstHeader(req, api.constants.TRACE_CONTEXT_HEADER_NAME),
skipFrames: 3
};
api.runInRootSpan(options, function(root) {
api.runInRootSpan(options, (root) => {
// Set response trace context.
var responseTraceContext =
api.getResponseTraceContext(options.traceContext, !!root);
const responseTraceContext =
api.getResponseTraceContext(options.traceContext || null, !!root);
if (responseTraceContext) {
res.setHeader(api.constants.TRACE_CONTEXT_HEADER_NAME, responseTraceContext);
res.setHeader(
api.constants.TRACE_CONTEXT_HEADER_NAME, responseTraceContext);
}

if (!root) {
Expand All @@ -42,8 +66,8 @@ function createMiddleware(api) {
api.wrapEmitter(req);
api.wrapEmitter(res);

var url = (req.headers['X-Forwarded-Proto'] || 'http') +
'://' + req.headers.host + req.originalUrl;
const url = (req.headers['X-Forwarded-Proto'] || 'http') + '://' +
req.headers.host + req.originalUrl;

// we use the path part of the url as the span name and add the full
// url as a label
Expand All @@ -52,18 +76,13 @@ function createMiddleware(api) {
root.addLabel(api.labels.HTTP_SOURCE_IP, req.connection.remoteAddress);

// wrap end
var originalEnd = res.end;
res.end = function() {
const originalEnd = res.end;
res.end = function(this: ServerResponse) {
res.end = originalEnd;
var returned = res.end.apply(this, arguments);

if (req.route && req.route.path) {
root.addLabel(
'connect/request.route.path', req.route.path);
}
const returned = res.end.apply(this, arguments);

root.addLabel(
api.labels.HTTP_RESPONSE_CODE_LABEL_KEY, res.statusCode);
root.addLabel('connect/request.route.path', req.originalUrl);
root.addLabel(api.labels.HTTP_RESPONSE_CODE_LABEL_KEY, res.statusCode);
root.endSpan();

return returned;
Expand All @@ -74,19 +93,16 @@ function createMiddleware(api) {
};
}

module.exports = [
{
file: '',
versions: SUPPORTED_VERSIONS,
intercept: function(connect, api) {
return function() {
var app = connect();
app.use(createMiddleware(api));
return app;
};
}
const plugin: PluginTypes.Plugin = [{
file: '',
versions: SUPPORTED_VERSIONS,
intercept: (connect, api) => {
return function(this: {}) {
const app = connect();
app.use(createMiddleware(api));
return app;
};
}
];

} as PluginTypes.Intercept<Connect3>];

export default {};
export = plugin;
78 changes: 45 additions & 33 deletions src/plugins/plugin-express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,36 @@
* limitations under the License.
*/

'use strict';
var shimmer = require('shimmer');
var methods = require('methods').concat('use', 'route', 'param', 'all');
// tslint:disable-next-line:no-reference
/// <reference path="../types.d.ts" />

var SUPPORTED_VERSIONS = '4.x';
import * as httpMethods from 'methods';
import * as shimmer from 'shimmer';

function patchModuleRoot(express, api) {
var labels = api.labels;
function applicationActionWrap(method) {
return function expressActionTrace() {
if (!this._google_trace_patched && !this._router) {
this._google_trace_patched = true;
this.use(middleware);
}
return method.apply(this, arguments);
};
}
import {PluginTypes} from '..';

import {express_4} from './types';

// application is an undocumented member of the express object.
type Express4Module = typeof express_4&{application: express_4.Application};

function middleware(req, res, next) {
var options = {
const methods = httpMethods.concat('use', 'route', 'param', 'all');

const SUPPORTED_VERSIONS = '4.x';

function patchModuleRoot(express: Express4Module, api: PluginTypes.TraceAgent) {
const labels = api.labels;
const middleware: express_4.RequestHandler = (req, res, next) => {
const options: PluginTypes.RootSpanOptions = {
name: req.path,
traceContext: req.get(api.constants.TRACE_CONTEXT_HEADER_NAME),
url: req.originalUrl,
skipFrames: 3
};
api.runInRootSpan(options, function(rootSpan) {
api.runInRootSpan(options, (rootSpan) => {
// Set response trace context.
var responseTraceContext =
api.getResponseTraceContext(options.traceContext, !!rootSpan);
const responseTraceContext =
api.getResponseTraceContext(options.traceContext || null, !!rootSpan);
if (responseTraceContext) {
res.set(api.constants.TRACE_CONTEXT_HEADER_NAME, responseTraceContext);
}
Expand All @@ -55,16 +56,16 @@ function patchModuleRoot(express, api) {
api.wrapEmitter(req);
api.wrapEmitter(res);

var url = req.protocol + '://' + req.hostname + req.originalUrl;
const url = req.protocol + '://' + req.hostname + req.originalUrl;
rootSpan.addLabel(labels.HTTP_METHOD_LABEL_KEY, req.method);
rootSpan.addLabel(labels.HTTP_URL_LABEL_KEY, url);
rootSpan.addLabel(labels.HTTP_SOURCE_IP, req.connection.remoteAddress);

// wrap end
var originalEnd = res.end;
res.end = function() {
const originalEnd = res.end;
res.end = function(this: express_4.Response) {
res.end = originalEnd;
var returned = res.end.apply(this, arguments);
const returned = res.end.apply(this, arguments);

if (req.route && req.route.path) {
rootSpan.addLabel('express/request.route.path', req.route.path);
Expand All @@ -76,23 +77,34 @@ function patchModuleRoot(express, api) {

next();
});
};

function applicationActionWrap<T extends Function>(method: T): () => T {
return function expressActionTrace(this: express_4.Application&
PluginTypes.TraceAgentExtension) {
if (!this._google_trace_patched && !this._router) {
this._google_trace_patched = true;
this.use(middleware);
}
return method.apply(this, arguments);
};
}

methods.forEach(function(method) {
methods.forEach((method) => {
shimmer.wrap(express.application, method, applicationActionWrap);
});
}

function unpatchModuleRoot(express) {
methods.forEach(function(method) {
function unpatchModuleRoot(express: Express4Module) {
methods.forEach((method) => {
shimmer.unwrap(express.application, method);
});
}

module.exports = [{
versions: SUPPORTED_VERSIONS,
patch: patchModuleRoot,
unpatch: unpatchModuleRoot
}];
const plugin: PluginTypes.Plugin = [{
versions: SUPPORTED_VERSIONS,
patch: patchModuleRoot,
unpatch: unpatchModuleRoot
} as PluginTypes.Patch<Express4Module>];

export default {};
export = plugin;
Loading

0 comments on commit d5f73f7

Please sign in to comment.