Skip to content

Commit e10d9d2

Browse files
Merge pull request #1 from prerender/add-prerender-java
Add Java Servlet filter
2 parents e71d588 + 56cb227 commit e10d9d2

9 files changed

Lines changed: 801 additions & 0 deletions

File tree

.github/workflows/pull-request.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Test
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
strategy:
12+
fail-fast: false
13+
matrix:
14+
java-version: ['17', '21']
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Setup Java
19+
uses: actions/setup-java@v4
20+
with:
21+
distribution: temurin
22+
java-version: ${{ matrix.java-version }}
23+
cache: maven
24+
25+
- name: Setup Node (for contract mock server)
26+
uses: actions/setup-node@v4
27+
with:
28+
node-version: 20.x
29+
30+
- name: Fetch contract mock server
31+
run: curl -fsSL -o mock-server.mjs https://raw.githubusercontent.com/prerender/integration-contract/main/mock-server.mjs
32+
33+
- name: Run tests
34+
run: mvn --batch-mode --no-transfer-progress test

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
target/
2+
*.iml
3+
.idea/
4+
.vscode/
5+
mock-server.mjs

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Prerender
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# prerender-java
2+
3+
Jakarta Servlet Filter for [Prerender.io](https://prerender.io). Intercepts requests from bots and crawlers and serves prerendered HTML, so your JavaScript-rendered app is fully indexable by search engines and social media scrapers.
4+
5+
Compatible with any **Jakarta EE** application server — Tomcat 10+, Jetty 11+, Spring Boot 3+, Quarkus, Micronaut.
6+
7+
Requires **Java 17+**.
8+
9+
## Installation
10+
11+
### Maven
12+
13+
```xml
14+
<dependency>
15+
<groupId>io.prerender</groupId>
16+
<artifactId>prerender-java</artifactId>
17+
<version>1.0.0</version>
18+
</dependency>
19+
```
20+
21+
### Gradle
22+
23+
```groovy
24+
implementation 'io.prerender:prerender-java:1.0.0'
25+
```
26+
27+
## Setup
28+
29+
### Option 1: Environment variables (recommended)
30+
31+
```bash
32+
export PRERENDER_TOKEN=your-token
33+
```
34+
35+
Register the filter in `web.xml`:
36+
37+
```xml
38+
<filter>
39+
<filter-name>PrerenderFilter</filter-name>
40+
<filter-class>io.prerender.PrerenderFilter</filter-class>
41+
</filter>
42+
<filter-mapping>
43+
<filter-name>PrerenderFilter</filter-name>
44+
<url-pattern>/*</url-pattern>
45+
</filter-mapping>
46+
```
47+
48+
### Option 2: web.xml init-params
49+
50+
```xml
51+
<filter>
52+
<filter-name>PrerenderFilter</filter-name>
53+
<filter-class>io.prerender.PrerenderFilter</filter-class>
54+
<init-param>
55+
<param-name>prerenderToken</param-name>
56+
<param-value>your-token</param-value>
57+
</init-param>
58+
</filter>
59+
<filter-mapping>
60+
<filter-name>PrerenderFilter</filter-name>
61+
<url-pattern>/*</url-pattern>
62+
</filter-mapping>
63+
```
64+
65+
### Spring Boot
66+
67+
```java
68+
@Bean
69+
public FilterRegistrationBean<PrerenderFilter> prerenderFilter() {
70+
FilterRegistrationBean<PrerenderFilter> registration = new FilterRegistrationBean<>();
71+
registration.setFilter(new PrerenderFilter());
72+
registration.addUrlPatterns("/*");
73+
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
74+
return registration;
75+
}
76+
```
77+
78+
Set `PRERENDER_TOKEN` as an environment variable before starting the app.
79+
80+
## Settings
81+
82+
| Setting | Init-param | Env var | Default |
83+
|---------|------------|---------|---------|
84+
| Token | `prerenderToken` | `PRERENDER_TOKEN` | none |
85+
| Service URL | `prerenderServiceUrl` | `PRERENDER_SERVICE_URL` | `https://service.prerender.io/` |
86+
87+
Init-params take precedence over environment variables.
88+
89+
## Self-hosted Prerender
90+
91+
```bash
92+
export PRERENDER_SERVICE_URL=http://your-prerender-server:3000
93+
```
94+
95+
## How it works
96+
97+
Requests are prerendered when **all** of the following are true:
98+
99+
- The HTTP method is `GET`
100+
- The `User-Agent` matches a known bot/crawler (Googlebot, Bingbot, Twitterbot, GPTBot, ClaudeBot, etc.)
101+
— OR the URL contains `_escaped_fragment_`
102+
— OR the `X-Bufferbot` header is present
103+
- The URL does not end with a static asset extension (`.js`, `.css`, `.png`, etc.)
104+
105+
Everything else passes through to your normal servlet chain.
106+
107+
If the Prerender service is unreachable, the filter falls back gracefully and serves the normal response.
108+
109+
## License
110+
111+
MIT

pom.xml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>io.prerender</groupId>
8+
<artifactId>prerender-java</artifactId>
9+
<version>1.0.0</version>
10+
<packaging>jar</packaging>
11+
12+
<name>prerender-java</name>
13+
<description>Jakarta Servlet Filter for prerendering JavaScript-rendered pages via Prerender.io</description>
14+
<url>https://github.com/prerender/integrations</url>
15+
16+
<licenses>
17+
<license>
18+
<name>MIT License</name>
19+
<url>https://opensource.org/licenses/MIT</url>
20+
</license>
21+
</licenses>
22+
23+
<scm>
24+
<connection>scm:git:[email protected]:prerender/integrations.git</connection>
25+
<developerConnection>scm:git:[email protected]:prerender/integrations.git</developerConnection>
26+
<url>https://github.com/prerender/integrations</url>
27+
</scm>
28+
29+
<properties>
30+
<maven.compiler.source>17</maven.compiler.source>
31+
<maven.compiler.target>17</maven.compiler.target>
32+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
33+
</properties>
34+
35+
<dependencies>
36+
<dependency>
37+
<groupId>jakarta.servlet</groupId>
38+
<artifactId>jakarta.servlet-api</artifactId>
39+
<version>6.0.0</version>
40+
<scope>provided</scope>
41+
</dependency>
42+
43+
<dependency>
44+
<groupId>org.junit.jupiter</groupId>
45+
<artifactId>junit-jupiter</artifactId>
46+
<version>5.10.2</version>
47+
<scope>test</scope>
48+
</dependency>
49+
<dependency>
50+
<groupId>org.mockito</groupId>
51+
<artifactId>mockito-core</artifactId>
52+
<version>5.11.0</version>
53+
<scope>test</scope>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.mockito</groupId>
57+
<artifactId>mockito-junit-jupiter</artifactId>
58+
<version>5.11.0</version>
59+
<scope>test</scope>
60+
</dependency>
61+
<dependency>
62+
<groupId>org.wiremock</groupId>
63+
<artifactId>wiremock</artifactId>
64+
<version>3.5.4</version>
65+
<scope>test</scope>
66+
</dependency>
67+
<dependency>
68+
<groupId>com.fasterxml.jackson.core</groupId>
69+
<artifactId>jackson-databind</artifactId>
70+
<version>2.17.0</version>
71+
<scope>test</scope>
72+
</dependency>
73+
</dependencies>
74+
75+
<build>
76+
<plugins>
77+
<plugin>
78+
<groupId>org.apache.maven.plugins</groupId>
79+
<artifactId>maven-surefire-plugin</artifactId>
80+
<version>3.2.5</version>
81+
</plugin>
82+
</plugins>
83+
</build>
84+
</project>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package io.prerender;
2+
3+
import java.util.List;
4+
5+
class PrerenderConfig {
6+
7+
static final List<String> CRAWLER_USER_AGENTS = List.of(
8+
"googlebot", "yahoo", "bingbot", "baiduspider",
9+
"facebookexternalhit", "twitterbot", "rogerbot", "linkedinbot",
10+
"embedly", "quora link preview", "showyoubot", "outbrain",
11+
"pinterest", "slackbot", "w3c_validator", "perplexity",
12+
"oai-searchbot", "chatgpt-user", "gptbot", "claudebot", "amazonbot"
13+
);
14+
15+
static final List<String> EXTENSIONS_TO_IGNORE = List.of(
16+
".js", ".css", ".xml", ".less", ".png", ".jpg", ".jpeg", ".gif",
17+
".pdf", ".doc", ".txt", ".ico", ".rss", ".zip", ".mp3", ".rar",
18+
".exe", ".wmv", ".avi", ".ppt", ".mpg", ".mpeg", ".tif", ".wav",
19+
".mov", ".psd", ".ai", ".xls", ".mp4", ".m4a", ".swf", ".dat",
20+
".dmg", ".iso", ".flv", ".m4v", ".torrent", ".ttf", ".woff", ".svg"
21+
);
22+
23+
private static final String DEFAULT_SERVICE_URL = "https://service.prerender.io/";
24+
25+
private final String token;
26+
private final String serviceUrl;
27+
28+
PrerenderConfig(String token, String serviceUrl) {
29+
this.token = token;
30+
this.serviceUrl = (serviceUrl != null && !serviceUrl.isBlank())
31+
? serviceUrl
32+
: DEFAULT_SERVICE_URL;
33+
}
34+
35+
static PrerenderConfig fromInitParams(String initToken, String initServiceUrl) {
36+
return new PrerenderConfig(
37+
resolve(initToken, "PRERENDER_TOKEN"),
38+
resolve(initServiceUrl, "PRERENDER_SERVICE_URL")
39+
);
40+
}
41+
42+
private static String resolve(String initParam, String envVar) {
43+
return (initParam != null && !initParam.isBlank()) ? initParam : System.getenv(envVar);
44+
}
45+
46+
String getToken() { return token; }
47+
48+
String getServiceUrl() { return serviceUrl; }
49+
50+
static boolean isBot(String userAgent) {
51+
String ua = userAgent.toLowerCase();
52+
return CRAWLER_USER_AGENTS.stream().anyMatch(ua::contains);
53+
}
54+
55+
static boolean isStaticAsset(String path) {
56+
String lower = path.toLowerCase();
57+
return EXTENSIONS_TO_IGNORE.stream().anyMatch(lower::endsWith);
58+
}
59+
}

0 commit comments

Comments
 (0)