├── .dockerignore ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── docker-compose.yml ├── index.js ├── package-lock.json ├── package.json ├── screenshots ├── screenshot.png ├── screenshot2.png └── screenshot3.png └── tests.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | ** 3 | 4 | # Except 5 | !generate-cert.sh 6 | !index.js 7 | !package.json 8 | !package-lock.json 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.sh text eol=lf -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Build 4 | 5 | # Controls when the action will run. 6 | on: 7 | push: 8 | branches-ignore: 9 | - "dependabot/**" 10 | pull_request: 11 | paths-ignore: 12 | - '**.md' 13 | workflow_dispatch: 14 | 15 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 16 | jobs: 17 | # This workflow contains a single job called "build" 18 | build: 19 | # The type of runner that the job will run on 20 | runs-on: ubuntu-latest 21 | 22 | # Steps represent a sequence of tasks that will be executed as part of the job 23 | steps: 24 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 25 | - uses: actions/checkout@v4 26 | 27 | - name: Set up QEMU 28 | uses: docker/setup-qemu-action@v3 29 | - name: Set up Docker Buildx 30 | id: buildx 31 | uses: docker/setup-buildx-action@v3 32 | 33 | - name: Inspect builder 34 | run: | 35 | echo "Name: ${{ steps.buildx.outputs.name }}" 36 | echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}" 37 | echo "Status: ${{ steps.buildx.outputs.status }}" 38 | echo "Flags: ${{ steps.buildx.outputs.flags }}" 39 | echo "Platforms: ${{ steps.buildx.outputs.platforms }}" 40 | 41 | - name: Docker metadata 42 | id: meta 43 | uses: docker/metadata-action@v5 44 | with: 45 | images: | 46 | mendhak/http-https-echo 47 | 48 | # Commenting out, possible bug: https://github.com/nodejs/docker-node/issues/1946 49 | # - name: Build the image multi-platform 50 | # uses: docker/build-push-action@v5 51 | # with: 52 | # context: . 53 | # platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le 54 | # push: false 55 | # cache-from: type=gha 56 | # cache-to: type=gha,mode=max 57 | # tags: ${{ steps.meta.outputs.tags }} 58 | # labels: ${{ steps.meta.outputs.labels }} 59 | 60 | # Due to bug https://github.com/docker/buildx/issues/59, need to build for single platform, load, then run tests. 61 | - name: Build a test image single platform and load it 62 | uses: docker/build-push-action@v5 63 | with: 64 | context: . 65 | push: false 66 | load: true 67 | cache-from: type=gha 68 | cache-to: type=gha,mode=max 69 | tags: | 70 | ${{ steps.meta.outputs.tags }} 71 | "mendhak/http-https-echo:testing" 72 | labels: ${{ steps.meta.outputs.labels }} 73 | 74 | - name: Run tests using the test image 75 | run: ./tests.sh 76 | 77 | - name: Scan the image 78 | id: scan 79 | uses: anchore/scan-action@v3 80 | with: 81 | image: "mendhak/http-https-echo:testing" 82 | output-format: sarif 83 | # severity-cutoff: critical 84 | fail-build: false 85 | 86 | - name: upload Anchore scan SARIF report 87 | uses: github/codeql-action/upload-sarif@v3 88 | with: 89 | sarif_file: ${{ steps.scan.outputs.sarif }} 90 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Publish to Docker Hub 3 | 4 | on: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | 11 | publish: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up QEMU 17 | uses: docker/setup-qemu-action@v3 18 | 19 | - name: Set up Docker Buildx 20 | id: buildx 21 | uses: docker/setup-buildx-action@v3 22 | 23 | - name: Inspect builder 24 | run: | 25 | echo "Name: ${{ steps.buildx.outputs.name }}" 26 | echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}" 27 | echo "Status: ${{ steps.buildx.outputs.status }}" 28 | echo "Flags: ${{ steps.buildx.outputs.flags }}" 29 | echo "Platforms: ${{ steps.buildx.outputs.platforms }}" 30 | 31 | - name: Log in to Docker Hub 32 | uses: docker/login-action@v3 33 | with: 34 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 35 | password: ${{ secrets.DOCKER_HUB_TOKEN }} 36 | 37 | - name: Log in to GitHub Container registry 38 | uses: docker/login-action@v3 39 | with: 40 | registry: ghcr.io 41 | username: ${{ github.actor }} 42 | password: ${{ secrets.GITHUB_TOKEN }} 43 | 44 | - name: Docker metadata 45 | id: meta 46 | uses: docker/metadata-action@v5 47 | with: 48 | images: | 49 | mendhak/http-https-echo 50 | ghcr.io/mendhak/http-https-echo 51 | 52 | - name: Build and push image 53 | uses: docker/build-push-action@v5 54 | with: 55 | context: . 56 | platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8 57 | push: true 58 | tags: ${{ steps.meta.outputs.tags }} 59 | labels: ${{ steps.meta.outputs.labels }} 60 | cache-from: type=gha 61 | cache-to: type=gha,mode=max 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw* 2 | node_modules/ 3 | *.log 4 | testarea 5 | *.pem 6 | *.pfx 7 | *.crt 8 | *.key -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "markdown.extension.toc.updateOnSave": false 3 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Version `37` - 2025-05-10 2 | * The `LOG_IGNORE_PATH` environment variable now takes a regex, so you can ignore multiple paths. 3 | 4 | ## Version `36` - 2025-03-22 5 | * Basic handling of gzip content-encoding on requests by [matt-mercer](https://github.com/mendhak/docker-http-https-echo/pull/79) 6 | 7 | ## Version `35` - 2024-10-19 8 | * Error handling for when invalid JSON body is passed in 9 | 10 | ## Version `34` - 2024-08-17 11 | * Set `PRESERVE_HEADER_CASE` to `1` to attempt to preserve the case of headers in the response. 12 | * Set `OVERRIDE_RESPONSE_BODY_FILE_PATH` to a path, to override the response body with the contents of that file. 13 | 14 | ## Version `33` - 2024-04-07 15 | * Implementing configurable CORS settings by [ash0ne](https://github.com/mendhak/docker-http-https-echo/pull/65). 16 | 17 | ## Version `32` - 2024-03-28 18 | * Update Express to 4.19.2 to address CVE-2024-29041 19 | 20 | ## Version `31` - 2023-12-03 21 | * Use `PROMETHEUS_ENABLED` which enables a Prometheus metrics endpoint at /metrics 22 | 23 | ## Version `30` - 2023-06-03 24 | * Use `HTTPS_CERT_FILE` and `HTTPS_KEY_FILE` to specify path to a certificate. 25 | 26 | ## Version `29` - 2023-03-16 27 | * Publishing this image to Github Container Registry. Available at `docker pull ghcr.io/mendhak/http-https-echo:latest` 28 | 29 | ## Version `28` - 2022-12-02 30 | * Certificate now includes Subject Alternative Names as well as Common Name 31 | 32 | ## Version `27` - 2022-11-24 33 | * Added some `org.opencontainers.image` labels to the Docker image 34 | 35 | ## Version `26` - 2022-10-30 36 | 37 | * Environment variable `MTLS_ENABLE=1` will show details of the client certificate passed in the response body 38 | * Environment variable `ECHO_INCLUDE_ENV_VARS=1` will include the container (or script's) environment variables in the response body. 39 | 40 | ## Version `25` - 2022-10-09 41 | 42 | * You can pass `x-set-response-content-type` in header, or querystring parameter, to set the content type of the response. 43 | * Upgrade to Node 16. 44 | 45 | ## Version `24` - 2022-05-24 46 | 47 | * Querystring parameter, `response_body_only=true` returns just the request body in the response. 48 | 49 | ## Version `23` - 2022-01-05 50 | 51 | * Environment variable `DISABLE_REQUEST_LOGS=true` will remove the ExpressJS request log lines 52 | * Updated to Node 16 53 | * Removed the `-----------` separator 54 | 55 | ## Version `22` - 2021-11-21 56 | 57 | * You can now also send the response delay and response code as querystring parameters. 58 | 59 | ## Version `21` - 2021-10-20 60 | 61 | * You can send an empty response to the client by setting the environment variable `ECHO_BACK_TO_CLIENT=false` 62 | 63 | ## Version `20` - 2021-09-27 64 | 65 | * The image is available for multiple architectures. This is being done via [docker buildx](https://github.com/mendhak/docker-http-https-echo/blob/9f511eae7c928d7f9543842598f9565c19828300/.github/workflows/publish.yml#L32) on Github Actions. 66 | 67 | ## Version `19` - 2021-04-08 68 | 69 | * You can run the container as a different user than the one defined in the image. 70 | 71 | ## Version `18` - 2021-02-26 72 | 73 | * You can pass a `x-set-response-delay-ms` to set a custom delay in milliseconds. 74 | 75 | ## Version `17` - 2021-01-15 76 | 77 | * You can pass a `x-set-response-status-code` header to set the response status code 78 | 79 | ## Version `16` - 2020-12-22 80 | 81 | * Dockerfile optimisation, slightly smaller image size 82 | * This changelog added to the repo 83 | 84 | ## Version `15` - 2020-12-15 85 | 86 | * The image now runs as a non-root user by default. 87 | 88 | ## Version `14` - 2020-11-26 89 | 90 | * Optionally allow running as a non root user. 91 | 92 | ``` 93 | docker run --user node -e HTTP_PORT=8080 -e HTTPS_PORT=8443 -p 8080:8080 -p 8443:8443 --rm mendhak/http-https-echo:issue-14-non-root 94 | #or 95 | docker run --user node --sysctl net.ipv4.ip_unprivileged_port_start=0 -p 8080:80 -p 8443:443 --rm mendhak/http-https-echo:issue-14-non-root 96 | ``` 97 | 98 | ## Version `latest` and others 99 | 100 | _Note: The `latest` tag is no longer being built, I've removed it from the automated builds. Please don't use the `latest` tag any longer._ 101 | 102 | * JWT header 103 | * Choose your own ports 104 | * Choose your own certs 105 | * Ignore a specific path 106 | * JSON payloads 107 | * Single line log output 108 | 109 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-alpine AS build 2 | 3 | WORKDIR /app 4 | COPY . /app 5 | 6 | RUN set -ex \ 7 | # Build JS-Application 8 | && npm install --production \ 9 | # Generate SSL-certificate (for HTTPS) 10 | && apk --no-cache add openssl \ 11 | && openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout privkey.pem -out fullchain.pem \ 12 | -subj "/C=GB/ST=London/L=London/O=Mendhak/CN=my.example.com" \ 13 | -addext "subjectAltName=DNS:my.example.com,DNS:my.example.net,IP:192.168.50.108,IP:127.0.0.1" \ 14 | && apk del openssl \ 15 | && rm -rf /var/cache/apk/* \ 16 | # Delete unnecessary files 17 | && rm package* \ 18 | # Correct User's file access 19 | && chown -R node:node /app \ 20 | && chmod +r /app/privkey.pem 21 | 22 | FROM node:22-alpine AS final 23 | LABEL \ 24 | org.opencontainers.image.title="http-https-echo" \ 25 | org.opencontainers.image.description="Docker image that echoes request data as JSON; listens on HTTP/S, with various extra features, useful for debugging." \ 26 | org.opencontainers.image.url="https://github.com/mendhak/docker-http-https-echo" \ 27 | org.opencontainers.image.documentation="https://github.com/mendhak/docker-http-https-echo/blob/master/README.md" \ 28 | org.opencontainers.image.source="https://github.com/mendhak/docker-http-https-echo" \ 29 | org.opencontainers.image.licenses="MIT" 30 | WORKDIR /app 31 | COPY --from=build /app /app 32 | ENV HTTP_PORT=8080 HTTPS_PORT=8443 33 | EXPOSE $HTTP_PORT $HTTPS_PORT 34 | USER 1000 35 | CMD ["node", "./index.js"] 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 mendhak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![pulls](https://img.shields.io/docker/pulls/mendhak/http-https-echo.svg?style=for-the-badge&logo=docker)](https://hub.docker.com/r/mendhak/http-https-echo) 2 | [![Docker Image Version (latest semver)](https://img.shields.io/docker/v/mendhak/http-https-echo?color=lightblue&label=latest&sort=semver&style=for-the-badge)](https://hub.docker.com/r/mendhak/http-https-echo) 3 | [![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) 4 | 5 | 6 | `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. 7 | It comes with various options that can manipulate the response output, see the table of contents for a full list. 8 | 9 | ![browser](./screenshots/screenshot.png) 10 | 11 | The image is available on [Docker Hub](https://hub.docker.com/r/mendhak/http-https-echo): `mendhak/http-https-echo:37` 12 | 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:37` 13 | 14 | Please do not use the `:latest` tag as it will break without warning, use a specific version instead. 15 | 16 | This image is executed as non root by default and is fully compliant with Kubernetes or Openshift deployment. 17 | 18 | 19 | - [Basic Usage](#basic-usage) 20 | - [Choose your ports](#choose-your-ports) 21 | - [Use your own certificates](#use-your-own-certificates) 22 | - [Decode JWT header](#decode-jwt-header) 23 | - [Disable ExpressJS log lines](#disable-expressjs-log-lines) 24 | - [Do not log specific path](#do-not-log-specific-path) 25 | - [JSON payloads and JSON output](#json-payloads-and-json-output) 26 | - [No newlines](#no-newlines) 27 | - [Send an empty response](#send-an-empty-response) 28 | - [Custom status code](#custom-status-code) 29 | - [Set response Content-Type](#set-response-content-type) 30 | - [Add a delay before response](#add-a-delay-before-response) 31 | - [Only return body in the response](#only-return-body-in-the-response) 32 | - [Include environment variables in the response](#include-environment-variables-in-the-response) 33 | - [Configuring CORS policy](#setting-corscross-origin-resource-sharing-headers-in-the-response) 34 | - [Client certificate details (mTLS) in the response](#client-certificate-details-mtls-in-the-response) 35 | - [Preserve the case of headers in response body](#preserve-the-case-of-headers-in-response-body) 36 | - [Override the response body with a file](#override-the-response-body-with-a-file) 37 | - [Prometheus Metrics](#prometheus-metrics) 38 | - [Screenshots](#screenshots) 39 | - [Building](#building) 40 | - [Changelog](#changelog) 41 | 42 | 43 | ## Basic Usage 44 | 45 | Run with Docker 46 | 47 | docker run -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:37 48 | 49 | Or run with Docker Compose 50 | 51 | docker-compose up 52 | 53 | Then, issue a request via your browser or curl, and watch the response, as well as container log output. 54 | 55 | curl -k -X PUT -H "Arbitrary:Header" -d aaa=bbb https://localhost:8443/hello-world 56 | 57 | 58 | ## Choose your ports 59 | 60 | You can choose a different internal port instead of 8080 and 8443 with the `HTTP_PORT` and `HTTPS_PORT` environment variables. 61 | 62 | In this example I'm setting http to listen on 8888, and https to listen on 9999. 63 | 64 | docker run -e HTTP_PORT=8888 -e HTTPS_PORT=9999 -p 8080:8888 -p 8443:9999 --rm -t mendhak/http-https-echo:37 65 | 66 | 67 | With docker compose, this would be: 68 | 69 | my-http-listener: 70 | image: mendhak/http-https-echo:37 71 | environment: 72 | - HTTP_PORT=8888 73 | - HTTPS_PORT=9999 74 | ports: 75 | - "8080:8888" 76 | - "8443:9999" 77 | 78 | 79 | ## Use your own certificates 80 | 81 | The certificates are at `/app/fullchain.pem` and `/app/privkey.pem`. 82 | 83 | You can use volume mounting to substitute the certificate and private key with your own. 84 | 85 | my-http-listener: 86 | image: mendhak/http-https-echo:37 87 | ports: 88 | - "8080:8080" 89 | - "8443:8443" 90 | volumes: 91 | - /etc/ssl/certs/ssl-cert-snakeoil.pem:/app/fullchain.pem 92 | - /etc/ssl/private/ssl-cert-snakeoil.key:/app/privkey.pem 93 | 94 | You can use the environment variables `HTTPS_CERT_FILE` and `HTTPS_KEY_FILE` to define the location of existing certificate and private key inside container. 95 | 96 | 97 | ## Decode JWT header 98 | 99 | If you specify the header that contains the JWT, the echo output will contain the decoded JWT. Use the `JWT_HEADER` environment variable for this. 100 | 101 | docker run -e JWT_HEADER=Authentication -p 8080:8080 -p 8443:8443 --rm -it mendhak/http-https-echo:37 102 | 103 | 104 | Now make your request with `Authentication: eyJ...` header (it should also work with the `Authentication: Bearer eyJ...` schema too): 105 | 106 | curl -k -H "Authentication: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" http://localhost:8080/ 107 | 108 | And in the output you should see a `jwt` section. 109 | 110 | ## Disable ExpressJS log lines 111 | 112 | In the log output set the environment variable `DISABLE_REQUEST_LOGS` to true, to disable the specific ExpressJS request log lines. The ones like `::ffff:172.17.0.1 - - [03/Jan/2022:21:31:51 +0000] "GET /xyz HTTP/1.1" 200 423 "-" "curl/7.68.0"`. The JSON output will still appear. 113 | 114 | docker run --rm -e DISABLE_REQUEST_LOGS=true --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:37 115 | 116 | 117 | ## Do not log specific path 118 | 119 | Set the environment variable `LOG_IGNORE_PATH` to a path or a regex you would like to exclude from verbose logging to stdout. 120 | This can help reduce noise from healthchecks in orchestration/infrastructure like Swarm, Kubernetes, ALBs, etc. 121 | 122 | # Ignore a single path 123 | docker run -e LOG_IGNORE_PATH=/ping -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:37 124 | # Ignore multiple paths 125 | docker run -e LOG_IGNORE_PATH="^\/ping|^\/health|^\/metrics" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:37 126 | # Ignore all paths 127 | docker run -e LOG_IGNORE_PATH=".*" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:37 128 | docker run -e LOG_IGNORE_PATH="^" -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:37 129 | 130 | 131 | 132 | ## JSON payloads and JSON output 133 | 134 | If you submit a JSON payload in the body of the request, with Content-Type: application/json, then the response will contain the escaped JSON as well. 135 | 136 | For example, 137 | 138 | curl -X POST -H "Content-Type: application/json" -d '{"a":"b"}' http://localhost:8080/ 139 | 140 | Will contain a `json` property in the response/output. 141 | 142 | ... 143 | "xhr": false, 144 | "connection": {}, 145 | "json": { 146 | "a": "b" 147 | } 148 | } 149 | 150 | ## No newlines 151 | 152 | You can disable new lines in the log output by setting the environment variable `LOG_WITHOUT_NEWLINE`. For example, 153 | 154 | ```bash 155 | docker run -e LOG_WITHOUT_NEWLINE=true -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:37 156 | ``` 157 | 158 | ## Send an empty response 159 | 160 | You can disable the JSON output in the response by setting the environment variable `ECHO_BACK_TO_CLIENT`. For example, 161 | 162 | ```bash 163 | docker run -e ECHO_BACK_TO_CLIENT=false -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:37 164 | ``` 165 | 166 | ## Custom status code 167 | 168 | Use `x-set-response-status-code` to set a custom status code. 169 | 170 | You can send it as a header: 171 | 172 | ```bash 173 | curl -v -H "x-set-response-status-code: 401" http://localhost:8080/ 174 | ``` 175 | 176 | You can send it as a querystring parameter: 177 | 178 | ```bash 179 | curl -v http://localhost:8080/some/path?x-set-response-status-code=401 180 | ``` 181 | 182 | That will cause the reponse status code to be: 183 | 184 | ``` 185 | HTTP/1.1 401 Unauthorized 186 | ``` 187 | 188 | ## Set response Content-Type 189 | 190 | Use `x-set-response-content-type` to set the Content-Type of the response. 191 | 192 | You can send it as a header: 193 | 194 | ```bash 195 | curl -H "X-Set-Response-Content-Type: text/plain" -kv https://localhost:8443/ 196 | ``` 197 | 198 | You can send it as a querystring parameter: 199 | 200 | ```bash 201 | curl -kv https://localhost:8443/path?x-set-response-content-type=text/plain 202 | ``` 203 | 204 | This will cause the response content type to be: 205 | 206 | ``` 207 | < Content-Type: text/plain; charset=utf-8 208 | ``` 209 | 210 | 211 | ## Add a delay before response 212 | 213 | Use `x-set-response-delay-ms` to set a custom delay in milliseconds. This will allow you to simulate slow responses. 214 | 215 | You can send it as a header: 216 | 217 | ```bash 218 | curl -v -H "x-set-response-delay-ms: 6000" http://localhost:8080/ 219 | ``` 220 | 221 | You can send it as a querystring parameter: 222 | 223 | ```bash 224 | curl -v http://localhost:8080/some/path?x-set-response-delay-ms=6000 225 | ``` 226 | 227 | ## Only return body in the response 228 | 229 | Use the querystring parameter, `response_body_only=true` to get just the request body in the response, none of the associated metadata. 230 | 231 | ```bash 232 | curl -s -k -X POST -d 'cauliflower' http://localhost:8080/a/b/c?response_body_only=true 233 | ``` 234 | 235 | The output will be 'cauliflower'. 236 | 237 | ## Include environment variables in the response 238 | 239 | 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. 240 | 241 | Pass the `ECHO_INCLUDE_ENV_VARS=1` environment variable in. 242 | 243 | ```bash 244 | 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:37 245 | ``` 246 | 247 | Then do a normal request via curl or browser, and you will see the `env` property in the response body. 248 | 249 | ## Setting CORS(Cross-Origin Resource Sharing) headers in the response 250 | 251 | Enable CORS headers in response by setting the environment variable `CORS_ALLOW_ORIGIN` to the list of allowed origins. 252 | CORS configuration can be further fine-tuned by using the following environment variables: 253 | 254 | - **`CORS_ALLOW_METHODS`**: List of Http methods allowed. 255 | - **`CORS_ALLOW_HEADERS`**: List of headers allowed. 256 | - **`CORS_ALLOW_CREDENTIALS`**: Comma-separated list of origin URLs from which the policy allows credentials to be sent. 257 | 258 | None of these CORS settings can be set without setting the `CORS_ALLOW_ORIGIN` first. By default, they will all be missing when only the `CORS_ALLOW_ORIGIN` is set and need to be explicitly specified alongside `CORS_ALLOW_ORIGIN`. 259 | 260 | 261 | ## Client certificate details (mTLS) in the response 262 | 263 | 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. 264 | 265 | For example, invoke using curl, passing a certificate and key. 266 | 267 | ```bash 268 | curl -k --cert cert.pem --key privkey.pem https://localhost:8443/ 269 | ``` 270 | 271 | The response body will contain details about the client certificate passed in. 272 | 273 | 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 274 | 275 | ```bash 276 | openssl pkcs12 -export -in cert.pem -inkey privkey.pem -out certpkcs12.pfx 277 | ``` 278 | 279 | ## Preserve the case of headers in response body 280 | 281 | By default, the headers in the response body are lowercased. To attempt to preserve the case of headers in the response body, set the environment variable `PRESERVE_HEADER_CASE` to true. 282 | 283 | ```bash 284 | docker run -e PRESERVE_HEADER_CASE=true -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:37 285 | ``` 286 | 287 | ## Override the response body with a file 288 | 289 | To override the response body with a file, set the environment variable `OVERRIDE_RESPONSE_BODY_FILE_PATH` to a file path. 290 | The file path needs to be in the `/app` directory. 291 | 292 | ```bash 293 | docker run -d --rm -v ${PWD}/test.html:/app/test.html -p 8080:8080 -e OVERRIDE_RESPONSE_BODY_FILE_PATH=/test.html -t mendhak/http-https-echo:37 294 | ``` 295 | 296 | 297 | ## Prometheus Metrics 298 | 299 | To expose http performance metrics, set the `PROMETHEUS_ENABLED` environment variable to true, the metrics will be available at `/metrics`. This uses the [`express-prom-bundle`](https://github.com/jochen-schweizer/express-prom-bundle) middleware 300 | 301 | You can configure these metrics using the following variables: 302 | 303 | | Variable | Description | Default Value | 304 | | ----------------------- | --------------------------------------------- | ------------- | 305 | | PROMETHEUS_ENABLED | Toggles on the prometheus middleware | false | 306 | | PROMETHEUS_METRICS_PATH | The path at which the metrics will be visible | /metrics | 307 | | PROMETHEUS_WITH_PATH | Partitions the metrics by the requested path | false | 308 | | PROMETHEUS_WITH_METHOD | Partitions the metrics by HTTP method | true | 309 | | PROMETHEUS_WITH_STATUS | Partitions the metrics by HTTP status | true | 310 | | PROMETHEUS_METRIC_TYPE | Sets the type of metric, histogram or summary | summary | 311 | 312 | > Please check the middleware [documentation](https://github.com/jochen-schweizer/express-prom-bundle#options) for more details. 313 | 314 | ## Screenshots 315 | 316 | #### Curl output 317 | 318 | ![curl](./screenshots/screenshot2.png) 319 | 320 | #### `docker logs` output 321 | 322 | ![dockerlogs](./screenshots/screenshot3.png) 323 | 324 | 325 | 326 | ## Building 327 | 328 | docker build -t mendhak/http-https-echo . 329 | 330 | Run some tests to make sure features are working as expected. 331 | 332 | ./tests.sh 333 | 334 | To create a new image on Docker Hub, I need to create a tag and push it. 335 | 336 | git tag -s 16 337 | git push --tags 338 | 339 | 340 | ## Changelog 341 | 342 | See the [changelog](CHANGELOG.md) 343 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | my-http-listener: 4 | image: mendhak/http-https-echo:37 5 | environment: 6 | - HTTP_PORT=8888 7 | - HTTPS_PORT=9999 8 | - PROMETHEUS_ENABLED=true 9 | - PROMETHEUS_METRICS_PATH=/metrics 10 | - PROMETHEUS_WITH_PATH=false 11 | - PROMETHEUS_WITH_METHOD=true 12 | - PROMETHEUS_WITH_STATUS=true 13 | - PROMETHEUS_METRIC_TYPE=summary 14 | ports: 15 | - "8080:8888" 16 | - "8443:9999" 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const jwt = require('jsonwebtoken'); 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'); 8 | const { promisify } = require('util'); 9 | const promBundle = require("express-prom-bundle"); 10 | const zlib = require("zlib"); 11 | 12 | const { 13 | PROMETHEUS_ENABLED = false, 14 | PROMETHEUS_METRICS_PATH = '/metrics', 15 | PROMETHEUS_WITH_PATH = false, 16 | PROMETHEUS_WITH_METHOD = 'true', 17 | PROMETHEUS_WITH_STATUS = 'true', 18 | PROMETHEUS_METRIC_TYPE = 'summary', 19 | } = process.env 20 | 21 | const sleep = promisify(setTimeout); 22 | const metricsMiddleware = promBundle({ 23 | metricsPath: PROMETHEUS_METRICS_PATH, 24 | includePath: (PROMETHEUS_WITH_PATH == 'true'), 25 | includeMethod: (PROMETHEUS_WITH_METHOD == 'true'), 26 | includeStatusCode: (PROMETHEUS_WITH_STATUS == 'true'), 27 | metricType: PROMETHEUS_METRIC_TYPE, 28 | }); 29 | 30 | const app = express() 31 | app.set('json spaces', 2); 32 | app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); 33 | 34 | if(PROMETHEUS_ENABLED === 'true') { 35 | app.use(metricsMiddleware); 36 | } 37 | 38 | if(process.env.DISABLE_REQUEST_LOGS !== 'true'){ 39 | app.use(morgan('combined')); 40 | } 41 | 42 | app.use(function(req, res, next){ 43 | req.pipe(concat(function(data){ 44 | 45 | if (req.get("Content-Encoding") === "gzip") { 46 | req.body = zlib.gunzipSync(data).toString('utf8'); 47 | } 48 | else { 49 | req.body = data.toString('utf8'); 50 | } 51 | next(); 52 | })); 53 | }); 54 | //Handle all paths 55 | app.all('*', (req, res) => { 56 | 57 | if(process.env.OVERRIDE_RESPONSE_BODY_FILE_PATH){ 58 | // Path is relative to current directory 59 | res.sendFile(process.env.OVERRIDE_RESPONSE_BODY_FILE_PATH, { root : __dirname}); 60 | return; 61 | } 62 | 63 | const echo = { 64 | path: req.path, 65 | headers: req.headers, 66 | method: req.method, 67 | body: req.body, 68 | cookies: req.cookies, 69 | fresh: req.fresh, 70 | hostname: req.hostname, 71 | ip: req.ip, 72 | ips: req.ips, 73 | protocol: req.protocol, 74 | query: req.query, 75 | subdomains: req.subdomains, 76 | xhr: req.xhr, 77 | os: { 78 | hostname: os.hostname() 79 | }, 80 | connection: { 81 | servername: req.connection.servername 82 | } 83 | }; 84 | 85 | if(process.env.PRESERVE_HEADER_CASE){ 86 | let newHeaders = {...req.headers}; 87 | 88 | // req.headers is in lowercase, processed, deduplicated. req.rawHeaders is not. 89 | // Match on the preserved case of the header name, populate newHeaders with preserved case and processed value. 90 | for (let i = 0; i < req.rawHeaders.length; i += 2) { 91 | let preservedHeaderName = req.rawHeaders[i]; 92 | if (preservedHeaderName == preservedHeaderName.toLowerCase()) { continue; } 93 | 94 | newHeaders[preservedHeaderName] = req.header(preservedHeaderName); 95 | delete newHeaders[preservedHeaderName.toLowerCase()]; 96 | } 97 | echo.headers = newHeaders; 98 | } 99 | 100 | 101 | //Add client certificate details to the output, if present 102 | //This only works if `requestCert` is true when starting the server. 103 | if(req.socket.getPeerCertificate){ 104 | echo.clientCertificate = req.socket.getPeerCertificate(); 105 | } 106 | 107 | //Include visible environment variables 108 | if(process.env.ECHO_INCLUDE_ENV_VARS){ 109 | echo.env = process.env; 110 | } 111 | 112 | //If the Content-Type of the incoming body `is` JSON, it can be parsed and returned in the body 113 | if(req.is('application/json')){ 114 | try { 115 | echo.json = JSON.parse(req.body) 116 | } catch (error) { 117 | console.warn("Invalid JSON Body received with Content-Type: application/json", error); 118 | } 119 | } 120 | 121 | //If there's a JWT header, parse it and decode and put it in the response 122 | if (process.env.JWT_HEADER) { 123 | let token = req.headers[process.env.JWT_HEADER.toLowerCase()]; 124 | if (!token) { 125 | echo.jwt = token; 126 | } else { 127 | token = token.split(" ").pop(); 128 | const decoded = jwt.decode(token, {complete: true}); 129 | echo.jwt = decoded; 130 | } 131 | } 132 | 133 | //Set the status code to what the user wants 134 | const setResponseStatusCode = parseInt(req.headers["x-set-response-status-code"] || req.query["x-set-response-status-code"], 10) 135 | if (100 <= setResponseStatusCode && setResponseStatusCode < 600) { 136 | res.status(setResponseStatusCode) 137 | } 138 | 139 | //Delay the response for a user defined time 140 | const sleepTime = parseInt(req.headers["x-set-response-delay-ms"] || req.query["x-set-response-delay-ms"], 0) 141 | sleep(sleepTime).then(() => { 142 | 143 | //Set the response content type to what the user wants 144 | const setResponseContentType = req.headers["x-set-response-content-type"] || req.query["x-set-response-content-type"]; 145 | 146 | if(setResponseContentType){ 147 | res.contentType(setResponseContentType); 148 | } 149 | 150 | //Set the CORS policy 151 | if (process.env.CORS_ALLOW_ORIGIN){ 152 | res.header('Access-Control-Allow-Origin', process.env.CORS_ALLOW_ORIGIN); 153 | if (process.env.CORS_ALLOW_METHODS) { 154 | res.header('Access-Control-Allow-Methods', process.env.CORS_ALLOW_METHODS); 155 | } 156 | if (process.env.CORS_ALLOW_HEADERS) { 157 | res.header('Access-Control-Allow-Headers', process.env.CORS_ALLOW_HEADERS); 158 | } 159 | if (process.env.CORS_ALLOW_CREDENTIALS) { 160 | res.header('Access-Control-Allow-Credentials', process.env.CORS_ALLOW_CREDENTIALS); 161 | } 162 | } 163 | 164 | //Ability to send an empty response back 165 | if (process.env.ECHO_BACK_TO_CLIENT != undefined && process.env.ECHO_BACK_TO_CLIENT == "false"){ 166 | res.end(); 167 | } 168 | //Ability to send just the request body in the response, nothing else 169 | else if ("response_body_only" in req.query && req.query["response_body_only"] == "true") { 170 | res.send(req.body); 171 | } 172 | //Normal behavior, send everything back 173 | else { 174 | res.json(echo); 175 | } 176 | 177 | //Certain paths can be ignored in the container logs, useful to reduce noise from healthchecks 178 | if (!process.env.LOG_IGNORE_PATH || !new RegExp(process.env.LOG_IGNORE_PATH).test(req.path)) { 179 | 180 | let spacer = 4; 181 | if(process.env.LOG_WITHOUT_NEWLINE){ 182 | spacer = null; 183 | } 184 | 185 | console.log(JSON.stringify(echo, null, spacer)); 186 | } 187 | }); 188 | 189 | 190 | }); 191 | 192 | let sslOpts = { 193 | key: require('fs').readFileSync(process.env.HTTPS_KEY_FILE || 'privkey.pem'), 194 | cert: require('fs').readFileSync(process.env.HTTPS_CERT_FILE || 'fullchain.pem') 195 | }; 196 | 197 | //Whether to enable the client certificate feature 198 | if(process.env.MTLS_ENABLE){ 199 | sslOpts = { 200 | requestCert: true, 201 | rejectUnauthorized: false, 202 | ...sslOpts 203 | } 204 | } 205 | 206 | var httpServer = http.createServer(app).listen(process.env.HTTP_PORT || 8080); 207 | var httpsServer = https.createServer(sslOpts,app).listen(process.env.HTTPS_PORT || 8443); 208 | console.log(`Listening on ports ${process.env.HTTP_PORT || 8080} for http, and ${process.env.HTTPS_PORT || 8443} for https.`); 209 | 210 | let calledClose = false; 211 | 212 | process.on('exit', function () { 213 | if (calledClose) return; 214 | console.log('Got exit event. Trying to stop Express server.'); 215 | server.close(function() { 216 | console.log("Express server closed"); 217 | }); 218 | }); 219 | 220 | process.on('SIGINT', shutDown); 221 | process.on('SIGTERM', shutDown); 222 | 223 | function shutDown(){ 224 | console.log('Got a kill signal. Trying to exit gracefully.'); 225 | calledClose = true; 226 | httpServer.close(function() { 227 | httpsServer.close(function() { 228 | console.log("HTTP and HTTPS servers closed. Asking process to exit."); 229 | process.exit() 230 | }); 231 | }); 232 | } 233 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mendhak/http-https-echo", 3 | "version": "1.0.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@mendhak/http-https-echo", 9 | "version": "1.0.1", 10 | "license": "BSD-3-Clause", 11 | "dependencies": { 12 | "concat-stream": "^2.0.0", 13 | "express": "^4.21.2", 14 | "express-prom-bundle": "^8.0.0", 15 | "jsonwebtoken": "^9.0.0", 16 | "morgan": "^1.10.0" 17 | }, 18 | "engines": { 19 | "node": ">=16.0.0" 20 | } 21 | }, 22 | "node_modules/@opentelemetry/api": { 23 | "version": "1.9.0", 24 | "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", 25 | "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", 26 | "license": "Apache-2.0", 27 | "peer": true, 28 | "engines": { 29 | "node": ">=8.0.0" 30 | } 31 | }, 32 | "node_modules/@types/body-parser": { 33 | "version": "1.19.5", 34 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", 35 | "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", 36 | "license": "MIT", 37 | "dependencies": { 38 | "@types/connect": "*", 39 | "@types/node": "*" 40 | } 41 | }, 42 | "node_modules/@types/connect": { 43 | "version": "3.4.38", 44 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 45 | "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 46 | "license": "MIT", 47 | "dependencies": { 48 | "@types/node": "*" 49 | } 50 | }, 51 | "node_modules/@types/express": { 52 | "version": "5.0.1", 53 | "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", 54 | "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", 55 | "license": "MIT", 56 | "dependencies": { 57 | "@types/body-parser": "*", 58 | "@types/express-serve-static-core": "^5.0.0", 59 | "@types/serve-static": "*" 60 | } 61 | }, 62 | "node_modules/@types/express-serve-static-core": { 63 | "version": "5.0.6", 64 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", 65 | "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", 66 | "license": "MIT", 67 | "dependencies": { 68 | "@types/node": "*", 69 | "@types/qs": "*", 70 | "@types/range-parser": "*", 71 | "@types/send": "*" 72 | } 73 | }, 74 | "node_modules/@types/http-errors": { 75 | "version": "2.0.4", 76 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", 77 | "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", 78 | "license": "MIT" 79 | }, 80 | "node_modules/@types/mime": { 81 | "version": "1.3.5", 82 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", 83 | "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", 84 | "license": "MIT" 85 | }, 86 | "node_modules/@types/node": { 87 | "version": "22.13.11", 88 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz", 89 | "integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==", 90 | "license": "MIT", 91 | "dependencies": { 92 | "undici-types": "~6.20.0" 93 | } 94 | }, 95 | "node_modules/@types/qs": { 96 | "version": "6.9.18", 97 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", 98 | "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", 99 | "license": "MIT" 100 | }, 101 | "node_modules/@types/range-parser": { 102 | "version": "1.2.7", 103 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", 104 | "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", 105 | "license": "MIT" 106 | }, 107 | "node_modules/@types/send": { 108 | "version": "0.17.4", 109 | "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", 110 | "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", 111 | "license": "MIT", 112 | "dependencies": { 113 | "@types/mime": "^1", 114 | "@types/node": "*" 115 | } 116 | }, 117 | "node_modules/@types/serve-static": { 118 | "version": "1.15.7", 119 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", 120 | "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", 121 | "license": "MIT", 122 | "dependencies": { 123 | "@types/http-errors": "*", 124 | "@types/node": "*", 125 | "@types/send": "*" 126 | } 127 | }, 128 | "node_modules/accepts": { 129 | "version": "1.3.8", 130 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 131 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 132 | "license": "MIT", 133 | "dependencies": { 134 | "mime-types": "~2.1.34", 135 | "negotiator": "0.6.3" 136 | }, 137 | "engines": { 138 | "node": ">= 0.6" 139 | } 140 | }, 141 | "node_modules/array-flatten": { 142 | "version": "1.1.1", 143 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 144 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", 145 | "license": "MIT" 146 | }, 147 | "node_modules/basic-auth": { 148 | "version": "2.0.1", 149 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 150 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 151 | "license": "MIT", 152 | "dependencies": { 153 | "safe-buffer": "5.1.2" 154 | }, 155 | "engines": { 156 | "node": ">= 0.8" 157 | } 158 | }, 159 | "node_modules/basic-auth/node_modules/safe-buffer": { 160 | "version": "5.1.2", 161 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 162 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 163 | "license": "MIT" 164 | }, 165 | "node_modules/bintrees": { 166 | "version": "1.0.2", 167 | "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", 168 | "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", 169 | "license": "MIT", 170 | "peer": true 171 | }, 172 | "node_modules/body-parser": { 173 | "version": "1.20.3", 174 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", 175 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 176 | "license": "MIT", 177 | "dependencies": { 178 | "bytes": "3.1.2", 179 | "content-type": "~1.0.5", 180 | "debug": "2.6.9", 181 | "depd": "2.0.0", 182 | "destroy": "1.2.0", 183 | "http-errors": "2.0.0", 184 | "iconv-lite": "0.4.24", 185 | "on-finished": "2.4.1", 186 | "qs": "6.13.0", 187 | "raw-body": "2.5.2", 188 | "type-is": "~1.6.18", 189 | "unpipe": "1.0.0" 190 | }, 191 | "engines": { 192 | "node": ">= 0.8", 193 | "npm": "1.2.8000 || >= 1.4.16" 194 | } 195 | }, 196 | "node_modules/buffer-equal-constant-time": { 197 | "version": "1.0.1", 198 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 199 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", 200 | "license": "BSD-3-Clause" 201 | }, 202 | "node_modules/buffer-from": { 203 | "version": "1.1.2", 204 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 205 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 206 | "license": "MIT" 207 | }, 208 | "node_modules/bytes": { 209 | "version": "3.1.2", 210 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 211 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 212 | "license": "MIT", 213 | "engines": { 214 | "node": ">= 0.8" 215 | } 216 | }, 217 | "node_modules/call-bind-apply-helpers": { 218 | "version": "1.0.2", 219 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 220 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 221 | "license": "MIT", 222 | "dependencies": { 223 | "es-errors": "^1.3.0", 224 | "function-bind": "^1.1.2" 225 | }, 226 | "engines": { 227 | "node": ">= 0.4" 228 | } 229 | }, 230 | "node_modules/call-bound": { 231 | "version": "1.0.4", 232 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 233 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 234 | "license": "MIT", 235 | "dependencies": { 236 | "call-bind-apply-helpers": "^1.0.2", 237 | "get-intrinsic": "^1.3.0" 238 | }, 239 | "engines": { 240 | "node": ">= 0.4" 241 | }, 242 | "funding": { 243 | "url": "https://github.com/sponsors/ljharb" 244 | } 245 | }, 246 | "node_modules/concat-stream": { 247 | "version": "2.0.0", 248 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", 249 | "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", 250 | "engines": [ 251 | "node >= 6.0" 252 | ], 253 | "license": "MIT", 254 | "dependencies": { 255 | "buffer-from": "^1.0.0", 256 | "inherits": "^2.0.3", 257 | "readable-stream": "^3.0.2", 258 | "typedarray": "^0.0.6" 259 | } 260 | }, 261 | "node_modules/content-disposition": { 262 | "version": "0.5.4", 263 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 264 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 265 | "license": "MIT", 266 | "dependencies": { 267 | "safe-buffer": "5.2.1" 268 | }, 269 | "engines": { 270 | "node": ">= 0.6" 271 | } 272 | }, 273 | "node_modules/content-type": { 274 | "version": "1.0.5", 275 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 276 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 277 | "license": "MIT", 278 | "engines": { 279 | "node": ">= 0.6" 280 | } 281 | }, 282 | "node_modules/cookie": { 283 | "version": "0.7.1", 284 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 285 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 286 | "license": "MIT", 287 | "engines": { 288 | "node": ">= 0.6" 289 | } 290 | }, 291 | "node_modules/cookie-signature": { 292 | "version": "1.0.6", 293 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 294 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", 295 | "license": "MIT" 296 | }, 297 | "node_modules/debug": { 298 | "version": "2.6.9", 299 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 300 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 301 | "license": "MIT", 302 | "dependencies": { 303 | "ms": "2.0.0" 304 | } 305 | }, 306 | "node_modules/depd": { 307 | "version": "2.0.0", 308 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 309 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 310 | "license": "MIT", 311 | "engines": { 312 | "node": ">= 0.8" 313 | } 314 | }, 315 | "node_modules/destroy": { 316 | "version": "1.2.0", 317 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 318 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 319 | "license": "MIT", 320 | "engines": { 321 | "node": ">= 0.8", 322 | "npm": "1.2.8000 || >= 1.4.16" 323 | } 324 | }, 325 | "node_modules/dunder-proto": { 326 | "version": "1.0.1", 327 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 328 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 329 | "license": "MIT", 330 | "dependencies": { 331 | "call-bind-apply-helpers": "^1.0.1", 332 | "es-errors": "^1.3.0", 333 | "gopd": "^1.2.0" 334 | }, 335 | "engines": { 336 | "node": ">= 0.4" 337 | } 338 | }, 339 | "node_modules/ecdsa-sig-formatter": { 340 | "version": "1.0.11", 341 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 342 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 343 | "license": "Apache-2.0", 344 | "dependencies": { 345 | "safe-buffer": "^5.0.1" 346 | } 347 | }, 348 | "node_modules/ee-first": { 349 | "version": "1.1.1", 350 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 351 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 352 | "license": "MIT" 353 | }, 354 | "node_modules/encodeurl": { 355 | "version": "2.0.0", 356 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 357 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 358 | "license": "MIT", 359 | "engines": { 360 | "node": ">= 0.8" 361 | } 362 | }, 363 | "node_modules/es-define-property": { 364 | "version": "1.0.1", 365 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 366 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 367 | "license": "MIT", 368 | "engines": { 369 | "node": ">= 0.4" 370 | } 371 | }, 372 | "node_modules/es-errors": { 373 | "version": "1.3.0", 374 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 375 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 376 | "license": "MIT", 377 | "engines": { 378 | "node": ">= 0.4" 379 | } 380 | }, 381 | "node_modules/es-object-atoms": { 382 | "version": "1.1.1", 383 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 384 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 385 | "license": "MIT", 386 | "dependencies": { 387 | "es-errors": "^1.3.0" 388 | }, 389 | "engines": { 390 | "node": ">= 0.4" 391 | } 392 | }, 393 | "node_modules/escape-html": { 394 | "version": "1.0.3", 395 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 396 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 397 | "license": "MIT" 398 | }, 399 | "node_modules/etag": { 400 | "version": "1.8.1", 401 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 402 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 403 | "license": "MIT", 404 | "engines": { 405 | "node": ">= 0.6" 406 | } 407 | }, 408 | "node_modules/express": { 409 | "version": "4.21.2", 410 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", 411 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 412 | "license": "MIT", 413 | "dependencies": { 414 | "accepts": "~1.3.8", 415 | "array-flatten": "1.1.1", 416 | "body-parser": "1.20.3", 417 | "content-disposition": "0.5.4", 418 | "content-type": "~1.0.4", 419 | "cookie": "0.7.1", 420 | "cookie-signature": "1.0.6", 421 | "debug": "2.6.9", 422 | "depd": "2.0.0", 423 | "encodeurl": "~2.0.0", 424 | "escape-html": "~1.0.3", 425 | "etag": "~1.8.1", 426 | "finalhandler": "1.3.1", 427 | "fresh": "0.5.2", 428 | "http-errors": "2.0.0", 429 | "merge-descriptors": "1.0.3", 430 | "methods": "~1.1.2", 431 | "on-finished": "2.4.1", 432 | "parseurl": "~1.3.3", 433 | "path-to-regexp": "0.1.12", 434 | "proxy-addr": "~2.0.7", 435 | "qs": "6.13.0", 436 | "range-parser": "~1.2.1", 437 | "safe-buffer": "5.2.1", 438 | "send": "0.19.0", 439 | "serve-static": "1.16.2", 440 | "setprototypeof": "1.2.0", 441 | "statuses": "2.0.1", 442 | "type-is": "~1.6.18", 443 | "utils-merge": "1.0.1", 444 | "vary": "~1.1.2" 445 | }, 446 | "engines": { 447 | "node": ">= 0.10.0" 448 | }, 449 | "funding": { 450 | "type": "opencollective", 451 | "url": "https://opencollective.com/express" 452 | } 453 | }, 454 | "node_modules/express-prom-bundle": { 455 | "version": "8.0.0", 456 | "resolved": "https://registry.npmjs.org/express-prom-bundle/-/express-prom-bundle-8.0.0.tgz", 457 | "integrity": "sha512-UHdpaMks6Z/tvxQsNzhsE7nkdXb4/zEh/jwN0tfZSZOEF+aD0dlfl085EU4jveOq09v01c5sIUfjV4kJODZ2eQ==", 458 | "license": "MIT", 459 | "dependencies": { 460 | "@types/express": "^5.0.0", 461 | "on-finished": "^2.3.0", 462 | "url-value-parser": "^2.0.0" 463 | }, 464 | "engines": { 465 | "node": ">=18" 466 | }, 467 | "peerDependencies": { 468 | "prom-client": ">=15.0.0" 469 | } 470 | }, 471 | "node_modules/finalhandler": { 472 | "version": "1.3.1", 473 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", 474 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 475 | "license": "MIT", 476 | "dependencies": { 477 | "debug": "2.6.9", 478 | "encodeurl": "~2.0.0", 479 | "escape-html": "~1.0.3", 480 | "on-finished": "2.4.1", 481 | "parseurl": "~1.3.3", 482 | "statuses": "2.0.1", 483 | "unpipe": "~1.0.0" 484 | }, 485 | "engines": { 486 | "node": ">= 0.8" 487 | } 488 | }, 489 | "node_modules/forwarded": { 490 | "version": "0.2.0", 491 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 492 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 493 | "license": "MIT", 494 | "engines": { 495 | "node": ">= 0.6" 496 | } 497 | }, 498 | "node_modules/fresh": { 499 | "version": "0.5.2", 500 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 501 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 502 | "license": "MIT", 503 | "engines": { 504 | "node": ">= 0.6" 505 | } 506 | }, 507 | "node_modules/function-bind": { 508 | "version": "1.1.2", 509 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 510 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 511 | "license": "MIT", 512 | "funding": { 513 | "url": "https://github.com/sponsors/ljharb" 514 | } 515 | }, 516 | "node_modules/get-intrinsic": { 517 | "version": "1.3.0", 518 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 519 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 520 | "license": "MIT", 521 | "dependencies": { 522 | "call-bind-apply-helpers": "^1.0.2", 523 | "es-define-property": "^1.0.1", 524 | "es-errors": "^1.3.0", 525 | "es-object-atoms": "^1.1.1", 526 | "function-bind": "^1.1.2", 527 | "get-proto": "^1.0.1", 528 | "gopd": "^1.2.0", 529 | "has-symbols": "^1.1.0", 530 | "hasown": "^2.0.2", 531 | "math-intrinsics": "^1.1.0" 532 | }, 533 | "engines": { 534 | "node": ">= 0.4" 535 | }, 536 | "funding": { 537 | "url": "https://github.com/sponsors/ljharb" 538 | } 539 | }, 540 | "node_modules/get-proto": { 541 | "version": "1.0.1", 542 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 543 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 544 | "license": "MIT", 545 | "dependencies": { 546 | "dunder-proto": "^1.0.1", 547 | "es-object-atoms": "^1.0.0" 548 | }, 549 | "engines": { 550 | "node": ">= 0.4" 551 | } 552 | }, 553 | "node_modules/gopd": { 554 | "version": "1.2.0", 555 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 556 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 557 | "license": "MIT", 558 | "engines": { 559 | "node": ">= 0.4" 560 | }, 561 | "funding": { 562 | "url": "https://github.com/sponsors/ljharb" 563 | } 564 | }, 565 | "node_modules/has-symbols": { 566 | "version": "1.1.0", 567 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 568 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 569 | "license": "MIT", 570 | "engines": { 571 | "node": ">= 0.4" 572 | }, 573 | "funding": { 574 | "url": "https://github.com/sponsors/ljharb" 575 | } 576 | }, 577 | "node_modules/hasown": { 578 | "version": "2.0.2", 579 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 580 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 581 | "license": "MIT", 582 | "dependencies": { 583 | "function-bind": "^1.1.2" 584 | }, 585 | "engines": { 586 | "node": ">= 0.4" 587 | } 588 | }, 589 | "node_modules/http-errors": { 590 | "version": "2.0.0", 591 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 592 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 593 | "license": "MIT", 594 | "dependencies": { 595 | "depd": "2.0.0", 596 | "inherits": "2.0.4", 597 | "setprototypeof": "1.2.0", 598 | "statuses": "2.0.1", 599 | "toidentifier": "1.0.1" 600 | }, 601 | "engines": { 602 | "node": ">= 0.8" 603 | } 604 | }, 605 | "node_modules/iconv-lite": { 606 | "version": "0.4.24", 607 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 608 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 609 | "license": "MIT", 610 | "dependencies": { 611 | "safer-buffer": ">= 2.1.2 < 3" 612 | }, 613 | "engines": { 614 | "node": ">=0.10.0" 615 | } 616 | }, 617 | "node_modules/inherits": { 618 | "version": "2.0.4", 619 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 620 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 621 | "license": "ISC" 622 | }, 623 | "node_modules/ipaddr.js": { 624 | "version": "1.9.1", 625 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 626 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 627 | "license": "MIT", 628 | "engines": { 629 | "node": ">= 0.10" 630 | } 631 | }, 632 | "node_modules/jsonwebtoken": { 633 | "version": "9.0.2", 634 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", 635 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", 636 | "license": "MIT", 637 | "dependencies": { 638 | "jws": "^3.2.2", 639 | "lodash.includes": "^4.3.0", 640 | "lodash.isboolean": "^3.0.3", 641 | "lodash.isinteger": "^4.0.4", 642 | "lodash.isnumber": "^3.0.3", 643 | "lodash.isplainobject": "^4.0.6", 644 | "lodash.isstring": "^4.0.1", 645 | "lodash.once": "^4.0.0", 646 | "ms": "^2.1.1", 647 | "semver": "^7.5.4" 648 | }, 649 | "engines": { 650 | "node": ">=12", 651 | "npm": ">=6" 652 | } 653 | }, 654 | "node_modules/jsonwebtoken/node_modules/ms": { 655 | "version": "2.1.3", 656 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 657 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 658 | "license": "MIT" 659 | }, 660 | "node_modules/jwa": { 661 | "version": "1.4.1", 662 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 663 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 664 | "license": "MIT", 665 | "dependencies": { 666 | "buffer-equal-constant-time": "1.0.1", 667 | "ecdsa-sig-formatter": "1.0.11", 668 | "safe-buffer": "^5.0.1" 669 | } 670 | }, 671 | "node_modules/jws": { 672 | "version": "3.2.2", 673 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 674 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 675 | "license": "MIT", 676 | "dependencies": { 677 | "jwa": "^1.4.1", 678 | "safe-buffer": "^5.0.1" 679 | } 680 | }, 681 | "node_modules/lodash.includes": { 682 | "version": "4.3.0", 683 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 684 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", 685 | "license": "MIT" 686 | }, 687 | "node_modules/lodash.isboolean": { 688 | "version": "3.0.3", 689 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 690 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", 691 | "license": "MIT" 692 | }, 693 | "node_modules/lodash.isinteger": { 694 | "version": "4.0.4", 695 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 696 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", 697 | "license": "MIT" 698 | }, 699 | "node_modules/lodash.isnumber": { 700 | "version": "3.0.3", 701 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 702 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", 703 | "license": "MIT" 704 | }, 705 | "node_modules/lodash.isplainobject": { 706 | "version": "4.0.6", 707 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 708 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", 709 | "license": "MIT" 710 | }, 711 | "node_modules/lodash.isstring": { 712 | "version": "4.0.1", 713 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 714 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", 715 | "license": "MIT" 716 | }, 717 | "node_modules/lodash.once": { 718 | "version": "4.1.1", 719 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 720 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", 721 | "license": "MIT" 722 | }, 723 | "node_modules/math-intrinsics": { 724 | "version": "1.1.0", 725 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 726 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 727 | "license": "MIT", 728 | "engines": { 729 | "node": ">= 0.4" 730 | } 731 | }, 732 | "node_modules/media-typer": { 733 | "version": "0.3.0", 734 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 735 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 736 | "license": "MIT", 737 | "engines": { 738 | "node": ">= 0.6" 739 | } 740 | }, 741 | "node_modules/merge-descriptors": { 742 | "version": "1.0.3", 743 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", 744 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", 745 | "license": "MIT", 746 | "funding": { 747 | "url": "https://github.com/sponsors/sindresorhus" 748 | } 749 | }, 750 | "node_modules/methods": { 751 | "version": "1.1.2", 752 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 753 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 754 | "license": "MIT", 755 | "engines": { 756 | "node": ">= 0.6" 757 | } 758 | }, 759 | "node_modules/mime": { 760 | "version": "1.6.0", 761 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 762 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 763 | "license": "MIT", 764 | "bin": { 765 | "mime": "cli.js" 766 | }, 767 | "engines": { 768 | "node": ">=4" 769 | } 770 | }, 771 | "node_modules/mime-db": { 772 | "version": "1.52.0", 773 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 774 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 775 | "license": "MIT", 776 | "engines": { 777 | "node": ">= 0.6" 778 | } 779 | }, 780 | "node_modules/mime-types": { 781 | "version": "2.1.35", 782 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 783 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 784 | "license": "MIT", 785 | "dependencies": { 786 | "mime-db": "1.52.0" 787 | }, 788 | "engines": { 789 | "node": ">= 0.6" 790 | } 791 | }, 792 | "node_modules/morgan": { 793 | "version": "1.10.0", 794 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", 795 | "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", 796 | "license": "MIT", 797 | "dependencies": { 798 | "basic-auth": "~2.0.1", 799 | "debug": "2.6.9", 800 | "depd": "~2.0.0", 801 | "on-finished": "~2.3.0", 802 | "on-headers": "~1.0.2" 803 | }, 804 | "engines": { 805 | "node": ">= 0.8.0" 806 | } 807 | }, 808 | "node_modules/morgan/node_modules/on-finished": { 809 | "version": "2.3.0", 810 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 811 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", 812 | "license": "MIT", 813 | "dependencies": { 814 | "ee-first": "1.1.1" 815 | }, 816 | "engines": { 817 | "node": ">= 0.8" 818 | } 819 | }, 820 | "node_modules/ms": { 821 | "version": "2.0.0", 822 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 823 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 824 | "license": "MIT" 825 | }, 826 | "node_modules/negotiator": { 827 | "version": "0.6.3", 828 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 829 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 830 | "license": "MIT", 831 | "engines": { 832 | "node": ">= 0.6" 833 | } 834 | }, 835 | "node_modules/object-inspect": { 836 | "version": "1.13.4", 837 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 838 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 839 | "license": "MIT", 840 | "engines": { 841 | "node": ">= 0.4" 842 | }, 843 | "funding": { 844 | "url": "https://github.com/sponsors/ljharb" 845 | } 846 | }, 847 | "node_modules/on-finished": { 848 | "version": "2.4.1", 849 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 850 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 851 | "license": "MIT", 852 | "dependencies": { 853 | "ee-first": "1.1.1" 854 | }, 855 | "engines": { 856 | "node": ">= 0.8" 857 | } 858 | }, 859 | "node_modules/on-headers": { 860 | "version": "1.0.2", 861 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 862 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", 863 | "license": "MIT", 864 | "engines": { 865 | "node": ">= 0.8" 866 | } 867 | }, 868 | "node_modules/parseurl": { 869 | "version": "1.3.3", 870 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 871 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 872 | "license": "MIT", 873 | "engines": { 874 | "node": ">= 0.8" 875 | } 876 | }, 877 | "node_modules/path-to-regexp": { 878 | "version": "0.1.12", 879 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", 880 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", 881 | "license": "MIT" 882 | }, 883 | "node_modules/prom-client": { 884 | "version": "15.1.3", 885 | "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", 886 | "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", 887 | "license": "Apache-2.0", 888 | "peer": true, 889 | "dependencies": { 890 | "@opentelemetry/api": "^1.4.0", 891 | "tdigest": "^0.1.1" 892 | }, 893 | "engines": { 894 | "node": "^16 || ^18 || >=20" 895 | } 896 | }, 897 | "node_modules/proxy-addr": { 898 | "version": "2.0.7", 899 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 900 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 901 | "license": "MIT", 902 | "dependencies": { 903 | "forwarded": "0.2.0", 904 | "ipaddr.js": "1.9.1" 905 | }, 906 | "engines": { 907 | "node": ">= 0.10" 908 | } 909 | }, 910 | "node_modules/qs": { 911 | "version": "6.13.0", 912 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 913 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 914 | "license": "BSD-3-Clause", 915 | "dependencies": { 916 | "side-channel": "^1.0.6" 917 | }, 918 | "engines": { 919 | "node": ">=0.6" 920 | }, 921 | "funding": { 922 | "url": "https://github.com/sponsors/ljharb" 923 | } 924 | }, 925 | "node_modules/range-parser": { 926 | "version": "1.2.1", 927 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 928 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 929 | "license": "MIT", 930 | "engines": { 931 | "node": ">= 0.6" 932 | } 933 | }, 934 | "node_modules/raw-body": { 935 | "version": "2.5.2", 936 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 937 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 938 | "license": "MIT", 939 | "dependencies": { 940 | "bytes": "3.1.2", 941 | "http-errors": "2.0.0", 942 | "iconv-lite": "0.4.24", 943 | "unpipe": "1.0.0" 944 | }, 945 | "engines": { 946 | "node": ">= 0.8" 947 | } 948 | }, 949 | "node_modules/readable-stream": { 950 | "version": "3.6.2", 951 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 952 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 953 | "license": "MIT", 954 | "dependencies": { 955 | "inherits": "^2.0.3", 956 | "string_decoder": "^1.1.1", 957 | "util-deprecate": "^1.0.1" 958 | }, 959 | "engines": { 960 | "node": ">= 6" 961 | } 962 | }, 963 | "node_modules/safe-buffer": { 964 | "version": "5.2.1", 965 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 966 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 967 | "funding": [ 968 | { 969 | "type": "github", 970 | "url": "https://github.com/sponsors/feross" 971 | }, 972 | { 973 | "type": "patreon", 974 | "url": "https://www.patreon.com/feross" 975 | }, 976 | { 977 | "type": "consulting", 978 | "url": "https://feross.org/support" 979 | } 980 | ], 981 | "license": "MIT" 982 | }, 983 | "node_modules/safer-buffer": { 984 | "version": "2.1.2", 985 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 986 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 987 | "license": "MIT" 988 | }, 989 | "node_modules/semver": { 990 | "version": "7.7.1", 991 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", 992 | "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", 993 | "license": "ISC", 994 | "bin": { 995 | "semver": "bin/semver.js" 996 | }, 997 | "engines": { 998 | "node": ">=10" 999 | } 1000 | }, 1001 | "node_modules/send": { 1002 | "version": "0.19.0", 1003 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", 1004 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 1005 | "license": "MIT", 1006 | "dependencies": { 1007 | "debug": "2.6.9", 1008 | "depd": "2.0.0", 1009 | "destroy": "1.2.0", 1010 | "encodeurl": "~1.0.2", 1011 | "escape-html": "~1.0.3", 1012 | "etag": "~1.8.1", 1013 | "fresh": "0.5.2", 1014 | "http-errors": "2.0.0", 1015 | "mime": "1.6.0", 1016 | "ms": "2.1.3", 1017 | "on-finished": "2.4.1", 1018 | "range-parser": "~1.2.1", 1019 | "statuses": "2.0.1" 1020 | }, 1021 | "engines": { 1022 | "node": ">= 0.8.0" 1023 | } 1024 | }, 1025 | "node_modules/send/node_modules/encodeurl": { 1026 | "version": "1.0.2", 1027 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1028 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 1029 | "license": "MIT", 1030 | "engines": { 1031 | "node": ">= 0.8" 1032 | } 1033 | }, 1034 | "node_modules/send/node_modules/ms": { 1035 | "version": "2.1.3", 1036 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1037 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1038 | "license": "MIT" 1039 | }, 1040 | "node_modules/serve-static": { 1041 | "version": "1.16.2", 1042 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", 1043 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 1044 | "license": "MIT", 1045 | "dependencies": { 1046 | "encodeurl": "~2.0.0", 1047 | "escape-html": "~1.0.3", 1048 | "parseurl": "~1.3.3", 1049 | "send": "0.19.0" 1050 | }, 1051 | "engines": { 1052 | "node": ">= 0.8.0" 1053 | } 1054 | }, 1055 | "node_modules/setprototypeof": { 1056 | "version": "1.2.0", 1057 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1058 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 1059 | "license": "ISC" 1060 | }, 1061 | "node_modules/side-channel": { 1062 | "version": "1.1.0", 1063 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1064 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1065 | "license": "MIT", 1066 | "dependencies": { 1067 | "es-errors": "^1.3.0", 1068 | "object-inspect": "^1.13.3", 1069 | "side-channel-list": "^1.0.0", 1070 | "side-channel-map": "^1.0.1", 1071 | "side-channel-weakmap": "^1.0.2" 1072 | }, 1073 | "engines": { 1074 | "node": ">= 0.4" 1075 | }, 1076 | "funding": { 1077 | "url": "https://github.com/sponsors/ljharb" 1078 | } 1079 | }, 1080 | "node_modules/side-channel-list": { 1081 | "version": "1.0.0", 1082 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1083 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1084 | "license": "MIT", 1085 | "dependencies": { 1086 | "es-errors": "^1.3.0", 1087 | "object-inspect": "^1.13.3" 1088 | }, 1089 | "engines": { 1090 | "node": ">= 0.4" 1091 | }, 1092 | "funding": { 1093 | "url": "https://github.com/sponsors/ljharb" 1094 | } 1095 | }, 1096 | "node_modules/side-channel-map": { 1097 | "version": "1.0.1", 1098 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1099 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1100 | "license": "MIT", 1101 | "dependencies": { 1102 | "call-bound": "^1.0.2", 1103 | "es-errors": "^1.3.0", 1104 | "get-intrinsic": "^1.2.5", 1105 | "object-inspect": "^1.13.3" 1106 | }, 1107 | "engines": { 1108 | "node": ">= 0.4" 1109 | }, 1110 | "funding": { 1111 | "url": "https://github.com/sponsors/ljharb" 1112 | } 1113 | }, 1114 | "node_modules/side-channel-weakmap": { 1115 | "version": "1.0.2", 1116 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1117 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1118 | "license": "MIT", 1119 | "dependencies": { 1120 | "call-bound": "^1.0.2", 1121 | "es-errors": "^1.3.0", 1122 | "get-intrinsic": "^1.2.5", 1123 | "object-inspect": "^1.13.3", 1124 | "side-channel-map": "^1.0.1" 1125 | }, 1126 | "engines": { 1127 | "node": ">= 0.4" 1128 | }, 1129 | "funding": { 1130 | "url": "https://github.com/sponsors/ljharb" 1131 | } 1132 | }, 1133 | "node_modules/statuses": { 1134 | "version": "2.0.1", 1135 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1136 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1137 | "license": "MIT", 1138 | "engines": { 1139 | "node": ">= 0.8" 1140 | } 1141 | }, 1142 | "node_modules/string_decoder": { 1143 | "version": "1.3.0", 1144 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1145 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1146 | "license": "MIT", 1147 | "dependencies": { 1148 | "safe-buffer": "~5.2.0" 1149 | } 1150 | }, 1151 | "node_modules/tdigest": { 1152 | "version": "0.1.2", 1153 | "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", 1154 | "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", 1155 | "license": "MIT", 1156 | "peer": true, 1157 | "dependencies": { 1158 | "bintrees": "1.0.2" 1159 | } 1160 | }, 1161 | "node_modules/toidentifier": { 1162 | "version": "1.0.1", 1163 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1164 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1165 | "license": "MIT", 1166 | "engines": { 1167 | "node": ">=0.6" 1168 | } 1169 | }, 1170 | "node_modules/type-is": { 1171 | "version": "1.6.18", 1172 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1173 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1174 | "license": "MIT", 1175 | "dependencies": { 1176 | "media-typer": "0.3.0", 1177 | "mime-types": "~2.1.24" 1178 | }, 1179 | "engines": { 1180 | "node": ">= 0.6" 1181 | } 1182 | }, 1183 | "node_modules/typedarray": { 1184 | "version": "0.0.6", 1185 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1186 | "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", 1187 | "license": "MIT" 1188 | }, 1189 | "node_modules/undici-types": { 1190 | "version": "6.20.0", 1191 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", 1192 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", 1193 | "license": "MIT" 1194 | }, 1195 | "node_modules/unpipe": { 1196 | "version": "1.0.0", 1197 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1198 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1199 | "license": "MIT", 1200 | "engines": { 1201 | "node": ">= 0.8" 1202 | } 1203 | }, 1204 | "node_modules/url-value-parser": { 1205 | "version": "2.2.0", 1206 | "resolved": "https://registry.npmjs.org/url-value-parser/-/url-value-parser-2.2.0.tgz", 1207 | "integrity": "sha512-yIQdxJpgkPamPPAPuGdS7Q548rLhny42tg8d4vyTNzFqvOnwqrgHXvgehT09U7fwrzxi3RxCiXjoNUNnNOlQ8A==", 1208 | "license": "MIT-0", 1209 | "engines": { 1210 | "node": ">=6.0.0" 1211 | } 1212 | }, 1213 | "node_modules/util-deprecate": { 1214 | "version": "1.0.2", 1215 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1216 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 1217 | "license": "MIT" 1218 | }, 1219 | "node_modules/utils-merge": { 1220 | "version": "1.0.1", 1221 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1222 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1223 | "license": "MIT", 1224 | "engines": { 1225 | "node": ">= 0.4.0" 1226 | } 1227 | }, 1228 | "node_modules/vary": { 1229 | "version": "1.1.2", 1230 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1231 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1232 | "license": "MIT", 1233 | "engines": { 1234 | "node": ">= 0.8" 1235 | } 1236 | } 1237 | } 1238 | } 1239 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mendhak/http-https-echo", 3 | "repository": { 4 | "type": "git", 5 | "url": "https://github.com/mendhak/docker-http-echo" 6 | }, 7 | "version": "1.0.1", 8 | "description": "JSON service for debugging a web setup", 9 | "main": "index.js", 10 | "scripts": { 11 | "start": "node index.js", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "author": "Mendhak ", 15 | "license": "BSD-3-Clause", 16 | "engines": { 17 | "node": ">=16.0.0" 18 | }, 19 | "dependencies": { 20 | "concat-stream": "^2.0.0", 21 | "express": "^4.21.2", 22 | "express-prom-bundle": "^8.0.0", 23 | "jsonwebtoken": "^9.0.0", 24 | "morgan": "^1.10.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /screenshots/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/docker-http-https-echo/c77a6dfd10f7300a0a8d4d99d445bc8e733fff94/screenshots/screenshot.png -------------------------------------------------------------------------------- /screenshots/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/docker-http-https-echo/c77a6dfd10f7300a0a8d4d99d445bc8e733fff94/screenshots/screenshot2.png -------------------------------------------------------------------------------- /screenshots/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/docker-http-https-echo/c77a6dfd10f7300a0a8d4d99d445bc8e733fff94/screenshots/screenshot3.png -------------------------------------------------------------------------------- /tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | function message { 6 | echo "" 7 | echo "---------------------------------------------------------------" 8 | echo $1 9 | echo "---------------------------------------------------------------" 10 | } 11 | 12 | RESTORE=$(echo -en '\033[0m') 13 | RED=$(echo -en '\033[01;31m') 14 | GREEN=$(echo -en '\033[01;32m') 15 | 16 | function failed { 17 | echo ${RED}✗$1${RESTORE} 18 | } 19 | 20 | function passed { 21 | echo ${GREEN}✓$1${RESTORE} 22 | } 23 | 24 | if ! [ -x "$(command -v jq)" ]; then 25 | message "JQ not installed. Installing..." 26 | sudo apt -y install jq 27 | fi 28 | 29 | message " Check if we're in Github Actions or local run " 30 | if [ -n "${GITHUB_ACTIONS:-}" ]; then 31 | echo " Github Actions. Image should already be built." 32 | docker images 33 | if [ -z "$(docker images -q mendhak/http-https-echo:testing 2> /dev/null)" ]; then 34 | echo "Docker image mendhak/http-https-echo:testing not found. Exiting." 35 | exit 1 36 | fi 37 | else 38 | echo " Local run. Build image " 39 | docker build -t mendhak/http-https-echo:testing . 40 | fi 41 | 42 | 43 | mkdir -p testarea 44 | pushd testarea 45 | 46 | message " Cleaning up from previous test run " 47 | docker ps -aq --filter "name=http-echo-tests" | grep -q . && docker stop http-echo-tests && docker rm -f http-echo-tests 48 | 49 | message " Start container normally " 50 | docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 51 | sleep 10 52 | 53 | 54 | message " Make http(s) request, and test the path, method, header and status code. " 55 | REQUEST=$(curl -s -k -X PUT -H "Arbitrary:Header" -d aaa=bbb 'https://localhost:8443/hello-world?ccc=ddd&myquery=98765') 56 | if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ 57 | [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ 58 | [ $(echo $REQUEST | jq -r '.query.myquery') == '98765' ] && \ 59 | [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] 60 | then 61 | passed "HTTPS request passed." 62 | else 63 | failed "HTTPS request failed." 64 | echo $REQUEST | jq 65 | exit 1 66 | fi 67 | REQUEST_WITH_STATUS_CODE=$(curl -s -k -o /dev/null -w "%{http_code}" -H "x-set-response-status-code: 404" https://localhost:8443/hello-world) 68 | REQUEST_WITH_STATUS_CODE_V=$(curl -v -k -o /dev/null -w "%{http_code}" -H "x-set-response-status-code: 404" https://localhost:8443/hello-world) 69 | if [ $(echo $REQUEST_WITH_STATUS_CODE == '404') ] 70 | then 71 | passed "HTTPS status code header passed." 72 | else 73 | failed "HTTPS status code header failed." 74 | echo $REQUEST_WITH_STATUS_CODE_V 75 | exit 1 76 | fi 77 | 78 | REQUEST_WITH_STATUS_CODE=$(curl -s -k -o /dev/null -w "%{http_code}" https://localhost:8443/status/test?x-set-response-status-code=419) 79 | REQUEST_WITH_STATUS_CODE_V=$(curl -v -k -o /dev/null -w "%{http_code}" https://localhost:8443/hello-world?x-set-response-status-code=419) 80 | if [ $(echo $REQUEST_WITH_STATUS_CODE == '419') ] 81 | then 82 | passed "HTTPS status code querystring passed." 83 | else 84 | failed "HTTPS status code querystring failed." 85 | echo $REQUEST_WITH_STATUS_CODE_V 86 | exit 1 87 | fi 88 | 89 | REQUEST_WITH_CONTENT_TYPE_HEADER=$(curl -o /dev/null -k -Ss -w "%{content_type}" -H "x-set-response-content-type: aaaa/bbbb" https://localhost:8443/) 90 | if [[ "$REQUEST_WITH_CONTENT_TYPE_HEADER" == *"aaaa/bbbb"* ]]; then 91 | passed "Request with custom response type header, passed" 92 | else 93 | echo $REQUEST_WITH_CONTENT_TYPE_HEADER 94 | failed "Request with custom response type header, failed." 95 | exit 1 96 | fi 97 | 98 | REQUEST_WITH_CONTENT_TYPE_PARAMETER=$(curl -o /dev/null -k -Ss -w "%{content_type}" https://localhost:8443/green/chocolate?x-set-response-content-type=jellyfish/cabbage) 99 | if [[ "$REQUEST_WITH_CONTENT_TYPE_PARAMETER" == *"jellyfish/cabbage"* ]]; then 100 | passed "Request with custom response type parameter, passed" 101 | else 102 | echo $REQUEST_WITH_CONTENT_TYPE_PARAMETER 103 | failed "Request with custom response type parameter, failed." 104 | exit 1 105 | fi 106 | 107 | 108 | REQUEST_WITH_SLEEP_MS=$(curl -o /dev/null -Ss -H "x-set-response-delay-ms: 6000" -k https://localhost:8443/ -w '%{time_total}') 109 | if [[ $(echo "$REQUEST_WITH_SLEEP_MS>5" | bc -l) == 1 ]]; then 110 | passed "Request header with response delay passed" 111 | else 112 | failed "Request header with response delay failed" 113 | echo $REQUEST_WITH_SLEEP_MS 114 | exit 1 115 | fi 116 | 117 | REQUEST_WITH_SLEEP_MS=$(curl -o /dev/null -Ss -k https://localhost:8443/sleep/test?x-set-response-delay-ms=5000 -w '%{time_total}') 118 | if [[ $(echo "$REQUEST_WITH_SLEEP_MS>4" | bc -l) == 1 ]]; then 119 | passed "Request query with response delay passed" 120 | else 121 | failed "Request query with response delay failed" 122 | echo $REQUEST_WITH_SLEEP_MS 123 | exit 1 124 | fi 125 | 126 | REQUEST_WITH_INVALID_SLEEP_MS=$(curl -o /dev/null -Ss -H "x-set-response-delay-ms: XXXX" -k https://localhost:8443/ -w '%{time_total}') 127 | if [[ $(echo "$REQUEST_WITH_INVALID_SLEEP_MS<2" | bc -l) == 1 ]]; then 128 | passed "Request with invalid response delay passed" 129 | else 130 | failed "Request with invalid response delay failed" 131 | echo $REQUEST_WITH_INVALID_SLEEP_MS 132 | exit 1 133 | fi 134 | 135 | REQUEST=$(curl -s -X PUT -H "Arbitrary:Header" -d aaa=bbb http://localhost:8080/hello-world) 136 | if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ 137 | [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ 138 | [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] 139 | then 140 | passed "HTTP request with arbitrary header passed." 141 | else 142 | failed "HTTP request with arbitrary header failed." 143 | echo $REQUEST | jq 144 | exit 1 145 | fi 146 | 147 | message " Make JSON request, and test that json is in the output. " 148 | REQUEST=$(curl -s -X POST -H "Content-Type: application/json" -d '{"a":"b"}' http://localhost:8080/) 149 | if [ $(echo $REQUEST | jq -r '.json.a') == 'b' ] 150 | then 151 | passed "JSON test passed." 152 | else 153 | failed "JSON test failed." 154 | echo $REQUEST | jq 155 | exit 1 156 | fi 157 | 158 | 159 | message " Make JSON request with gzip Content-Encoding, and test that json is in the output. " 160 | REQUEST=$(echo -n '{"a":"b"}' | gzip | curl -s -X POST -H "Content-Encoding: gzip" -H "Content-Type: application/json" --data-binary @- http://localhost:8080/) 161 | if [ $(echo $REQUEST | jq -r '.json.a') == 'b' ] 162 | then 163 | passed "JSON test passed." 164 | else 165 | failed "JSON test failed." 166 | echo $REQUEST | jq 167 | exit 1 168 | fi 169 | 170 | REQUEST=$(curl -s -X POST -H "Content-Type: application/json" -d 'not-json' http://localhost:8080) 171 | if [ $(echo $REQUEST | jq -r '.json') == 'null' ]; then 172 | passed "JSON with Invalid Body test passed." 173 | else 174 | failed "JSON with Invalid Body test failed." 175 | echo $REQUEST | jq 176 | exit 1 177 | fi 178 | 179 | message " Stop containers " 180 | docker stop http-echo-tests 181 | sleep 5 182 | 183 | message " Start container with different internal ports " 184 | docker run -d --rm -e HTTP_PORT=8888 -e HTTPS_PORT=9999 --name http-echo-tests -p 8080:8888 -p 8443:9999 -t mendhak/http-https-echo:testing 185 | sleep 5 186 | 187 | message " Make http(s) request, and test the path, method and header. " 188 | REQUEST=$(curl -s -k -X PUT -H "Arbitrary:Header" -d aaa=bbb https://localhost:8443/hello-world) 189 | if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ 190 | [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ 191 | [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] 192 | then 193 | passed "HTTPS request passed." 194 | else 195 | failed "HTTPS request failed." 196 | echo $REQUEST | jq 197 | exit 1 198 | fi 199 | 200 | REQUEST=$(curl -s -X PUT -H "Arbitrary:Header" -d aaa=bbb http://localhost:8080/hello-world) 201 | if [ $(echo $REQUEST | jq -r '.path') == '/hello-world' ] && \ 202 | [ $(echo $REQUEST | jq -r '.method') == 'PUT' ] && \ 203 | [ $(echo $REQUEST | jq -r '.headers.arbitrary') == 'Header' ] 204 | then 205 | passed "HTTP request passed." 206 | else 207 | failed "HTTP request failed." 208 | echo $REQUEST | jq 209 | exit 1 210 | fi 211 | 212 | 213 | message " Stop containers " 214 | docker stop http-echo-tests 215 | sleep 5 216 | 217 | message " Start container with empty responses " 218 | docker run -d --rm -e ECHO_BACK_TO_CLIENT=false --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 219 | sleep 5 220 | REQUEST=$(curl -s -k http://localhost:8080/a/b/c) 221 | if [[ -z ${REQUEST} ]] 222 | then 223 | passed "Response is empty." 224 | else 225 | failed "Expected empty response, but got a non-empty response." 226 | echo $REQUEST 227 | exit 1 228 | fi 229 | 230 | message " Stop containers " 231 | docker stop http-echo-tests 232 | sleep 5 233 | 234 | message " Start container with response body only " 235 | docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 236 | sleep 5 237 | RESPONSE=$(curl -s -k -X POST -d 'cauliflower' http://localhost:8080/a/b/c?response_body_only=true) 238 | if [[ ${RESPONSE} == "cauliflower" ]] 239 | then 240 | passed "Response body only received." 241 | else 242 | failed "Expected response body only." 243 | echo $RESPONSE 244 | exit 1 245 | fi 246 | 247 | 248 | message " Stop containers " 249 | docker stop http-echo-tests 250 | sleep 5 251 | 252 | message " Start container with JWT_HEADER " 253 | docker run -d --rm -e JWT_HEADER=Authentication --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 254 | sleep 5 255 | 256 | REQUEST=$(curl -s -k -H "Authentication: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" https://localhost:8443/ ) 257 | if [ $(echo $REQUEST | jq -r '.jwt.header.typ') == 'JWT' ] && \ 258 | [ $(echo $REQUEST | jq -r '.jwt.header.alg') == 'HS256' ] && \ 259 | [ $(echo $REQUEST | jq -r '.jwt.payload.sub') == '1234567890' ] 260 | then 261 | passed "JWT request passed." 262 | else 263 | failed "JWT request failed." 264 | echo $REQUEST | jq 265 | exit 1 266 | fi 267 | 268 | message " Stop containers " 269 | docker stop http-echo-tests 270 | sleep 5 271 | 272 | 273 | message " Start container with LOG_IGNORE_PATH (normal path)" 274 | docker run -d --rm -e LOG_IGNORE_PATH=/ping --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 275 | sleep 5 276 | curl -s -k -X POST -d "banana" https://localhost:8443/ping > /dev/null 277 | 278 | if [ $(docker logs http-echo-tests | wc -l) == 2 ] && \ 279 | ! [ $(docker logs http-echo-tests | grep banana) ] 280 | then 281 | passed "LOG_IGNORE_PATH ignored the /ping path" 282 | else 283 | failed "LOG_IGNORE_PATH failed" 284 | docker logs http-echo-tests 285 | exit 1 286 | fi 287 | 288 | message " Stop containers " 289 | docker stop http-echo-tests 290 | sleep 5 291 | 292 | message " Start container with LOG_IGNORE_PATH (regex path)" 293 | docker run -d --rm -e LOG_IGNORE_PATH="^\/ping|^\/health|^\/metrics" --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 294 | sleep 5 295 | curl -s -k -X POST -d "banana" https://localhost:8443/metrics > /dev/null 296 | 297 | if [ $(docker logs http-echo-tests | wc -l) == 2 ] && \ 298 | ! [ $(docker logs http-echo-tests | grep banana) ] 299 | then 300 | passed "LOG_IGNORE_PATH ignored the /metrics path" 301 | else 302 | failed "LOG_IGNORE_PATH failed" 303 | docker logs http-echo-tests 304 | exit 1 305 | fi 306 | 307 | # Test a positive case where the path is not ignored 308 | curl -s -k -X POST -d "strawberry" https://localhost:8443/veryvisible > /dev/null 309 | 310 | if [[ $(docker logs http-echo-tests | grep strawberry) ]] 311 | then 312 | passed "LOG_IGNORE_PATH didn't ignore the /veryvisible path" 313 | else 314 | failed "LOG_IGNORE_PATH failed, it should not ignore the /veryvisible path" 315 | docker logs http-echo-tests 316 | exit 1 317 | fi 318 | 319 | 320 | message " Stop containers " 321 | docker stop http-echo-tests 322 | sleep 5 323 | 324 | message " Start container with LOG_IGNORE_PATH (ignore all paths) " 325 | docker run -d --rm -e LOG_IGNORE_PATH=".*" --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 326 | sleep 5 327 | curl -s -k -X POST -d "banana" https://localhost:8443/ > /dev/null 328 | 329 | if [ $(docker logs http-echo-tests | wc -l) == 2 ] && \ 330 | ! [ $(docker logs http-echo-tests | grep banana) ] 331 | then 332 | passed "LOG_IGNORE_PATH ignored all paths" 333 | else 334 | failed "LOG_IGNORE_PATH failed" 335 | docker logs http-echo-tests 336 | exit 1 337 | fi 338 | 339 | message " Stop containers " 340 | docker stop http-echo-tests 341 | sleep 5 342 | 343 | 344 | message " Start container with DISABLE_REQUEST_LOGS " 345 | docker run -d --rm -e DISABLE_REQUEST_LOGS=true --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 346 | sleep 5 347 | curl -s -k -X GET https://localhost:8443/strawberry > /dev/null 348 | if [ $(docker logs http-echo-tests | grep -c "GET /strawberry HTTP/1.1") -eq 0 ] 349 | then 350 | passed "DISABLE_REQUEST_LOGS disabled Express HTTP logging" 351 | else 352 | failed "DISABLE_REQUEST_LOGS failed" 353 | docker logs http-echo-tests 354 | exit 1 355 | fi 356 | 357 | message " Stop containers " 358 | docker stop http-echo-tests 359 | sleep 5 360 | 361 | message " Start container with CORS_CONFIG" 362 | docker run -d --rm \ 363 | -e CORS_ALLOW_ORIGIN="http://example.com" -e CORS_ALLOW_HEADERS="x-custom-test-header" \ 364 | --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 365 | sleep 5 366 | # Check if the expected CORS headers are present in the response 367 | if curl -s -i http://localhost:8080/ 2>&1 | grep -q -E \ 368 | "Access-Control-Allow-Headers: x-custom-test-header" && 369 | curl -s -i http://localhost:8080/ 2>&1 | grep -q -E \ 370 | "Access-Control-Allow-Origin: http://example.com"; then 371 | passed "CORS_CONFIG expected CORS headers found in response" 372 | else 373 | failed "CORS_CONFIG failed." 374 | docker logs http-echo-tests 375 | exit 1 376 | fi 377 | 378 | message " Stop containers " 379 | docker stop http-echo-tests 380 | sleep 5 381 | 382 | message " Start container with LOG_WITHOUT_NEWLINE " 383 | docker run -d --rm -e LOG_WITHOUT_NEWLINE=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 384 | sleep 5 385 | curl -s -k -X POST -d "tiramisu" https://localhost:8443/ > /dev/null 386 | 387 | if [ $(docker logs http-echo-tests | wc -l) == 3 ] && \ 388 | [ $(docker logs http-echo-tests | grep tiramisu) ] 389 | then 390 | passed "LOG_WITHOUT_NEWLINE logged output in single line" 391 | else 392 | failed "LOG_WITHOUT_NEWLINE failed" 393 | docker logs http-echo-tests 394 | exit 1 395 | fi 396 | 397 | 398 | message " Stop containers " 399 | docker stop http-echo-tests 400 | sleep 5 401 | 402 | message " Check that container is running as a NON ROOT USER by default" 403 | docker run -d --name http-echo-tests --rm mendhak/http-https-echo:testing 404 | 405 | WHOAMI=$(docker exec http-echo-tests whoami) 406 | 407 | if [ "$WHOAMI" == "node" ] 408 | then 409 | passed "Running as non root user" 410 | else 411 | failed "Running as root user" 412 | exit 1 413 | fi 414 | 415 | message " Stop containers " 416 | docker stop http-echo-tests 417 | sleep 5 418 | 419 | message " Check that container is running as user different that the user defined in image" 420 | IMAGE_USER="$(docker image inspect mendhak/http-https-echo:testing -f '{{ .Config.User }}')" 421 | CONTAINER_USER="$((IMAGE_USER + 1000000))" 422 | docker run -d --name http-echo-tests --rm -u "${CONTAINER_USER}" -p 8080:8080 mendhak/http-https-echo:testing 423 | sleep 5 424 | curl -s http://localhost:8080 > /dev/null 425 | 426 | WHOAMI="$(docker exec http-echo-tests id -u)" 427 | 428 | if [ "$WHOAMI" == "$CONTAINER_USER" ] 429 | then 430 | passed "Running as $CONTAINER_USER user" 431 | else 432 | failed "Not running as $CONTAINER_USER user or failed to start" 433 | exit 1 434 | fi 435 | 436 | message " Stop containers " 437 | docker stop http-echo-tests 438 | sleep 5 439 | 440 | message " Check that mTLS server responds with client certificate details" 441 | # Generate a new self signed cert locally 442 | openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout privkey.pem -out fullchain.pem \ 443 | -subj "/CN=client.example.net" \ 444 | -addext "subjectAltName=DNS:client.example.net" 445 | docker run -d --rm -e MTLS_ENABLE=1 --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 446 | sleep 5 447 | COMMON_NAME="$(curl -sk --cert fullchain.pem --key privkey.pem https://localhost:8443/ | jq -r '.clientCertificate.subject.CN')" 448 | SAN="$(curl -sk --cert fullchain.pem --key privkey.pem https://localhost:8443/ | jq -r '.clientCertificate.subjectaltname')" 449 | if [ "$COMMON_NAME" == "client.example.net" ] && [ "$SAN" == "DNS:client.example.net" ] 450 | then 451 | passed "Client certificate details are present in the output" 452 | else 453 | failed "Client certificate details not found in output" 454 | exit 1 455 | fi 456 | 457 | message " Check if certificate is not passed, then client certificate details are empty" 458 | CLIENT_CERT="$(curl -sk https://localhost:8443/ | jq -r '.clientCertificate')" 459 | if [ "$CLIENT_CERT" == "{}" ] 460 | then 461 | passed "Client certificate details are not present in the response" 462 | else 463 | failed "Client certificate details found in output? ${CLIENT_CERT}" 464 | exit 1 465 | fi 466 | 467 | message " Check that HTTP server does not have any client certificate property" 468 | CLIENT_CERT=$(curl -sk --cert cert.pem --key privkey.pem http://localhost:8080/ | jq 'has("clientCertificate")') 469 | if [ "$CLIENT_CERT" == "false" ] 470 | then 471 | passed "Client certificate details are not present in regular HTTP server" 472 | else 473 | failed "Client certificate details found in output? ${CLIENT_CERT}" 474 | exit 1 475 | fi 476 | 477 | message " Stop containers " 478 | docker stop http-echo-tests 479 | sleep 5 480 | 481 | message " Check that SSL certificate and private key are loaded from custom location" 482 | cert_common_name="server.example.net" 483 | https_cert_file="$(pwd)/server_fullchain.pem" 484 | https_key_file="$(pwd)/server_privkey.pem" 485 | # Generate a new self signed cert locally 486 | openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout "${https_key_file}" -out "${https_cert_file}" \ 487 | -subj "/CN=${cert_common_name}" \ 488 | -addext "subjectAltName=DNS:${cert_common_name}" 489 | chmod a+r "${https_cert_file}" 490 | chmod a+r "${https_key_file}" 491 | container_https_cert_file="/test/tls.crt" 492 | container_https_key_file="/test/tls.key" 493 | docker run -d --rm \ 494 | -v "${https_cert_file}:${container_https_cert_file}:ro,z" \ 495 | -e HTTPS_CERT_FILE="${container_https_cert_file}" \ 496 | -v "${https_key_file}:${container_https_key_file}:ro,z" \ 497 | -e HTTPS_KEY_FILE="${container_https_key_file}" \ 498 | --name http-echo-tests -p 8443:8443 -t mendhak/http-https-echo:testing 499 | sleep 5 500 | 501 | REQUEST_WITH_STATUS_CODE="$(curl -s --cacert "$(pwd)/server_fullchain.pem" -o /dev/null -w "%{http_code}" \ 502 | --resolve "${cert_common_name}:8443:127.0.0.1" "https://${cert_common_name}:8443/hello-world")" 503 | if [ "${REQUEST_WITH_STATUS_CODE}" = 200 ] 504 | then 505 | passed "Server certificate and private key are loaded from configured custom location" 506 | else 507 | failed "Custom certificate location test failed" 508 | exit 1 509 | fi 510 | 511 | message " Stop containers " 512 | docker stop http-echo-tests 513 | sleep 5 514 | 515 | message " Check that environment variables returned in response if enabled" 516 | 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:testing 517 | sleep 5 518 | RESPONSE_BODY="$(curl -sk https://localhost:8443/ | jq -r '.env.ECHO_INCLUDE_ENV_VARS')" 519 | 520 | if [ "$RESPONSE_BODY" == "1" ] 521 | then 522 | passed "Environment variables present in the output" 523 | else 524 | failed "Client certificate details found in output? ${RESPONSE_BODY}" 525 | exit 1 526 | fi 527 | 528 | message " Stop containers " 529 | docker stop http-echo-tests 530 | sleep 5 531 | 532 | message " Check that environment variables are not present in response by default" 533 | docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 534 | sleep 5 535 | RESPONSE_BODY_ENV_CHECK="$(curl -sk https://localhost:8443/ | jq 'has("env")')" 536 | 537 | if [ "$RESPONSE_BODY_ENV_CHECK" == "false" ] 538 | then 539 | passed "Environment variables not present in the output by default" 540 | else 541 | failed "Environment variables found in output?" 542 | exit 1 543 | fi 544 | 545 | message " Stop containers " 546 | docker stop http-echo-tests 547 | sleep 5 548 | 549 | message " Start container with PROMETHEUS disabled " 550 | docker run -d --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 551 | sleep 5 552 | curl -s -k -X POST -d "tiramisu" https://localhost:8443/ > /dev/null 553 | 554 | # grep for http_request_duration_seconds_count ensure it is not present at /metric path 555 | 556 | METRICS_CHECK="$(curl -sk http://localhost:8080/metrics | grep -v http_request_duration_seconds_count )" 557 | 558 | if [[ "$METRICS_CHECK" == *"http_request_duration_seconds_count"* ]] 559 | then 560 | failed "PROMETHEUS metrics are enabled" 561 | exit 1 562 | else 563 | passed "PROMETHEUS metrics are disabled by default" 564 | fi 565 | 566 | message " Stop containers " 567 | docker stop http-echo-tests 568 | sleep 5 569 | 570 | message " Start container with PROMETHEUS enabled " 571 | docker run -d -e PROMETHEUS_ENABLED=true --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 572 | sleep 5 573 | curl -s -k -X POST -d "tiramisu" https://localhost:8443/ > /dev/null 574 | 575 | METRICS_CHECK="$(curl -sk http://localhost:8080/metrics | grep http_request_duration_seconds_count )" 576 | 577 | if [[ "$METRICS_CHECK" == *"http_request_duration_seconds_count"* ]] 578 | then 579 | passed "PROMETHEUS metrics are enabled" 580 | else 581 | failed "PROMETHEUS metrics are disabled" 582 | exit 1 583 | fi 584 | 585 | 586 | message " Stop containers " 587 | docker stop http-echo-tests 588 | sleep 5 589 | 590 | message " Start container with PRESERVE_HEADER_CASE enabled " 591 | docker run -d -e PRESERVE_HEADER_CASE=true --rm --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:testing 592 | 593 | sleep 5 594 | HEADER_CASE_CHECK=$(curl -s -H "prEseRVe-CaSE: A1b2C3" -H 'x-a-b: 999' -H 'X-a-B: 13' localhost:8080 | jq -r '.headers."prEseRVe-CaSE"') 595 | if [[ "$HEADER_CASE_CHECK" == "A1b2C3" ]] 596 | then 597 | passed "PRESERVE_HEADER_CASE enabled" 598 | else 599 | failed "PRESERVE_HEADER_CASE failed" 600 | exit 1 601 | fi 602 | 603 | message " Stop containers " 604 | docker stop http-echo-tests 605 | sleep 5 606 | 607 | message " Start container with a custom response body from a file " 608 | echo "

Hello World

" > test.html 609 | docker run -d --rm -v ${PWD}/test.html:/app/test.html --name http-echo-tests -p 8080:8080 -e OVERRIDE_RESPONSE_BODY_FILE_PATH=/test.html -t mendhak/http-https-echo:testing 610 | sleep 5 611 | RESPONSE_BODY=$(curl -s http://localhost:8080) 612 | if [[ "$RESPONSE_BODY" == "

Hello World

" ]] 613 | then 614 | passed "Custom response body from file" 615 | else 616 | failed "Custom response body from file failed" 617 | exit 1 618 | fi 619 | 620 | message " Stop containers " 621 | docker stop http-echo-tests 622 | sleep 5 623 | 624 | popd 625 | rm -rf testarea 626 | message "DONE" 627 | --------------------------------------------------------------------------------