Caching reverse proxy for testing written in Go
chameleon is a caching reverse proxy.
chameleon supports recording and replaying requests with the ability to customize how responses are stored.
chameleon has no runtime dependencies. You can download a prebuilt binary for your platform.
If you have Go installed, you may
go get github.com/nickpresta/chameleonto download it to your
$GOPATHdirectory.
To run chameleon, you can:
chameleon -data ./httpbin -url http://httpbin.org -verbose
The directory
httpbinmust already exist before running.
See
chameleon -helpfor more information.
There may be a reason in your tests to manually create responses - perhaps the backing service doesn't exist yet, or in test mode a service behaves differently than production. When this is the case, you can create custom responses and signal to chameleon the hash you want to use for a given request.
Set the
chameleon-request-hashheader with a unique hash value (which is a valid filename) and chameleon will look for that hash in the
spec.jsonfile and for all subsequent requests.
This allows you to not only have total control over the response for a given request but also makes your test code easier to reason about -- it is clear where the "special case" response is coming from.
All responses from chameleon will have a
chameleon-request-hashheader set which is the hash used for that request. This header is present even if you did not set it on the incoming request.
If you want to configure the cache at runtime without having to depend on an external service, you may preseed the cache via HTTP. This is particularly useful for mocking out services which don't yet exist.
To preseed a request, issue a JSON
POSTrequest to chameleon at the
_seedendpoint with the following payload:
Field |
Description |
---|
Request| Request is the request payload including a URL, Method and Body
Response| Response is the response to be cached and sent back for a given request
Request
Field |
Description |
---|
Body| Body is the content for the request. May be empty where body doesn't make sense (e.g.
GETrequests)
Method| Method is the HTTP method used to match the incoming request. Case insensitive, supports arbitrary methods
URL| URL is the absolute or relative URL to match in requests. Only the path and querystring are used
Response
Field |
Description |
---|
Body| Body is the content for the request. May be empty where body doesn't make sense (e.g.
GETrequests)
Headers| Headers is a map of headers in the format of string key to string value
StatusCode| StatusCode is the HTTP status code of the response
Repeated, duplicate requests to preseed the cache will be discarded and the cache unaffected.
Successful new preseed requests will return an
HTTP 201 CREATEDon success or
HTTP 500 INTERNAL SERVER ERROR. Duplicate preseed requests will return an
HTTP 200 OKon success or
HTTP 500 INTERNAL SERVER ERRORon failure.
Here is an example of preseeding the cache with a JSON response for a
GETrequest for
/foobar.
import requestspreseed = json.dumps({ 'Request': { 'Body': '', 'URL': '/foobar', 'Method': 'GET', }, 'Response': { 'Body': '{"key": "value"}', 'Headers': { 'Content-Type': 'application/json', 'Other-Header': 'something-else', }, 'StatusCode': 200, }, })
response = requests.post('http://localhost:6005/_seed', data=preseed) if response.status_code in (200, 201): # Created, or duplicate else: # Error, print it out print(response.content)
Continue tests as normal
Making requests to
/foobar
will return{"key": "value"}
without hitting the proxied service
Check out the example directory to see preseeding in action.
chameleon makes a hash for a given request URI, request method and request body and uses that to cache content. What that means:
GET /foo/will be cached differently than
GET /bar/
GET /foo/5will be cached differently than
GET /foo/6
DELETE /foo/5will be cached differently than
DELETE /foo/6
POST /foowith a body of
{"hi":"hello}will be cached differently than a request of
POST /foowith a body of
{"spam":"eggs"}. To ignore the request body, set a header of
chameleon-no-hash-bodyto any value. This will instruct chameleon to ignore the body as part of the hash.
You can specify a custom hasher, which could be any program in any language, to determine what makes a request unique.
chameleon will communicate with this program via STDIN/STDOUT and feed the hasher a serialized
Request(see below). You are then responsible for returning data to chameleon to be used for that given request (which will be hashed).
This feature is especially useful if you have to cache content based on the body of a request (XML payload, specific keys in JSON payload, etc).
See the example hasher for a sample hasher that emulates the default hasher.
Below is an example Request serialized to JSON.
{ "BodyBase64":"eyJmb28iOiAiYmFyIn0=", "ContentLength":14, "Headers":{ "Accept":[ "application/json" ], "Accept-Encoding":[ "gzip, deflate" ], "Authorization":[ "Basic dXNlcjpwYXNzd29yZA==" ], "Connection":[ "keep-alive" ], "Content-Length":[ "14" ], "Content-Type":[ "application/json; charset=utf-8" ], "User-Agent":[ "HTTPie/0.7.2" ] }, "Method":"POST", "URL":{ "Host":"httpbin.org", "Path":"/post", "RawQuery":"q=search+term%23home", "Scheme":"https" } }
Field | Description ----- | ----------- BodyBase64 | Body is the request's body, base64 encoded ContentLength | ContentLength records the length of the associated content after being base64 decoded Headers | Headers is a map of request lines to value lists. HTTP defines that header names are case-insensitive. Header names have been canonicalized, making the first character and any characters following a hyphen uppercase and the rest lowercase Method | Method specifies the HTTP method (
GET,
POST,
PUT, etc.) URL | URL is an object containing
Host, the HTTP Host in the form of 'host' or 'host:port',
Path, the request path including trailing slash,
RawQuery, encoded query string values without '?', and
Scheme, the URL scheme 'http', 'https'
Please open an issue for any bugs encountered, features requests, or general troubleshooting.
Thanks to @mdibernardo for the inspiration.
Please see LICENSE