UNPKG

8.09 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
5 * This code may only be used under the BSD style license found at
6 * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
7 * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
8 * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
9 * Google as part of the polymer project is also subject to an additional IP
10 * rights grant found at http://polymer.github.io/PATENTS.txt
11 */
12var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13 if (k2 === undefined) k2 = k;
14 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
15}) : (function(o, m, k, k2) {
16 if (k2 === undefined) k2 = k;
17 o[k2] = m[k];
18}));
19var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20 Object.defineProperty(o, "default", { enumerable: true, value: v });
21}) : function(o, v) {
22 o["default"] = v;
23});
24var __importStar = (this && this.__importStar) || function (mod) {
25 if (mod && mod.__esModule) return mod;
26 var result = {};
27 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
28 __setModuleDefault(result, mod);
29 return result;
30};
31Object.defineProperty(exports, "__esModule", { value: true });
32exports.Server = void 0;
33const http = __importStar(require("http"));
34const path = __importStar(require("path"));
35const Koa = require("koa");
36const mount = require("koa-mount");
37const send = require("koa-send");
38const getStream = require("get-stream");
39const serve = require("koa-static");
40const bodyParser = require("koa-bodyparser");
41const koa_node_resolve_1 = require("koa-node-resolve");
42const types_1 = require("./types");
43const clientLib = path.resolve(__dirname, '..', 'client', 'lib');
44class Server {
45 constructor(server, opts) {
46 this.session = { bytesSent: 0, userAgent: '' };
47 this.deferredResults = new types_1.Deferred();
48 this.urlCache = new Map();
49 this.server = server;
50 const app = new Koa();
51 app.use(bodyParser());
52 app.use(mount('/submitResults', this.submitResults.bind(this)));
53 app.use(this.instrumentRequests.bind(this));
54 if (opts.cache) {
55 app.use(this.cache.bind(this));
56 }
57 app.use(this.serveBenchLib.bind(this));
58 if (opts.resolveBareModules === true) {
59 const npmRoot = opts.npmInstalls.length > 0 ?
60 opts.npmInstalls[0].installDir :
61 opts.root;
62 app.use(koa_node_resolve_1.nodeResolve({
63 root: npmRoot,
64 // TODO Use default logging options after issues resolved:
65 // https://github.com/Polymer/koa-node-resolve/issues/16
66 // https://github.com/Polymer/koa-node-resolve/issues/17
67 logger: false,
68 }));
69 }
70 for (const { diskPath, urlPath } of opts.mountPoints) {
71 app.use(mount(urlPath, serve(diskPath, { index: 'index.html' })));
72 }
73 this.server.on('request', app.callback());
74 const address = this.server.address();
75 let host = address.address;
76 if (address.family === 'IPv6') {
77 host = `[${host}]`;
78 }
79 this.port = address.port;
80 this.url = `http://${host}:${this.port}`;
81 }
82 static start(opts) {
83 const server = http.createServer();
84 const ports = [...opts.ports];
85 return new Promise((resolve, reject) => {
86 const tryNextPort = () => {
87 if (ports.length === 0) {
88 reject(`No ports available, tried: ${opts.ports.join(', ')}`);
89 }
90 else {
91 server.listen({ host: opts.host, port: ports.shift() });
92 }
93 };
94 server.on('listening', () => resolve(new Server(server, opts)));
95 server.on('error', (e) => {
96 if (e.code === 'EADDRINUSE' || e.code === 'EACCES') {
97 tryNextPort();
98 }
99 else {
100 reject(e);
101 }
102 });
103 tryNextPort();
104 });
105 }
106 /**
107 * Mark the end of one session, return the data instrumented from it, and
108 * begin a new session.
109 */
110 endSession() {
111 const session = this.session;
112 this.session = { bytesSent: 0, userAgent: '' };
113 this.deferredResults = new types_1.Deferred();
114 return session;
115 }
116 async nextResults() {
117 return this.deferredResults.promise;
118 }
119 async close() {
120 return new Promise((resolve, reject) => {
121 this.server.close((error) => {
122 if (error) {
123 reject(error);
124 }
125 else {
126 resolve();
127 }
128 });
129 });
130 }
131 async instrumentRequests(ctx, next) {
132 const session = this.session;
133 if (session === undefined) {
134 return next();
135 }
136 session.userAgent = ctx.headers['user-agent'];
137 // Note this assumes serial runs, as we guarantee in automatic mode.
138 // If we ever wanted to support parallel requests, we would require
139 // some kind of session tracking.
140 await next();
141 if (typeof ctx.response.length === 'number') {
142 session.bytesSent += ctx.response.length;
143 }
144 else if (ctx.status === 200) {
145 console.log(`No response length for 200 response for ${ctx.url}, ` +
146 `byte count may be inaccurate.`);
147 }
148 }
149 /**
150 * Cache all downstream middleware responses by URL in memory. This is
151 * especially helpful when bare module resolution is enabled, because that
152 * requires expensive parsing of all HTML and JavaScript that we really don't
153 * want to do for every benchmark sample.
154 */
155 async cache(ctx, next) {
156 const entry = this.urlCache.get(ctx.url);
157 if (entry !== undefined) {
158 ctx.response.set(entry.headers);
159 ctx.response.body = entry.body;
160 // Note we must set status after we set body, because when we set body to
161 // undefined (which happens on e.g. 404s), Koa overrides the status to
162 // 204.
163 ctx.response.status = entry.status;
164 return;
165 }
166 await next();
167 const body = ctx.response.body;
168 let bodyString;
169 if (typeof body === 'string') {
170 bodyString = body;
171 }
172 else if (Buffer.isBuffer(body)) {
173 bodyString = body.toString();
174 }
175 else if (isStream(body)) {
176 bodyString = await getStream(body);
177 // We consumed the stream.
178 ctx.response.body = bodyString;
179 }
180 else if (body === null || body === undefined) {
181 // The static middleware sets no body for errors. Koa automatically
182 // creates a body for errors later. Just cache as-is so that the same
183 // thing happens on cache hits.
184 bodyString = body;
185 }
186 else {
187 throw new Error(`Unknown response type ${typeof body} for ${ctx.url}`);
188 }
189 this.urlCache.set(ctx.url, {
190 body: bodyString,
191 status: ctx.response.status,
192 headers: ctx.response.headers,
193 });
194 }
195 async serveBenchLib(ctx, next) {
196 if (ctx.path === '/bench.js') {
197 await send(ctx, 'bench.js', { root: clientLib });
198 }
199 else {
200 await next();
201 }
202 }
203 async submitResults(ctx) {
204 this.deferredResults.resolve(ctx.request.body);
205 ctx.body = 'ok';
206 }
207}
208exports.Server = Server;
209function isStream(value) {
210 return value !== null && typeof value === 'object' &&
211 typeof value.pipe === 'function';
212}
213//# sourceMappingURL=server.js.map
\No newline at end of file