Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

StatusExtra header
301, 302, 303, 307, 308Location: /echo
401WWW-Authenticate: Basic realm="rustybin"
407Proxy-Authenticate: Basic realm="rustybin"
429, 503Retry-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