Commit 83cec05e authored by Aral Balkan's avatar Aral Balkan

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/),
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
## Changed
......
......@@ -5,7 +5,7 @@ An HTTPS server that uses [nodecert](https://source.ind.ie/hypha/tools/nodecert)
## Design goals
* ✔ 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
## Installation
......@@ -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.
### 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
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)
* Linux with yum
* 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
......
#!/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')
const os = require('os')
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) {
console.log('Usage: https-server [folder-to-serve (default=.)] [port (default=443)]')
process.exit()
}
const nodecertDirectory = path.join(os.homedir(), '.nodecert')
// 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]
if (!fs.existsSync(nodecertDirectory)) {
console.log('\nError: requires nodecert.\n\nInstall: npm i -g nodecert\nRun : nodecert\n\nMore information: https://source.ind.ie/hypha/tools/nodecert\n')
process.exit(1)
}
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)
}
class HttpsServer {
//
// If the requested port is < 1024 ensure that we can bind to it. Note: this is
// only an issue on Linux systems. As of macOS Mojave, privileged ports are
// history on macOS (source regarding version:
// https://news.ycombinator.com/item?id=18302380 confirmed with first-party
// tests) and are not an issue on (at least client versions of) Windows.
// Good riddance too, as these so-called privileged ports are a relic from the
// 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)
// Returns an https server instance – the same as you’d get with
// require('https').createServer – configured with your nodecert certificates.
// If you do pass a key and cert, they will be overwritten.
createServer (options = {}, requestListener = undefined) {
const defaultOptions = {
key: fs.readFileSync(path.join(nodecertDirectory, 'localhost-key.pem')),
cert: fs.readFileSync(path.join(nodecertDirectory, 'localhost.pem'))
}
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)) {
console.log('\nError: requires nodecert.\n\nInstall: npm i -g nodecert\nRun : nodecert\n\nMore information: https://source.ind.ie/hypha/tools/nodecert\n')
process.exit(1)
}
// Create an express server to serve the path using Morgan for logging.
const app = express()
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 {
const server = https.createServer({
key: fs.readFileSync(path.join(nodecertDirectory, 'localhost-key.pem')),
cert: fs.readFileSync(path.join(nodecertDirectory, 'localhost.pem'))
}, app).listen(port, () => {
const serverPort = server.address().port
let portSuffix = ''
if (serverPort !== 443) {
portSuffix = `:${serverPort}`
// If we’re on Linux and the requested port is < 1024 ensure that we can bind to it.
// (As of macOS Mojave, privileged ports are only an issue on Linux. Good riddance too,
// as these so-called privileged ports are a relic from the days of mainframes and they
// actually have a negative impact on security today:
// https://www.staldal.nu/tech/2007/10/31/why-can-only-root-listen-to-ports-below-1024/
ensureWeCanBindToPort (port) {
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)
}
}
}
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",
"version": "1.0.4",
"version": "1.0.5",
"description": "HTTPS server that uses nodecert",
"main": "index.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