Killgrave is a simulator for HTTP-based APIs, in simple words a Mock Server, very easy to use, made in Go.
Killgrave is a tool providing a simple way to create a powerful simulator for HTTP-based APIs.
The Killgrave's philosophy is provides an easy way to configure your mock server, trying always that you don't need learn
another tooling language, for that reason we use json
and yaml
to generate all necessary configurations.
Killgrave provides:
- Easy way to create imposters files, using
json
- The possibility to validate your owns json schemas on requests.
- Validations the requests headers.
- Using regex to allow different parameters on headers and urls.
- Custom body and dynamic body responses
- Using all content-types bodies, (
application/json
,text/html
,text/plain
,image/jpeg
, etc. ) - Configure the headers on body responses.
- Configure your CORS server options.
- Simulate network issues and server high loads with imposter responses delay.
- Using configurable proxy server to call to the original server.
- Run the tool using flag or using a config file.
- Run your mock server using a watcher to not reload on each change.
Maybe the most common concept inside of Killgrave tool is the imposters
. Imposters are the files that we will use to configure
how we want that our server respond.
You can identify an imposter
file on Killgrave because they must have the extension .imp.json
.
Yo can learn more about how to configure the imposters on the Imposter Configuration Section.
⚠️ Killgrave is a very robust tool and is using on some companies even on production environments, but we don't have a version 1 for today. For that Killgrave works usingSemVer
but in 0 version, which means that the 'minor' will be changed when some broken changes are introduced into the application, and the 'patch' will be changed when a new feature with new changes is added or for bug fixing. As soon as v1.0.0 be released,Killgrave
will start to useSemVer
as usual.
You can install Killgrave on different ways, but all of them are very simple:
One of them is of course using go get
, Killgrave is a Go project so could be compiled using the go toolchain
:
$ go get -u github.com/friendsofgo/killgrave/cmd/killgrave@{version}
version
must be substituted by the version
that you want to install, otherwise master would be installed.
If you are a mac user, you could install Killgrave using, Homebrew:
$ brew install friendsofgo/tap/killgrave
The application is also available through Docker, just run:
docker run -it --rm -p 3000:3000 -v $PWD/:/home -w /home friendsofgo/killgrave
Remember to use the -p flag to expose the container port where the application is listening (3000 by default).
NOTE: If you want to use killgrave
in Docker at the same time as using your own dockerised HTTP-based API please be careful with networking issues.
If you are using, windows or linux you could get the binary for your arch on the release section:
https://github.com/friendsofgo/killgrave/releases
The very easy way to start with Killgrave is just execute at it is:
$ killgrave
While you are welcome to provide your own configuration, Killgrave will follow the next default configuration:
- imposters path:
imposters
- host:
localhost
- port:
3000
- CORS:
[]
- proxy:
none
- watcher:
false
As we see below Killgrave have a default configuration to start using out of the box, but you could change all of this using
his command tool, except for the CORS
that you only will can configure using the config file.
$ killgrave -h
-config string
path with configuration file
-host string
if you run your server on a different host (default "localhost")
-imposters string
directory where your imposters are saved (default "imposters")
-port int
port to run the server (default 3000)
-proxy-mode string
proxy mode you can choose between (all, missing or none) (default "none")
-proxy-url string
proxy url, you need to choose a proxy-mode
-version
show the _version of the application
-watcher
file watcher, reload the server with each file change
If we want a more permanent configuration, we could use the option -config
to define where is our config file.
The config file must be a yml file
, his structure should be like this:
#config.yml
imposters_path: "imposters"
port: 3000
host: "localhost"
proxy:
url: https://example.com
mode: missing
watcher: true
cors:
methods: ["GET"]
headers: ["Content-Type"]
exposed_headers: ["Cache-Control"]
origins: ["*"]
allow_credentials: true
As you can see, you could configure all the options on a very easy way. Keep in mind that the routes are based on the config file are.
example:
mymock/
imposters/
config.yml
swapi_people.imp.json
swapi_planets.imp.json
Dockerfile
MakeFile
Then on your config file, you will need configure imposters
option like .
because the imposters file
are located in the
same folder.
Historically, the options imposters_path
, port
, host
were mandatory when using a configuration file.
However, since the last version, they are no longer needed, so you can simply override those options if you want to.
Furthermore, the imposters_path
option in previous version towards reference to the path where the app was launched, but in the last version it is relative on where the config file is.
The option cors
still being optional and its options can be an empty array.
If you want more information about the CORS options, visit the CORS section.
The option proxy
allow to configure the mock on a proxy mode, that means that you could configure an fallback urls, for all
your calls or only the missing ones or none. More information: Proxy Section
If you want to use killgrave
on your client application you must consider to configure correctly all about CORS, thus we offer the possibility to configure it as you need through a config file.
In the CORS section of the file you can find the following options:
-
methods (string array)
Represent the Access-Control-Request-Method header, if you don't specify it or if you do leave it as any empty array, the default values will be:
"GET", "HEAD", "POST", "PUT", "OPTIONS", "DELETE", "PATCH", "TRACE", "CONNECT"
-
headers (string array)
Represent the Access-Control-Request-Headers header, if you don't specify it or if you do leave it as any empty array, the default values will be:
"X-Requested-With", "Content-Type", "Authorization"
-
exposed_headers (string array)
Represent the Access-Control-Expose-Headers header, if you don't specify it or if you do leave it as any empty array, the default values will be:
"Cache-Control", "Content-Language", "Content-Type", "Expires", "Last-Modified", "Pragma"
-
origins (string array)
Represent the Access-Control-Allow-Origin header, if you don't specify or leave as empty array this option has not default value
-
allow_credentials (boolean)
Represent the Access-Control-Allow-Credentials header you must indicate if true or false
You can use Killgrave with a proxy mode which means that you can use the flags proxy-mode
and proxy-url
or the configuration file to declare one of these three modes:
- none: by default mode, with this mode you don't use any proxy, and the mock server will always look into the files with
.imp.json
extension. - missing: with this mode, the mock server will look into the files with
.imp.json
extension, but if you call to an endpoint that doesn't exist, then the mock server will call to the real server, declared on theproxy-url
configuration variable. - all: the mock server only will call to the real server, declared on the
proxy-url
configuration variable.
The proxy-url
must be the root path. For example, if we have endpoint api like, http://example.com/things
, the proxy-url
will be, http://example.com
You must be creating an imposter to start using the application, only files with the .imp.json
extension will be interpreted as imposter files, and the base path for the rest of the files will be the path of the .imp.json
file.
You need to organize your imposters from more restrictive to less. We use a rule-based system for creating each imposter, for this reason you need to organize your imposters from the most restrictive to the least, like the example below.
imposter example:
[
{
"request": {
"method": "GET",
"endpoint": "/gophers/01D8EMQ185CA8PRGE20DKZTGSR"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": "{\"data\":{\"type\":\"gophers\",\"id\":\"01D8EMQ185CA8PRGE20DKZTGSR\",\"attributes\":{\"name\":\"Zebediah\",\"color\":\"Purples\",\"age\":55}}}"
}
}
]
This a very simple example but Killgrave have more possibilities to configure your imposters, let see some of them on the next sections.
The imposter object can be divided on two parts:
For configure our imposter we will need to declare a request
, the request
is the object which allow the Killgrave
engine identify with which endpoint should match, and have the next properties:
method
(mandatory): with this property we will define the method of our request, you could configure whatever verb on the http protocol was allowed.endpoint
(mandatory): it is a relative path with the endpoint that you want to mock. Admit regex to configure dynamic params.schemaFile
: if you want that the request could be validated using a json schema, you will need to indicate the path where your json schema are.- `params: if you want to specify a restrictive query param, you can add a list of them, more information: Create an imposter with query params, this option admit regex.
headers
: if you want to specify a restrictive headers, like authorization or any other, you can define a list of headers that you request will need to match, more info: Create an imposter with headers.
On the other hand we have the response
object, the response
object will be in charge to generate the output that
we want, related with the request
that was executed.
The response, allow the next properties:
status
(mandatory): this will be the status that the response will return.body
orbodyFile
: if you want that your response return any kind result, you need to definebody
orbodyFile
param, the difference between them, is thatbody
allow writing directly the output, andbodyFile
, is a route to the file with the output, this is very useful in case of a large output. You can also remove, this property if you want not return any content.headers
: sometime we need a specific header on ourresponse
, for example if you want to specify a special `content-type`` so with this property you can define which headers the response will return.delay
: is the time the server waits before responding. This can help simulate network issues, or server high load. You must writedelay
as a string with postfix indicating time unit (see this for more info about an actual format). Also, you can specify minimum and maximum delays using separator ':', the server respond delay will be chosen at random between these values, default value is no delay at all.
If we want to define a regex
in our endpoint, we will need using the gorilla mux nomenclature, that is
{anyvariablename:{regex}}
.
In the next example, we have an endpoint configured to match with any kind of ULID Id:
[
{
"request": {
"method": "GET",
"endpoint": "/gophers/{_id:[\\w]{26}}"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": "{\"data\":{\"type\":\"gophers\",\"id\":\"01D8EMQ185CA8PRGE20DKZTGSR\",\"attributes\":{\"name\":\"Zebediah\",\"color\":\"Purples\",\"age\":55}}}"
}
}
]
In the case of use a regex
in our query params, we also will need using the gorilla mux nomenclature, that is
{anyvariablename:{regex}}
.
For example, we want an imposter that only do match if we receive an apiKey as param:
[
{
"request": {
"method": "GET",
"endpoint": "/gophers/{_id:[\\w]{26}}",
"params": {
"apiKey": "{_apiKey:[\\w]+}"
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": "{\"data\":{\"type\":\"gophers\",\"id\":\"01D8EMQ185CA8PRGE20DKZTGSR\",\"attributes\":{\"name\":\"Zebediah\",\"color\":\"Purples\",\"age\":55}}}"
}
}
]
In this case we will not need the gorilla mux nomenclature
to write our regex.
Then if we want for example do a restrictive imposter
that need an Authorization
header to do match, we could do something like
this:
[
{
"request": {
"method": "GET",
"endpoint": "/gophers/{id:[\\w]{26}}",
"headers": {
"Authorization": "\\w+"
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": "{\"data\":{\"type\":\"gophers\",\"id\":\"01D8EMQ185CA8PRGE20DKZTGSR\",\"attributes\":{\"name\":\"Zebediah\",\"color\":\"Purples\",\"age\":55}}}"
}
}
]
So as you can see, in the headers
option you can write directly your value or your regex.
Sometime, we will need to ensure that the request that we will receive is a valid request, for that reason we can
create an imposter
that only match with a valid json schema.
To do that we will need to define our json schema
first:
imposters/schemas/create_gopher_request.json
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"gophers"
]
},
"attributes": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"color": {
"type": "string"
},
"age": {
"type": "integer"
}
},
"required": [
"name",
"color",
"age"
]
}
},
"required": [
"type",
"attributes"
]
}
},
"required": [
"data"
]
}
So with this json schema
, we expect a request
like this:
{
"data": {
"type": "gophers",
"attributes": {
"name": "Zebediah",
"color": "Purples",
"age": 55
}
}
}
Then our imposter
will be configure in the next way:
[
{
"request": {
"method": "POST",
"endpoint": "/gophers",
"schemaFile": "schemas/create_gopher_request.json",
"headers": {
"Content-Type": "application/json"
}
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
}
}
}
]
The path where the schema is located is relative to where the imposters are.
If we want to simulate a problem with the network, or create a more realistic response, we can use the delay
property.
delay
property is a range, between two times that use parser unit of time of Go language.
For example, we can modify our previous POST call to add a delay
, we want that our call wait between 1 second to 5 second:
[
{
"request": {
"method": "POST",
"endpoint": "/gophers",
"schemaFile": "schemas/create_gopher_request.json",
"headers": {
"Content-Type": "application/json"
}
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
},
"delay": "1s:5s"
}
}
]
Killgrave allow dynamic responses, with that feature we can use one endpoint and obtain different response or even errors.
We need to understand that to do Killgrave need all the imposters
order from more restrictive to less, because the server
try to do match with each request
and stop when his find one that have the requirements to be executed.
For example, think on our previous POST example, we want to create a gopher, but the request
doesn't comply with the
json schema declared.
[
{
"request": {
"method": "POST",
"endpoint": "/gophers",
"schemaFile": "schemas/create_gopher_request.json",
"headers": {
"Content-Type": "application/json"
}
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
}
}
},
{
"request": {
"method": "POST",
"endpoint": "/gophers"
},
"response": {
"status": 400,
"headers": {
"Content-Type": "application/json"
},
"body": "{\"errors\":\"bad request\"}"
}
}
]
So as you can see, we have first of all the imposters with the restrictive, headers
and json schema
, but
our last imposter
is a simple imposter
that it will match with any call via POST to /gophers
.
Contributions are more than welcome, if you are interested please follow our guidelines to help you get started.
MIT License, see LICENSE