Proxy server for AMO development (fixes #1716) (#1766)

This commit is contained in:
Mark Striemer 2017-02-17 10:08:14 -06:00 коммит произвёл GitHub
Родитель 6e1a3c09f7
Коммит f6410554f0
8 изменённых файлов: 142 добавлений и 9 удалений

Просмотреть файл

@ -42,7 +42,8 @@ Generic scripts that don't need env vars. Use these for development:
| Script | Description |
|-------------------------|-------------------------------------------------------|
| npm run dev:admin | Starts the dev server (admin app) |
| npm run dev:amo | Starts the dev server (amo) |
| npm run dev:amo | Starts the dev server and proxy (amo) |
| npm run dev:amo:no-proxy| Starts the dev server without proxy (amo) |
| npm run dev:disco | Starts the dev server (discovery pane) |
| npm run eslint | Lints the JS |
| npm run stylelint | Lints the SCSS |
@ -94,6 +95,24 @@ To see this report while running tests locally, type:
open ./coverage/index.html
### Running AMO for local development
A proxy server is provided for running the AMO app with the API on the same host as the frontend.
This provides a setup that is closer to production than running the frontend on its own. The
default configuration for this is to use a local addons-server for the API which can be setup
according to the
[addons-server docs](https://addons-server.readthedocs.io/en/latest/topics/install/index.html).
Docker is the preferred method of running addons-server.
Authentication will work when initiated from addons-frontend and will persist to addons-server but
it will not work when logging in from an addons-server page. See
[mozilla/addons-server#4684](https://github.com/mozilla/addons-server/issues/4684) for more
information on fixing this.
If you would like to use `https://addons-dev.allizom.org` for the API you should use the
`npm run dev:amo:no-proxy` command with an `API_HOST` to start the server without the proxy. For
example: `API_HOST=https://addons-dev.allizom.org npm run dev:amo:no-proxy`.
### Configuring for local development
The `dev` scripts above will connect to a hosted development API by default.

75
bin/proxy.js Normal file
Просмотреть файл

@ -0,0 +1,75 @@
require('babel-register');
const http = require('http');
const bunyan = require('bunyan');
const config = require('config');
const cookie = require('cookie');
const httpProxy = require('http-proxy');
const log = bunyan.createLogger({
name: 'proxy',
app: config.get('appName'),
serializers: bunyan.stdSerializers,
});
const proxy = httpProxy.createProxyServer();
const apiHost = config.get('proxyApiHost', null) || config.get('apiHost');
const frontendHost = `http://${config.get('serverHost')}:${config.get('serverPort')}`;
log.info(`apiHost: ${apiHost}`);
log.info(`frontendHost: ${frontendHost}`);
const array = (value) => {
if (!value) {
return [];
} else if (Array.isArray(value)) {
return value;
}
return [value];
};
function unsecureCookie(req, res, proxyRes) {
const proxyCookies = array(proxyRes.headers['set-cookie']);
// eslint-disable-next-line no-param-reassign
proxyRes.headers['set-cookie'] = proxyCookies.map((rewrittenCookie) => {
if (!req.connection.encrypted) {
return rewrittenCookie.replace(/;\s*?(Secure)/i, '');
}
return rewrittenCookie;
});
}
function getHost(req) {
const useDesktop = req.headers.cookie && cookie.parse(req.headers.cookie).mamo === 'off';
if (useDesktop || req.url.startsWith('/api/')) {
return apiHost;
}
return frontendHost;
}
const server = http.createServer((req, res) => {
const host = getHost(req);
return proxy.web(req, res, {
target: host,
changeOrigin: true,
autoRewrite: true,
protocolRewrite: 'http',
cookieDomainRewrite: '',
});
});
proxy.on('proxyRes', (proxyRes, req, res) => {
log.info(`${proxyRes.statusCode} ~> ${getHost(req)}${req.url}`);
unsecureCookie(req, res, proxyRes);
});
proxy.on('error', (error, req, res) => {
log.error(`ERR ~> ${getHost(req)}${req.url} ${error}`);
res.writeHead(500, { 'Content-type': 'text/plain' });
res.end('Proxy error');
});
const port = parseInt(config.get('proxyPort', '3333'), 10);
log.info(`🚦 Proxy running at http://localhost:${port}`);
server.listen(port);

Просмотреть файл

@ -1,5 +1,8 @@
{
"apiHost": "API_HOST",
"proxyApiHost": "PROXY_API_HOST",
"proxyEnabled": "PROXY_ENABLED",
"proxyPort": "PROXY_PORT",
"serverHost": "SERVER_HOST",
"serverPort": "SERVER_PORT",
"staticHost": "STATIC_HOST",

Просмотреть файл

@ -173,4 +173,6 @@ module.exports = {
defaultClientApp: 'firefox',
fxaConfig: null,
proxyEnabled: false,
};

Просмотреть файл

@ -1,3 +1,7 @@
module.exports = {
apiHost: 'http://localhost:3000',
proxyApiHost: 'http://olympia.dev',
proxyPort: 3000,
proxyEnabled: true,
fxaConfig: 'local',
};

Просмотреть файл

@ -10,6 +10,7 @@
"clean": "rimraf './dist/*!(.gitkeep)' './webpack-assets.json'",
"dev:admin": "better-npm-run dev:admin",
"dev:amo": "better-npm-run dev:amo",
"dev:amo:no-proxy": "better-npm-run dev:amo:no-proxy",
"dev:disco": "better-npm-run dev:disco",
"eslint": "eslint .",
"stylelint": "stylelint --syntax scss **/*.scss",
@ -38,11 +39,18 @@
}
},
"dev:amo": {
"command": "better-npm-run start-dev",
"command": "better-npm-run start-dev-proxy",
"env": {
"NODE_APP_INSTANCE": "amo"
}
},
"dev:amo:no-proxy": {
"command": "better-npm-run start-dev",
"env": {
"NODE_APP_INSTANCE": "amo",
"PROXY_ENABLED": "false"
}
},
"dev:disco": {
"command": "better-npm-run start-dev",
"env": {
@ -64,6 +72,15 @@
"NODE_PATH": "./:./src"
}
},
"start-dev-proxy": {
"command": "npm run clean && concurrently --kill-others 'npm run webpack-dev-server' 'node bin/server.js | bunyan' 'node bin/proxy.js | bunyan'",
"env": {
"ENABLE_PIPING": "true",
"NODE_ENV": "development",
"NODE_PATH": "./:./src",
"SERVER_PORT": "3333"
}
},
"servertest": {
"command": "mocha --compilers js:babel-register --timeout 10000 tests/server/",
"env": {
@ -215,6 +232,7 @@
"chalk": "1.1.3",
"cheerio": "0.22.0",
"concurrently": "3.3.0",
"cookie": "0.3.1",
"css-loader": "0.26.1",
"deepcopy": "0.6.3",
"eslint": "3.14.1",
@ -225,6 +243,7 @@
"fetch-mock": "5.9.3",
"file-loader": "0.10.0",
"glob": "7.1.1",
"http-proxy": "1.16.2",
"json-loader": "0.5.4",
"karma": "1.4.1",
"karma-chai": "0.1.0",

Просмотреть файл

@ -26,7 +26,7 @@ export default (
<Route path=":visibleAddonType/categories/" component={CategoryList} />
<Route path=":visibleAddonType/featured/" component={FeaturedAddons} />
<Route path=":visibleAddonType/:slug/" component={CategoryPage} />
<Route path="fxa-authenticate" component={HandleLogin} />
<Route path="/api/v3/accounts/authenticate/" component={HandleLogin} />
<Route path="search/" component={SearchPage} />
<Route path="401/"
component={config.get('isDevelopment') ? NotAuthorized : NotFound} />

Просмотреть файл

@ -322,12 +322,23 @@ export function runServer({
if (err) {
return reject(err);
}
log.info(oneLine`🔥 Addons-frontend server is running [ENV:${env}]
[APP:${app}] [isDevelopment:${isDevelopment}
[isDeployed:${isDeployed}] [apiHost:${config.get('apiHost')}]
[apiPath:${config.get('apiPath')}]`);
log.info(
`👁 Open your browser at http://${host}:${port} to view it.`);
const proxyEnabled = convertBoolean(config.get('proxyEnabled'));
// Not using oneLine here since it seems to change ' ' to ' '.
log.info([
`🔥 Addons-frontend server is running [ENV:${env}] [APP:${app}]`,
`[isDevelopment:${isDevelopment}] [isDeployed:${isDeployed}]`,
`[apiHost:${config.get('apiHost')}] [apiPath:${config.get('apiPath')}]`,
].join(' '));
if (proxyEnabled) {
const proxyPort = config.get('proxyPort');
log.info(
`🚦 Proxy detected, frontend running at http://${host}:${port}.`);
log.info(
`👁 Open your browser at http://localhost:${proxyPort} to view it.`);
} else {
log.info(
`👁 Open your browser at http://${host}:${port} to view it.`);
}
return resolve(server);
});
} else {