Skip to content

Commit

Permalink
feat: reverse proxy dynamic subpath (#540)
Browse files Browse the repository at this point in the history
* Removed unused template

* Use EJS for index page in dev.

* Fix webpack; move default config

* Dynamically render index

* Built production works

* Mostly working

* Add some documentation

* Fix up documentation

* Link to subpath docs from main README

* Add unit tests

* Add missing (?) ejs package

* TIL: Socket Namespaces

* Update docs/subpath.md

Co-authored-by: Chris Nesbitt-Smith <[email protected]>

* Update src/apis/ConfigApis.js

Co-authored-by: Chris Nesbitt-Smith <[email protected]>

* Update docs/subpath.md

Co-authored-by: Chris Nesbitt-Smith <[email protected]>

* Add reverse proxy setup to Readme TOC

* Remove trailing white line

* Remove trailing semicolon from webpack config

* Move require to the requirements block

* Fix MD language for NGinx

* Remove `favicons` comment

* Add 'proxyquire' as a dev dependency

* Ensure base path has always a trailing slash

* Fix development assets layout

* Add full practical example of reverse proxy

* Proxy to nodejs service for development of UI

* Fix missing mapping

* Linter fixes -- need to start auto running it!

* Update README.md

Co-authored-by: Chris Nesbitt-Smith <[email protected]>

* Update build/webpack.dev.conf.js

Co-authored-by: Chris Nesbitt-Smith <[email protected]>

* Update src/App.vue

Co-authored-by: Chris Nesbitt-Smith <[email protected]>

* Update test/lib/renderIndex.test.js

Co-authored-by: Chris Nesbitt-Smith <[email protected]>

* Make proxy URL configurable; UT fixes

Co-authored-by: Daniel Lando <[email protected]>
Co-authored-by: Chris Nesbitt-Smith <[email protected]>
  • Loading branch information
3 people authored Jun 8, 2020
1 parent 47c3abf commit b10e8c9
Show file tree
Hide file tree
Showing 20 changed files with 600 additions and 180 deletions.
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
"eslint.format.enable": true,
"editor.defaultFormatter": "numso.prettier-standard-vscode",
"eslint.lintTask.enable": true,
"wallaby.startAutomatically": true
"wallaby.startAutomatically": true,
"[vue]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ After a [discussion](https://github.com/OpenZWave/Zwave2Mqtt/issues/201) with Op
- [DOCKER :tada: way](#docker-tada-way)
- [Kubernetes way](#kubernetes-way)
- [NodeJS or PKG version](#nodejs-or-pkg-version)
- [Reverse Proxy Setup](#reverse-proxy-setup)
- [:nerd_face: Development](#nerdface-development)
- [:wrench: Usage](#wrench-usage)
- [Zwave](#zwave)
Expand Down Expand Up @@ -168,6 +169,11 @@ kubectl apply -k https://raw.githubusercontent.com/openzwave/zwave2mqtt/master/k

4. Open the browser <http://localhost:8091>

### Reverse Proxy Setup

If you need to setup ZWave To MQTT behind a reverse proxy that needs a _subpath_ to
work, take a look at [the reverse proxy configuraiton docs](docs/subpath.md).

## :nerd_face: Development

Developers who wants to debug the application have to open 2 terminals.
Expand All @@ -179,6 +185,19 @@ In the second terminal run `npm run dev:server` to start the backend server with
To package the application run `npm run pkg` command and follow the steps
### Developing against a different backend
By default running `npm run dev:server` will proxy the reequests to a backend listening on _localhost_ on port _8091_.
If you want to run the development frontend against a different backend you have the following environment variables
that you can use to redirect to a different backend:
- **SERVER_HOST**: [Default: 'localhost'] the hostname or IP of the backend server you want to use;
- **SERVER_PORT**: [Default: '8091'] the port of the backend server you want to use;
- **SERVER_SSL**: [Default: undefined] if set to a value it will use _https_/_wss_ to connect to the backend;
- **SERVER_URL**: [Default: use the other variables] the full URL for the backend API, IE: `https://zwavetomqtt.home.net:8443/`
- **SERVER_WS_URL**: [Default: use the other variables] the full URL for the backend Socket, IE: `wss://zwavetomqtt.home.net:8443/`
## :wrench: Usage
Firstly you need to open the browser at the link <http://localhost:8091> and edit the settings for Zwave, MQTT and the Gateway.
Expand Down
4 changes: 3 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var store = reqlib('config/store.js')
var debug = reqlib('/lib/debug')('App')
var history = require('connect-history-api-fallback')
var utils = reqlib('/lib/utils.js')

const renderIndex = reqlib('/lib/renderIndex')
var gw //the gateway instance
let io

Expand All @@ -36,6 +36,8 @@ app.use(
)
app.use(cookieParser())

app.get('/', renderIndex)

app.use('/', express.static(utils.joinPath(false, 'dist')))

app.use(cors())
Expand Down
3 changes: 2 additions & 1 deletion build/webpack.base.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ module.exports = {
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
name: utils.assetsPath('fonts/[name].[hash:7].[ext]'),
publicPath: '../..'
}
},
{
Expand Down
16 changes: 7 additions & 9 deletions build/webpack.dev.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const appConfig = require('../config/webConfig')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
Expand All @@ -27,14 +28,7 @@ const devWebpackConfig = merge(baseWebpackConfig, {
// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{
from: /.*/,
to: path.posix.join(config.dev.assetsPublicPath, 'index.html')
}
]
},
historyApiFallback: true,
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
Expand All @@ -61,8 +55,12 @@ const devWebpackConfig = merge(baseWebpackConfig, {
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
title: 'ZWave To MQTT',
filename: 'index.html',
template: 'index.html',
template: 'views/index.ejs',
templateParameters: {
config: appConfig
},
inject: true
}),
// copy custom static assets
Expand Down
16 changes: 0 additions & 16 deletions build/webpack.prod.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
Expand Down Expand Up @@ -52,21 +51,6 @@ const webpackConfig = merge(baseWebpackConfig, {
? { safe: true, map: { inline: false } }
: { safe: true }
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
}
}),
// keep module.id stable when vendor modules does not change
new webpack.HashedModuleIdsPlugin(),
// enable scope hoisting
Expand Down
2 changes: 2 additions & 0 deletions config/app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// config/app.js
module.exports = {
title: 'ZWave To MQTT',
storeDir: 'store',
base: '/',
port: 8091
}
24 changes: 23 additions & 1 deletion config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,34 @@

const path = require('path')

const proxyScheme = process.env.SERVER_SSL ? 'https' : 'http'
const proxyWebSocketScheme = process.env.SERVER_SSL ? 'wss' : 'ws'
const proxyHostname = process.env.SERVER_HOST
? process.env.SERVER_HOST
: 'localhost'
const proxyPort = process.env.SERVER_PORT ? process.env.SERVER_PORT : '8091'

const proxyURL = process.env.SERVER_URL
? process.env.SERVER_URL
: `${proxyScheme}://${proxyHostname}:${proxyPort}`

const proxyWSURL = process.env.SERVER_WS_URL
? process.env.SERVER_WS_URL
: `${proxyWebSocketScheme}://${proxyHostname}:${proxyPort}`

module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
proxyTable: {
'/socket.io': {
target: proxyWSURL,
ws: true
},
'/health': proxyURL,
'/api': proxyURL
},

// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
Expand Down
13 changes: 13 additions & 0 deletions config/webConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const appConfig = require('./app')

appConfig.base = appConfig.base && appConfig.base.replace(/\/?$/, '/')

const defaultConfig = {
base: '/',
title: 'ZWave To MQTT'
}

module.exports = {
...defaultConfig,
...appConfig
}
61 changes: 61 additions & 0 deletions docs/subpath.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# ZWave To MQTT Behind a Reverse Proxy

There are two ways to enable ZWave To MQTT to sit behing a proxy that uses
subpaths to serve the pages and services.

You can use a header to signal where the external path is or you can configure
the base path. In both cases these are dynamic configurations, so you can deploy
without having to build again the frontend.

## Using an HTTP header

You can pass the external path by setting the `X-External-Path` header, for example
suppose you had the following `nginx` configuration:

```nginx
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 9000 default_server;
listen [::]:9000 default_server;
location /hassio/ingress/ {
proxy_pass http://localhost:8091/;
proxy_set_header X-External-Path /hassio/ingress;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
```

This will tell the application to serve the application and relevant elements under
`/some/deep/map`.

In case you are using the [ingress of Home Assistant](https://www.home-assistant.io/blog/2019/04/15/hassio-ingress/) you will want to
pick up the `X-Ingress-Path;` and map it, something along
these lines:

```nginx
proxy_set_header X-External-Path $http_x_ingress_path;
```

## Using the configuration

You can simply change the `config/app.js` and set `base` to whatever is
the subpath you will be serving this from.

As an example, if your proxy is placing the app behind `/zwave/` your configuration
would look like:

```javascript
module.exports = {
title: 'ZWave to MQTT',
storeDir: 'store',
base: '/zwave/',
port: 8091
}
```
48 changes: 0 additions & 48 deletions index.html

This file was deleted.

37 changes: 37 additions & 0 deletions lib/renderIndex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const fs = require('fs')
const path = require('path')
const reqlib = require('app-root-path').require

const webConfig = reqlib('/config/webConfig')

function findFiles (folder, ext) {
const folderPath = path.join(__dirname, '..', 'dist', folder)
const folderFiles = fs.readdirSync(folderPath)
return folderFiles
.filter(function (file) {
return path.extname(file).toLowerCase() === `.${ext.toLowerCase()}`
})
.map(function (file) {
return path.join(folder, file)
})
}

let cssFiles
let jsFiles

function basePath (config, headers) {
return (headers['x-external-path'] || config.base).replace(/\/?$/, '/')
}

module.exports = function (req, res) {
cssFiles = cssFiles || findFiles(path.join('static', 'css'), 'css')
jsFiles = jsFiles || findFiles(path.join('static', 'js'), 'js')
res.render('index.ejs', {
config: {
...webConfig,
base: basePath(webConfig, req.headers)
},
cssFiles,
jsFiles
})
}
Loading

0 comments on commit b10e8c9

Please sign in to comment.