From 13b5220b930f327b898c46676b86b4b715ba0d7a Mon Sep 17 00:00:00 2001 From: MattIPv4 <me@mattcowley.co.uk> Date: Thu, 11 Jun 2020 15:46:51 +0100 Subject: [PATCH] Add global reverse proxy timeout settings (fixes #74) --- src/nginxconfig/generators/conf/proxy.conf.js | 8 +- .../generators/conf/website.conf.js | 2 +- src/nginxconfig/generators/index.js | 2 +- src/nginxconfig/i18n/en/common.js | 2 + .../domain_sections/reverse_proxy.js | 9 +- .../en/templates/global_sections/index.js | 3 +- .../global_sections/reverse_proxy.js | 32 +++ .../domain_sections/reverse_proxy.vue | 16 +- .../templates/global_sections/index.js | 1 + .../global_sections/reverse_proxy.vue | 213 ++++++++++++++++++ 10 files changed, 271 insertions(+), 17 deletions(-) create mode 100644 src/nginxconfig/i18n/en/templates/global_sections/reverse_proxy.js create mode 100644 src/nginxconfig/templates/global_sections/reverse_proxy.vue diff --git a/src/nginxconfig/generators/conf/proxy.conf.js b/src/nginxconfig/generators/conf/proxy.conf.js index 1164877..3f136f3 100644 --- a/src/nginxconfig/generators/conf/proxy.conf.js +++ b/src/nginxconfig/generators/conf/proxy.conf.js @@ -24,12 +24,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -export default () => { +export default global => { const config = {}; config.proxy_http_version = '1.1'; config.proxy_cache_bypass = '$http_upgrade'; + config['# Proxy headers'] = ''; config['proxy_set_header Upgrade'] = '$http_upgrade'; config['proxy_set_header Connection'] = '"upgrade"'; config['proxy_set_header Host'] = '$host'; @@ -39,6 +40,11 @@ export default () => { config['proxy_set_header X-Forwarded-Host'] = '$host'; config['proxy_set_header X-Forwarded-Port'] = '$server_port'; + config['# Proxy timeouts'] = ''; + config['proxy_connect_timeout'] = global.reverseProxy.proxyConnectTimeout.computed; + config['proxy_send_timeout'] = global.reverseProxy.proxySendTimeout.computed; + config['proxy_read_timeout'] = global.reverseProxy.proxyReadTimeout.computed; + // Done! return config; }; diff --git a/src/nginxconfig/generators/conf/website.conf.js b/src/nginxconfig/generators/conf/website.conf.js index 06ddc53..393a13c 100644 --- a/src/nginxconfig/generators/conf/website.conf.js +++ b/src/nginxconfig/generators/conf/website.conf.js @@ -200,7 +200,7 @@ export default (domain, domains, global) => { locConf.push(['include', 'nginxconfig.io/proxy.conf']); } else { // Unified - locConf.push(...Object.entries(proxyConf())); + locConf.push(...Object.entries(proxyConf(global))); } serverConfig.push(['# reverse proxy', '']); diff --git a/src/nginxconfig/generators/index.js b/src/nginxconfig/generators/index.js index 89f9706..89d41d5 100644 --- a/src/nginxconfig/generators/index.js +++ b/src/nginxconfig/generators/index.js @@ -70,7 +70,7 @@ export default (domains, global) => { // Reverse proxy if (domains.some(d => d.reverseProxy.reverseProxy.computed)) - files['nginxconfig.io/proxy.conf'] = toConf(proxyConf()); + files['nginxconfig.io/proxy.conf'] = toConf(proxyConf(global)); // WordPress if (domains.some(d => d.php.wordPressRules.computed)) diff --git a/src/nginxconfig/i18n/en/common.js b/src/nginxconfig/i18n/en/common.js index 1bfe8c8..42c1450 100644 --- a/src/nginxconfig/i18n/en/common.js +++ b/src/nginxconfig/i18n/en/common.js @@ -40,4 +40,6 @@ export default { magento: 'Magento', django: 'Django', logging: 'Logging', + reverseProxy: 'Reverse proxy', + reverseProxyLower: 'reverse proxy', }; diff --git a/src/nginxconfig/i18n/en/templates/domain_sections/reverse_proxy.js b/src/nginxconfig/i18n/en/templates/domain_sections/reverse_proxy.js index 2870b80..5a7ed97 100644 --- a/src/nginxconfig/i18n/en/templates/domain_sections/reverse_proxy.js +++ b/src/nginxconfig/i18n/en/templates/domain_sections/reverse_proxy.js @@ -27,10 +27,9 @@ THE SOFTWARE. import common from '../../common'; export default { - reverseProxy: 'Reverse proxy', - reverseProxyIsDisabled: 'Reverse proxy is disabled.', - reverseProxyCannotBeEnabledWithPhp: `Reverse proxy cannot be enabled whilst ${common.php} is enabled.`, - reverseProxyCannotBeEnabledWithPython: `Reverse proxy cannot be enabled whilst ${common.python} is enabled.`, - enableReverseProxy: `${common.enable} reverse proxy`, + reverseProxyIsDisabled: `${common.reverseProxy} is disabled.`, + reverseProxyCannotBeEnabledWithPhp: `${common.reverseProxy} cannot be enabled whilst ${common.php} is enabled.`, + reverseProxyCannotBeEnabledWithPython: `${common.reverseProxy} cannot be enabled whilst ${common.python} is enabled.`, + enableReverseProxy: `${common.enable} ${common.reverseProxyLower}`, path: 'Path', }; diff --git a/src/nginxconfig/i18n/en/templates/global_sections/index.js b/src/nginxconfig/i18n/en/templates/global_sections/index.js index 0805d80..32c3a17 100644 --- a/src/nginxconfig/i18n/en/templates/global_sections/index.js +++ b/src/nginxconfig/i18n/en/templates/global_sections/index.js @@ -30,7 +30,8 @@ import nginx from './nginx'; import performance from './performance'; import php from './php'; import python from './python'; +import reverseProxy from './reverse_proxy'; import security from './security'; import tools from './tools'; -export default { https, logging, nginx, performance, php, python, security, tools }; +export default { https, logging, nginx, performance, php, python, reverseProxy, security, tools }; diff --git a/src/nginxconfig/i18n/en/templates/global_sections/reverse_proxy.js b/src/nginxconfig/i18n/en/templates/global_sections/reverse_proxy.js new file mode 100644 index 0000000..612a9aa --- /dev/null +++ b/src/nginxconfig/i18n/en/templates/global_sections/reverse_proxy.js @@ -0,0 +1,32 @@ +/* +Copyright 2020 DigitalOcean + +This code is licensed under the MIT License. +You may obtain a copy of the License at +https://github.com/digitalocean/nginxconfig.io/blob/master/LICENSE or https://mit-license.org/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +import common from '../../common'; + +export default { + reverseProxyMustBeEnabledOnOneSite: `${common.reverseProxy} must be enabled on at least one site to configure global ${common.reverseProxyLower} settings.`, + seconds: 'seconds', +}; diff --git a/src/nginxconfig/templates/domain_sections/reverse_proxy.vue b/src/nginxconfig/templates/domain_sections/reverse_proxy.vue index 5d19763..aa842d1 100644 --- a/src/nginxconfig/templates/domain_sections/reverse_proxy.vue +++ b/src/nginxconfig/templates/domain_sections/reverse_proxy.vue @@ -28,7 +28,7 @@ THE SOFTWARE. <div> <div v-if="!reverseProxyEnabled" class="field is-horizontal is-aligned-top"> <div class="field-label"> - <label class="label">{{ i18n.templates.domainSections.reverseProxy.reverseProxy }}</label> + <label class="label">{{ i18n.common.reverseProxy }}</label> </div> <div class="field-body"> <div class="field"> @@ -49,7 +49,7 @@ THE SOFTWARE. <div v-else class="field is-horizontal"> <div class="field-label"> - <label class="label">{{ i18n.templates.domainSections.reverseProxy.reverseProxy }}</label> + <label class="label">{{ i18n.common.reverseProxy }}</label> </div> <div class="field-body"> <div :class="`field${reverseProxyChanged ? ' is-changed' : ''}`"> @@ -123,22 +123,22 @@ THE SOFTWARE. }; export default { - name: 'DomainReverseProxy', // Component name - display: i18n.templates.domainSections.reverseProxy.reverseProxy, // Display name for tab - key: 'reverseProxy', // Key for data in parent - delegated: delegatedFromDefaults(defaults), // Data the parent will present here + name: 'DomainReverseProxy', // Component name + display: i18n.common.reverseProxy, // Display name for tab + key: 'reverseProxy', // Key for data in parent + delegated: delegatedFromDefaults(defaults), // Data the parent will present here components: { PrettyCheck, }, props: { - data: Object, // Data delegated back to us from parent + data: Object, // Data delegated back to us from parent }, data () { return { i18n, }; }, - computed: computedFromDefaults(defaults, 'reverseProxy'), // Getters & setters for the delegated data + computed: computedFromDefaults(defaults, 'reverseProxy'), // Getters & setters for the delegated data watch: { // If the PHP or Python is enabled, the Reverse proxy will be forced off '$parent.$props.data': { diff --git a/src/nginxconfig/templates/global_sections/index.js b/src/nginxconfig/templates/global_sections/index.js index 28b123f..1b850bc 100644 --- a/src/nginxconfig/templates/global_sections/index.js +++ b/src/nginxconfig/templates/global_sections/index.js @@ -28,6 +28,7 @@ export { default as HTTPS } from './https'; export { default as Security } from './security'; export { default as PHP } from './php'; export { default as Python } from './python'; +export { default as ReverseProxy } from './reverse_proxy'; export { default as Performance } from './performance'; export { default as Logging } from './logging'; export { default as NGINX } from './nginx'; diff --git a/src/nginxconfig/templates/global_sections/reverse_proxy.vue b/src/nginxconfig/templates/global_sections/reverse_proxy.vue new file mode 100644 index 0000000..520ffba --- /dev/null +++ b/src/nginxconfig/templates/global_sections/reverse_proxy.vue @@ -0,0 +1,213 @@ +<!-- +Copyright 2020 DigitalOcean + +This code is licensed under the MIT License. +You may obtain a copy of the License at +https://github.com/digitalocean/nginxconfig.io/blob/master/LICENSE or https://mit-license.org/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +--> + +<template> + <div> + <div v-if="!reverseProxyEnabled" class="field is-horizontal is-aligned-top"> + <div class="field-label"> + <label class="label">{{ i18n.common.reverseProxy }}</label> + </div> + <div class="field-body"> + <div class="field"> + <div class="control"> + <label class="text"> + {{ i18n.templates.globalSections.reverseProxy.reverseProxyMustBeEnabledOnOneSite }} + </label> + </div> + </div> + </div> + </div> + + <template v-else> + <div class="field is-horizontal"> + <div class="field-label"> + <label class="label">proxy_connect_timeout</label> + </div> + <div class="field-body"> + <div class="field has-addons"> + <div :class="`control is-expanded${proxyConnectTimeoutChanged ? ' is-changed' : ''}`"> + <input v-model.number="proxyConnectTimeout" + class="input" + type="number" + min="0" + step="1" + :placeholder="$props.data.proxyConnectTimeout.default" + /> + </div> + <div class="control"> + <a class="button is-static"> + {{ i18n.templates.globalSections.reverseProxy.seconds }} + </a> + </div> + </div> + </div> + </div> + + <div class="field is-horizontal"> + <div class="field-label"> + <label class="label">proxy_send_timeout</label> + </div> + <div class="field-body"> + <div class="field has-addons"> + <div :class="`control is-expanded${proxySendTimeoutChanged ? ' is-changed' : ''}`"> + <input v-model.number="proxySendTimeout" + class="input" + type="number" + min="0" + step="1" + :placeholder="$props.data.proxySendTimeout.default" + /> + </div> + <div class="control"> + <a class="button is-static"> + {{ i18n.templates.globalSections.reverseProxy.seconds }} + </a> + </div> + </div> + </div> + </div> + + <div class="field is-horizontal"> + <div class="field-label"> + <label class="label">proxy_read_timeout</label> + </div> + <div class="field-body"> + <div class="field has-addons"> + <div :class="`control is-expanded${proxyReadTimeoutChanged ? ' is-changed' : ''}`"> + <input v-model.number="proxyReadTimeout" + class="input" + type="number" + min="0" + step="1" + :placeholder="$props.data.proxyReadTimeout.default" + /> + </div> + <div class="control"> + <a class="button is-static"> + {{ i18n.templates.globalSections.reverseProxy.seconds }} + </a> + </div> + </div> + </div> + </div> + </template> + </div> +</template> + +<script> + import i18n from '../../i18n'; + import delegatedFromDefaults from '../../util/delegated_from_defaults'; + import computedFromDefaults from '../../util/computed_from_defaults'; + + const defaults = { + proxyConnectTimeout: { + default: 60, + computed: '60s', // We use a watcher to append 's' + enabled: false, + }, + proxySendTimeout: { + default: 60, + computed: '60s', // We use a watcher to append 's' + enabled: false, + }, + proxyReadTimeout: { + default: 60, + computed: '60s', // We use a watcher to append 's' + enabled: false, + }, + }; + + const validTimeout = data => { + let val = parseFloat(data.computed); + + // Use default if we've got an invalid setting + if (isNaN(val)) { + val = data.default; + } + + // Set the value with 's' appended + data.computed = `${val}s`; + }; + + export default { + name: 'GlobalReverseProxy', // Component name + display: i18n.common.reverseProxy, // Display name for tab + key: 'reverseProxy', // Key for data in parent + delegated: delegatedFromDefaults(defaults), // Data the parent will present here + props: { + data: Object, // Data delegated back to us from parent + }, + data() { + return { + i18n, + reverseProxyEnabled: false, + }; + }, + computed: computedFromDefaults(defaults, 'reverseProxy'), // Getters & setters for the delegated data + watch: { + // Disable all options if Reverse proxy is disabled + '$parent.$parent.$data.domains': { + handler(data) { + for (const domain of data) { + if (domain && domain.reverseProxy && domain.reverseProxy.reverseProxy + && domain.reverseProxy.reverseProxy.computed) { + this.$data.reverseProxyEnabled = true; + this.$props.data.proxyConnectTimeout.enabled = true; + this.$props.data.proxyConnectTimeout.computed = this.$props.data.proxyConnectTimeout.value; + this.$props.data.proxySendTimeout.enabled = true; + this.$props.data.proxySendTimeout.computed = this.$props.data.proxySendTimeout.value; + this.$props.data.proxyReadTimeout.enabled = true; + this.$props.data.proxyReadTimeout.computed = this.$props.data.proxyReadTimeout.value; + return; + } + } + + this.$data.reverseProxyEnabled = false; + this.$props.data.proxyConnectTimeout.enabled = false; + this.$props.data.proxyConnectTimeout.computed = ''; + this.$props.data.proxySendTimeout.enabled = false; + this.$props.data.proxySendTimeout.computed = ''; + this.$props.data.proxyReadTimeout.enabled = false; + this.$props.data.proxyReadTimeout.computed = ''; + }, + deep: true, + }, + // Ensure the timeouts are valid numbers + '$props.data.proxyConnectTimeout': { + handler: validTimeout, + deep: true, + }, + '$props.data.proxySendTimeout': { + handler: validTimeout, + deep: true, + }, + '$props.data.proxyReadTimeout': { + handler: validTimeout, + deep: true, + }, + }, + }; +</script>