HTTP DIFF
Tool to ensure multiple versions of the web server work the same. Useful for big refactors and sanity tests, where you need to check a lot of endpoints work the same and return the same data.
Dual-licensed under MIT or the UNLICENSE.

The tool works by looking at the configuration file that can be specified by --config argument.
http-diff --config=./config.json
./config.json- is the default value for this argument so it can be omitted.
Config example:
{
"domains": [
{
"domain": "http://domain-a.com/",
"headers": {
"cookie": "auth=secret"
}
},
"http://stage.domain-a.com"
],
"endpoints": [
{
"endpoint": "/health"
},
{
"endpoint": "/api/v1/users/<userId>",
"http_method": "GET",
"response_processor": [
"jq",
"del(.body.timestamp)"
],
"headers": {
"X-custom": "custom-header"
}
},
{
"endpoint": "/api/v1/products?omit_id=<productId>&include_empty=<include_empty>",
"request_builder": [
"python3",
"add_auth_to_requests.py"
],
"variables": {
"include_empty": [
"true",
"false"
]
}
}
],
"variables": {
"userId": [
123,
444
],
"productId": "UUID"
}
}
this config will be translated to following:
-
GETrequest with{"cookie":"auth=secret"}in headers will be issued tohttp://domain-a.com/healthand response be compared to response ofGET http://stage.domain-a.com/healthwithout any headers. -
Next endpoint
/api/v1/users/<userId>has variable defined in it -<userId>. Anything within the brackets considered a variable name. In this case -userId. Variable then is looked up in the global variables property. In this caseuserIdhas two values:123and444. This will be mapped to following requests:GET http://domain-a.com/users/123with{"cookie":"auth=secret","X-custom":"custom-header"}in headers and compared withGET http://stage.domain-a.com/users/123with{"X-custom":"custom-header"}in headers.GET http://domain-a.com/users/444with{"cookie":"auth=secret","X-custom":"custom-header"}in headers and compared withGET http://stage.domain-a.com/users/444with{"X-custom":"custom-header"}in headers.
Before comparing any response, response_processor will be applied. This endpoint has following preprocessor:
["jq", "del(.body.timestamp)"]. Preprocessor is an external command that will be called with any arguments, before comparing responses. In this case after request tohttp://domain-a.com/users/123and request tohttp://stage.domain-a.com/users/123each response will passed tojq 'del(.body.timestamp)'which basically will remove thetimestampfield from the body.timestampfield will be omitted from the compare process.response_processorcan be any program, script or cli tool you can think of. Anything program that acceptsstdin, and outputs tostdout. Output then will be compared. Not the original response. -
Next endpoint (
/api/v1/products?omit_id=&include_empty=<include_empty>) will be mapped to:GET http://domain-a.com/api/v1/products?omit_id=22fae888-7bf9-45d6-87f4-83087cc80ba1&include_empty=truewith{"cookie":"auth=secret"}in headers and compared withGET http://domain-a.com/api/v1/products?omit_id=22fae888-7bf9-45d6-87f4-83087cc80ba1&include_empty=truewithout custom headers.productIdvariable hasUUIDvalue. This is a 'generator' variable. Random uuid will be generated and passed to both requests.include_emptyvariable has two values:trueandfalse. These requests will usetrue, next one will befalse.GET http://domain-a.com/api/v1/products?omit_id=4558903b-d4a6-46e8-869e-1b77677832f4&include_empty=falsewith{"cookie":"auth=secret"}in headers and compared withGET http://domain-a.com/api/v1/products?omit_id=4558903b-d4a6-46e8-869e-1b77677832f4&include_empty=falsewithout custom headers.productIdgets new random uuid for both requests.include_emptybecomesfalse.
Before executing any requests,
request_buildercommand will be applied on each request. In this casepython3 add_auth_to_requests.pywill be executed 4 times, before each request.request_buildergives you option to modify request in any possible way. We can add headers here, remove, modify the body, handle complicated authentication methods etc. Same as theresponse_processor,request_buildergets an stdin in the following structure, and expects stdout with the same structure:{ "uri": "string", "http_method": "GET | POST | PUT | PATCH | DELETE", "headers": "key value headers map", "body": "string" }
Requirements
- Latest rust installed when building from source
Running
cargo run- for developmentcargo test- to run testscargo build -r- to build in release mode./build-osx.sh- to build binary for apple as a target on linux machine./build-deb.sh- to build debian package .deb