Echo, status codes and response shaping
Echo
/echo, /echo/{*path}, /anything and /anything/{*path} accept any method and
return what arrived: method, url, path, path_info (the path segments),
query_string, query_params (repeated keys become arrays), every header (each
name maps to a list of values), host, port, scheme, remote_ip, the body
and timestamp_unix_ms.
This is the workhorse for transformation demos: point the gateway route at /echo
and show the headers it added, the query parameters it rewrote or the body it
transformed.
GET {{base_url}}/echo/orders/42?expand=items&expand=customer
X-Demo: hello
HTTP 200
[Asserts]
jsonpath "$.method" == "GET"
jsonpath "$.path" == "/echo/orders/42"
jsonpath "$.path_info" count == 3
jsonpath "$.query_params.expand" count == 2
jsonpath "$.headers['x-demo'][0]" == "hello"
jsonpath "$.scheme" == "http"
header "X-Request-Id" exists
POST {{base_url}}/anything
Content-Type: application/json
{"message": "hello", "number": 42}
HTTP 200
[Asserts]
jsonpath "$.method" == "POST"
jsonpath "$.body.present" == true
jsonpath "$.body.utf8" == true
jsonpath "$.body.bytes" == 34
jsonpath "$.body.body" contains "hello"
The body object reports present, included, body (the text), bytes,
truncated and utf8. Binary bodies are not included (reason: "binary"); text
bodies above RUSTYBIN_BODY_LIMIT are truncated, although such requests are
rejected with 413 before they reach the route anyway.
scheme, host, port and remote_ip describe the client's view: the HTTPS
listener reports https, and with RUSTYBIN_TRUST_FORWARD=true the
X-Forwarded-*, Forwarded and Fly-Client-IP headers are honoured.
JSON or XML
Plain data endpoints (echo, status, delay, cache, the auth checks, info and random
values, orchestration and more) negotiate the representation from Accept:
application/json (the default, also for browsers and */*), application/xml
or text/xml, with q-values. They never answer 406 and always send
Vary: Accept. The XML form has a <response> root and one element per key.
GET {{base_url}}/echo
Accept: application/xml
HTTP 200
[Asserts]
header "Content-Type" contains "application/xml"
header "Vary" contains "Accept"
xpath "string(/response/method)" == "GET"
Status codes
/status/{code} answers any method with the status you ask for (200 to 599).
GET {{base_url}}/status/418
HTTP 418
[Asserts]
jsonpath "$.status" == 418
DELETE {{base_url}}/status/204
HTTP 204
[Asserts]
bytes count == 0
204, 205 and 304 have no body. Some statuses carry the header a client expects:
| Status | Extra header |
|---|---|
| 301, 302, 303, 307, 308 | Location: /echo |
| 401 | WWW-Authenticate: Basic realm="rustybin" |
| 407 | Proxy-Authenticate: Basic realm="rustybin" |
| 429, 503 | Retry-After: 1 |
GET {{base_url}}/status/503
HTTP 503
[Asserts]
header "Retry-After" == "1"
GET {{base_url}}/status/401
HTTP 401
[Asserts]
header "WWW-Authenticate" == "Basic realm=\"rustybin\""
GET {{base_url}}/status/301
HTTP 301
[Asserts]
header "Location" == "/echo"
A comma-separated list picks at random: /status/200,500 uniformly, or with
weights, /status/200:0.9,500:0.1 (weights are relative, at most 20 codes). The
body then has chosen_from.
# Weights are relative: 0 never wins, so this always answers 500.
GET {{base_url}}/status/200:0,500:1
HTTP 500
[Asserts]
jsonpath "$.status" == 500
jsonpath "$.chosen_from" == "200:0,500:1"
1xx codes are refused with 400 (an informational status cannot be a final
response):
GET {{base_url}}/status/101
HTTP 400
[Asserts]
jsonpath "$.error" == "unsupported_status_code"
GET {{base_url}}/status/404
Accept: application/xml
HTTP 404
[Asserts]
header "Content-Type" contains "application/xml"
xpath "string(/response/status)" == "404"
Delays
/delay/{duration} (any method) waits, then echoes the request with delay_ms,
requested_ms and max_delay_ms. The duration is milliseconds (1500), 250ms
or seconds (1.5s), at most 60 s (10 s in public mode); ?jitter=true varies it
by up to 20 percent either way. For delays on any other route use the
X-Rustybin-Delay header.
GET {{base_url}}/delay/250ms
HTTP 200
[Asserts]
jsonpath "$.delay_ms" == 250
jsonpath "$.requested_ms" == 250
jsonpath "$.max_delay_ms" == 60000
duration >= 250
# Plain milliseconds, `ms` or seconds (fractions allowed); ?jitter=true adds +-20%.
POST {{base_url}}/delay/0.1s?jitter=true
HTTP 200
[Asserts]
jsonpath "$.requested_ms" == 100
jsonpath "$.delay_ms" >= 80
jsonpath "$.delay_ms" <= 120
jsonpath "$.method" == "POST"
GET {{base_url}}/delay/61s
HTTP 400
[Asserts]
jsonpath "$.error" == "invalid_delay"
Response headers
GET /response-headers?Name=value&... sets each query parameter as a response
header (repeat a key for several values; at most 50) and returns them as JSON. Use it
to show a gateway stripping, rewriting or adding response headers, or caching by
Cache-Control.
GET {{base_url}}/response-headers?X-Demo=gateway&Cache-Control=no-store&X-Multi=a&X-Multi=b
HTTP 200
[Asserts]
header "X-Demo" == "gateway"
header "Cache-Control" == "no-store"
header "X-Multi" count == 2
jsonpath "$['x-demo']" == "gateway"
jsonpath "$['x-multi']" count == 2
Hop-by-hop and framing headers (Connection, Content-Length,
Transfer-Encoding, Keep-Alive, Proxy-Connection, TE, Trailer, Upgrade,
Host, HTTP2-Settings) are refused, so the endpoint cannot be used to corrupt a
response:
GET {{base_url}}/response-headers?Transfer-Encoding=chunked
HTTP 400
[Asserts]
jsonpath "$.error" == "forbidden_header"
Caching
GET /cache/{ttl} returns Cache-Control: public, max-age=<ttl>, a strong ETag
and a Last-Modified (the server start time), and answers 304 Not Modified to a
matching If-None-Match (weak comparison, lists and * supported) or to an
If-Modified-Since not older than Last-Modified. The body is stable for the
lifetime of the process, which makes it a good target for proxy cache demos.
GET {{base_url}}/cache/60
HTTP 200
[Captures]
etag: header "ETag"
[Asserts]
header "Cache-Control" == "public, max-age=60"
header "ETag" exists
header "Last-Modified" exists
jsonpath "$.cached" == true
jsonpath "$.ttl" == 60
GET {{base_url}}/cache/60
If-None-Match: {{etag}}
HTTP 304
[Asserts]
header "ETag" == "{{etag}}"
bytes count == 0
GET {{base_url}}/cache/30
If-Modified-Since: Fri, 01 Jan 2100 00:00:00 GMT
HTTP 304