Skip to content
This repository was archived by the owner on Oct 18, 2021. It is now read-only.

Commit b931f13

Browse files
author
Nikki Nikkhoui
committed
copy over service-template-node contents
1 parent a159a5f commit b931f13

35 files changed

+12954
-0
lines changed

.dockerignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.git
2+
coverage
3+
node_modules

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
coverage/

.eslintrc.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"extends": [
3+
"wikimedia/server"
4+
],
5+
"plugins": ["json", "jsdoc"],
6+
"rules": {
7+
"array-bracket-spacing": "off",
8+
"camelcase": [
9+
"error",
10+
{
11+
"properties": "never"
12+
}
13+
],
14+
"computed-property-spacing": "off",
15+
"indent": ["off", 4],
16+
"jsdoc/no-undefined-types": "off",
17+
"no-multi-spaces": "off",
18+
"no-shadow": "off",
19+
"no-underscore-dangle": "off",
20+
"no-unused-vars": [
21+
"error",
22+
{
23+
"args": "none"
24+
}
25+
],
26+
"space-in-parens": ["error", "never"]
27+
}
28+
}

.pipeline/blubber.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
version: v4
2+
base: docker-registry.wikimedia.org/buster-nodejs10-slim
3+
lives:
4+
in: /srv/service
5+
runs:
6+
environment: { APP_BASE_PATH: /srv/service }
7+
8+
variants:
9+
build:
10+
base: docker-registry.wikimedia.org/buster-nodejs10-devel
11+
copies: [local]
12+
apt: { packages: [git, build-essential, python-pkgconfig] }
13+
node: { requirements: [package.json] }
14+
runs: { environment: { LINK: g++ } }
15+
development:
16+
includes: [build]
17+
apt: { packages: [ca-certificates] }
18+
entrypoint: [node, server.js]
19+
test:
20+
includes: [build]
21+
apt: { packages: [ca-certificates] }
22+
entrypoint: [npm, test]
23+
prep:
24+
includes: [build]
25+
node: { env: production }
26+
production:
27+
copies: [prep]
28+
node: { env: production }
29+
entrypoint: [node, server.js]

.travis.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
language: node_js
2+
3+
sudo: false
4+
5+
node_js:
6+
- "10"
7+
- "node"

.vscode/launch.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "pwa-node",
9+
"request": "launch",
10+
"name": "Launch Program",
11+
"skipFiles": [
12+
"<node_internals>/**"
13+
],
14+
"program": "${workspaceFolder}/app.js"
15+
}
16+
]
17+
}

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM debian:jessie
2+
RUN apt-get update && apt-get install -y nodejs git wget build-essential python && rm -rf /var/lib/apt/lists/*
3+
RUN mkdir -p /usr/local/nvm
4+
ENV NVM_DIR /usr/local/nvm
5+
RUN wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash && . $NVM_DIR/nvm.sh && nvm install 10.15.2
6+
RUN mkdir /opt/service
7+
ADD . /opt/service
8+
WORKDIR /opt/service
9+
RUN . $NVM_DIR/nvm.sh && nvm use 10.15.2 && npm install && npm dedupe
10+
ENV HOME=/root/ LINK=g++
11+
ENV IN_DOCKER=1
12+
CMD . $NVM_DIR/nvm.sh && nvm use 10.15.2 && npm start

app.js

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
'use strict';
2+
3+
const http = require('http');
4+
const BBPromise = require('bluebird');
5+
const express = require('express');
6+
const compression = require('compression');
7+
const bodyParser = require('body-parser');
8+
const fs = BBPromise.promisifyAll(require('fs'));
9+
const sUtil = require('./lib/util');
10+
const apiUtil = require('./lib/api-util');
11+
const packageInfo = require('./package.json');
12+
const yaml = require('js-yaml');
13+
const addShutdown = require('http-shutdown');
14+
const path = require('path');
15+
16+
/**
17+
* Creates an express app and initialises it
18+
*
19+
* @param {Object} options the options to initialise the app with
20+
* @return {bluebird} the promise resolving to the app object
21+
*/
22+
function initApp(options) {
23+
24+
// the main application object
25+
const app = express();
26+
27+
// get the options and make them available in the app
28+
app.logger = options.logger; // the logging device
29+
app.metrics = options.metrics; // the metrics
30+
app.conf = options.config; // this app's config options
31+
app.info = packageInfo; // this app's package info
32+
33+
// ensure some sane defaults
34+
app.conf.port = app.conf.port || 8888;
35+
app.conf.interface = app.conf.interface || '0.0.0.0';
36+
// eslint-disable-next-line max-len
37+
app.conf.compression_level = app.conf.compression_level === undefined ? 3 : app.conf.compression_level;
38+
app.conf.cors = app.conf.cors === undefined ? '*' : app.conf.cors;
39+
if (app.conf.csp === undefined) {
40+
app.conf.csp = "default-src 'self'; object-src 'none'; media-src 'none'; img-src 'none'; style-src 'none'; base-uri 'self'; frame-ancestors 'self'";
41+
}
42+
43+
// set outgoing proxy
44+
if (app.conf.proxy) {
45+
process.env.HTTP_PROXY = app.conf.proxy;
46+
// if there is a list of domains which should
47+
// not be proxied, set it
48+
if (app.conf.no_proxy_list) {
49+
if (Array.isArray(app.conf.no_proxy_list)) {
50+
process.env.NO_PROXY = app.conf.no_proxy_list.join(',');
51+
} else {
52+
process.env.NO_PROXY = app.conf.no_proxy_list;
53+
}
54+
}
55+
}
56+
57+
// set up header whitelisting for logging
58+
if (!app.conf.log_header_whitelist) {
59+
app.conf.log_header_whitelist = [
60+
'cache-control', 'content-type', 'content-length', 'if-match',
61+
'user-agent', 'x-request-id'
62+
];
63+
}
64+
app.conf.log_header_whitelist = new RegExp(`^(?:${app.conf.log_header_whitelist.map((item) => {
65+
return item.trim();
66+
}).join('|')})$`, 'i');
67+
68+
// set up the request templates for the APIs
69+
apiUtil.setupApiTemplates(app);
70+
71+
// set up the spec
72+
if (!app.conf.spec) {
73+
app.conf.spec = `${__dirname}/spec.yaml`;
74+
}
75+
if (app.conf.spec.constructor !== Object) {
76+
try {
77+
app.conf.spec = yaml.safeLoad(fs.readFileSync(app.conf.spec));
78+
} catch (e) {
79+
app.logger.log('warn/spec', `Could not load the spec: ${e}`);
80+
app.conf.spec = {};
81+
}
82+
}
83+
if (!app.conf.spec.openapi) {
84+
app.conf.spec.openapi = '3.0.0';
85+
}
86+
if (!app.conf.spec.info) {
87+
app.conf.spec.info = {
88+
version: app.info.version,
89+
title: app.info.name,
90+
description: app.info.description
91+
};
92+
}
93+
app.conf.spec.info.version = app.info.version;
94+
if (!app.conf.spec.paths) {
95+
app.conf.spec.paths = {};
96+
}
97+
98+
// set the CORS and CSP headers
99+
app.all('*', (req, res, next) => {
100+
if (app.conf.cors !== false) {
101+
res.header('access-control-allow-origin', app.conf.cors);
102+
res.header('access-control-allow-headers', 'accept, x-requested-with, content-type');
103+
res.header('access-control-expose-headers', 'etag');
104+
}
105+
if (app.conf.csp !== false) {
106+
res.header('x-xss-protection', '1; mode=block');
107+
res.header('x-content-type-options', 'nosniff');
108+
res.header('x-frame-options', 'SAMEORIGIN');
109+
res.header('content-security-policy', app.conf.csp);
110+
}
111+
sUtil.initAndLogRequest(req, app);
112+
next();
113+
});
114+
115+
// set up the user agent header string to use for requests
116+
app.conf.user_agent = app.conf.user_agent || app.info.name;
117+
118+
// disable the X-Powered-By header
119+
app.set('x-powered-by', false);
120+
// disable the ETag header - users should provide them!
121+
app.set('etag', false);
122+
// enable compression
123+
app.use(compression({ level: app.conf.compression_level }));
124+
// use the JSON body parser
125+
app.use(bodyParser.json({ limit: app.conf.max_body_size || '100kb' }));
126+
// use the application/x-www-form-urlencoded parser
127+
app.use(bodyParser.urlencoded({ extended: true }));
128+
129+
return BBPromise.resolve(app);
130+
131+
}
132+
133+
/**
134+
* Loads all routes declared in routes/ into the app
135+
*
136+
* @param {Application} app the application object to load routes into
137+
* @param {string} dir routes folder
138+
* @return {bluebird} a promise resolving to the app object
139+
*/
140+
function loadRoutes(app, dir) {
141+
142+
// recursively load routes from .js files under routes/
143+
return fs.readdirAsync(dir).map((fname) => {
144+
return BBPromise.try(() => {
145+
const resolvedPath = path.resolve(dir, fname);
146+
const isDirectory = fs.statSync(resolvedPath).isDirectory();
147+
if (isDirectory) {
148+
loadRoutes(app, resolvedPath);
149+
} else if (/\.js$/.test(fname)) {
150+
// import the route file
151+
const route = require(`${dir}/${fname}`);
152+
return route(app);
153+
}
154+
}).then((route) => {
155+
if (route === undefined) {
156+
return undefined;
157+
}
158+
// check that the route exports the object we need
159+
if (route.constructor !== Object || !route.path || !route.router ||
160+
!(route.api_version || route.skip_domain)) {
161+
throw new TypeError(`routes/${fname} does not export the correct object!`);
162+
}
163+
// normalise the path to be used as the mount point
164+
if (route.path[0] !== '/') {
165+
route.path = `/${route.path}`;
166+
}
167+
if (route.path[route.path.length - 1] !== '/') {
168+
route.path = `${route.path}/`;
169+
}
170+
if (!route.skip_domain) {
171+
route.path = `/:domain/v${route.api_version}${route.path}`;
172+
}
173+
// wrap the route handlers with Promise.try() blocks
174+
sUtil.wrapRouteHandlers(route, app);
175+
// all good, use that route
176+
app.use(route.path, route.router);
177+
});
178+
}).then(() => {
179+
// catch errors
180+
sUtil.setErrorHandler(app);
181+
// route loading is now complete, return the app object
182+
return BBPromise.resolve(app);
183+
});
184+
185+
}
186+
187+
/**
188+
* Creates and start the service's web server
189+
*
190+
* @param {Application} app the app object to use in the service
191+
* @return {bluebird} a promise creating the web server
192+
*/
193+
function createServer(app) {
194+
195+
// return a promise which creates an HTTP server,
196+
// attaches the app to it, and starts accepting
197+
// incoming client requests
198+
let server;
199+
return new BBPromise((resolve) => {
200+
server = http.createServer(app).listen(
201+
app.conf.port,
202+
app.conf.interface,
203+
resolve
204+
);
205+
server = addShutdown(server);
206+
}).then(() => {
207+
app.logger.log('info',
208+
`Worker ${process.pid} listening on ${app.conf.interface || '*'}:${app.conf.port}`);
209+
210+
// Don't delay incomplete packets for 40ms (Linux default) on
211+
// pipelined HTTP sockets. We write in large chunks or buffers, so
212+
// lack of coalescing should not be an issue here.
213+
server.on('connection', (socket) => {
214+
socket.setNoDelay(true);
215+
});
216+
217+
return server;
218+
});
219+
220+
}
221+
222+
/**
223+
* The service's entry point. It takes over the configuration
224+
* options and the logger- and metrics-reporting objects from
225+
* service-runner and starts an HTTP server, attaching the application
226+
* object to it.
227+
*
228+
* @param {Object} options the options to initialise the app with
229+
* @return {bluebird} HTTP server
230+
*/
231+
module.exports = (options) => {
232+
233+
return initApp(options)
234+
.then((app) => loadRoutes(app, `${__dirname}/routes`))
235+
.then((app) => {
236+
// serve static files from static/
237+
app.use('/static', express.static(`${__dirname}/static`));
238+
return app;
239+
}).then(createServer);
240+
241+
};

0 commit comments

Comments
 (0)