Skip to content

Commit 546b735

Browse files
committed
ISSUE-56 Add middleware for prometheus metrics
1 parent 9a3b1e4 commit 546b735

File tree

5 files changed

+105
-45
lines changed

5 files changed

+105
-45
lines changed

README.md

+35-18
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/mendhak/docker-http-https-echo/build.yml?color=darkgreen&style=for-the-badge&branch=master)](https://github.com/mendhak/docker-http-https-echo/actions?query=workflow%3ABuild)
44

55

6-
76
`mendhak/http-https-echo` is a Docker image that can echo various HTTP request properties back to client in the response, as well as in the Docker container logs.
87
It comes with various options that can manipulate the response output, see the table of contents for a full list.
98

109
![browser](https://raw.githubusercontent.com/mendhak/docker-http-https-echo/master/screenshots/screenshot.png)
1110

12-
The image is available on [Docker Hub](https://hub.docker.com/r/mendhak/http-https-echo): `mendhak/http-https-echo:30`
11+
The image is available on [Docker Hub](https://hub.docker.com/r/mendhak/http-https-echo): `mendhak/http-https-echo:30`
1312
The image is available on [Github Container Registry](https://github.com/mendhak/docker-http-https-echo/pkgs/container/http-https-echo): `ghcr.io/mendhak/http-https-echo:30`
1413

1514
Please do not use the `:latest` tag as it will break without warning, use a specific version instead.
@@ -31,8 +30,11 @@ This image is executed as non root by default and is fully compliant with Kubern
3130
- [Add a delay before response](#add-a-delay-before-response)
3231
- [Only return body in the response](#only-return-body-in-the-response)
3332
- [Include environment variables in the response](#include-environment-variables-in-the-response)
34-
- [Client certificate details (mTLS) in the response](#client-certificate-details--mtls--in-the-response)
33+
- [Client certificate details (mTLS) in the response](#client-certificate-details-mtls-in-the-response)
34+
- [Prometheus Metrics](#prometheus-metrics)
3535
- [Output](#output)
36+
- [Curl output](#curl-output)
37+
- [`docker logs` output](#docker-logs-output)
3638
- [Building](#building)
3739
- [Changelog](#changelog)
3840

@@ -75,9 +77,9 @@ With docker compose, this would be:
7577

7678
## Use your own certificates
7779

78-
The certificates are at `/app/fullchain.pem` and `/app/privkey.pem`.
80+
The certificates are at `/app/fullchain.pem` and `/app/privkey.pem`.
7981

80-
You can use volume mounting to substitute the certificate and private key with your own.
82+
You can use volume mounting to substitute the certificate and private key with your own.
8183

8284
my-http-listener:
8385
image: mendhak/http-https-echo:30
@@ -166,7 +168,7 @@ docker run -e ECHO_BACK_TO_CLIENT=false -p 8080:8080 -p 8443:8443 --rm -t mendha
166168

167169
## Custom status code
168170

169-
Use `x-set-response-status-code` to set a custom status code.
171+
Use `x-set-response-status-code` to set a custom status code.
170172

171173
You can send it as a header:
172174

@@ -188,7 +190,7 @@ That will cause the reponse status code to be:
188190

189191
## Set response Content-Type
190192

191-
Use `x-set-response-content-type` to set the Content-Type of the response.
193+
Use `x-set-response-content-type` to set the Content-Type of the response.
192194

193195
You can send it as a header:
194196

@@ -211,61 +213,76 @@ This will cause the response content type to be:
211213

212214
## Add a delay before response
213215

214-
Use `x-set-response-delay-ms` to set a custom delay in milliseconds. This will allow you to simulate slow responses.
216+
Use `x-set-response-delay-ms` to set a custom delay in milliseconds. This will allow you to simulate slow responses.
215217

216218
You can send it as a header:
217219

218220
```bash
219221
curl -v -H "x-set-response-delay-ms: 6000" http://localhost:8080/
220222
```
221223

222-
You can send it as a querystring parameter:
224+
You can send it as a querystring parameter:
223225

224226
```bash
225227
curl -v http://localhost:8080/some/path?x-set-response-delay-ms=6000
226228
```
227229

228230
## Only return body in the response
229231

230-
Use the querystring parameter, `response_body_only=true` to get just the request body in the response, none of the associated metadata.
232+
Use the querystring parameter, `response_body_only=true` to get just the request body in the response, none of the associated metadata.
231233

232234
```bash
233235
curl -s -k -X POST -d 'cauliflower' http://localhost:8080/a/b/c?response_body_only=true
234236
```
235237

236-
The output will be 'cauliflower'.
238+
The output will be 'cauliflower'.
237239

238240
## Include environment variables in the response
239241

240-
You can have environment variables (that are visible to the echo server's process) added to the response body. Because this could contain sensitive information, it is not a default behavior.
242+
You can have environment variables (that are visible to the echo server's process) added to the response body. Because this could contain sensitive information, it is not a default behavior.
241243

242-
Pass the `ECHO_INCLUDE_ENV_VARS=1` environment variable in.
244+
Pass the `ECHO_INCLUDE_ENV_VARS=1` environment variable in.
243245

244246
```bash
245247
docker run -d --rm -e ECHO_INCLUDE_ENV_VARS=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:30
246248
```
247249

248-
Then do a normal request via curl or browser, and you will see the `env` property in the response body.
250+
Then do a normal request via curl or browser, and you will see the `env` property in the response body.
249251

250252

251253
## Client certificate details (mTLS) in the response
252254

253-
To get client certificate details in the response body, start the container with `MTLS_ENABLE=1` environment variable. When passing a client certificate, the details about that certificate can be echoed back in the response body. The client certificate will not be validated.
255+
To get client certificate details in the response body, start the container with `MTLS_ENABLE=1` environment variable. When passing a client certificate, the details about that certificate can be echoed back in the response body. The client certificate will not be validated.
254256

255-
For example, invoke using curl, passing a certificate and key.
257+
For example, invoke using curl, passing a certificate and key.
256258

257259
```bash
258260
curl -k --cert cert.pem --key privkey.pem https://localhost:8443/
259261
```
260262

261-
The response body will contain details about the client certificate passed in.
263+
The response body will contain details about the client certificate passed in.
262264

263-
If you browse to https://localhost:8443/ in Firefox, you won't get prompted to supply a client certificate unless you have [an imported certificate by the same issuer as the server](https://superuser.com/questions/1043415/firefox-doesnt-ask-me-for-a-certificate-when-visiting-a-site-that-needs-one). If you need browser prompting to work, you'll need to follow the 'use your own certificates' section. Firefox needs the imported certificate to be in a PKCS12 format, so if you have a certificate and key already, you can combine them using
265+
If you browse to https://localhost:8443/ in Firefox, you won't get prompted to supply a client certificate unless you have [an imported certificate by the same issuer as the server](https://superuser.com/questions/1043415/firefox-doesnt-ask-me-for-a-certificate-when-visiting-a-site-that-needs-one). If you need browser prompting to work, you'll need to follow the 'use your own certificates' section. Firefox needs the imported certificate to be in a PKCS12 format, so if you have a certificate and key already, you can combine them using
264266

265267
```bash
266268
openssl pkcs12 -export -in cert.pem -inkey privkey.pem -out certpkcs12.pfx
267269
```
268270

271+
## Prometheus Metrics
272+
273+
By default the server uses `express-prom-bundle` middleware to expose http performance
274+
metrics in `/metrics`.
275+
You can configure these metrics using the following variables:
276+
277+
| Variable | Description | Default Value |
278+
| -------- | ----------- | ------------- |
279+
| PROMETHEUS_DISABLED | Toggles off the prometheus middleware | false |
280+
| PROMETHEUS_WITH_PATH | Partitions the metrics by the requested path | false |
281+
| PROMETHEUS_WITH_METHOD | Partitions the metrics by HTTP method | true |
282+
| PROMETHEUS_WITH_STATUS | Partitions the metrics by HTTP status | true |
283+
| PROMETHEUS_METRIC_TYPE | Sets the type of metric, histogram or summary | summary |
284+
285+
> Please check the middleware [documentation](https://github.com/jochen-schweizer/express-prom-bundle#options) for more details.
269286
270287
## Output
271288

docker-compose.yml

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
version: '3'
22
services:
3-
43
my-http-listener:
54
image: mendhak/http-https-echo:30
6-
environment:
5+
environment:
76
- HTTP_PORT=8888
87
- HTTPS_PORT=9999
8+
- PROMETHEUS_DISABLED=false
9+
- PROMETHEUS_WITH_PATH=false
10+
- PROMETHEUS_WITH_METHOD=true
11+
- PROMETHEUS_WITH_STATUS=true
12+
- PROMETHEUS_METRIC_TYPE=summary
913
ports:
1014
- "8080:8888"
1115
- "8443:9999"

index.js

+37-18
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
1-
var express = require('express')
2-
const morgan = require('morgan');
3-
var http = require('http')
4-
var https = require('https')
5-
var app = express()
61
const os = require('os');
72
const jwt = require('jsonwebtoken');
8-
var concat = require('concat-stream');
3+
const http = require('http')
4+
const https = require('https')
5+
const morgan = require('morgan');
6+
const express = require('express')
7+
const concat = require('concat-stream');
98
const { promisify } = require('util');
9+
const promBundle = require("express-prom-bundle");
10+
11+
const {
12+
PROMETHEUS_DISABLED = false,
13+
PROMETHEUS_WITH_PATH = false,
14+
PROMETHEUS_WITH_METHOD = 'true',
15+
PROMETHEUS_WITH_STATUS = 'true',
16+
PROMETHEUS_METRIC_TYPE = 'summary',
17+
} = process.env
18+
1019
const sleep = promisify(setTimeout);
20+
const metricsMiddleware = promBundle({
21+
includePath: (PROMETHEUS_WITH_PATH == 'true'),
22+
includeMethod: (PROMETHEUS_WITH_METHOD == 'true'),
23+
includeStatusCode: (PROMETHEUS_WITH_STATUS == 'true'),
24+
metricType: PROMETHEUS_METRIC_TYPE,
25+
});
1126

27+
const app = express()
1228
app.set('json spaces', 2);
1329
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
30+
if(PROMETHEUS_DISABLED !== 'true') {
31+
app.use(metricsMiddleware);
32+
}
1433

1534
if(process.env.DISABLE_REQUEST_LOGS !== 'true'){
1635
app.use(morgan('combined'));
@@ -48,11 +67,11 @@ app.all('*', (req, res) => {
4867
};
4968

5069
//Add client certificate details to the output, if present
51-
//This only works if `requestCert` is true when starting the server.
70+
//This only works if `requestCert` is true when starting the server.
5271
if(req.socket.getPeerCertificate){
5372
echo.clientCertificate = req.socket.getPeerCertificate();
5473
}
55-
74+
5675
//Include visible environment variables
5776
if(process.env.ECHO_INCLUDE_ENV_VARS){
5877
echo.env = process.env;
@@ -91,33 +110,33 @@ app.all('*', (req, res) => {
91110
if(setResponseContentType){
92111
res.contentType(setResponseContentType);
93112
}
94-
113+
95114
//Ability to send an empty response back
96115
if (process.env.ECHO_BACK_TO_CLIENT != undefined && process.env.ECHO_BACK_TO_CLIENT == "false"){
97116
res.end();
98-
}
117+
}
99118
//Ability to send just the request body in the response, nothing else
100119
else if ("response_body_only" in req.query && req.query["response_body_only"] == "true") {
101120
res.send(req.body);
102-
}
121+
}
103122
//Normal behavior, send everything back
104123
else {
105124
res.json(echo);
106125
}
107126

108127
//Certain paths can be ignored in the container logs, useful to reduce noise from healthchecks
109128
if (process.env.LOG_IGNORE_PATH != req.path) {
110-
129+
111130
let spacer = 4;
112131
if(process.env.LOG_WITHOUT_NEWLINE){
113132
spacer = null;
114133
}
115-
134+
116135
console.log(JSON.stringify(echo, null, spacer));
117136
}
118137
});
119138

120-
139+
121140
});
122141

123142
let sslOpts = {
@@ -127,10 +146,10 @@ let sslOpts = {
127146

128147
//Whether to enable the client certificate feature
129148
if(process.env.MTLS_ENABLE){
130-
sslOpts = {
131-
requestCert: true,
132-
rejectUnauthorized: false,
133-
...sslOpts
149+
sslOpts = {
150+
requestCert: true,
151+
rejectUnauthorized: false,
152+
...sslOpts
134153
}
135154
}
136155

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"concat-stream": "^2.0.0",
2121
"express": "^4.17.1",
2222
"jsonwebtoken": "^9.0.0",
23-
"morgan": "^1.10.0"
23+
"morgan": "^1.10.0",
24+
"express-prom-bundle": "^6.6.0"
2425
}
2526
}

tests.sh

+25-6
Original file line numberDiff line numberDiff line change
@@ -95,27 +95,27 @@ fi
9595

9696

9797
REQUEST_WITH_SLEEP_MS=$(curl -o /dev/null -Ss -H "x-set-response-delay-ms: 6000" -k https://localhost:8443/ -w '%{time_total}')
98-
if [[ $(echo "$REQUEST_WITH_SLEEP_MS>5" | bc -l) == 1 ]]; then
98+
if [[ $(echo "$REQUEST_WITH_SLEEP_MS>5" | bc -l) == 1 ]]; then
9999
passed "Request header with response delay passed"
100-
else
100+
else
101101
failed "Request header with response delay failed"
102102
echo $REQUEST_WITH_SLEEP_MS
103103
exit 1
104104
fi
105105

106106
REQUEST_WITH_SLEEP_MS=$(curl -o /dev/null -Ss -k https://localhost:8443/sleep/test?x-set-response-delay-ms=5000 -w '%{time_total}')
107-
if [[ $(echo "$REQUEST_WITH_SLEEP_MS>4" | bc -l) == 1 ]]; then
107+
if [[ $(echo "$REQUEST_WITH_SLEEP_MS>4" | bc -l) == 1 ]]; then
108108
passed "Request query with response delay passed"
109-
else
109+
else
110110
failed "Request query with response delay failed"
111111
echo $REQUEST_WITH_SLEEP_MS
112112
exit 1
113113
fi
114114

115115
REQUEST_WITH_INVALID_SLEEP_MS=$(curl -o /dev/null -Ss -H "x-set-response-delay-ms: XXXX" -k https://localhost:8443/ -w '%{time_total}')
116-
if [[ $(echo "$REQUEST_WITH_INVALID_SLEEP_MS<2" | bc -l) == 1 ]]; then
116+
if [[ $(echo "$REQUEST_WITH_INVALID_SLEEP_MS<2" | bc -l) == 1 ]]; then
117117
passed "Request with invalid response delay passed"
118-
else
118+
else
119119
failed "Request with invalid response delay failed"
120120
echo $REQUEST_WITH_INVALID_SLEEP_MS
121121
exit 1
@@ -442,6 +442,25 @@ message " Stop containers "
442442
docker stop http-echo-tests
443443
sleep 5
444444

445+
message " Start container with PROMETHEUS "
446+
docker run -d --rm -e --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo
447+
sleep 5
448+
curl -s -k -X POST -d "tiramisu" https://localhost:8443/ > /dev/null
449+
METRICS_CHECK="$(curl -sk http://localhost:8080/metrics | grep http_request_duration_seconds_count | grep GET | cut -d' ' -f2)"
450+
451+
if [ "$METRICS_CHECK" == "1" ]
452+
then
453+
passed "PROMETHEUS metrics are working"
454+
else
455+
failed "PROMETHEUS metrics are not working"
456+
docker logs http-echo-tests
457+
exit 1
458+
fi
459+
460+
message " Stop containers "
461+
docker stop http-echo-tests
462+
sleep 5
463+
445464
popd
446465
rm -rf testarea
447466
message "DONE"

0 commit comments

Comments
 (0)