Simple jetty server to record HTTP requests.
URLs
-
Project home (this page)
| 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
<dependency>
<groupId>com.unblu.tools</groupId>
<artifactId>http-request-recorder-core</artifactId>
<version>5.0.0</version>
</dependency>
"com.unblu.tools:http-request-recorder-core:5.0.0"
cli
<dependency>
<groupId>com.unblu.tools</groupId>
<artifactId>http-request-recorder-cli</artifactId>
<version>5.0.0</version>
</dependency>
"com.unblu.tools:http-request-recorder-cli:5.0.0"
cli (fat jar)
A standalone jar (containing the dependencies) is also published:
<dependency>
<groupId>com.unblu.tools</groupId>
<artifactId>http-request-recorder-cli</artifactId>
<version>5.0.0</version>
<classifier>all</classifier>
</dependency>
"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
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)
@BeforeAll
public static void before() throws Exception {
server = new JettyServer();
server.start();
}
@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)
Get in touch
Use the issue tracker on GitHub.
You can also contact me on Twitter: @j2r2b
License
Code is under Eclipse Public License - v 2.0.