Echoserver is a simple HTTP server that listens on incoming requests and echoes back information about the request. It is inspired by the echoserver from the ingress-controller-conformance project. Additionally, the server can be used to test authentication and authorization features in ingress controllers by providing browser based clients that make requests to the echoserver. See also echoclient project, a programmable HTTP load testing library.
To run the server locally it can be installed or run directly using following commands:
go install github.com/tsaarni/echoserver@latest # Install the binary.
go run github.com/tsaarni/echoserver@latest # Run the server without installing.Echoserver is also available as a container image:
ghcr.io/tsaarni/echoserver:latest
Echoserver can be configured either using command line arguments or environment variables. Command line arguments take precedence over environment variables. Following table lists the available configuration options:
| Command line | Variable | Description | Default |
|---|---|---|---|
-http-addr |
HTTP_ADDR |
Address to bind the HTTP server socket. | :8080 |
-https-addr |
HTTPS_ADDR |
Address to bind the HTTPS server socket. | :8443 |
-tls-cert-file |
TLS_CERT_FILE |
Path to TLS certificate file. | |
-tls-key-file |
TLS_KEY_FILE |
Path to TLS key file. | |
ENV_* |
List of environment variables to be included in the env field of the JSON response and accessible in HTML templates. |
||
-live |
Serve static files directly from the ./apps directory instead of using bundled files in the binary. |
false |
|
SSLKEYLOGFILE |
Path to write the TLS master secret log file to. See Wireshark documentation for more information. | ||
-log-level |
LOG_LEVEL |
Log level. Possible values are debug, info, warn, and error. |
debug |
The certificate and key files will be loaded from the filesystem every time a request is made to the server, so it is possible to update the certificate and key files without restarting the server. Client can provide an optional client certificate when making HTTPS requests to the server.
Example commands in the descriptions are given using the HTTPie tool.
The gRPC endpoint examples are given using the grpcurl tool.
| Status | Description |
|---|---|
| 200 OK | Request details in JSON format. |
Following fields is included in the response:
content_length: Length of the request body.env: Environment variables provided in the configuration.headers: Request headers.host: Host and port of the server.method: HTTP method of the request.url: Request URL.proto: HTTP protocol version.remote: Remote address of the client.env: Variables from the process environment that match theENV_*prefix.tls: TLS details if the request was made over HTTPS.alpn_negotiated_protocol: Application Layer Protocol Negotiation protocol.cipher_suite: Cipher suite used in the connection.peer_certificates: Peer certificates in PEM format if the client provided a certificate.peer_certificates_decoded: Decoded peer certificate details if the client provided a certificate.subject: Subject of the certificate.issuer: Issuer of the certificate.serial_number: Serial number of the certificate.not_before: Not before date of the certificate.not_after: Not after date of the certificate.
version: TLS version.
query: Query parameters of the request if the URL contains a query string.form: Form parameters of the request body, if the content type isapplication/x-www-form-urlencoded.cookies: Cookies in the request if the request had aCookieheader.body: Request body.jwt: JWT claims if the request had JWT in theAuthorizationheader.header: JWT header.claims: JWT claims.- If the
claimsfield containsiatorexpclaims, they are converted to human-readable format and added asiat_dateandexp_datefields (these fields are added by the server and are not part of the original token).
- If the
basic_auth: Basic authentication credentials if the request hadAuthorizationheader withBasicscheme.username: Username.password: Password.
$ http --cert testdata/certs/client.pem --cert-key testdata/certs/client-key.pem --verify testdata/certs/ca.pem https://localhost:8443/foobar{
"content_length": 0,
"headers": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"gzip, deflate"
],
"Connection": [
"keep-alive"
],
"User-Agent": [
"HTTPie/3.2.4"
]
},
"host": "localhost:8443",
"method": "GET",
"proto": "HTTP/1.1",
"remote": "[::1]:57961",
"tls": {
"alpn_negotiated_protocol": "http/1.1",
"cipher_suite": "TLS_AES_128_GCM_SHA256",
"peer_certificates": "-----BEGIN CERTIFICATE-----\nMIIBRTCB7aADAgECAggYXbRNl099CjAKBggqhkjOPQQDAjANMQswCQYDVQQDEwJj\nYTAeFw0yNTA4MjEwNjI3NTVaFw0yNjA4MjEwNjI3NTVaMBExDzANBgNVBAMTBmNs\naWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPhO4qIu71Cm5Ox5U5Pb6Og2\n4EMh/lWU4+OGDBkHIRXtyKTZCXzH5a1vQ0TO1jq6sjShZR8ihDYXRuPfcNVefj6j\nMzAxMA4GA1UdDwEB/wQEAwIFoDAfBgNVHSMEGDAWgBQGgzaN2tRzErVZCEe7Ucju\nTY5XBzAKBggqhkjOPQQDAgNHADBEAiB+Oc4DPody43cZ0e+MY7F63DnIPM5xtgwR\nG6IYdhXiAwIgYxOlBxxupGDvvhXyS7IV8KadGD8LVm8G059OJC9vIG0=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIBUTCB+KADAgECAggYXbRNlzUisDAKBggqhkjOPQQDAjANMQswCQYDVQQDEwJj\nYTAeFw0yNTA4MjEwNjI3NTVaFw0yNjA4MjEwNjI3NTVaMA0xCzAJBgNVBAMTAmNh\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFWG+fC3bE4X0oOHIGbH0d1VY1vQc\nDeu/ey1+bCXTsyFLld8rwk5KDjPGI+QGlL5lnEVYWZUQ8QQLYQLhK//uKKNCMEAw\nDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFAaDNo3a\n1HMStVkIR7tRyO5NjlcHMAoGCCqGSM49BAMCA0gAMEUCIAJLtjBdGvDYO18xZ2wI\nJYyzoxN8K4I5VodVwuF/4J5cAiEAmMeY8VbFFKBRLJhnd2wPHaK3pbbMozJ99nSC\nI2xUCvs=\n-----END CERTIFICATE-----\n",
"peer_certificates_decoded": [
{
"issuer": "CN=ca",
"not_after": "2026-08-21T06:27:55Z",
"not_before": "2025-08-21T06:27:55Z",
"serial_number": "1755757675088411914",
"subject": "CN=client"
},
{
"issuer": "CN=ca",
"not_after": "2026-08-21T06:27:55Z",
"not_before": "2025-08-21T06:27:55Z",
"serial_number": "1755757675086684848",
"subject": "CN=ca"
}
],
"version": "TLS 1.3"
},
"url": "/foobar"
}By default, this endpoint returns an HTTP 200 OK status code.
You can modify the status code using the set query parameter.
The server will persist the updated status code, and subsequent requests to /status will return the new status code until it is changed again.
| Name | Description |
|---|---|
| set | HTTP status code to persist (integer). |
| Status | Description |
|---|---|
| code | Request details in JSON format. |
code is the HTTP status code set by the set query parameter or 200 OK by default.
Body contains the request details in JSON format as described in the /* endpoint.
$ http GET http://localhost:8080/statusHTTP/1.1 200 OK
Content-Length: 0
Date: Fri, 29 Nov 2024 06:24:46 GMT
... Request details in JSON format ...$ http GET http://localhost:8080/status?set=503HTTP/1.1 503 Service Unavailable
Content-Length: 0
Date: Sun, 19 Oct 2025 18:05:20 GMT
... Request details in JSON format ...Returns the specified HTTP status code.
| Status | Description |
|---|---|
| code | Request details in JSON format. |
code is the HTTP status code specified in the URL path.
Body contains the request details in JSON format as described in the /* endpoint.
Optionally, you can include additional HTTP headers in the response by providing a JSON object in the body or using a query string.
$ http POST http://localhost:8080/status/301 Location=http://localhost/barBody of the request
{
"Location": "http://localhost/bar"
}Response:
HTTP/1.1 301 Moved Permanently
Content-Length: 0
Date: Fri, 29 Nov 2024 06:10:25 GMT
Location: http://localhost/bar
... Request details in JSON format ...$ http "http://localhost:8080/status/200?Set-Cookie=foo%3Dbar&Set-Cookie=hello%3Dworld"Response:
HTTP/1.1 200 OK
Content-Length: 0
Date: Sun, 15 Dec 2024 12:00:06 GMT
Set-Cookie: foo=bar
Set-Cookie: hello=world
... Request details in JSON format ...| Status | Description |
|---|---|
| 200 OK | Server is operational. |
Server will respond with Content-Type: text/event-stream with the following content:
$ http http://localhost:8080/sseHTTP/1.1 200 OK
Cache-Control: no-cache
Connection: keep-alive
Content-Type: text/event-stream
Date: Wed, 19 Feb 2025 10:10:15 GMT
Transfer-Encoding: chunked
data: { "counter": "1", "timestamp": "2025-02-19T12:10:15+02:00" }
data: { "counter": "2", "timestamp": "2025-02-19T12:10:16+02:00" }
...Accepts POST requests with bodies of any size. Responds with the total number of bytes received, rather than request details.
| Name | Description | Default |
|---|---|---|
| throttle | Throttle the upload speed to bytes/sec (integer with optional suffix "K", "M", "G") |
| Status | Description |
|---|---|
| 200 OK | Uploaded bytes JSON format. |
Following fields is included in the response:
bytes_uploaded: Length of the request body.
$ dd if=/dev/zero bs=10M count=1 | http POST http://localhost:8080/uploadHTTP/1.1 200 OK
content-length: 26
content-type: application/json
date: Thu, 21 Aug 2025 12:27:06 GMT
server: envoy
x-envoy-upstream-service-time: 0
{
"bytes_uploaded": 1048576
}To upload a file, while throttling the upload speed to 1MB/s at the server side:
$ dd if=/dev/zero bs=10M count=1 | http http://localhost:8080/upload?throttle=1MAccepts GET requests and responds with a binary stream of bytes.
The number of bytes can be specified with the bytes query parameter.
The default size for the download is 1MB.
The response body consists of a sequence of bytes from 1 to 256.
| Name | Description | Default |
|---|---|---|
| bytes | Number of bytes to download (integer) | 1048576 |
| throttle | Throttle the download speed to bytes/sec (integer with optional suffix "K", "M", "G") |
| Status | Description |
|---|---|
| 200 OK | File download in progress. |
Download the default 1MB file:
$ http --download http://localhost:8080/download --output download.binResponse headers:
HTTP/1.1 200 OK
content-length: 1048576
content-type: application/octet-stream
date: Thu, 21 Aug 2025 16:37:15 GMT
server: envoy
x-envoy-upstream-service-time: 0Download a 10-byte file:
$ http --download http://localhost:8080/download?bytes=10 --output download.binDownload a 10MB file while throttling the upload speed to 1MB/s at the server side:
$ http --download http://localhost:8080/download?bytes=10M\&throttle=1M --output download.binThis endpoint provides Prometheus-compatible metrics for monitoring the server.
| Status | Description |
|---|---|
| 200 OK | Metrics in prometheus exposition format. |
$ http GET http://localhost:8080/metrics# HELP http_requests_total Total number of HTTP requests received.
# TYPE http_requests_total counter
http_requests_total{method="GET",status_code="200"} 5
...
See metrics.go for details about the available metrics.
Endpoint that responds with details about incoming gRPC request.
For the service definition see proto/echo.proto.
The service supports gRPC reflection.
To call the Echo method over HTTP/2:
$ grpcurl -cacert testdata/certs/ca.pem -cert testdata/certs/client.pem -key testdata/certs/client-key.pem -d '{"message": "Hello"}' localhost:8443 echo.EchoService/Echo{
"message": "Hello",
"headers": {
":authority": {
"values": [
"localhost:8443"
]
},
"content-type": {
"values": [
"application/grpc"
]
},
"grpc-accept-encoding": {
"values": [
"gzip"
]
},
"user-agent": {
"values": [
"grpcurl/dev-build (no version set) grpc-go/1.61.0"
]
}
},
"remoteAddr": "127.0.0.1:57270",
"tlsInfo": {
"version": "TLS 1.3",
"cipherSuite": "TLS_AES_128_GCM_SHA256",
"alpnNegotiatedProtocol": "h2",
"peerCertificates": [
{
"subject": "CN=client",
"issuer": "CN=ca",
"serialNumber": "1755757675088411914",
"notBefore": "2025-08-21T06:27:55Z",
"notAfter": "2026-08-21T06:27:55Z"
}
]
}
}Or using H2C (plaintext) transport:
$ grpcurl -plaintext -d '{"message": "Hello"}' localhost:8080 echo.EchoService/Echo{
"message": "Hello",
"headers": {
":authority": {
"values": [
"localhost:8080"
]
},
"content-type": {
"values": [
"application/grpc"
]
},
"grpc-accept-encoding": {
"values": [
"gzip"
]
},
"user-agent": {
"values": [
"grpcurl/dev-build (no version set) grpc-go/1.61.0"
]
}
},
"remoteAddr": "127.0.0.1:60740"
}Streams countdown messages starting from the specified value start down to zero, emitting one message per second.
For the service definition see proto/echo.proto.
The service supports gRPC reflection.
$ grpcurl -cacert testdata/certs/ca.pem -cert testdata/certs/client.pem -key testdata/certs/client-key.pem -emit-defaults -d '{"start": 2}' localhost:8443 echo.EchoService/EchoCountdown
{
"count": 2
}
{
"count": 1
}
{
"count": 0
}A JavaScript application that enables users to make HTTP requests towards the echoserver using different methods and view the responses.
An HTML form that enables data submission using both POST and GET methods
towards the echoserver.
OAuth2-aware JavaScript application that implements the Authorization Code flow. It allows users to interactively trigger login/refresh/logout and to make authenticated requests towards the echoserver and view the responses.
A JavaScript application that uses the Keycloak-js JavaScript adapter to authenticate users.
A JavaScript application that makes Server-Sent Events (SSE) or WebSocket connection towards the echoserver and displays the responses.
Please refer to CONTRIBUTING.md.




