reverse-proxy-middleware 0.0.145 → 0.0.147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md CHANGED
@@ -7,11 +7,13 @@ License
7
7
  -------
8
8
 
9
9
  This library written by Torben Sickert stands under a creative commons naming
10
- 3.0 unported license. See https://creativecommons.org/licenses/by/3.0/deed.de
10
+ 4.0 international license.
11
+ See https://creativecommons.org/licenses/by/4.0/deed.de
11
12
  endregion -->
12
13
 
13
14
  <!--|deDE:Projektstatus-->
14
- Project Status
15
+ <!--|frFR:Statut du projet-->
16
+ Project status
15
17
  --------------
16
18
 
17
19
  [![npm](https://img.shields.io/npm/v/reverse-proxy-middleware?color=%23d55e5d&label=npm%20package%20version&logoColor=%23d55e5d&style=for-the-badge)](https://www.npmjs.com/package/reverse-proxy-middleware)
@@ -31,16 +33,38 @@ Project Status
31
33
  [![deploy web documentation](https://img.shields.io/github/actions/workflow/status/thaibault/reverse-proxy-middleware/deploy-web-documentation.yaml?label=deploy%20web%20documentation&style=for-the-badge)](https://github.com/thaibault/reverse-proxy-middleware/actions/workflows/deploy-web-documentation.yaml)
32
34
  [![web documentation](https://img.shields.io/website-up-down-green-red/https/tsickert.com/reverse-proxy-middleware.svg?label=web-documentation&style=for-the-badge)](https://tsickert.com/reverse-proxy-middleware)
33
35
 
34
- <!--|deDE:Verwendung-->
36
+ <!--|deDE:Einsatzmöglichkeiten-->
37
+ <!--|frFR:Cas d'utilisation-->
35
38
  Use case
36
39
  --------
37
40
 
38
- Easy and dynamically configurable reverse proxy. This tool can act as a
39
- middleware to check incoming requests via external bot filtering services such
40
- as ReCaptcha or friendlycaptcha.
41
- Adding state via session token with external authentication apis is possible as
42
- applying any external api given data to the incoming and conditionally
43
- forwarded requests.
41
+ A small reverse proxy configured with a single JSON file. Each incoming request
42
+ is passed on to one of the configured backends ("forwarders"). Before that, the
43
+ proxy can ask third party services ("state APIs"), e.g. a bot filter like
44
+ reCAPTCHA or friendlycaptcha or an authentication service, and answer the
45
+ request itself depending on their responses. Request and response headers can
46
+ be rewritten on the way through. All decisions are short JavaScript
47
+ expressions inside the configuration.
48
+ <!--deDE:
49
+ Ein kleiner Reverse-Proxy, der über eine einzige JSON-Datei konfiguriert
50
+ wird. Jede eingehende Anfrage wird an eines der konfigurierten Backends
51
+ ("Forwarder") weitergereicht. Vorher kann der Proxy Dienste von
52
+ Drittanbietern ("State-APIs") befragen, z. B. einen Bot-Filter wie
53
+ reCAPTCHA oder friendlycaptcha oder einen Authentifizierungsdienst, und
54
+ die Anfrage abhängig von deren Antworten selbst beantworten. Anfrage- und
55
+ Antwort-Header lassen sich unterwegs umschreiben. Alle Entscheidungen
56
+ sind kurze JavaScript-Ausdrücke in der Konfiguration.
57
+ -->
58
+ <!--frFR:
59
+ Un petit proxy inverse configuré par un seul fichier JSON. Chaque requête
60
+ entrante est transmise à l'un des backends configurés ("forwarders").
61
+ Auparavant, le proxy peut interroger des services tiers ("state APIs"),
62
+ par exemple un filtre anti-bots comme reCAPTCHA ou friendlycaptcha ou un
63
+ service d'authentification, et répondre lui-même à la requête selon
64
+ leurs réponses. Les en-têtes de requête et de réponse peuvent être
65
+ réécrits au passage. Toutes les décisions sont de courtes expressions
66
+ JavaScript dans la configuration.
67
+ -->
44
68
 
45
69
  <div class="wd-table-of-contents">
46
70
  <h2 id="content">Content<!--deDE:Inhalt--><!--frFR:Contenu--></h2>
@@ -50,416 +74,582 @@ forwarded requests.
50
74
  Installation
51
75
  ------------
52
76
 
53
- You can install via package manager, simply download the compiled version as
54
- zip file here and inject or request via CDN in HTML:
77
+ Install the command globally via npm and start it in the folder which holds
78
+ your "configuration.json":
55
79
  <!--deDE:
56
- Sie können das Paket über den Paketmanager installieren oder einfach die
57
- kompilierte Version als ZIP-Datei hier herunterladen und in HTML einbinden
58
- oder über ein CDN abrufen:
80
+ Installieren Sie den Befehl global über npm und starten Sie ihn in dem
81
+ Ordner, der Ihre "configuration.json" enthält:
59
82
  -->
60
83
  <!--frFR:
61
- Vous pouvez installer le paquet via le gestionnaire de paquets ou
62
- simplement télécharger ici la version compilée sous forme de fichier ZIP,
63
- puis l'intégrer dans une page HTML ou la récupérer via un CDN:
84
+ Installez la commande globalement via npm et lancez-la dans le dossier
85
+ qui contient votre "configuration.json":
64
86
  -->
65
87
 
66
88
  ```bash
67
- npm install reverse-proxy-middleware
89
+ npm install --global reverse-proxy-middleware
90
+ reverse-proxy-middleware
68
91
  ```
69
92
 
70
- <!--|deDE:Beispiel-->
71
- <!--|frFR:Exemple-->
72
- Example
73
- -------
74
-
75
- ### Simple forwarding
76
-
77
- Simple reverse proxy request from `http://localhost:8080` to
78
- `https://www.google.com` without modifying the entire request.
93
+ Or run the container image and mount the configuration into it:
94
+ <!--deDE:
95
+ Oder starten Sie das Container-Image und binden Sie die Konfiguration
96
+ ein:
97
+ -->
98
+ <!--frFR:Ou lancez l'image de conteneur et montez-y la configuration:-->
79
99
 
80
- ```
81
- {
82
- "forwarders": {
83
- "google": {
84
- "host": "www.google.com"
85
- }
86
- }
87
- }
100
+ ```bash
101
+ docker run \
102
+ --name reverse-proxy-middleware \
103
+ --publish 8080:8080 \
104
+ --rm \
105
+ --volume "$(pwd)/configuration.json:/application/configuration.json" \
106
+ ghcr.io/thaibault/reverse-proxy-middleware:latest
88
107
  ```
89
108
 
90
- Since the proxy starts at localhost on port 8080 as a default configuration you
91
- can check the configuration via a simple curl command:
109
+ "systemd.service" in this repository is a unit which runs the container in
110
+ the Docker network "proxy" (e.g. behind the
111
+ [proxy](https://tsickert.com/proxy) project) and reads its configuration from
112
+ "/srv/http/reverse-proxy-middleware/configuration.json".
113
+ <!--deDE:
114
+ "systemd.service" in diesem Repository ist eine Unit, die den Container
115
+ im Docker-Netzwerk "proxy" betreibt (z. B. hinter dem Projekt
116
+ [proxy](https://tsickert.com/proxy)) und ihre Konfiguration aus
117
+ "/srv/http/reverse-proxy-middleware/configuration.json" liest.
118
+ -->
119
+ <!--frFR:
120
+ "systemd.service" dans ce dépôt est une unité qui exécute le conteneur
121
+ dans le réseau Docker "proxy" (par exemple derrière le projet
122
+ [proxy](https://tsickert.com/proxy)) et lit sa configuration depuis
123
+ "/srv/http/reverse-proxy-middleware/configuration.json".
124
+ -->
92
125
 
93
- ```curl --verbose http://localhost:8080```
126
+ Configuration
127
+ -------------
94
128
 
95
- Behind there are a some commonly use defaults configured under key
96
- "configuration" in [package.json](package.json). Please have a look.
129
+ On start, the defaults under the key "configuration" in
130
+ [package.json](package.json) are extended by "configuration.json" and then by
131
+ "secure-configuration.json" from the current working directory. Both files
132
+ are optional, the second one is meant for secrets. The top level options are:
133
+ <!--deDE:
134
+ Beim Start werden die Standardwerte unter dem Schlüssel "configuration"
135
+ in [package.json](package.json) durch "configuration.json" und danach
136
+ durch "secure-configuration.json" aus dem aktuellen Arbeitsverzeichnis
137
+ erweitert. Beide Dateien sind optional, die zweite ist für Geheimnisse
138
+ gedacht. Die Optionen der obersten Ebene sind:
139
+ -->
140
+ <!--frFR:
141
+ Au démarrage, les valeurs par défaut sous la clé "configuration" dans
142
+ [package.json](package.json) sont étendues par "configuration.json" puis
143
+ par "secure-configuration.json" du répertoire de travail courant. Les
144
+ deux fichiers sont facultatifs, le second est destiné aux secrets. Les
145
+ options de premier niveau sont:
146
+ -->
97
147
 
98
- ### Distributing requests to different backends
148
+ - "port" (default: the environment variable "PORT" or "8080"): where the proxy listens.<!--deDE:"port" (Standard: die Umgebungsvariable "PORT" oder "8080"): wo der Proxy lauscht.--><!--frFR:"port" (par défaut: la variable d'environnement "PORT" ou "8080"): où le proxy écoute.-->
149
+ - "host" (default empty, meaning all network interfaces): the address to listen on, e.g. "127.0.0.1".<!--deDE:"host" (Standard leer, also alle Netzwerkschnittstellen): die Adresse, auf der gelauscht wird, z. B. "127.0.0.1".--><!--frFR:"host" (vide par défaut, donc toutes les interfaces réseau): l'adresse d'écoute, par exemple "127.0.0.1".-->
150
+ - "forwarders": the backends, see below.<!--deDE:"forwarders": die Backends, siehe unten.--><!--frFR:"forwarders": les backends, voir ci-dessous.-->
151
+ - "parseBody" (default "false"): parses JSON request bodies for expressions.<!--deDE:"parseBody" (Standard "false"): wertet JSON-Anfragekörper für Ausdrücke aus.--><!--frFR:"parseBody" (par défaut "false"): analyse les corps de requête JSON pour les expressions.-->
152
+ - "logLevel" (default "info"): one of "debug", "info", "warn", "error" and "critical". "debug" logs every forwarded header.<!--deDE:"logLevel" (Standard "info"): einer von "debug", "info", "warn", "error" und "critical". "debug" protokolliert jeden weitergeleiteten Header.--><!--frFR:"logLevel" (par défaut "info"): l'un de "debug", "info", "warn", "error" et "critical". "debug" journalise chaque en-tête transmis.-->
153
+ - "publicKeyPath" and "privateKeyPath": certificate and key files which switch the proxy to HTTPS, see below.<!--deDE:"publicKeyPath" und "privateKeyPath": Zertifikats- und Schlüsseldatei, die den Proxy auf HTTPS umstellen, siehe unten.--><!--frFR:"publicKeyPath" et "privateKeyPath": fichiers de certificat et de clé qui font passer le proxy en HTTPS, voir ci-dessous.-->
154
+ - "nodeServerOptions": further options of the HTTPS server, see [Node.js](https://nodejs.org/api/https.html#httpscreateserveroptions-requestlistener).<!--deDE:"nodeServerOptions": weitere Optionen des HTTPS-Servers, siehe [Node.js](https://nodejs.org/api/https.html#httpscreateserveroptions-requestlistener).--><!--frFR:"nodeServerOptions": autres options du serveur HTTPS, voir [Node.js](https://nodejs.org/api/https.html#httpscreateserveroptions-requestlistener).-->
99
155
 
100
- Here is how to distribute incoming requests randomly between google and bing:
156
+ ### HTTPS
101
157
 
102
- ```
158
+ With "publicKeyPath" and "privateKeyPath" set, the proxy only accepts HTTPS
159
+ connections. Relative paths start at the working directory, the files are
160
+ read on start:
161
+ <!--deDE:
162
+ Sind "publicKeyPath" und "privateKeyPath" gesetzt, akzeptiert der Proxy
163
+ nur HTTPS-Verbindungen. Relative Pfade beginnen im Arbeitsverzeichnis,
164
+ die Dateien werden beim Start gelesen:
165
+ -->
166
+ <!--frFR:
167
+ Lorsque "publicKeyPath" et "privateKeyPath" sont définis, le proxy
168
+ n'accepte que des connexions HTTPS. Les chemins relatifs partent du
169
+ répertoire de travail, les fichiers sont lus au démarrage:
170
+ -->
171
+
172
+ ```JSON
103
173
  {
104
- "forwarders": {
105
- "bing": {
106
- "host": "www.bing.com",
107
- "useExpression": "Math.random() < 0.5"
108
- },
109
-
110
- "google": {
111
- "host": "www.google.com"
112
- }
113
- }
174
+ "publicKeyPath": "certificate.pem",
175
+ "privateKeyPath": "key.pem"
114
176
  }
115
177
  ```
116
178
 
117
- Forwarders are iterated in alphabetical order of their name and `useExpression`
118
- are getting evaluated. Since "google" is always "used" it depends on the random
119
- outcome of bing's expression if it is beeing used or not.
120
-
121
- Since this proxy just streams the whole request through it could be used as
122
- basic load balancer with this configuration.
179
+ Encrypted connections always use HTTP/1.1. Certificate and key end up in
180
+ "nodeServerOptions" as "cert" and "key" unless these are given there already.
181
+ <!--deDE:
182
+ Verschlüsselte Verbindungen nutzen immer HTTP/1.1. Zertifikat und
183
+ Schlüssel landen als "cert" und "key" in "nodeServerOptions", sofern
184
+ diese dort nicht schon angegeben sind.
185
+ -->
186
+ <!--frFR:
187
+ Les connexions chiffrées utilisent toujours HTTP/1.1. Le certificat et
188
+ la clé sont placés dans "nodeServerOptions" sous "cert" et "key", sauf
189
+ s'ils y sont déjà indiqués.
190
+ -->
123
191
 
124
- ### Rewriting headers for underlying backend
192
+ <!--|deDE:Weiterleitung-->
193
+ <!--|frFR:Redirection-->
194
+ ### Simple forwarding
125
195
 
126
- Headers can be replaced in both directions. Client-Request to forward or
127
- retrieved responses given from configured backend:
196
+ This configuration passes every request on "http://localhost:8080" to
197
+ "https://example.com" unchanged:
198
+ <!--deDE:
199
+ Diese Konfiguration reicht jede Anfrage an "http://localhost:8080"
200
+ unverändert an "https://example.com" weiter:
201
+ -->
202
+ <!--frFR:
203
+ Cette configuration transmet telle quelle chaque requête sur
204
+ "http://localhost:8080" à "https://example.com":
205
+ -->
128
206
 
129
- ```
207
+ ```JSON
130
208
  {
131
- "forwarders": {
132
- "bing": {
133
- "host": "www.bing.com",
134
-
135
- "headerTransformations": {
136
- "send": {
137
- "source": "/X-Special-Client-Header-Name: (.+)/gi",
138
- "target": "'New-Header-Name: $1'"
139
- },
140
-
141
- "retrieve": {
142
- "source": "/set-cookie: (.+)/gi",
143
- "target": "'X-Prevented-Cookie: $1'"
209
+ "forwarders": {
210
+ "example": {
211
+ "host": "example.com"
144
212
  }
145
- }
146
213
  }
147
- }
148
214
  }
149
215
  ```
150
216
 
151
- Test via:
152
-
153
- ```
154
- curl \
155
- --header 'X-Special-Client-Header-Name: value' \
156
- --verbose \
157
- http://localhost:8080 \
158
- 1>/dev/null
217
+ ```bash
218
+ curl --verbose http://localhost:8080
159
219
  ```
160
220
 
161
- You should see a lot of cookie header ("set-cookie: ..." replaced by
162
- "X-Prevented-Cookie: ...".
163
- Note that muting the standart output ("1>/dev/null") enables you to focus on
164
- retrieved headers printed via secondary error output.
221
+ A forwarder has the following options, the defaults come from the special
222
+ forwarder "base":
223
+ <!--deDE:
224
+ Ein Forwarder hat die folgenden Optionen, die Standardwerte stammen vom
225
+ besonderen Forwarder "base":
226
+ -->
227
+ <!--frFR:
228
+ Un forwarder possède les options suivantes, les valeurs par défaut
229
+ viennent du forwarder spécial "base":
230
+ -->
165
231
 
166
- ### State-APIs
232
+ - "host" (default "localhost"), "port" (default "443") and "tls" (default "true"): the backend.<!--deDE:"host" (Standard "localhost"), "port" (Standard "443") und "tls" (Standard "true"): das Backend.--><!--frFR:"host" (par défaut "localhost"), "port" (par défaut "443") et "tls" (par défaut "true"): le backend.-->
233
+ - "rejectUnauthorized" (default "false"): set it to "true" to only accept backends with a valid certificate.<!--deDE:"rejectUnauthorized" (Standard "false"): auf "true" setzen, um nur Backends mit gültigem Zertifikat zu akzeptieren.--><!--frFR:"rejectUnauthorized" (par défaut "false"): le mettre à "true" pour n'accepter que les backends ayant un certificat valide.-->
234
+ - "useExpression" (default "true"): decides whether this forwarder takes the request.<!--deDE:"useExpression" (Standard "true"): entscheidet, ob dieser Forwarder die Anfrage übernimmt.--><!--frFR:"useExpression" (par défaut "true"): décide si ce forwarder prend la requête.-->
235
+ - "headerTransformations": rewrites headers, see below.<!--deDE:"headerTransformations": schreibt Header um, siehe unten.--><!--frFR:"headerTransformations": réécrit les en-têtes, voir ci-dessous.-->
236
+ - "stateAPIs": requests to third party services, see below.<!--deDE:"stateAPIs": Anfragen an Dienste von Drittanbietern, siehe unten.--><!--frFR:"stateAPIs": requêtes vers des services tiers, voir ci-dessous.-->
167
237
 
168
- State-APIs enables you to conditionally trigger requests to third party
169
- endpoints and use responses for further decisions how to proceed.
170
- When a state api is used for a specified request that response can be used via
171
- expressions to transform subsequent api requests, decide which backend to use
172
- or transform the final backend request.
238
+ For a plain HTTP backend set "tls" to "false" and "port" to e.g. "80".
239
+ <!--deDE:
240
+ Für ein reines HTTP-Backend setzen Sie "tls" auf "false" und "port" z.
241
+ B. auf "80".
242
+ -->
243
+ <!--frFR:
244
+ Pour un backend HTTP simple, mettez "tls" à "false" et "port" par
245
+ exemple à "80".
246
+ -->
173
247
 
174
- In the following example you can see how a state api is configured to catch
175
- specific request to deal with. The last running pre expressions which results
176
- in a boolean value will trigger if the referenced state api should be used for
177
- given request.
248
+ <!--|deDE:Anfragen auf mehrere Backends verteilen-->
249
+ <!--|frFR:Répartir les requêtes sur plusieurs backends-->
250
+ ### Distributing requests
178
251
 
179
- Here is an example:
252
+ Forwarders are tried in alphabetical order of their names. The first one whose
253
+ "useExpression" results in "true" gets the request. This distributes requests
254
+ randomly between two backends, like a basic load balancer:
255
+ <!--deDE:
256
+ Forwarder werden in alphabetischer Reihenfolge ihrer Namen geprüft. Der
257
+ erste, dessen "useExpression" "true" ergibt, erhält die Anfrage. So
258
+ werden Anfragen zufällig auf zwei Backends verteilt, wie bei einem
259
+ einfachen Load-Balancer:
260
+ -->
261
+ <!--frFR:
262
+ Les forwarders sont essayés dans l'ordre alphabétique de leurs noms. Le
263
+ premier dont "useExpression" donne "true" reçoit la requête. Ainsi, les
264
+ requêtes sont réparties aléatoirement entre deux backends, comme avec un
265
+ simple répartiteur de charge:
266
+ -->
180
267
 
181
- ```
268
+ ```JSON
182
269
  {
183
- "forwarders": {
184
- "bing": {
185
- "host": "www.bing.com",
186
-
187
- "stateAPIs": {
188
- "name": "sense",
189
- "url": "https://www.google.com/search?q=${data.question}",
190
-
191
- "data": {
192
- "question": "What is the meaning of life?"
270
+ "forwarders": {
271
+ "first": {
272
+ "host": "first.example.com",
273
+ "useExpression": "Math.random() < 0.5"
193
274
  },
194
-
195
- "expressions": {
196
- "pre": "request.headers['ask-for-sense-of-live'] ? true : null",
197
- "post": "response.statusCode >= 200 && response.statusCode < 300 ? null : response.statusCode"
275
+ "second": {
276
+ "host": "second.example.com"
198
277
  }
199
- }
200
278
  }
201
- }
202
279
  }
203
280
  ```
204
281
 
205
- What is going on here?
206
- We generally forward requests to "www.bing.com" but if there is a header called
207
- "ask-for-sense-of-live" present in client request we will first ask google for
208
- the meaning of life. If google answers with a "negative" response code not
209
- between 200 and 300 we will just do the final forwarding to bing. If google
210
- responses in a positive manner the resulting status code we will be used for
211
- answering client and no forwarding to "www.bing.com" happens.
212
-
213
- Here es a test curl command:
214
-
215
- ```
216
- curl \
217
- --header 'ask-for-sense-of-live: value' \
218
- --verbose \
219
- http://localhost:8080 \
220
- 1>/dev/null
221
- ```
222
-
223
- This should result in a simple response but:
224
-
225
- ```curl --verbose http://localhost:8080 1>/dev/null```
226
-
227
- will finally request "www.bing.com".
228
-
229
- Pre and post evaluations can have various results. The meanings of them are
230
- described here:
231
-
232
- ### Pre-Evaluation Results
233
-
234
- | Result | Meaning |
235
- |-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
236
- | break (string) | Do not evaluate subsequent pre evaluations. |
237
- | null or undefined | Just jump to the next evaluation to run. |
238
- | true (boolean) | Use this state api configuration. Run the configured request. |
239
- | false (boolean) | Do not use this state api and to not run subsequent pre evaluations. |
240
- | code (number) | Answer client request with provided http status code and do not run any subsequent pre-evaluations, state-api request or request forwarding to the underlying backend. |
241
-
242
- ### Post-Evaluation Results
243
-
244
- | Result | Meaning |
245
- |-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
246
- | break (string) | Do not evaluate subsequent post evaluations. |
247
- | null or undefined | Just jump to the next evaluation to run. |
248
- | code (number) | Answer client request with provided http status code and do not run any subsequent post-evaluations, state-api request or request forwarding to the underlying backend. |
282
+ Expressions can also route by path, e.g.
283
+ "request.url.startsWith('/api/')". If no forwarder matches, the client gets
284
+ status code "502".
285
+ <!--deDE:
286
+ Ausdrücke können auch nach Pfad weiterleiten, z. B.
287
+ "request.url.startsWith('/api/')". Passt kein Forwarder, erhält der Client
288
+ den Statuscode "502".
289
+ -->
290
+ <!--frFR:
291
+ Les expressions peuvent aussi aiguiller selon le chemin, par exemple
292
+ "request.url.startsWith('/api/')". Si aucun forwarder ne correspond, le
293
+ client reçoit le code de statut "502".
294
+ -->
249
295
 
250
- ### Smart configurations
296
+ <!--|deDE:Header umschreiben-->
297
+ <!--|frFR:Réécrire les en-têtes-->
298
+ ### Rewriting headers
251
299
 
252
- Whenever you can configure list of items you can either use just one or a list
253
- of them. Consider this configuration example:
300
+ "send" transformations apply to the request header sent to the backend,
301
+ "retrieve" transformations to the response header sent back to the client.
302
+ "source" is an expression resulting in a regular expression or string to
303
+ search for, "target" one resulting in its replacement:
304
+ <!--deDE:
305
+ "send"-Transformationen gelten für den Anfrage-Header, der an das Backend
306
+ geht, "retrieve"-Transformationen für den Antwort-Header, der zum Client
307
+ zurückgeht. "source" ist ein Ausdruck, der einen regulären Ausdruck oder
308
+ eine zu suchende Zeichenkette ergibt, "target" einer, der deren Ersatz
309
+ ergibt:
310
+ -->
311
+ <!--frFR:
312
+ Les transformations "send" s'appliquent à l'en-tête de requête envoyé au
313
+ backend, les transformations "retrieve" à l'en-tête de réponse renvoyé au
314
+ client. "source" est une expression donnant une expression régulière ou
315
+ une chaîne à rechercher, "target" une expression donnant son
316
+ remplacement:
317
+ -->
254
318
 
255
- ```
319
+ ```JSON
256
320
  {
257
- "forwarders": {
258
- "bing": {
259
- "host": "www.bing.com",
260
-
261
- "headerTransformations": [{...}, {...}, ...],
262
-
263
- "stateAPIs": {
264
- "expressions": {
265
- "pre": ["...", "...", ...],
266
- "post": ["...", "...", ...]
321
+ "forwarders": {
322
+ "example": {
323
+ "host": "example.com",
324
+ "headerTransformations": {
325
+ "send": {
326
+ "source": "/X-Special-Client-Header-Name: (.+)/gi",
327
+ "target": "'New-Header-Name: $1'"
328
+ },
329
+ "retrieve": {
330
+ "source": "/set-cookie: (.+)/gi",
331
+ "target": "'X-Prevented-Cookie: $1'"
332
+ }
333
+ }
267
334
  }
268
- }
269
335
  }
270
- }
271
336
  }
272
337
  ```
273
338
 
274
- If only one item is needed please consider that:
275
-
339
+ ```bash
340
+ curl \
341
+ --header 'X-Special-Client-Header-Name: value' \
342
+ --output /dev/null \
343
+ --verbose \
344
+ http://localhost:8080
276
345
  ```
277
- {
278
- "forwarders": {
279
- "bing": {
280
- "host": "www.bing.com",
281
346
 
282
- "headerTransformations": [{...}],
347
+ The backend gets "New-Header-Name: value" and every "set-cookie" header of the
348
+ response arrives as "X-Prevented-Cookie". A transformation without "source"
349
+ adds the header given by "target", e.g.
350
+ "{\"target\": \"'X-Forwarded-By: proxy'\"}". The "host" header is always
351
+ replaced by the backend's host.
352
+ <!--deDE:
353
+ Das Backend erhält "New-Header-Name: value" und jeder
354
+ "set-cookie"-Header der Antwort kommt als "X-Prevented-Cookie" an. Eine
355
+ Transformation ohne "source" fügt den in "target" angegebenen Header
356
+ hinzu, z. B. "{\"target\": \"'X-Forwarded-By: proxy'\"}". Der
357
+ "host"-Header wird immer durch den Host des Backends ersetzt.
358
+ -->
359
+ <!--frFR:
360
+ Le backend reçoit "New-Header-Name: value" et chaque en-tête
361
+ "set-cookie" de la réponse arrive sous la forme "X-Prevented-Cookie".
362
+ Une transformation sans "source" ajoute l'en-tête donné par "target",
363
+ par exemple "{\"target\": \"'X-Forwarded-By: proxy'\"}". L'en-tête
364
+ "host" est toujours remplacé par l'hôte du backend.
365
+ -->
366
+
367
+ ### State APIs
283
368
 
284
- "stateAPIs": {
285
- "expressions": {
286
- "pre": ["..."],
287
- "post": ["..."]
369
+ A state API is a request to a third party service which runs before the
370
+ request is forwarded. Its "pre" expressions decide whether it runs, its
371
+ "post" expressions evaluate its response. This forwarder only passes requests
372
+ on if an authentication service accepts their token:
373
+ <!--deDE:
374
+ Eine State-API ist eine Anfrage an einen Dienst eines Drittanbieters,
375
+ die vor dem Weiterleiten der Anfrage läuft. Ihre "pre"-Ausdrücke
376
+ entscheiden, ob sie läuft, ihre "post"-Ausdrücke werten ihre Antwort
377
+ aus. Dieser Forwarder reicht Anfragen nur weiter, wenn ein
378
+ Authentifizierungsdienst ihr Token akzeptiert:
379
+ -->
380
+ <!--frFR:
381
+ Une state API est une requête vers un service tiers qui s'exécute avant
382
+ la transmission de la requête. Ses expressions "pre" décident si elle
383
+ s'exécute, ses expressions "post" évaluent sa réponse. Ce forwarder ne
384
+ transmet les requêtes que si un service d'authentification accepte leur
385
+ jeton:
386
+ -->
387
+
388
+ ```JSON
389
+ {
390
+ "forwarders": {
391
+ "example": {
392
+ "host": "example.com",
393
+ "stateAPIs": {
394
+ "name": "authentication",
395
+ "urlExpression": "`https://auth.example.com/check?token=${encodeURIComponent(request.headers['x-token'])}`",
396
+ "options": {"headers": {"accept": "application/json"}},
397
+ "expressions": {
398
+ "pre": "request.headers['x-token'] ? true : 401",
399
+ "post": "stateAPI.response?.ok ? null : 403"
400
+ }
401
+ }
288
402
  }
289
- }
290
403
  }
291
- }
292
404
  }
293
405
  ```
294
406
 
295
- is equivalent to:
407
+ Requests without an "X-Token" header are answered with "401", requests whose
408
+ token the service rejects with "403", all others reach "example.com". The
409
+ service is requested via
410
+ [fetch](https://developer.mozilla.org/docs/Web/API/fetch) with the given
411
+ "options". "url" sets a fixed address instead of
412
+ "urlExpression".
413
+ <!--deDE:
414
+ Anfragen ohne "X-Token"-Header werden mit "401" beantwortet, Anfragen,
415
+ deren Token der Dienst ablehnt, mit "403", alle anderen erreichen
416
+ "example.com". Der Dienst wird über
417
+ [fetch](https://developer.mozilla.org/docs/Web/API/fetch) mit den
418
+ angegebenen "options" angefragt. "url" setzt statt "urlExpression" eine
419
+ feste Adresse.
420
+ -->
421
+ <!--frFR:
422
+ Les requêtes sans en-tête "X-Token" reçoivent "401", celles dont le
423
+ service rejette le jeton "403", toutes les autres atteignent
424
+ "example.com". Le service est interrogé via
425
+ [fetch](https://developer.mozilla.org/docs/Web/API/fetch) avec les
426
+ "options" données. "url" définit une adresse fixe à la place de
427
+ "urlExpression".
428
+ -->
296
429
 
297
- ```
298
- {
299
- "forwarders": {
300
- "bing": {
301
- "host": "www.bing.com",
430
+ "pre" and "post" accept a single expression or a list, which runs in order.
431
+ The result of each "pre" expression means:
432
+ <!--deDE:
433
+ "pre" und "post" akzeptieren einen einzelnen Ausdruck oder eine Liste,
434
+ die der Reihe nach läuft. Das Ergebnis jedes "pre"-Ausdrucks bedeutet:
435
+ -->
436
+ <!--frFR:
437
+ "pre" et "post" acceptent une seule expression ou une liste, exécutée
438
+ dans l'ordre. Le résultat de chaque expression "pre" signifie:
439
+ -->
302
440
 
303
- "headerTransformations": {...},
441
+ - "true": run the request of this state API.<!--deDE:"true": die Anfrage dieser State-API ausführen.--><!--frFR:"true": exécuter la requête de cette state API.-->
442
+ - "false": skip this state API and its remaining "pre" expressions.<!--deDE:"false": diese State-API und ihre übrigen "pre"-Ausdrücke überspringen.--><!--frFR:"false": ignorer cette state API et ses expressions "pre" restantes.-->
443
+ - "'break'": stop evaluating "pre" expressions.<!--deDE:"'break'": keine weiteren "pre"-Ausdrücke auswerten.--><!--frFR:"'break'": arrêter d'évaluer les expressions "pre".-->
444
+ - "null" or "undefined": continue with the next expression.<!--deDE:"null" oder "undefined": mit dem nächsten Ausdruck fortfahren.--><!--frFR:"null" ou "undefined": continuer avec l'expression suivante.-->
445
+ - a number: answer the client with this status code and stop.<!--deDE:eine Zahl: dem Client mit diesem Statuscode antworten und abbrechen.--><!--frFR:un nombre: répondre au client avec ce code de statut et s'arrêter.-->
304
446
 
305
- "stateAPIs": {
306
- "expressions": {
307
- "pre": "...",
308
- "post": "..."
309
- }
310
- }
311
- }
312
- }
313
- }
314
- ```
447
+ "post" expressions know the same results except "true" and "false". If no
448
+ expression answers the client, the next state API runs and finally the
449
+ request is forwarded.
450
+ <!--deDE:
451
+ "post"-Ausdrücke kennen dieselben Ergebnisse außer "true" und "false".
452
+ Beantwortet kein Ausdruck den Client, läuft die nächste State-API und
453
+ schließlich wird die Anfrage weitergeleitet.
454
+ -->
455
+ <!--frFR:
456
+ Les expressions "post" connaissent les mêmes résultats sauf "true" et
457
+ "false". Si aucune expression ne répond au client, la state API suivante
458
+ s'exécute et la requête est finalement transmise.
459
+ -->
315
460
 
316
- ### Use environment variables
461
+ <!--|deDE:Basis-Konfigurationen-->
462
+ <!--|frFR:Configurations de base-->
463
+ ### Base configurations
317
464
 
318
- While some configuration values are interpret as expression to be evalued at
319
- runtime e.g. to decide which endpoint to use:
465
+ Every forwarder inherits from the forwarder "base", and every state API of a
466
+ forwarder inherits from its state API named "base". This configuration
467
+ forwards to two backends under a sub path and asks one service with
468
+ different data depending on the request:
469
+ <!--deDE:
470
+ Jeder Forwarder erbt vom Forwarder "base" und jede State-API eines
471
+ Forwarders von dessen State-API namens "base". Diese Konfiguration
472
+ leitet an zwei Backends unter einem Unterpfad weiter und befragt einen
473
+ Dienst je nach Anfrage mit unterschiedlichen Daten:
474
+ -->
475
+ <!--frFR:
476
+ Chaque forwarder hérite du forwarder "base", et chaque state API d'un
477
+ forwarder hérite de sa state API nommée "base". Cette configuration
478
+ transmet vers deux backends sous un sous-chemin et interroge un service
479
+ avec des données différentes selon la requête:
480
+ -->
320
481
 
321
- ```
482
+ ```JSON
322
483
  {
323
- "forwarders": {
324
- "endpoint": {
325
- ...
326
- "useExpression": "..."
484
+ "forwarders": {
485
+ "base": {
486
+ "headerTransformations": {
487
+ "send": {
488
+ "source": "/^(GET|POST) \\/(.*) (HTTP.*)$/m",
489
+ "target": "'$1 /sub/path/$2 $3'"
490
+ }
491
+ }
492
+ },
493
+ "first": {
494
+ "host": "first.example.com",
495
+ "useExpression": "Math.random() < 0.5"
496
+ },
497
+ "second": {
498
+ "host": "second.example.com",
499
+ "stateAPIs": [
500
+ {
501
+ "name": "base",
502
+ "urlExpression": "`https://service.example.com/?q=${data.query}`",
503
+ "expressions": {
504
+ "pre": "request.headers[data.header] ? true : null",
505
+ "post": "stateAPI.response?.data?.allowed ? null : 403"
506
+ }
507
+ },
508
+ {
509
+ "name": "cats",
510
+ "data": {"header": "x-cats", "query": "cats"}
511
+ },
512
+ {
513
+ "name": "dogs",
514
+ "data": {"header": "x-dogs", "query": "dogs"}
515
+ }
516
+ ]
517
+ }
327
518
  }
328
- }
329
519
  }
330
520
  ```
331
521
 
332
- Every item can utilize expression to dynamically derive intial configurations:
522
+ "data" holds any values for the expressions. JSON responses of state APIs are
523
+ available as "stateAPI.response.data".
524
+ <!--deDE:
525
+ "data" enthält beliebige Werte für die Ausdrücke. JSON-Antworten von
526
+ State-APIs stehen als "stateAPI.response.data" zur Verfügung.
527
+ -->
528
+ <!--frFR:
529
+ "data" contient des valeurs quelconques pour les expressions. Les
530
+ réponses JSON des state APIs sont disponibles sous
531
+ "stateAPI.response.data".
532
+ -->
333
533
 
334
- ```
335
- {
336
- "port": {
337
- "__evaluate__": "environment.PORT ?? 8080"
338
- },
339
- ...
340
- }
341
- ```
534
+ <!--|deDE:Ausdrücke-->
535
+ <!--|frFR:Expressions-->
536
+ ### Expressions
342
537
 
343
- If an environment variabel "PORT" is set it will be used or "8080" as a
344
- fallback.
538
+ Every expression is a JavaScript expression with access to:
539
+ <!--deDE:Jeder Ausdruck ist ein JavaScript-Ausdruck mit Zugriff auf:-->
540
+ <!--frFR:Chaque expression est une expression JavaScript ayant accès à:-->
345
541
 
346
- ### Use Base Forwarder
542
+ - "request": the client request with "url", "method" and "headers".<!--deDE:"request": die Client-Anfrage mit "url", "method" und "headers".--><!--frFR:"request": la requête du client avec "url", "method" et "headers".-->
543
+ - "response": the response to the client (not the one of a state API).<!--deDE:"response": die Antwort an den Client (nicht die einer State-API).--><!--frFR:"response": la réponse au client (pas celle d'une state API).-->
544
+ - "stateAPI": the current state API with its "configuration", "error" and "response".<!--deDE:"stateAPI": die aktuelle State-API mit "configuration", "error" und "response".--><!--frFR:"stateAPI": la state API courante avec "configuration", "error" et "response".-->
545
+ - "stateAPIs": all state APIs of the forwarder by name, e.g. in header transformations.<!--deDE:"stateAPIs": alle State-APIs des Forwarders nach Namen, z. B. in Header-Transformationen.--><!--frFR:"stateAPIs": toutes les state APIs du forwarder par nom, par exemple dans les transformations d'en-têtes.-->
546
+ - "data": the state API's "data", the forwarder in "useExpression" and both sockets in header transformations.<!--deDE:"data": die "data" der State-API, der Forwarder in "useExpression" und beide Sockets in Header-Transformationen.--><!--frFR:"data": les "data" de la state API, le forwarder dans "useExpression" et les deux sockets dans les transformations d'en-têtes.-->
547
+ - "error": the error of a failed state API request.<!--deDE:"error": der Fehler einer fehlgeschlagenen State-API-Anfrage.--><!--frFR:"error": l'erreur d'une requête de state API échouée.-->
548
+ - "array", "datetime", "object", "string", "utility" and more: helpers of [clientnode](https://tsickert.com/clientnode).<!--deDE:"array", "datetime", "object", "string", "utility" und mehr: Hilfsfunktionen von [clientnode](https://tsickert.com/clientnode).--><!--frFR:"array", "datetime", "object", "string", "utility" et d'autres: fonctions utilitaires de [clientnode](https://tsickert.com/clientnode).-->
347
549
 
348
- Base forwarder are inherited by every specific forwarder. This configuration:
550
+ For example, this send transformation passes the answer of the
551
+ authentication service from above on to the backend:
552
+ <!--deDE:
553
+ Diese Send-Transformation gibt zum Beispiel die Antwort des
554
+ Authentifizierungsdienstes von oben an das Backend weiter:
555
+ -->
556
+ <!--frFR:
557
+ Par exemple, cette transformation "send" transmet au backend la réponse
558
+ du service d'authentification ci-dessus:
559
+ -->
349
560
 
350
- ```
561
+ ```JSON
351
562
  {
352
- "forwarders": {
353
- "base: {
354
- "headerTransformations": {
355
- "send": {
356
- "source": "/(GET|POST) \\/.* (.*)/",
357
- "target": "'$1 /sub/path/to/forward/to $2'"
358
- }
359
- }
360
- },
361
-
362
- "bing": {
363
- "host": "www.bing.com",
364
- "useExpression": "Math.random() < 0.5"
365
- },
366
-
367
- "google": {
368
- "host": "www.google.com"
369
- }
370
- }
563
+ "target": "`X-Allowed: ${stateAPIs.authentication.response?.data?.allowed}`"
371
564
  }
372
565
  ```
373
566
 
374
- is equivalent to:
567
+ <!--|deDE:Einzelwerte oder Listen-->
568
+ <!--|frFR:Valeurs uniques ou listes-->
569
+ ### Single values or lists
375
570
 
376
- ```
377
- {
378
- "forwarders": {
379
- "bing": {
380
- "host": "www.bing.com",
381
-
382
- "useExpression": "Math.random() < 0.5",
571
+ "headerTransformations.send", "headerTransformations.retrieve", "stateAPIs",
572
+ "expressions.pre" and "expressions.post" take a single item or a list of
573
+ items. "{...}" means the same as "[{...}]".
574
+ <!--deDE:
575
+ "headerTransformations.send", "headerTransformations.retrieve",
576
+ "stateAPIs", "expressions.pre" und "expressions.post" nehmen ein
577
+ einzelnes Element oder eine Liste von Elementen. "{...}" bedeutet
578
+ dasselbe wie "[{...}]".
579
+ -->
580
+ <!--frFR:
581
+ "headerTransformations.send", "headerTransformations.retrieve",
582
+ "stateAPIs", "expressions.pre" et "expressions.post" acceptent un seul
583
+ élément ou une liste d'éléments. "{...}" signifie la même chose que
584
+ "[{...}]".
585
+ -->
383
586
 
384
- "headerTransformations": {
385
- "send": {
386
- "source": "/(GET|POST) \\/.* (.*)/",
387
- "target": "'$1 /sub/path/to/forward/to $2'"
388
- }
389
- }
390
- },
587
+ <!--|deDE:Umgebungsvariablen-->
588
+ <!--|frFR:Variables d'environnement-->
589
+ ### Environment variables
391
590
 
392
- "google": {
393
- "host": "www.google.com",
591
+ Any value of the configuration can be computed once on start with
592
+ "__evaluate__", which has access to "environment" and "configuration":
593
+ <!--deDE:
594
+ Jeder Wert der Konfiguration lässt sich beim Start einmalig über
595
+ "__evaluate__" berechnen, das Zugriff auf "environment" und
596
+ "configuration" hat:
597
+ -->
598
+ <!--frFR:
599
+ Toute valeur de la configuration peut être calculée une fois au
600
+ démarrage via "__evaluate__", qui a accès à "environment" et
601
+ "configuration":
602
+ -->
394
603
 
395
- "headerTransformations": {
396
- "send": {
397
- "source": "/(GET|POST) \\/.* (.*)/",
398
- "target": "'$1 /sub/path/to/forward/to $2'"
604
+ ```JSON
605
+ {
606
+ "forwarders": {
607
+ "example": {
608
+ "host": {
609
+ "__evaluate__": "environment.BACKEND_HOST ?? 'example.com'"
610
+ }
399
611
  }
400
- }
401
612
  }
402
- }
403
613
  }
404
614
  ```
405
615
 
406
- ### Use base State-APIs
407
-
408
- As we support generic base forwarder confgurations there are also base state
409
- api configuraton sections. Consider the follwing configuration example:
410
-
411
- ```
412
- {
413
- "forwarders": {
414
- "bing": {
415
- "host": "www.bing.com",
416
-
417
- "stateAPIs": [
418
- {
419
- "name": "base",
420
- "url": "https://www.google.com/search?q=${data.query}",
421
-
422
- "expressions": {
423
- "pre": "request.headers['ask-google'] ? true : null",
424
- "post": "response.statusCode >= 200 && response.statusCode < 300 ? null : response.statusCode"
425
- }
426
- }
427
-
428
- {
429
- "name": "sense",
430
-
431
- "data": {
432
- "query": "What is the meaning of life?"
433
- }
434
- },
616
+ The proxy then forwards to the host given in the environment variable
617
+ "BACKEND_HOST" or to "example.com". The default "port" works the same way
618
+ with "PORT".
619
+ <!--deDE:
620
+ Der Proxy leitet dann an den in der Umgebungsvariable "BACKEND_HOST"
621
+ angegebenen Host weiter oder an "example.com". Der Standardwert von
622
+ "port" funktioniert genauso mit "PORT".
623
+ -->
624
+ <!--frFR:
625
+ Le proxy transmet alors à l'hôte donné dans la variable d'environnement
626
+ "BACKEND_HOST" ou à "example.com". La valeur par défaut de "port"
627
+ fonctionne de la même façon avec "PORT".
628
+ -->
435
629
 
436
- {
437
- "name": "nonesense",
630
+ <!--|deDE:Entwicklung-->
631
+ <!--|frFR:Développement-->
632
+ Development
633
+ -----------
438
634
 
439
- "data": {
440
- "query": "baby cats"
441
- }
442
- }
443
- ]
444
- }
445
- }
446
- }
635
+ ```bash
636
+ yarn install
637
+ yarn check
638
+ yarn test
639
+ yarn serve:simple
447
640
  ```
448
641
 
449
- Note that url and expression are inherited to the state apis "sense" and
450
- "nonsense".
451
- The Data field can save various configuration items to be used in runtime
452
- expressions.
453
- Please also note that it is possible to access configuration, request or
454
- response informations from other state apis in every runtime expression.
455
-
456
- Every expression can access the following environment:
457
-
458
- | Name | Meaning |
459
- |-----------|--------------------------------------------------------|
460
- | data | Generic configuration items. |
461
- | error | Error object if some occured. |
462
- | request | Client request informations. |
463
- | response | Response informations (available in post evaluations). |
464
- | stateAPIs | Access oher state api informations. |
465
- | Tools | [see](https://www.npmjs.com/package/clientnode) |
642
+ "yarn check" checks types and lints, "yarn serve:&lt;name&gt;" builds the
643
+ proxy and starts it with one of the configurations in "examples/" ("headers",
644
+ "random", "simple" or "state-apis").
645
+ <!--deDE:
646
+ "yarn check" prüft Typen und lintet, "yarn serve:&lt;name&gt;" baut den
647
+ Proxy und startet ihn mit einer der Konfigurationen in "examples/"
648
+ ("headers", "random", "simple" oder "state-apis").
649
+ -->
650
+ <!--frFR:
651
+ "yarn check" vérifie les types et lance le linter, "yarn
652
+ serve:&lt;name&gt;" construit le proxy et le lance avec l'une des
653
+ configurations de "examples/" ("headers", "random", "simple" ou
654
+ "state-apis").
655
+ -->