Simple jetty server to record HTTP requests.

URLs

Do you want to improve this page? Please edit it on GitHub.

Description

http-request-recorder is a small tool that can be used to record HTTP requests. It is based on Eclipse Jetty and runs on the JVM. Recorded requests are available in memory (as RxJava stream), can be printed in the console or stored as JSON file on the disk.

Requirements

Version 5.x requires Java 17 or newer. It is built on Jetty 12 with the ee9 environment (Jakarta Servlet 5.0). Use 4.x for Java 11 and Jetty 11.

When upgrading from 4.x, note that the signature of JettyServer.start(BiConsumer) changed: the custom handler now receives a jakarta.servlet.http.HttpServletRequest instead of the Jetty Request, and the request is marked as handled by the server once the handler returns, so calling setHandled(true) is no longer necessary.

Download

Binaries are hosted on maven central.

core

Maven coordinates of the 'core' project (xml notation)
<dependency>
  <groupId>com.unblu.tools</groupId>
  <artifactId>http-request-recorder-core</artifactId>
  <version>5.0.0</version>
</dependency>
Maven coordinates of the 'core' project (single line notation)
"com.unblu.tools:http-request-recorder-core:5.0.0"

cli

Maven coordinates of the 'cli' project (xml notation)
<dependency>
  <groupId>com.unblu.tools</groupId>
  <artifactId>http-request-recorder-cli</artifactId>
  <version>5.0.0</version>
</dependency>
Maven coordinates of the 'cli' project (single line notation)
"com.unblu.tools:http-request-recorder-cli:5.0.0"

cli (fat jar)

A standalone jar (containing the dependencies) is also published:

Maven coordinates of the 'cli fat jar' (xml notation)
<dependency>
  <groupId>com.unblu.tools</groupId>
  <artifactId>http-request-recorder-cli</artifactId>
  <version>5.0.0</version>
  <classifier>all</classifier>
</dependency>
Maven coordinates of the 'cli fat jar' (single line notation)
"com.unblu.tools:http-request-recorder-cli:5.0.0:all"

Usage

cli

You can start the server with:

java -jar libs/hrr.jar --log

Some usefull flags:

--log: each request will be logged into the console

--port: a fixed port for the server will be used

--output: stores each request as json file (see example bellow). The --tree flag can be usefull to influence the way the files are stored.

Usage example:

Started with:

java -jar libs/hrr.jar --port 8090 --output out

For following requests:

GET http://localhost:8090/service1/ping
POST http://localhost:8090/service1/ping
POST http://localhost:8090/service1/check
GET http://localhost:8090/service2

You will get one file per request in the out/ folder:

out/1542289573057-service1_ping.json
out/1542289576028-service1_ping.json
out/1542289591111-service1_check.json
out/1542289607227-service2.json

With the --tree flag the requested URI is used to separate the files in subfolders that correspond to the file. The same requests will produce:

out
|____service1
| |____ping
| | |____1542289573057.json
| | |____1542289576028.json
| |____check
|   |____1542289591111.json
|____service2
  |____1542289607227.json

Help:

You can see the list of the flags with:

java -jar libs/hrr.jar --help

core

The core can be embedded in an other java application (for example when used during some unit-tests).

Just include com.unblu.tools:http-request-recorder-core:$hrrVersion as test dependency.

With server being a com.unblu.tools.hrr.core.JettyServer instance (that is created and started before the test execution and stopped after the test), a simple test can look like this:

//Access the request recorder:
Observable<RequestRecord> observable$ = server.onRequest();

//create test observer
TestObserver<RequestRecord> testObserver = observable$.test();

//Simple subscription: log the HTTP record as INFO log
observable$.map(RequestRecordConverter::asLog).subscribe(LOG::info);

//Perform a GET Request:
HttpURLConnection con = performPinGetRequest();

//Perform assertions on the server response:
int status = con.getResponseCode();
assertThat(status).isEqualTo(200);
String content = new BufferedReader(new InputStreamReader(con.getInputStream())).lines().collect(Collectors.joining("\n"));
assertThat(content).isEqualTo("{ \"status\": \"ok\"}");

//Perform assertions on the recorded server event:
testObserver.assertNoErrors().assertValueCount(1).assertValueAt(0, record -> {
        assertThat(record.getMethod()).isEqualTo("GET");
        assertThat(record.getRequestURI()).isEqualTo("/ping");
        return true;
});

In this case onRequest is used, which only emits for events that happen after subscription.

Use getRequest to also get the last previous one:

//Access the request recorder:
Observable<RequestRecord> observable$ = server.getRequest();

//Perform a GET Request:
HttpURLConnection con = performPinGetRequest();

//Simple subscription: log the HTTP record as INFO log
//Retrieves still the last record
observable$.map(RequestRecordConverter::asLog).subscribe(LOG::info);

//Perform assertions on the server response:
int status = con.getResponseCode();
assertThat(status).isEqualTo(200);
String content = new BufferedReader(new InputStreamReader(con.getInputStream())).lines().collect(Collectors.joining("\n"));
assertThat(content).isEqualTo("{ \"status\": \"ok\"}");

//Perform assertions on the recorded server event:
observable$.test().assertNoErrors().assertValueCount(1).assertValueAt(0, record -> {
        assertThat(record.getMethod()).isEqualTo("GET");
        assertThat(record.getRequestURI()).isEqualTo("/ping");
        return true;
});

With JUnit 5 the server can be managed like this (this is only one example, you might prefer to manage the server globally somewhere else)

Start the server before all tests in the class.
@BeforeAll
public static void before() throws Exception {
        server = new JettyServer();
        server.start();
}
Stop the server after all tests in the class.
@AfterAll
public static void after() throws Exception {
        server.stop();
}

Instead of recording, the server can also be started with a custom handler, which receives each request as a jakarta.servlet.http.HttpServletRequest together with its HttpServletResponse. The request is marked as handled once the handler returns. In this mode nothing is emitted on onRequest() nor on getRequest().

server.start((request, response) -> {
    response.setStatus(204);
});

Development

This project is tested with Eclipse IDE, it should however work with any other IDE as well.

Build

Build sources:

./gradlew build

Deploy to maven local:

./gradlew publishToMavenLocal

Build the documentation page:

./gradlew asciidoctor

Upload the documentation page on GitHub pages:

./gradlew gitPublishPush

Perform a release:

./gradlew release -Prelease.useAutomaticVersion=true

The artifacts are staged in build/staging-deploy by ./gradlew publish, then signed and uploaded to Maven Central by JReleaser (./gradlew jreleaserDeploy). JReleaser reads its credentials (Maven Central token, GPG key, GitHub token) from the environment or from ~/.jreleaser/config.toml, never from this repository.

Gradle needs Java 17 or newer to run.

Using ssh-agent

Some tasks requires to push into the distant git repository (release task or updating the gh-pages branch). If they are failing with errors like this:

org.eclipse.jgit.api.errors.TransportException: ... Permission denied (publickey).

Then ssh-agent can be used.

eval `ssh-agent -s`
ssh-add ~/.ssh/id_rsa

(source for this approach)

Run the cli using Gradle

To run the cli project using gradle:

./gradlew cli:run --args ' --port 8090 --log'
./gradlew cli:run --args ' --help'

Get in touch

Use the issue tracker on GitHub.

You can also contact me on Twitter: @j2r2b

License