Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Spring Web Captor Demo

An interactive demo showcasing the power of Spring Web Captor. Send HTTP requests through a React UI and see every captured field in real-time — headers, bodies, path params, query params, file uploads, durations, and more.

## Prerequisites

- Java 21+
- Maven 3.9+
- Node.js 18+

## Setup

### 1. Install the library

From the repository root:

```bash
mvn install -DskipTests
```

### 2. Start the backend

```bash
cd examples/backend
mvn spring-boot:run
```

The backend starts on http://localhost:8085.

### 3. Start the frontend

```bash
cd examples/frontend
npm install
npm run dev
```

The frontend starts on http://localhost:5173.

## Docker

Run the entire demo with Docker Compose:

```bash
cd examples
docker compose up --build
```

- Frontend: http://localhost:3000
- Backend: http://localhost:8085

## Features Showcased

- **All HTTP methods** — GET, POST, PUT, PATCH, DELETE with JSON bodies
- **Path variables** — Including catch-all wildcard `{*rest}` patterns
- **Query parameters** — Multi-value parameter capturing
- **File uploads** — Multipart with base64-encoded file metadata
- **Form data** — URL-encoded form submissions
- **Plain text** — Text body parsing
- **Error capturing** — 400, 404, 500, 418 with full error details
- **Duration tracking** — Request timing via the duration extension
- **Concurrent requests** — Thread-safe parallel capture
- **IP & User-Agent** — Automatic client metadata extraction
45 changes: 45 additions & 0 deletions examples/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# https://medium.com/@RoussiAbdelghani/optimizing-java-base-docker-images-size-from-674mb-to-58mb-c1b7c911f622
FROM maven:3.9.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

FROM eclipse-temurin:21-jdk-alpine AS jre-builder
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
RUN apk add --no-cache binutils && \
# Determine the modules required by the application
jar -xf app.jar && \
jdeps --ignore-missing-deps -q \
--recursive \
--multi-release 21 \
--print-module-deps \
--class-path 'BOOT-INF/lib/*' \
app.jar > modules.txt && \
# Create a custom JRE
"$JAVA_HOME/bin/jlink" \
--verbose \
--add-modules $(cat modules.txt) \
--strip-debug \
--no-man-pages \
--no-header-files \
--compress=2 \
--output /javaruntime && \
rm -rf BOOT-INF META-INF app.jar modules.txt

# Create a final image
FROM alpine:3.20
ENV JAVA_HOME=/opt/jdk/jdk-21
ENV PATH="${JAVA_HOME}/bin:${PATH}"
ARG APP_USER=spring
COPY --from=jre-builder /javaruntime $JAVA_HOME
RUN addgroup --system --gid 1001 "$APP_USER" && \
adduser --system --uid 1001 --home /app --ingroup "$APP_USER" "$APP_USER" && \
chown -R "$APP_USER":"$APP_USER" /app
WORKDIR /app
COPY --from=build --chown=$APP_USER:$APP_USER /app/target/*.jar app.jar
USER $APP_USER
EXPOSE 8085
ENV server.port=8085
CMD ["java", "-jar", "app.jar"]
55 changes: 55 additions & 0 deletions examples/backend/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.3</version>
<relativePath/>
</parent>
<groupId>com.davidrandoll</groupId>
<artifactId>spring-web-captor-demo</artifactId>
<version>1.0.0</version>
<name>Spring Web Captor Demo</name>
<description>Demo application showcasing Spring Web Captor</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.davidrandoll</groupId>
<artifactId>spring-web-captor</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.davidrandoll.webcaptor.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.davidrandoll.webcaptor.demo.config;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.List;

@Configuration
public class CorsConfig {

@Bean
public FilterRegistrationBean<CorsFilter> corsFilterRegistration() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:5173", "http://localhost:3000"));
config.setAllowedMethods(List.of("*"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);

FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.davidrandoll.webcaptor.demo.controller;

import com.davidrandoll.webcaptor.demo.listener.CapturedEventStore;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping("/api/captured-events")
@RequiredArgsConstructor
public class CapturedEventsController {

private final CapturedEventStore store;

@GetMapping
public Map<String, Object> getEvents() {
return Map.of(
"requestEvents", store.getRequestEvents(),
"responseEvents", store.getResponseEvents()
);
}

@DeleteMapping
public ResponseEntity<Void> clearEvents() {
store.clear();
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.davidrandoll.webcaptor.demo.controller;

import com.davidrandoll.spring_web_captor.properties.WebCaptorProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/config")
@RequiredArgsConstructor
public class ConfigViewController {

private final WebCaptorProperties properties;

@GetMapping
public WebCaptorProperties getConfig() {
return properties;
}
}
Loading
Loading