npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

http-reverse-proxy-ts

v1.0.1

Published

An Http reverse proxy implementation in Typescript

Downloads

49

Readme

HTTP/HTTPS Reverse Proxy

This package implements an http/https reverse proxy. It allows multiple web services/sites to share a single internet interface (router) and share ports 80 and 443 (or any other ports you use). The goal is to provide a secure, easy to implement, proxy for privately hosted servers.

Installation

If you already have nodejs and npm installed then the package can be installed from npm via

npm install --save http-reverse-proxy-ts

Running a simple http reverse proxy server

For this simple test you need a couple of entries in the hosts file:

# add host entries for testing
127.0.0.1  server1.qzqzqz.com
127.0.0.1  server2.qzqzqz.com

A good tutorial for editing the hosts file on most common system types can be found here

The simplest example of using this package is demonstrated by setting up some routes using http.

The index.ts would contain:

TypeScript

import { 
  HttpReverseProxy,
  SimpleHttpServer,
  Statistics, 
  StatisticsServer, 
  StatisticsServerOptions,
  Logger 
  } from 'http-reverse-proxy-ts'

const stats = new Statistics()

const statisticsServerOptions: StatisticsServerOptions = {

  stats: stats,
  htmlFilename: './public/statisticsAndLoggingPage.html'
}

const statisticsServer = new StatisticsServer(statisticsServerOptions)
const logger = new Logger()

const server1 = new SimpleHttpServer(1, 8001)
const server2 = new SimpleHttpServer(2, 8002)

server1.start()
server2.start()

const proxy = new HttpReverseProxy({ stats: stats, log: logger })

proxy.addRoute('http://server1.qzqzqz.com', 'localhost:8001')
proxy.addRoute('http://server2.qzqzqz.com', 'localhost:8002')

logger.info(null,'Proxy server started')

| StatisticsServerOptions |

The SimpleHTTPServer is a small implementation of a web server; it responds with the server number, hostname and url.

You will need to compile the project. It is recommended for this example that you set the outDir in the tsconfig.json file to ..

Run the project:

node index.js

If you want to view the statistics from the server you will need to copy the the file 'statisticsAndLoggingPage.html' from the public folder of the package (in node_modules) to a public folder at the root of your project. The statistics server defaults to localhost:3001.

If you want to view the log from the browser change the line:

const logger = new Logger()

to

const logger = new Logger({port: 3002, logLevel: 10})

This is cause the logger to create an http server at port 3002 which will forward log message to the statistics web page.

In the browser address bar on the same machine type: http://server1.qzqzqz.com

This should bring up the hello message from server1.

Change the address to http://server2.qzqzqz.com and the second server should respond.

You are now running a reverse proxy sharing a single front end ip address (port 80) and routing requests to two applications.

Change the address to http://localhost:3001 to view the statistics.

If you add another route: proxy.addRoute('server1.qzqzqz.com/statistics', 'localhost:3001') the statistics can be viewed via the address http://server1.qzqzqz.com/statistics.

Running a simple secure HTTPS server

Running an Https server is a bit more complex. The complexity is due to the requirement for certficates.

Certificates verify the authenticity of the server (usually the domain or host name) and set the groundwork for encrypting the data passing between the browser and the server.

A certificate is only valid if it is backed-up by a trusted certificate authority.

There are a large number of organizations that will grant a certificate for a domain or host name. Most of them charge a fee.

For this example of an https server we will use a self-signed certificate. A self-signed certificate is generated locally and does not have a valid certificate authority backing it up.

There is also free service that will grant certificates if you can prove you own the domain or host name. This service is called LetsEncrypt. A later example shows how to set that up.

Typescript

import {
  HttpReverseProxyOptions,
  HttpReverseProxy,
  LetsEncryptSelfSignedOptions,
  LetsEncryptUsingSelfSigned,
  RouteRegistrationOptions,
  SimpleHttpServer,
  Statistics,
  StatisticsServerOptions,
  StatisticsServer,
  Logger
} from 'http-reverse-proxy-ts'

const stats = new Statistics()
const logger = new Logger()

const statisticsServerOptions: StatisticsServerOptions = {

  stats: stats,
  htmlFilename: './public/statisticsAndLoggingPage.html'
}

const letsEncryptServerOptions: LetsEncryptSelfSignedOptions = {

  organizationName: 'Self testing',
  country: 'US',
  state: 'AnyState',
  locality: 'AnyTown'
}

const httpReverseProxyOptions: HttpReverseProxyOptions = {

  letsEncryptOptions: letsEncryptServerOptions,

  httpsOptions: {

    port: 443,

    certificates: {

      certificateStoreRoot: './certificates'
    },
  },

  stats: stats,
  log: logger,
}

const routingOptions: RouteRegistrationOptions = {

  https: {

    redirectToHttps: true,

    letsEncrypt: {

      email: '[email protected]',
      production: false,
    }
  }
}

const statisticsServer = new StatisticsServer(statisticsServerOptions)

const server1 = new SimpleHttpServer(1, 8001)
const server2 = new SimpleHttpServer(2, 8002)

server1.start()
server2.start()

const proxy = new HttpReverseProxy(httpReverseProxyOptions, LetsEncryptUsingSelfSigned)

proxy.addRoute('https://server1.qzqzqz.com', 'localhost:8001', routingOptions)
proxy.addRoute('https://server2.qzqzqz.com', 'localhost:8002', routingOptions)

logger.info(null, 'Https Reverse Proxy server started')

HttpReverseProxyOptions | LetsEncryptSelfSignedOptions | RouteRegistrationOptions | StatisticsServerOptions

As in the http example we set up two local http servers. These servers do not use https.

The reverse proxy is configured to accept http and https connections. If certificates are required (which will be true the first time the example is run) they will be provided by the Let's Encrypt self signed service which will run locally.

These certificates will will give you a warning in the browser.

The routes are configured to force an http connection from the browser to be redirected to an https connection on the proxy.

The certificates will be stored in the file system at the location specified by the certificateStoreRoot. The directory structure for the certificate store will be:

{certificateStoreRoot}
  |
  |-> server1_qzqzqz_com
  | |
  | |- server1_qzqzqz_com-crt.pem
  | |- server1_qzqzqz_com-key.pem
  |
  |-> server2_qzqzqz_com
    |
    |- server2_qzqzqz_com-crt.pem
    |- server2_qzqzqz_com-key.pem

Compile and run the project.

In the browser address bar on the same machine type: http://server1.qzqzqz.com

The browser should be redirected to an https connection. This connection should display an error in the browser stating that the connection is not secure. Select the option to open the page anyway (this varies by prowser).

This should bring up the hello message from server1.

Change the address to http://server2.qzqzqz.com and after being redirected and accepting the insecure certificates the second server should respond.

You are now running a (somewhat) secure reverse proxy sharing a single front end ip address (port 443).

Notice that the servers themselves (localhost:8001 and localhost:8002) are not secure. The packets from the proxy to the local servers are not encrypted. This is fine when the servers are on your local machine or on a local (secure) network. For servers that are outside of your control the connection on the back side should also be secure. See below.


Running a Let's Encrypt secure proxy

To test the retrieval of certificates from Let's Encrypt, you need a hostname directed to your ip-address (the network side of the router). You also need to instruct your router to forward packets arriving on port 80 and port 443 to your local system. A starting point can be found here

You can obtain a temporary host name from sites like no-ip or DysDNS. I am sure there a many others.

Typescript

import {
  HttpReverseProxyOptions,
  HttpReverseProxy,
  LetsEncryptClientOptions,
  LetsEncryptUsingAcmeClient,
  RouteRegistrationOptions,
  SimpleHttpServer,
  Statistics,
  StatisticsServerOptions,
  StatisticsServer,
  Logger
} from 'http-reverse-proxy-ts'

const hostname = '<Your Host Name>' // replace this with your actual host name
const stats = new Statistics()
const logger = new Logger()

const statisticsServerOptions: StatisticsServerOptions = {

  stats: stats,
  htmlFilename: './public/statisticsAndLoggingPage.html'
}

const letsEncryptServerOptions: LetsEncryptClientOptions = {
  noVerify: true
}

const httpReverseProxyOptions: HttpReverseProxyOptions = {

  letsEncryptOptions: letsEncryptServerOptions,

  httpsOptions: {

    port: 443,

    certificates: {

      certificateStoreRoot: './certificates'
    },
  },

  stats: stats,
  log: logger,
}

const routingOptions: RouteRegistrationOptions = {

  https: {

    redirectToHttps: true,

    letsEncrypt: {

      email: '[email protected]', // This needs a real email address
      production: false, // change this to true once testing is complete
    }
  }
}

const server1 = new SimpleHttpServer(1, 8001)
const server2 = new SimpleHttpServer(2, 8002)

const statisticsServer = new StatisticsServer(statisticsServerOptions)

// @ts-ignore
if ( hostname === '<Your Host Name>'){

  logger.error({hostname:hostname}, `hostname in 'letsEncryptHostTestProxy.ts' must be set to your registered host name`)
  
  process.exit(0)
}

server1.start()
server2.start()

const proxy = new HttpReverseProxy(httpReverseProxyOptions, LetsEncryptUsingAcmeClient)

proxy.addRoute(hostname, 'localhost:8001', routingOptions)
proxy.addRoute(hostname, 'localhost:8002', routingOptions) // round robin between servers

logger.info({hostname: hostname}, 'Https Lets Encrypt Test Proxy started')

HttpReverseProxyOptions | LetsEncryptClientOptions | RouteRegistrationOptions | StatisticsServerOptions

Once the ground-work is laid, replace <Your Host Name> with your registered hostname. Compile and run the project.

The proxy will start and request a certificate from Let's Encrypt for your hostname. This certificate will not be backed by a certificate authority. However, Once you have verified the system is working you can change the routingOptions.https.letsEncrypt.production to 'true', delete the old certificates and run it again.

In a manner similar to the prior examples, enter your host name into the browser and you should receive a response from Server1 or Server2. If you have not received a producton certificate you will get the same warning as the self signed certificates.

Some modern routers will not allow you to open a page with the web address of the router. This is an attempt to twart a hack called DNS rebinding. If the browser cannot open the page, try your phone with the wi-fi turned off.


Http Reverse Proxy

This is the class providing the primary interface to the reverse poxy server. Other than the plethora of options it has two main interfaces:

Add Route

  addRoute (from: string | Partial<URL>,
    to: string | ProxyUrl | (string | ProxyUrl)[],
    registrationOptions?: RouteRegistrationOptions): HttpReverseProxy

addRoute() will add a non-duplicate route to the routing server. from refers to the inbound host and url (the source) and to refers to the outbound host and url (target). Routes are duplicate if the source host and url are equivalent and the destination host and url are equivalent. The RouteRegistrationOptions are the specifications for this particular route. Adding additional targets to a route will not override the options from the first instantiation of the route.

Remove route

  removeRoute (from: string | Partial<URL>,
    to?: string | ProxyUrl | (string | ProxyUrl)[]): HttpReverseProxy

removeRoute() will remove one or more routes. If no targets are specified, all of the targets will be removed. When the route has no more targets, it will be removed. RemoveRoute will silently ignore requests to remove a route that does not exist.

Both addRoute and removeRoute can be chained in standard . notation:


proxy = new HttpReverseProxy ()
  .addRoute('server1.qzqzqz.com', 'localhost:8000')
  .addRoute('server2.qzqzqz.com', 'localhost:8001')

Statistics

The statistics service will collect runtime statistics for the proxy. For performance considerations the in-memory statistics table is about as simple as possible. Each statistic consists of a name and a count. All counts are updated in place and no history is provided. The Statistics container and Statistics service should be started before the proxy:

const statistics = new Statistics()

const statisticsServerOptions: StatisticsServerOptions = {
  stats: statistics,
  http: {
    port: 3001
  },

  webSocket: {
    interval: 1000
  }
}

const statisticsServer = new StatisticsServer(statisticsServerOptions)

const httpProxyOptions: HttpProxyOptions = {
  // any http options required

  stats: statistics
}

const proxy = new HttpReverseProxy(httpProxyOptions)

// add a route to the proxy to access the statistics server from the outside

proxy.addRoute ('server1.qzqzqz.com/statistics', 'localhost:3001')

This configuration will allow access to the statistics server through 'localhost:3001' or as 'server1.qzqzqz.com/statistics'.

The server does not provide any security.

The Statistics container will collect the statistics as long as the proxy is running.

The server will provide the current state of the statistics table through a webSocket interface. The table is sent as a single object in standard JSON format.

Each key (property) of the object is a measurement point and the value of the property is the current count. The key consists of the workerId (number) followed by a : followed by the name. The name portion may also contain additional : characters so splitting out the workderId should be done carefully.

The default web page served by the statistics server is read from ./public/statisticsAndLoggingPage.html. This default page can be overridden by setting the htmlFilename in the StatisticsServerOptions.

The default web page is a minimal implementation requiring no outside libraries. It will display the table with a single row for each statistic name and a column for each workerId. WorkerId 0 is the master. In a non-clustered configuration all statistics will be associated with the master.

Logging

The logging component will function in non-clustered and clustered environments without any options.

The options for the Logger are only required if you wish to view the log remotely via a web page or custom application. Or if you what to change the default logging level.

Clustering

The examples given above each run in a single process. This is sufficent for small scale testing.

For a larger production environment the proxy can be run as a cluster. In a cluster a single master process is started with a number of worker processes providing the routing. The master monitors the workers and restarts any worker that exits unexpectedly.

Clustering is enabled by setting the clustered option of the httpServerOptions to either true or a number.

If the value is set to true the master process will start worker processes based on the number of cores in the cpu. You can override this by setting clustered to a number. The minimum is 2 the maximum is 32.

Clustering should be employed only after the non-clustered router is tested and running properly.

An example clustered server:

import cluster from 'cluster'
import { 
 HttpReverseProxy,
 SimpleHttpServer,
 Statistics,
 StatisticsServer,
 StatisticsServerOptions,
 Logger } from 'http-reverse-proxy-ts'

const stats = new Statistics()
let logger: Logger

/**
 * In a clustered environment you only want the support services 
 * running on the master
 */

if (cluster.isMaster) {

  const statisticsServerOptions: StatisticsServerOptions = {

    stats: stats,
    htmlFilename: './public/statisticsAndLoggingPage.html'
  }

  const server1 = new SimpleHttpServer(1, 8001)
  const server2 = new SimpleHttpServer(2, 8002)

  const statisticsServer = new StatisticsServer(statisticsServerOptions)

  logger = new Logger(
    {
      port: 3002,
      logLevel: 10
    }
  )

  server1.start()
  server2.start()
}
else {

  logger = new Logger()
}

const proxy = new HttpReverseProxy({ clustered: true, stats: stats, log: logger })

proxy.addRoute('http://server1.test.com', 'localhost:8001')
proxy.addRoute('http://server2.test.com', 'localhost:8002')

logger.info(null, 'Proxy server started')

This example will allow you to view the statistics and log from a single web page similar to the examples above.

Configuration Options

HTTP Server options

interface HTTPReverseProxyOptions {
  port?: number
  host?: string
  proxyOptions?: ExtendedProxyOptions
  httpsOptions?: HttpsServerOptions
  clustered?: boolean | number
  letsEncryptOptions?: BaseLetsEncryptOptions
  preferForwardedHost?: boolean,
  log?: Logger
  stats?: Statistics
}

ExtendedProxyOptions | HttpsServerOptions | BaseLetsEncryptOptions

Option | Type | Default | Description --- | --- | --- | --- port | number | 80 | The inbound port used to listen for http connections. host | network-address | all | The network interface to listen for http connections. Defaults to all interfaces. This would only be used to force the system to listen on a single network. The format is a standard IPV4 or IPV6 network address. This has no relation to a host or hostname in a URL. proxyOptions | object | See below | Options passed to the node-http-proxy instance used by this package. A complete list of the options can be found here. Defaults below. httpsOptions | object | See below | The https interface options. clustered | boolean or number | false | If specified the system will run a number of individual monitored proxy processes. The master process will automatically restart any worker process that dies unexpectedly. If this option is a boolean true the number of worker processes will equal the number of cores on the processor. If this option is a number it is the number of worker processes to start. The minimum is 2 the maximum is 32 and is silently enforced. letsEncryptOptions | object | See below | The Let's Encrypt server options. preferForwardedHost | boolean | false | This is not normally set unless the proxy server is behind other proxies. When true the forwarded host (if one is specified) from the http header is used as the key to the routing table, otherwise it is the host field of the request. log | object | null | The logging element stats | object | null | An instance of a statitics class

HTTPS server options

interface HttpsServerOptions {
  port?: number
  certificates: Certificates | CertificateOptions
  host?: string
  keyFilename?: string
  certificateFilename?: string
  caFilename?: string
  httpsServerOptions?: https.ServerOptions
}

Option | Type | Default | Description ---|---|---|--- port | number | 443 | The inbound port used to listen for https connections certificates | object | See Below | Certificate object. host | network-address | http host | The network interface to listen for https connections. This would only be used to force the system to listen on a single network. The format is a standard IPV4 or IPV6 network address. This has no relation to a host or hostname in a URL. keyFilename | string | null | Optional path and file name for the default certificate private key. The default certificate is used when a https route does not specify key and certificate files or is not configured to use LetsEncrypt. This should be a PEM encoded private key file. certificateFilename | string | null | Optional path and file name for the default certificate file. This should be a PEM encoded certificate file. caFilename | string | null | Optional path and file name for the default certificate authority file. This should be a PEM encoded certificate authority file. httpsServerOptions | object | null | The set of options as specified by the node https create server found here.


Let's Encrypt Options

interface BaseLetsEncryptOptions {
  host?: string
  port?: number
  certificates?: Certificates
  dnsChallenge?: AbstractDNSUpdate
  dnsNameServer?: string
  log?: Logger
  stats?: Statistics
}

Certificates | AbstractDNSUpdate | Logger | Statistics

Option | Type | Default | Description ---|---|---|--- host | string | all | The network interface to listen for http connections. Defaults to all interfaces. This would only be used to force the system to listen on a single network. The format is a standard IPV4 or IPV6 network address. This has no relation to a host or hostname in a URL. port | number | 3000 | The inbound port used to listen for http connections for the LetsEncrypt local server. certificate | Certificates | httpOptions.certificates | The certificate store for theLetsEncrypt managed certificates. dnsChallenge | BaseDNSUpdate | null | For LetsEncrypt registrations that require the use of the dns-01 challenge (i.e. wildcard host names: *.qzqzqz.com) this is the implementation of the DNS challenge handler for the DNS service. If the challenge handler for the DNS service you use is not provided one must be written to access the DNS and add/remove the appropriate DNS TXT record. dnsNameServer | string | null | After writing the entry to the DNS table, the DNS challenge may verify the entry has been propagated within the cluster of name servers on the service before asking LetsEncrypt to look for it. log | object | null | The logging element stats | object | null | the Statistics element

DNS Update Options

The DNS update requires a targeted implementation for each DNS service. The initial release only supports GoDaddy. However, this should provide users with an understanding of the framework required to implement other interfaces.

To facilitate the implementation of other interfaces the DNS update is supported by an abstract base class:

export interface BaseDNSUpdateOptions{
  
  stats?: Statistics
  log?:Logger
}

Option | Type | Default | Description ---|---|---|--- stats | Statistics | null | A reference to the Statistics object. log | Logger | null | A reference to a logging object.

The DNS class exposes two abstract methods for managing the update:

  abstract async addAcmeChallengeToDNS (domain: string, challenge: string): Promise<boolean>
  abstract async removeAcmeChallengeFromDNS (domain: string): Promise<boolean>

addAcmeChallengeToDNS should add a DNS TXT record for the domain.

removeAcmeChallengeFromDNS should remove the TXT record.

The package contains a implementation of the DNS challenge for the GoDaddy DNS service. You can use this as a template for implementing the DNS challenge on other services.


DNS Update Using GoDaddy

The GoDaddy DNS update requires an APIKey and secret. These can be generated via the developer interface on GoDaddy

export interface GoDaddyDNSUpdateOptions extends BaseDNSUpdateOptions {

  APIKey: string
  secret: string
}

Option | Type | Default | Description ---|---|---|--- APIKey | string | none | The APIKey generated by GoDaddy secret | string | none | The secret generated by GoDaddy. This should not be published in your code.


Route Registration Options

export interface RouteRegistrationOptions {
  https?: RegistrationHttpsOptions,
  secureOutbound?: boolean
  useTargetHostHeader?: boolean
  stats?: Statistics
}

Option | Type | Default | Description ---|---|---|--- https | object | null | The specification of the front side (inbound) https connection. secureOutbound | boolean | false | Specifies the outbound connection should be secure (https) and the credentials should be checked. useTargetHostHeader | boolean | false | If true and the inbound http packet has an x-forwarded-host header the first element of the x-forwarded-host header is used as the host name. Otherwise the host header is used. This should be set to true if your proxy sits behind another proxy. stats | object | null | If not null (or undefined) the Statistics object will be used to keep track of the route statistics.


Route registration https options

export interface RegistrationHttpsOptions {
  redirectToHttps: boolean
  keyFilename?: string
  certificateFilename?: string
  caFilename?: string
  letsEncrypt?: RegistrationLetsEncryptOptions
}

Option | Type | Default | Description ---|---|---|--- redirectToHttps | boolean | none | If true http connections will be redirected to use the https connection. Otherwise the http connection will will be routed to the specified server. In normal https proxying this should be set to true. keyFilename | string | none | If the host for this route has a commercially generated certificate this should be the path and filename for the private key file for the certificate. This file reference is global. It is not relative to the certificateStoreRoot. certificateFilename | string | none | If the host for this route has a commercially generated certificate this should be the path and filename for the certificate file for the certificate. This file reference is global. It is not relative to the certificateStoreRoot. caFilename | string | none | If the host for this route has a commercially generated certificate this should be the path and filename for the certificate authority file for the certificate. This file reference is global. It is not relative to the certificateStoreRoot. letsEncrypt | object | none | An https route should have either a set of commercial certificate files (keyFilename, certificateFilename) or use letsEncrypt to generate the certificate pair.


Route registration Lets Encrypt options

export interface RegistrationLetsEncryptOptions {
  email: string
  production?: boolean,
  renewWithin?: number,
  forceRenew?: boolean
}

Option | Type | Default | Description ---|---|---|--- email | string | none | This is the email address that will be used to set up an account on the LetsEncrypt service. It must be a vaild email address. production | boolean | false | The LetsEncrypt service provides a testing/staging environment which allows you to verify you have everything configured correctly before you request a real certificate. The certificates generated in the staging environment will have the same issues in the browser as the self signed certificates. renewWithin | number | 30 | The LetsEncrypt certificates are valid for 90 days. They need to be renewed periodically. This value is the number of days prior to expiration a new certificate should be requested. forceRenew | boolean | false | If true a new certificate will always be requested at startup.

The proxy maintains timers to re-generate the request for a new certificate before they expire. If the request fails, it will wait a day and try again.


Certificates

certificateStoreRoot: string
log?: Logger
stats?: Statistics

Option | Type | Default | Description ---|---|---|--- certificateStoreRoot | string | none | This is the path (relative or fixed) to the root folder of the certificate files managed by LetsEncrypt. log | object | null | The logging element stats | object | null | The Statistics element


At a minimum this must specify the file path to the root of the certificate store. i.e.:

certificateStoreRoot: '../certificates'

This is the default value used by the https server if none is provided.

Statistics server options

export interface StatisticsServerOptions {
  stats: Statistics
  noStart?: boolean
  htmlFilename?: string
  http?: StatisticsServerHttpOptions
  websocket?: StatisticsServerWebsocketOptions
}

Option | Type | Default | Description ---|---|---|--- stats | object | none | The instance of the Statistics class maintaining the counts. noStart | boolean | false | When set to true the server must be started manually later in the startup process. htmlFilename | string | ./public/statisticsPage.html | The page served from the Statistics server. http | object | {port: 3001} | The configuration options for the http side of the statistics server. websocket | object | {interval: 5000} | The configuration options for the websocket side of the statistics server.


Statistics server http options

export interface StatisticsServerHttpOptions {
  host?: string
  port: number
}

Option | Type | Default | Description ---|---|---|--- host | network-interface | all | The network interface. port | number | 3001 | The inbound port used to listen for http connections.


Statistics server websocket options

export interface StatisticsServerWebsocketOptions {
  updateInterval?: number
  filter?: string[]
}

Option | Type | Default | Description ---|---|---|--- updateInterval | number | 5000 | The interval between updates being pushed from the statistics server to the web client, in milliseconds filter | string[] | none | A set of filters to limit the number of properties sent. Each filter is compared to the start of the name portion of the property. An exact match allows the property to be sent.

Logging server options

export interface LoggingServerOptions {
  host?: string
  port?: number
  htmlFilename?: string
  logLevel?: number
}

Option | Type | Default | Description ---|---|---|--- host | network-interface | all | The network interface. port | number | 3002 | The inbound port used to listen for http connections. htmlFilename | string | none | The page served from the Logging server. logLevel | number | 40 | The severity of the type of log message. 10 - debugging and above, 20 - tracing and above, 30 - info and above, 40 warnings and above, 50 - errors and above, 60 - Fatal errors only

Defaults

When no options are passed the following default options are used:

Default http options

port: 80,
proxyOptions: defaultProxyOptions,
httpsOptions: null,
preferForwardedHost: false,

Default proxy options

ntlm: false,
prependPath: false,
secure: true,

Default https options

port: 443,
certificates: {
  certificateStoreRoot: '../certificates'
}

Default letsEncrypt options

port: 3000

Default statistics server options

port = 3001
htmlFilename = './public/statisticsAndLoggingPage.html'
updateInterval = 5000

How it works

The server listens on the designated http port. This defaults to port 80. The server will also listen on the designated https port when it is configured with HttpsServerOptions. The port defaults to 443. Both servers will listen on all networks if a host is not specified.

When a request is received on either port, the system will determine the correct outbound server for the request. The process of specifying the relationship between the inbound request and the outbound request is handled via the addRoute method.

Each call to addRoute specifies an inbound host:port/url and an outbound host:port/url. The simplest route would be something like:

proxyServer.addRoute('myserver.mydomain.com', 'localhost:9000')

This would forward http requests with a host header of myserver.mydomain.com to the http server listening to port 9000 on the local machine. The url from the inbound request would be appended to the outbound request:

myserver.mydomain.com/login => localhost:9000/login

Adding a second route using the same inbound host would cause the proxy server to alternate requests between the servers:

proxyServer.addRoute('myserver.mydomain.com', 'localhost:9001')

This adds a second server (listening on a different port) to handle requests to 'myserver.mydomain.com'. The first request might go to the server on port 9000, the second to the server on port 9001, the third to the server on port 9000, etc.

The round robin forwarding is for each http request, not each page. There are frequently many http requests to retrieve the contents of a single page. There should be no expectation of the server choosen.

Both calls can be combined as follows:

proxyServer.addRoute('myserver.mydomain.com', ['localhost:9000', 'localhost:9001'])

There are practical limits to the number of target servers. A request to add a target server more than once will be silently ignored.

The load or responsibilities of the target servers can also be managed by routing some requests to one server and other requests to a different server:

proxyServer.addRoute('myserver.mydomain.com', 'localhost:9000')
proxyServer.addRoute('myserver.mydomain.com/api', 'localhost:9001')

This configuration would route requests with '/api' as the root of the inbound url to go to the server on port 9001. All other requests would go to the server on port 9000. The url forwarded to the server on port 9001 would have the root ('/api') removed:

myserver.mydomain.com/api/getusers => localhost:9001/getusers

The target server can also specify a base route:

proxyServer.addRoute('myserver.mydomain.com', 'localhost:9001/api')

The resulting requests would have the root ('/api') prepended to the url received by the server at port 9001

myserver.mydomain.com/getusers => localhost:9001/api/getusers

Urls can be on both sides of the route specification:

proxyServer.addRoute('myserver.mydomain.com/api', 'localhost:9001/apihandler')
myserver.mydomain.com/api/getusers => localhost:9001/apihandler/getusers

Acknowledgements

The node team continues to do exceptional work.

The core of the proxy system is provided by http-proxy. It is a solid package.

The acme-client provides the Lets Encrypt interface. Again, this is a solid piece of work.

The certificate and encryption tools are provided by forge. These people are deep in the weeds.

I got the idea for this from redbird. This is a great http reverse proxy implementation. I just wanted one coded in TypeScript.

Then there is TypeScript. If you don't use it, you should try. It attempts to bring the past 50 years of programming language development to the JavaScript world.

I work exclusively in vsCode. Pretty close to perfect.