Verified Commit 83cec05e authored by Aral Balkan's avatar Aral Balkan
Browse files

Add API; expose createServer() and serve() methods

parent 9f55fcd4
...@@ -8,6 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ...@@ -8,6 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
Nothing yet. Nothing yet.
## [1.0.5] - 2019-02-27
## Added
- API: you can now use https-server programmatically from your own Node.js apps. It exposes a `createServer` method that’s polymorphic with its namesake from the base `https` module and it provides a `serve` convenience method that uses Express to serve a static site at the passed directory and port (or the current directory at port 443 by default).
## [1.0.4] - 2019-02-26 ## [1.0.4] - 2019-02-26
## Changed ## Changed
......
...@@ -5,7 +5,7 @@ An HTTPS server that uses [nodecert](https://source.ind.ie/hypha/tools/nodecert) ...@@ -5,7 +5,7 @@ An HTTPS server that uses [nodecert](https://source.ind.ie/hypha/tools/nodecert)
## Design goals ## Design goals
* ✔ Command-line app * ✔ Command-line app
* To-do: Easy integration into Express * Easy integration with Express, etc.
* To-do: Seamless switch to using ACME/Let’s Encrypt in production * To-do: Seamless switch to using ACME/Let’s Encrypt in production
## Installation ## Installation
...@@ -31,15 +31,56 @@ If you do not already have TLS certificates, they will be created for you automa ...@@ -31,15 +31,56 @@ If you do not already have TLS certificates, they will be created for you automa
All dependencies will be installed automatically for you if they do not exist if you have apt, pacman, or yum (untested) on Linux or if you have [Homebrew](https://brew.sh/) or [MacPorts](https://www.macports.org/) (untested) on macOS. All dependencies will be installed automatically for you if they do not exist if you have apt, pacman, or yum (untested) on Linux or if you have [Homebrew](https://brew.sh/) or [MacPorts](https://www.macports.org/) (untested) on macOS.
### API
http-server provides a `createServer` method that behaves like the built-in _https_ module’s createServer function so anywhere you use `https.createServer`, you can simply replace it with `httpsServer.createServer`.
### createServer([options], [requestListener])
* options: [(Object)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) Accepts options from [tls.createServer()](https://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener), [tls.createSecureContext()](https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options) and [http.createServer()](https://nodejs.org/api/http.html#http_http_createserver_options_requestlistener). Populates the `cert` and `key` properties from the automatically-created [nodecert](https://source.ind.ie/hypha/tools/nodecert/) certificates and will overwrite them if they exist in the options object you pass in.
* requestListener: [(Function)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) A listener to be added to the 'request' event.
* Returns: [https.Server](https://nodejs.org/api/https.html#https_class_https_server) instance, configured with locally-trusted certificates.
#### Example
```js
const httpsServer = require('https-server')
const express = require('express')
const app = express()
app.use(express.static('.'))
const server = httpsServer.createServer(options, app).listen(443, () => {
console.log(` 🎉 Serving on https://localhost\n`)
})
```
### serve([pathToServe], [port])
* pathToServe: [(string)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/string) path to serve using [Express](http://expressjs.com/).static.
* port: [(number)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type) the port to serve on. Defaults to 443. (On Linux, privileges to bind to the port are automatically obtained for you.)
#### Example
```js
const httpsServer = require('https-server')
// Serve the current directory over https://localhost
const server = httpsServer.serve()
```
## Help wanted ## Help wanted
I’ve currently only tested this on Pop!_OS 18.10 (Ubuntu-based distro). I can use your help to test this on other platforms: I can use your help to test https-server on other the following platforms:
* Windows 64-bit (should work without requiring any dependencies) * Windows 64-bit (should work without requiring any dependencies)
* Linux with yum * Linux with yum
* macOS with MacPorts * macOS with MacPorts
If you get a chance to try out https-server on the above platforms, please [let me know how/if it works](https://github.com/indie-mirror/https-server/issues). Thank you. Please [let me know how/if it works](https://github.com/indie-mirror/https-server/issues). Thank you!
## Thanks ## Thanks
......
#!/usr/bin/env node #!/usr/bin/env node
require('../index.js') const fs = require('fs')
const httpsServer = require('../index.js')
const arguments = process.argv
if (arguments.length > 4) {
console.log('Usage: https-server [folder-to-serve (default=.)] [port (default=443)]')
process.exit()
}
// If no path is passed, serve the current folder.
// If there is a path, serve that.
let pathToServe = '.'
if (arguments.length >= 3) {
pathToServe = arguments[2]
}
let port = 443
// If a port is specified, use that instead.
if (arguments.length === 4) {
port = parseInt(arguments[3])
}
if (!fs.existsSync(pathToServe)) {
console.log(`\n 🤔 Error: could not find path ${pathToServe}\n`)
process.exit(1)
}
httpsServer.serve(pathToServe, port)
...@@ -6,90 +6,86 @@ const path = require('path') ...@@ -6,90 +6,86 @@ const path = require('path')
const os = require('os') const os = require('os')
const childProcess = require('child_process') const childProcess = require('child_process')
const arguments = process.argv // Requiring nodecert ensures that locally-trusted TLS certificates exist.
require('@ind.ie/nodecert')
if (arguments.length > 4) { const nodecertDirectory = path.join(os.homedir(), '.nodecert')
console.log('Usage: https-server [folder-to-serve (default=.)] [port (default=443)]')
process.exit()
}
// If no path is passed, serve the current folder. if (!fs.existsSync(nodecertDirectory)) {
// If there is a path, serve that. console.log('\nError: requires nodecert.\n\nInstall: npm i -g nodecert\nRun : nodecert\n\nMore information: https://source.ind.ie/hypha/tools/nodecert\n')
let pathToServe = '.' process.exit(1)
if (arguments.length >= 3) {
pathToServe = arguments[2]
} }
let port = 443
// If a port is specified, use that instead.
if (arguments.length === 4) {
port = parseInt(arguments[3])
}
if (!fs.existsSync(pathToServe)) { class HttpsServer {
console.log(`\n 🤔 Error: could not find path ${pathToServe}\n`)
process.exit(1)
}
// // Returns an https server instance – the same as you’d get with
// If the requested port is < 1024 ensure that we can bind to it. Note: this is // require('https').createServer – configured with your nodecert certificates.
// only an issue on Linux systems. As of macOS Mojave, privileged ports are // If you do pass a key and cert, they will be overwritten.
// history on macOS (source regarding version: createServer (options = {}, requestListener = undefined) {
// https://news.ycombinator.com/item?id=18302380 confirmed with first-party const defaultOptions = {
// tests) and are not an issue on (at least client versions of) Windows. key: fs.readFileSync(path.join(nodecertDirectory, 'localhost-key.pem')),
// Good riddance too, as these so-called privileged ports are a relic from the cert: fs.readFileSync(path.join(nodecertDirectory, 'localhost.pem'))
// days of mainframes and they actually have a negative impact on security today.
//
// More background:
// https://www.staldal.nu/tech/2007/10/31/why-can-only-root-listen-to-ports-below-1024/
//
if (port < 1024 && os.platform() === 'linux') {
const options = {env: process.env}
try {
childProcess.execSync("setcap -v 'cap_net_bind_service=+ep' $(which node)", options)
} catch (error) {
try {
// Allow Node.js to bind to ports < 1024.
childProcess.execSync("sudo setcap 'cap_net_bind_service=+ep' $(which node)", options)
// Fork a new instance of the server so that it is launched with the privileged Node.js.
childProcess.fork(path.join(__dirname, 'index.js'), [pathToServe, port], {env: process.env, shell: true})
// We’re done here. Go into an endless loop. Exiting (Ctrl+C) this will also exit the child process.
while(1){}
} catch (error) {
console.log(`\n Error: could not get privileges for Node.js to bind to port ${port}.`, error)
process.exit(1)
} }
Object.assign(options, defaultOptions)
return https.createServer(options, requestListener)
} }
}
// Requiring nodecert ensures that locally-trusted TLS certificates exist.
require('@ind.ie/nodecert')
const nodecertDirectory = path.join(os.homedir(), '.nodecert') // Starts a static server serving the contents of the passed path at the passed port
// and returns the server.
serve(pathToServe = '.', port = 443) {
this.ensureWeCanBindToPort(port)
if (!fs.existsSync(nodecertDirectory)) { // Create an express server to serve the path using Morgan for logging.
console.log('\nError: requires nodecert.\n\nInstall: npm i -g nodecert\nRun : nodecert\n\nMore information: https://source.ind.ie/hypha/tools/nodecert\n') const app = express()
process.exit(1) app.use(morgan('tiny'))
} app.use(express.static(pathToServe))
const app = express() try {
const server = this.createServer({}, app).listen(port, () => {
const serverPort = server.address().port
let portSuffix = ''
if (serverPort !== 443) {
portSuffix = `:${serverPort}`
}
console.log(` 🎉 Serving ${pathToServe} on https://localhost${portSuffix}\n`)
})
return server
} catch (error) {
console.log('\nError: could not start server', error)
process.exit(1)
}
}
app.use(morgan('tiny'))
app.use(express.static(pathToServe))
try { // If we’re on Linux and the requested port is < 1024 ensure that we can bind to it.
const server = https.createServer({ // (As of macOS Mojave, privileged ports are only an issue on Linux. Good riddance too,
key: fs.readFileSync(path.join(nodecertDirectory, 'localhost-key.pem')), // as these so-called privileged ports are a relic from the days of mainframes and they
cert: fs.readFileSync(path.join(nodecertDirectory, 'localhost.pem')) // actually have a negative impact on security today:
}, app).listen(port, () => { // https://www.staldal.nu/tech/2007/10/31/why-can-only-root-listen-to-ports-below-1024/
const serverPort = server.address().port ensureWeCanBindToPort (port) {
let portSuffix = '' if (port < 1024 && os.platform() === 'linux') {
if (serverPort !== 443) { const options = {env: process.env}
portSuffix = `:${serverPort}` try {
childProcess.execSync("setcap -v 'cap_net_bind_service=+ep' $(which node)", options)
} catch (error) {
try {
// Allow Node.js to bind to ports < 1024.
childProcess.execSync("sudo setcap 'cap_net_bind_service=+ep' $(which node)", options)
// Fork a new instance of the server so that it is launched with the privileged Node.js.
childProcess.fork(path.join(__dirname, 'index.js'), [pathToServe, port], {env: process.env, shell: true})
// We’re done here. Go into an endless loop. Exiting (Ctrl+C) this will also exit the child process.
while(1){}
} catch (error) {
console.log(`\n Error: could not get privileges for Node.js to bind to port ${port}.`, error)
process.exit(1)
}
}
} }
console.log(` 🎉 Serving ${pathToServe} on https://localhost${portSuffix}\n`) }
})
} catch (error) {
console.log('\nError: could not start server', error)
process.exit(1)
} }
module.exports = new HttpsServer()
{ {
"name": "@ind.ie/https-server", "name": "@ind.ie/https-server",
"version": "1.0.4", "version": "1.0.5",
"description": "HTTPS server that uses nodecert", "description": "HTTPS server that uses nodecert",
"main": "index.js", "main": "index.js",
"bin": "bin/https-server.js", "bin": "bin/https-server.js",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment