Skip to content
forked from int128/httpstub

Declarative YAML based HTTP stub server for integration tests such as enterprise external APIs

License

Notifications You must be signed in to change notification settings

gleb619/httpstub

 
 

Repository files navigation

httpstub build Gradle Status

This is a HTTP stub server for integration test with external APIs.

Key features:

  • Single JAR
  • Declarative API definition using YAML
  • Template rendering and pattern matching using Groovy
  • File watcher
  • Gradle plugin (fork feature)
  • Reverse proxy (fork feature)
  • Request recording aka vhs (fork feature)

Please see fork features details

Getting Started

Download the latest release. Java 11 or later is required.
At the moment the fork has no releases, you must download project and build it manually.
Or just use docker image from ghcr.io

Define a route as follows:

mkdir -p data
vim data/users.get.yaml
# data/users.get.yaml
- response:
    headers:
      content-type: application/json
    body:
      - id: 1
        name: Foo
      - id: 2
        name: Bar

Or even simpler, with an old declaration

# data/users.get.yaml
- response:
    headers:
      content-type: application/json
    body:
      - id: 1
        name: Foo
      - id: 2
        name: Bar

Run the application:

java -jar httpstub.jar

Call the API:

curl -v http://localhost:8080/users
> GET /users HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 16 Nov 2017 06:50:13 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
<
[{"name":"Foo","id":1},{"name":"Bar","id":2}]

The stub will reload YAML files if they have been changed or new one has been created.

Docker image is available on ghcr.io/gleb619/httpstub.

docker run -v $PWD/data:/app/data:ro -p 8080:8080 ghcr.io/gleb619/httpstub

The fork is not yet available to download via docker, please download and build it locally

Options

Logging

You can write log to a file.

By following option, the stub writes log to logs/spring.log, rotates when it reaches 10MB and keeps up to 8 files. See Spring Boot features: Logging for more.

# Command line option
java -jar httpstub.jar --logging.path=logs

# Environment variable
export LOGGING_PATH=logs
java -jar httpstub.jar

Request and response logging

The stub shows following log for each request.

2017-12-05 10:44:20.042  INFO 19694 --- [ctor-http-nio-2] o.h.stubyaml.app.RequestResponseLogger   : > GET /users
2017-12-05 10:44:20.043  INFO 19694 --- [ctor-http-nio-2] o.h.stubyaml.app.RequestResponseLogger   : > Host: localhost:8080
2017-12-05 10:44:20.044  INFO 19694 --- [ctor-http-nio-2] o.h.stubyaml.app.RequestResponseLogger   : > User-Agent: curl/7.54.0
2017-12-05 10:44:20.044  INFO 19694 --- [ctor-http-nio-2] o.h.stubyaml.app.RequestResponseLogger   : > Accept: */*
2017-12-05 10:44:20.044  INFO 19694 --- [ctor-http-nio-2] o.h.stubyaml.app.RequestResponseLogger   : >
2017-12-05 10:44:20.047  INFO 19694 --- [ctor-http-nio-2] o.h.stubyaml.app.RequestResponseLogger   : < 200 OK
2017-12-05 10:44:20.048  INFO 19694 --- [ctor-http-nio-2] o.h.stubyaml.app.RequestResponseLogger   : < content-type: application/json
2017-12-05 10:44:20.049  INFO 19694 --- [ctor-http-nio-2] o.h.stubyaml.app.RequestResponseLogger   : < x-uuid: 1992cb3d-7bbf-4c2e-aa65-a19fa656f77e
2017-12-05 10:44:20.050  INFO 19694 --- [ctor-http-nio-2] o.h.stubyaml.app.RequestResponseLogger   : <
2017-12-05 10:44:20.050  INFO 19694 --- [ctor-http-nio-2] o.h.stubyaml.app.RequestResponseLogger   : < [{"name":"Foo","id":1},{"name":"Bar","id":2}]

You can turn off logging by creating data/config.yaml:

logging:
  headers: false
  body: false

Recipes

HTTP methods

Specify HTTP method in the extension part of filename. For example, create a route file data/users.post.yaml for handling POST method. Following methods are supported.

  • GET
  • HEAD
  • POST
  • PUT
  • PATCH
  • DELETE
  • OPTIONS
  • TRACE

Path variables

A braced string in the file path is treated as a path variable. For example, create /users/{userId}.get.yaml for handling /users/1, /users/2 and so on.

Response header

You can set pairs of key and value to the headers. The value must be a string and is parsed as a template (see also the later section).

- response:
    headers:
      content-type: text/plain
      x-uuid: "1234567890"

You can set multiple values.

- response:
    headers:
      set-cookie:
        - sessionId=38afes7a8
        - id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT

Response body

You can serve a text body as follows:

- response:
    headers:
      content-type: application/xml
    body: |
      <?xml version="1.0" encoding="UTF-8"?>
      <users>
        <user>
          <id>1</id>
          <name>Foo</name>
        </user>
      </users>

You can serve a JSON body as follows:

- response:
    headers:
      content-type: application/json
    body:
      id: 1
      name: Alice

If a character set is specified in the content-type header, the response body is encoded to the character set.

- response:
    headers:
      content-type: text/plain;charset=Shift_JIS
    body: あいうえお

You can serve a file content as follows:

- response:
    headers:
      content-type: image/jpeg
    file: photo.jpg

That means that you can store the larger response bodies in a separate file.

- response:
    headers:
      content-type: application/json
    file: big-file.json
#big-file.json
{
  "id": "123123",
  "name": "Oliver",
  ...
  "account": "YU89RE00"
}

Template

Following values are parsed as a Groovy template:

  • Response header value
  • Response body (body)
  • Response filename (file)
  • Table key (key of tables)

Following variables are available in a script block ${}.

Variable Object
path Path variables
headers Request headers
params Query parameters
query Query parameters (alternative name)
body Request body
requestBody Request body (alternative name)

Type of the request body may be one of following:

Content type of request Type of request body
application/x-www-form-urlencoded Map<String, String>
multipart/form-data Map<String, Part>
application/json and subset Map<String, Object>
application/xml and subset, text/xml and subset Map<String, Object>
text/* String
Otherwise null

For example, create /users/{userId}.get.yaml as following:

- response:
    headers:
      content-type: application/json
    body:
      id: ${path.userId},
      name: User${path.userId}

The stub will return the following response on the request GET /users/100:

{
  "id": 100,
  "name": "User100"
}

Pattern matching

A YAML file has one or more rules. The stub evaluates each when of all rules and returns the first matched response.

Here is the example of /numbers.get.yaml as follows:

- when: params.order == 'desc'
  response:
    headers:
      content-type: application/json
    body: [3, 2, 1]

- when: params.order == 'asc'
  response:
    headers:
      content-type: application/json
    body: [1, 2, 3]
    
#default response should be at the end of the list
- response:                           
    headers:
      content-type: application/json
    body: [0]

The stub will return the following response on the request GET /numbers?order=asc:

[1, 2, 3]

And on the request GET /numbers?order=desc:

[3, 2, 1]

And on the request GET /numbers:

[0]

If the last resort is not defined, the stub will return 404.

Constants

Define constants in data/config.yaml:

constants:
  today: "2017-12-01"

You can use constants in a route YAML:

- response:
    headers:
      content-type: application/json
    body:
      - id: 1
        name: Foo
        registeredDate: ${constants.today}

Tables

Tables are usuful for data variation testing.

Let's see the example.

Request condition: path.userId Response variable: tables.userName Response variable: tables.age
1 Foo 35
2 Bar 100
3 Baz 3

Create /users/{userId}.get.yaml with following rule.

- response:
    headers:
      content-type: application/json
    body:
      id: ${path.userId}
      name: ${tables.userName}
      age: ${tables.age}
    tables:
    - name: userName
      key: path.userId
      values:
        1: Foo
        2: Bar
        3: Baz
    - name: age
      key: path.userId
      values:
        1: 35
        2: 100
        3: 3

The stub will return the following response on the request GET /users/1:

{
  "id": 1,
  "name": "Foo",
  "age": 35
}

And on the request GET /users/2:

{
  "id": 2,
  "name": "Bar",
  "age": 100
}

Delay

Use delay attribute in milliseconds to simulate network latency.

For example, create /users.post.yaml as following:

- response:
    delay: 500
    headers:
      content-type: application/json
    body:
      id: 1

Send the request POST /users and the stub will return a response after 500 ms.

Troubleshooting

If you see in logs next exception java.io.IOException: User limit of inotify watches reached don't afraid just execute echo 16384 | sudo tee /proc/sys/fs/inotify/max_user_watches on your linux machine

Versioning

After 12.2019 for backward compatibilities added version to manifest files. Config files introduction of a versioning system for further development and addition of new features.

Variable Object
v1.0 Old version of project
v1.1 Added RuleContainer
v1.2 Added Reverse proxy, Recorder, Printer, Writer
v1.3 Container's logic refactor

Please see fork features details

Contributions

This is an open source software. Feel free to open issues and pull requests.

Languages

  • Java 84.9%
  • Groovy 14.9%
  • Dockerfile 0.2%