Skip to content

feat(proxy): Add base for event proxy server #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
_This repository is a work in progress_

The main purpose of this repository is to visualize the differences between the
[Sentry JS SDK](https://github.com/getsentry/sentry-javascript) version 7 and version 8. Those example applications can
also be used as a reference for using the JS SDKs.
[Sentry JS SDK](https://github.com/getsentry/sentry-javascript) version 7 and version 8. Those
example applications can also be used as a reference for using the JS SDKs.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
},
"packageManager": "[email protected]",
"scripts": {
"start:proxy-server": "yarn workspace event-proxy-server run start",
"start:express": "yarn workspace express-test-application run start",
"fix:prettier": "prettier . --write",
"fix:lint": "yarn run eslint --fix",
"lint": "yarn run eslint"
},
"workspaces": [
"utils/event-proxy-server",
"apps/express"
],
"devDependencies": {
Expand All @@ -32,7 +34,7 @@
},
"prettier": {
"arrowParens": "avoid",
"printWidth": 120,
"printWidth": 100,
"proseWrap": "always",
"singleQuote": true,
"trailingComma": "all"
Expand Down
25 changes: 25 additions & 0 deletions utils/event-proxy-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"private": true,
"version": "8.0.0-alpha.7",
"name": "event-proxy-server",
"author": "Sentry",
"license": "MIT",
"sideEffects": false,
"engines": {
"node": ">=14.18"
},
"scripts": {
"start": "ts-node start-event-proxy.ts",
"fix": "eslint . --format stylish --fix",
"lint": "eslint . --format stylish",
"build:dev": "yarn build",
"clean": "rimraf -g ./node_modules ./build"
},
"dependencies": {
"@sentry/types": "7.109.0",
"@sentry/utils": "7.109.0"
},
"volta": {
"extends": "../../package.json"
}
}
150 changes: 150 additions & 0 deletions utils/event-proxy-server/src/event-proxy-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import * as fs from 'fs';
import * as http from 'http';
import * as https from 'https';
import type { AddressInfo } from 'net';
import * as os from 'os';
import * as path from 'path';
import * as util from 'util';
import * as zlib from 'zlib';
import type { Envelope } from '@sentry/types';
import { parseEnvelope } from '@sentry/utils';

const writeFile = util.promisify(fs.writeFile);

interface EventProxyServerOptions {
/** Port to start the event proxy server at. */
port: number;
/** The name for the proxy server used for referencing it with listener functions */
proxyServerName: string;
}

interface SentryRequestCallbackData {
envelope: Envelope;
rawProxyRequestBody: string;
rawSentryResponseBody: string;
sentryResponseStatusCode?: number;
}

/**
* Starts an event proxy server that will proxy events to sentry when the `tunnel` option is used. Point the `tunnel`
* option to this server (like this `tunnel: http://localhost:${port option}/`).
*
*/
export async function startEventProxyServer(options: EventProxyServerOptions): Promise<void> {
const eventCallbackListeners: Set<(data: string) => void> = new Set();

const proxyServer = http.createServer((proxyRequest, proxyResponse) => {
const proxyRequestChunks: Uint8Array[] = [];

proxyRequest.addListener('data', (chunk: Buffer) => {
proxyRequestChunks.push(chunk);
});

proxyRequest.addListener('error', err => {
throw err;
});

proxyRequest.addListener('end', () => {
const proxyRequestBody =
proxyRequest.headers['content-encoding'] === 'gzip'
? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString()
: Buffer.concat(proxyRequestChunks).toString();

let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]);

if (!envelopeHeader.dsn) {
throw new Error(
'[event-proxy-server] No dsn on envelope header. Please set tunnel option.',
);
}

const { origin, pathname, host } = new URL(envelopeHeader.dsn);

const projectId = pathname.substring(1);
const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`;

proxyRequest.headers.host = host;

const sentryResponseChunks: Uint8Array[] = [];

const sentryRequest = https.request(
sentryIngestUrl,
{ headers: proxyRequest.headers, method: proxyRequest.method },
sentryResponse => {
sentryResponse.addListener('data', (chunk: Buffer) => {
proxyResponse.write(chunk, 'binary');
sentryResponseChunks.push(chunk);
});

sentryResponse.addListener('end', () => {
eventCallbackListeners.forEach(listener => {
const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString();

const data: SentryRequestCallbackData = {
envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()),
rawProxyRequestBody: proxyRequestBody,
rawSentryResponseBody,
sentryResponseStatusCode: sentryResponse.statusCode,
};

listener(Buffer.from(JSON.stringify(data)).toString('base64'));
});
proxyResponse.end();
});

sentryResponse.addListener('error', err => {
throw err;
});

proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers);
},
);

sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary');
sentryRequest.end();
});
});

const proxyServerStartupPromise = new Promise<void>(resolve => {
proxyServer.listen(options.port, () => {
resolve();
});
});

const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => {
eventCallbackResponse.statusCode = 200;
eventCallbackResponse.setHeader('connection', 'keep-alive');

const callbackListener = (data: string): void => {
eventCallbackResponse.write(data.concat('\n'), 'utf8');
};

eventCallbackListeners.add(callbackListener);

eventCallbackRequest.on('close', () => {
eventCallbackListeners.delete(callbackListener);
});

eventCallbackRequest.on('error', () => {
eventCallbackListeners.delete(callbackListener);
});
});

const eventCallbackServerStartupPromise = new Promise<void>(resolve => {
eventCallbackServer.listen(0, () => {
const port = String((eventCallbackServer.address() as AddressInfo).port);
void registerCallbackServerPort(options.proxyServerName, port).then(resolve);
});
});

await eventCallbackServerStartupPromise;
await proxyServerStartupPromise;
return;
}

const TEMP_FILE_PREFIX = 'event-proxy-server-';

async function registerCallbackServerPort(serverName: string, port: string): Promise<void> {
const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
await writeFile(tmpFilePath, port, { encoding: 'utf8' });
}
1 change: 1 addition & 0 deletions utils/event-proxy-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { startEventProxyServer } from './event-proxy-server';
6 changes: 6 additions & 0 deletions utils/event-proxy-server/start-event-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { startEventProxyServer } from './src/event-proxy-server';

startEventProxyServer({
port: 3031,
proxyServerName: 'event-proxy-server',
});
5 changes: 5 additions & 0 deletions utils/event-proxy-server/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {},
"include": ["src/**/*.ts"]
}
9 changes: 9 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,15 @@ __metadata:
languageName: node
linkType: hard

"event-proxy-server@workspace:utils/event-proxy-server":
version: 0.0.0-use.local
resolution: "event-proxy-server@workspace:utils/event-proxy-server"
dependencies:
"@sentry/types": "npm:7.109.0"
"@sentry/utils": "npm:7.109.0"
languageName: unknown
linkType: soft

"exponential-backoff@npm:^3.1.1":
version: 3.1.1
resolution: "exponential-backoff@npm:3.1.1"
Expand Down