Compare commits

...

17 Commits

Author SHA1 Message Date
Anthony Stirling
fd39f28e46 Merge pull request #280 from Frooodle/dependabot/gradle/org.springframework.boot-spring-boot-starter-test-3.1.2
Bump org.springframework.boot:spring-boot-starter-test from 3.1.0 to 3.1.2
2023-08-08 20:29:23 +01:00
Anthony Stirling
710125852a Merge branch 'main' into dependabot/gradle/org.springframework.boot-spring-boot-starter-test-3.1.2 2023-08-08 20:27:51 +01:00
Anthony Stirling
e8ec208390 Merge pull request #281 from Frooodle/dependabot/gradle/org.springframework.boot-spring-boot-starter-web-3.1.2
Bump org.springframework.boot:spring-boot-starter-web from 3.1.0 to 3.1.2
2023-08-08 20:27:23 +01:00
dependabot[bot]
4584562607 Bump org.springframework.boot:spring-boot-starter-test
Bumps [org.springframework.boot:spring-boot-starter-test](https://github.com/spring-projects/spring-boot) from 3.1.0 to 3.1.2.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.1.0...v3.1.2)

---
updated-dependencies:
- dependency-name: org.springframework.boot:spring-boot-starter-test
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 19:25:51 +00:00
dependabot[bot]
2c1412a088 Bump org.springframework.boot:spring-boot-starter-web
Bumps [org.springframework.boot:spring-boot-starter-web](https://github.com/spring-projects/spring-boot) from 3.1.0 to 3.1.2.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.1.0...v3.1.2)

---
updated-dependencies:
- dependency-name: org.springframework.boot:spring-boot-starter-web
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 19:25:50 +00:00
Anthony Stirling
0a65382979 Merge pull request #279 from Frooodle/dependabot/gradle/org.springframework.boot-3.1.2
Bump org.springframework.boot from 3.1.1 to 3.1.2
2023-08-08 20:25:32 +01:00
Anthony Stirling
4f404f66e5 Merge branch 'main' into dependabot/gradle/org.springframework.boot-3.1.2 2023-08-08 20:24:34 +01:00
Anthony Stirling
89505ada00 Merge pull request #273 from Frooodle/dependabot/gradle/io.spring.dependency-management-1.1.2
Bump io.spring.dependency-management from 1.1.0 to 1.1.2
2023-08-08 20:24:01 +01:00
dependabot[bot]
374a30ac5a Bump org.springframework.boot from 3.1.1 to 3.1.2
Bumps [org.springframework.boot](https://github.com/spring-projects/spring-boot) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.1.1...v3.1.2)

---
updated-dependencies:
- dependency-name: org.springframework.boot
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 19:20:25 +00:00
Anthony Stirling
cee4ee4128 Merge branch 'main' into dependabot/gradle/io.spring.dependency-management-1.1.2 2023-08-08 20:19:47 +01:00
Anthony Stirling
58fc5e2ffa Merge pull request #259 from Frooodle/dependabot/gradle/org.apache.pdfbox-pdfbox-2.0.29
Bump org.apache.pdfbox:pdfbox from 2.0.28 to 2.0.29
2023-08-08 20:19:27 +01:00
Anthony Stirling
951ea43f8b Merge branch 'main' into dependabot/gradle/org.apache.pdfbox-pdfbox-2.0.29 2023-08-08 20:09:34 +01:00
Anthony Stirling
ca16ecef24 Create StartupApplicationListener.java 2023-08-08 20:09:05 +01:00
Anthony Stirling
bb025dc2a1 Merge branch 'main' into dependabot/gradle/org.apache.pdfbox-pdfbox-2.0.29 2023-08-08 19:57:32 +01:00
dependabot[bot]
d797169bd0 Bump io.spring.dependency-management from 1.1.0 to 1.1.2
Bumps [io.spring.dependency-management](https://github.com/spring-gradle-plugins/dependency-management-plugin) from 1.1.0 to 1.1.2.
- [Release notes](https://github.com/spring-gradle-plugins/dependency-management-plugin/releases)
- [Commits](https://github.com/spring-gradle-plugins/dependency-management-plugin/compare/v1.1.0...v1.1.2)

---
updated-dependencies:
- dependency-name: io.spring.dependency-management
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-08 18:56:05 +00:00
Anthony Stirling
891f9e2252 api changes 2023-08-08 19:55:18 +01:00
dependabot[bot]
f6262c82e1 Bump org.apache.pdfbox:pdfbox from 2.0.28 to 2.0.29
Bumps org.apache.pdfbox:pdfbox from 2.0.28 to 2.0.29.

---
updated-dependencies:
- dependency-name: org.apache.pdfbox:pdfbox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-26 21:09:07 +00:00
4 changed files with 213 additions and 32 deletions

View File

@@ -178,6 +178,7 @@ Using the same method you can also change
- Disable and remove endpoints and functionality from Stirling-PDF. Currently the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma seperated lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image to pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Frooodle/Stirling-PDF/blob/main/groups.md)
- Change the max file size allowed through the server with the environment variable MAX_FILE_SIZE. default 2000MB
- Customise static files such as app logo by placing files in the /customFiles/static/ directory. Example to customise app logo is placing a /customFiles/static/favicon.svg to override current SVG. This can be used to change any images/icons/css/fonts/js etc in Stirling-PDF
- Enable/Disable metric api endpoints with ENABLE_API_METRICS. Default enabled
## API
For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation

View File

@@ -1,14 +1,14 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.1'
id 'io.spring.dependency-management' version '1.1.0'
id 'org.springframework.boot' version '3.1.2'
id 'io.spring.dependency-management' version '1.1.2'
id 'org.springdoc.openapi-gradle-plugin' version '1.6.0'
id "io.swagger.swaggerhub" version "1.2.0"
id 'edu.sc.seis.launch4j' version '3.0.3'
}
group = 'stirling.software'
version = '0.12.1'
version = '0.12.2'
sourceCompatibility = '17'
repositories {
@@ -45,9 +45,9 @@ launch4j {
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0'
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.1'
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.2'
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
implementation 'commons-io:commons-io:2.13.0'
@@ -55,7 +55,7 @@ dependencies {
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
//general PDF
implementation 'org.apache.pdfbox:pdfbox:2.0.28'
implementation 'org.apache.pdfbox:pdfbox:2.0.29'
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
implementation 'com.itextpdf:itext7-core:7.2.5'

View File

@@ -0,0 +1,20 @@
package stirling.software.SPDF.config;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
public static LocalDateTime startTime;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
startTime = LocalDateTime.now();
}
}

View File

@@ -1,8 +1,16 @@
package stirling.software.SPDF.controller.web;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -14,6 +22,8 @@ import io.micrometer.core.instrument.MeterRegistry;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.PostConstruct;
import stirling.software.SPDF.config.StartupApplicationListener;
@RestController
@RequestMapping("/api/v1")
@@ -22,6 +32,20 @@ public class MetricsController {
private final MeterRegistry meterRegistry;
private boolean isEndpointEnabled;
@PostConstruct
public void init() {
String isEndpointEnabled = System.getProperty("ENABLE_API_METRICS");
if (isEndpointEnabled == null) {
isEndpointEnabled = System.getenv("ENABLE_API_METRICS");
if (isEndpointEnabled == null) {
isEndpointEnabled = "true";
}
}
this.isEndpointEnabled = "true".equalsIgnoreCase(isEndpointEnabled);
}
public MetricsController(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@@ -29,18 +53,25 @@ public class MetricsController {
@GetMapping("/status")
@Operation(summary = "Application status and version",
description = "This endpoint returns the status of the application and its version number.")
public Map<String, String> getStatus() {
public ResponseEntity<?> getStatus() {
if (!isEndpointEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
Map<String, String> status = new HashMap<>();
status.put("status", "UP");
status.put("version", getClass().getPackage().getImplementationVersion());
return status;
return ResponseEntity.ok(status);
}
@GetMapping("/loads")
@Operation(summary = "GET request count",
description = "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.")
public Double getPageLoads(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
try {
public ResponseEntity<?> getPageLoads(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
if (!isEndpointEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
double count = 0.0;
@@ -68,36 +99,165 @@ public class MetricsController {
}
}
return count;
return ResponseEntity.ok(count);
} catch (Exception e) {
return -1.0;
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/loads/all")
@Operation(summary = "GET requests count for all endpoints",
description = "This endpoint returns the count of GET requests for each endpoint.")
public ResponseEntity<?> getAllEndpointLoads() {
if (!isEndpointEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
Map<String, Double> counts = new HashMap<>();
for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method");
if (method != null && method.equals("GET")) {
String uri = meter.getId().getTag("uri");
if (uri != null) {
double currentCount = counts.getOrDefault(uri, 0.0);
if (meter instanceof Counter) {
currentCount += ((Counter) meter).count();
}
counts.put(uri, currentCount);
}
}
}
}
List<EndpointCount> results = counts.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
return ResponseEntity.ok(results);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
public class EndpointCount {
private String endpoint;
private double count;
public EndpointCount(String endpoint, double count) {
this.endpoint = endpoint;
this.count = count;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public double getCount() {
return count;
}
public void setCount(double count) {
this.count = count;
}
}
@GetMapping("/requests")
@Operation(summary = "POST request count",
description = "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.")
public Double getTotalRequests(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
try {
Counter counter;
if (endpoint.isPresent() && !endpoint.get().isBlank()) {
if(!endpoint.get().startsWith("/")) {
endpoint = Optional.of("/" + endpoint.get());
}
System.out.println("loads " + endpoint.get() + " vs " + meterRegistry.get("http.requests").tags("uri", endpoint.get()).toString());
counter = meterRegistry.get("http.requests")
.tags("method", "POST", "uri", endpoint.get()).counter();
} else {
counter = meterRegistry.get("http.requests")
.tags("method", "POST").counter();
}
return counter.count();
} catch (Exception e) {
e.printStackTrace();
return 0.0;
public ResponseEntity<?> getTotalRequests(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional<String> endpoint) {
if (!isEndpointEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
double count = 0.0;
for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method");
if (method != null && method.equals("POST")) {
if (endpoint.isPresent() && !endpoint.get().isBlank()) {
if (!endpoint.get().startsWith("/")) {
endpoint = Optional.of("/" + endpoint.get());
}
if (endpoint.get().equals(meter.getId().getTag("uri"))) {
if (meter instanceof Counter) {
count += ((Counter) meter).count();
}
}
} else {
if (meter instanceof Counter) {
count += ((Counter) meter).count();
}
}
}
}
}
return ResponseEntity.ok(count);
} catch (Exception e) {
return ResponseEntity.ok(-1);
}
}
@GetMapping("/requests/all")
@Operation(summary = "POST requests count for all endpoints",
description = "This endpoint returns the count of POST requests for each endpoint.")
public ResponseEntity<?> getAllPostRequests() {
if (!isEndpointEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
try {
Map<String, Double> counts = new HashMap<>();
for (Meter meter : meterRegistry.getMeters()) {
if (meter.getId().getName().equals("http.requests")) {
String method = meter.getId().getTag("method");
if (method != null && method.equals("POST")) {
String uri = meter.getId().getTag("uri");
if (uri != null) {
double currentCount = counts.getOrDefault(uri, 0.0);
if (meter instanceof Counter) {
currentCount += ((Counter) meter).count();
}
counts.put(uri, currentCount);
}
}
}
}
List<EndpointCount> results = counts.entrySet().stream()
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
.collect(Collectors.toList());
return ResponseEntity.ok(results);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/uptime")
public ResponseEntity<?> getUptime() {
if (!isEndpointEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
}
LocalDateTime now = LocalDateTime.now();
Duration uptime = Duration.between(StartupApplicationListener.startTime, now);
return ResponseEntity.ok(formatDuration(uptime));
}
private String formatDuration(Duration duration) {
long days = duration.toDays();
long hours = duration.toHoursPart();
long minutes = duration.toMinutesPart();
long seconds = duration.toSecondsPart();
return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds);
}
}