kassette is a customizable proxy server, focused on handling mocking and persistence of requests made to one or more backends.
Run kassette -c kassette.config.js
with kassette.config.js
file:
/**
* @return { import("@amadeus-it-group/kassette").ConfigurationSpec }
*/
exports.getConfiguration = () => {
return {
port: 4200,
mocksFolder: './mocks-folder',
remoteURL: 'http://127.0.0.1:3000',
mode: 'local_or_download',
};
};
The option -c
is used to specify the configuration file. The configuration file is an executable file which must export a getConfiguration
function, which can be asynchronous if needed, and returns the configuration object.
This will:
- run the proxy on port
4200
- store local mock files under the tree
./mocks-folder
- serve data persisted in the local files if present, otherwise will forward request to a backend accessible at
http://127.0.0.1:3000
, persist the response for next requests and serve it
Thanks to default behavior it will also:
- use a local path built with the URL pathname followed by the HTTP method of the input request
Run kassette -c kassette.config.js
with kassette.config.js
file:
/**
* @return { import("@amadeus-it-group/kassette").ConfigurationSpec }
*/
exports.getConfiguration = () => {
return {
port: 4200,
mocksFolder: './mocks-folder',
remoteURL: 'http://127.0.0.1:3000',
mode: 'local_or_download',
hook: async ({ mock }) => {
if (!mock.request.pathname.startsWith('/api/')) {
mock.setMode('remote');
return;
}
mock.setLocalPath([mock.request.pathname.split('/').slice(1), mock.request.method]);
},
};
};
The hook
property is the new thing here, and constitutes the core of the tool: it is called for every input request, and allows to handle it as well as its associated mock, by tweaking configuration for this instance and/or using APIs to interact with it and its data.
This will:
- run the proxy on port
4200
- store local mock files under the tree
./mocks-folder
- by default serve data persisted in the local files if present, otherwise will forward request to a backend accessible at
http://127.0.0.1:3000
, persist the response for next requests and serve it - except for requests whose pathname starts with
/api/
, in which case the proxy acts a a pass-through - use a mock local path built with the significant URL pathname (without the common leading
/api
) followed by the HTTP method of the input request
Run kassette -c kassette.config.js
with kassette.config.js
file:
/**
* @return { import("@amadeus-it-group/kassette").ConfigurationSpec }
*/
exports.getConfiguration = async () => {
return {
// default properties for the "mock" instance
port: 4200,
mocksFolder: './mocks-folder',
remoteURL: 'http://127.0.0.1:3000',
mode: 'local_or_download',
// hook
hook: async ({mock}) => {
//////////////////////////////////////////////////////////////////////////
// Ignore requests not related to your API (e.g. statics)
// and serve them normally by setting the mode to 'remote'
//////////////////////////////////////////////////////////////////////////
if (!mock.request.pathname.startsWith('/api/')) {
mock.setMode('remote');
return;
}
//////////////////////////////////////////////////////////////////////////
// Change the local path of the mock for the current request,
// by removing the first part of the URL pathname,
// which would always contain "api" here,
// and append the HTTP method at the end.
// Also, by joining the parts, make sure there's a flat structure instead
// of a tree of folders
//////////////////////////////////////////////////////////////////////////
mock.setLocalPath([
...mock.request.pathname.split('/').slice(1),
mock.request.method,
].join('-'));
//////////////////////////////////////////////////////////////////////////
// If local files are matching this request
// (i.e. local files are present under the path you just computed),
// serve them.
// Otherwise, download the payload from the remote backend
// to record it and serve it.
// The reason to do this "manually" using the API is to handle the case
// of a 404 response. In this case an empty mock is generated
// (you can later on fill/alter its manually if you will)
//////////////////////////////////////////////////////////////////////////
if (await mock.hasLocalMock()) { return; }
mock.setMode('manual');
let wrappedPayload = await mock.downloadPayload();
if (wrappedPayload.payload.data.status.code === 404) {
wrappedPayload = mock.createPayload({
data: {
headers: {
'content-type': 'application/json',
},
ignoredHeaders: {}
status: {
code: '200',
message: 'OK',
},
bodyFileName: 'body.json'
time: 20,
}
body: '{}',
});
await mock.persistPayload(wrappedPayload);
}
mock.fillResponseFromPayload(wrappedPayload);
await mock.sendResponse();
}
};
}
Besides the basic properties already explained for previous use cases, this will also:
- simply forward any request whose URL is not starting with
/api/
- customize the path of the local files to have a flat hierarchy based on the request URL and HTTP method, using a dash (
-
) as a separator - replace any remote payload having a status code
404
by a custom empty payload (persisting it for subsequent requests and serving it)
Run kassette -c kassette.config.js -p 8000 --folder ./snapshots
with kassette.config.js
file:
/**
* @return { import("@amadeus-it-group/kassette").ConfigurationSpec }
*/
exports.getConfiguration = async () => {
return {
// overridden by command line, will be 8000 eventually
port: 4200,
remoteURL: 'http://127.0.0.1:3000',
mode: 'local_or_download',
// mocksFolder not specified, since specified in command line
};
};
port
gets overridden by command linemocksFolder
gets specified by command line
Run: kassette -m remote
This starts kassette as a browser proxy. Using kassette as the browser proxy allows to easily intercept all requests made by the browser without having to change any URL. When running kassette without specifying any remote URL, or when explicitly specifying *
as the remote URL (i.e. with -u '*'
on the command line) kassette will read from each request which remote server to target for that request (as it is transmitted when using a browser proxy).
The -m remote
argument means kassette will transfer the requests to the remote server and will not store anything locally.
Once kassette is started, you have to configure your browser to use kassette as its proxy. For example, if you use playwright, you can start it with the --proxy-server
argument, and also with the --ignore-https-errors
argument to allow kassette to intercept https communications with its own self-signed root certificate without warnings from the browser:
# you can also use ff (Firefox) or wk (WebKit) instead of cr (Chromium) below:
npx playwright cr --proxy-server=http://127.0.0.1:8080 --ignore-https-errors
Run kassette --har-file mocks.har
.
This will start kassette as a browser proxy on port 8080 in the local_or_download
mode and store all new requests in the mocks.har
file.
In the local_or_download
mode, existing requests are served from the HAR file instead of calling the remote server. To find a matching request, kassette by default only uses the HTTP method and the full URL (ignoring any header and the request body). This can be changed by using a configuration file and calling setMockHarKey
in the hook
function.
To only replay mocks from the HAR file without changing it and without calling the remote server (answering with a 404 error when there is no matching request in the HAR file), start kassette in the local
mode instead: kassette --har-file mocks.har -m local
Once kassette is started, as explained in the previous example, configure your browser to use kassette as its proxy. You can use playwright:
npx playwright cr --proxy-server=http://127.0.0.1:8080 --ignore-https-errors
Run kassette -c kassette.config.js
with kassette.config.js
file:
/**
* @return { import("@amadeus-it-group/kassette").ConfigurationSpec }
*/
exports.getConfiguration = () => {
return {
port: 4200,
mocksFolder: './mocks-folder',
proxyConnectMode: 'forward',
mode: 'local_or_download',
onProxyConnect: async (request) => {
if (request.hostname.endsWith('.mydomain.com') && request.port === 443) {
// override the config to intercept TLS connections to *.mydomain.com
// those requests will pass through the hook method
request.setMode('intercept');
}
// otherwise, proxyConnectMode: 'forward' will be used (as defined in the config)
// those requests will go without decryption directly to the destination server,
// so they will not pass through the hook method
},
hook: async ({ mock }) => {
mock.setLocalPath([
// include the protocol, hostname and port in the local path
mock.request.protocol,
mock.request.hostname,
mock.request.port,
mock.request.pathname.split('/'),
mock.request.method,
]);
},
};
};
Run kassette -h
or kassette --help
to get help in the terminal.
Example output:
kassette version 1.5.0
Options:
-h, --help Show help [boolean]
-c, --conf, --config, --configuration path to configuration file
-q, --quiet, --skip-logs skip logs [boolean]
--hostname, --host hostname on which to run the server [string]
-p, --port port on which to run the server [number]
-u, --url, --remote, --remote-url remote server url [string]
--http2, --h2 enables http/2.0 in the kassette server (enabled by default, use --no-h2 to disable) [boolean]
-f, --folder, --mocks-folder path to mocks base folder (if mocks-format is "folder") [string]
--har-file, --mocks-har-file path to the har file containing mocks (if mocks-format is "har") [string]
--har-file-cache-time time in milliseconds during which a har file is kept in memory after its last usage [number]
--mocks-format mocks file format [string] [choices: "folder", "har"]
--save-checksum-content save the content used to create a checksum when creating a new mock with a checksum [boolean]
--save-detailed-timings save detailed timings (blocked, dns, connect, send, wait, receive, ssl) when creating a new mock [boolean]
--save-input-request-data save the input request data (headers, method, URL) when creating a new mock [boolean]
--save-input-request-body save the content of the input request body when creating a new mock [boolean]
--save-forwarded-request-data save the forwarded request data (headers, method, URL) when creating a new mock [boolean]
--save-forwarded-request-body save the forwarded request body when creating a new mock [boolean]
-m, --mode server mode [choices: "download", "local_or_download", "local_or_remote", "local", "remote", "manual"]
-x, --proxy-connect-mode proxy connect mode [choices: "close", "intercept", "forward", "manual"]
-k, --tls-ca-key path to a PEM-encoded CA certificate and key file, created if it does not exist. [string]
--tls-key-size size in bits of generated RSA keys. [number]
-d, --delay mock response artificial delay
-v, --version Show version number [boolean]
Examples:
kassette -c proxy.config.js Start proxy with configuration file proxy.config.js
Please visit repository for more information: https://github.com/AmadeusITGroup/kassette
It is also possible to run kassette
with no option, but besides checking that the product properly runs, this won't be useful for real usage.
We are trying to set sensible and consistent default options, but the goal of the tool is also to work in different modes for different development stages, so at some point you will be setting specific options.
The central piece of customization is a configuration system, and its most important element is the hook
function, which is called upon incoming requests, and is controlling the behavior of the proxy to react to each request.
- configuration: to learn about the customization
- API: to learn mainly how to use the
hook
or how to use the proxy from the API - philosophy: if you want to know more about the design principles of the tool and its API/usage