http-diff
CLI tool to verify consistency across web server versions. Ideal for large-scale refactors, sanity tests and maintaining data integrity across versions.
Archives of precompiled binaries for http-diff are available for macOS and Linux on every release.
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_processorwill 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
Installation
Archives are available on every release as well as .deb files for Linux.
Autocomplete for arguments and man pages are included.
Developing
cargo run- for developmentcargo test- to run testscargo build -r- to build in release mode