mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-03-04 02:20:19 +01:00
large query reduction (#5754)
# Description of Changes Reduce endpoint-availability call so that an empty param to it returns all endpoints to avoid pointlessly large http headers Before: GET /api/v1/config/endpoints-availability?endpoints=compress-pdf%2Crotate-pdf%2Cmerge-pdfs%2Csplit-pages%2Cocr-pdf for all 74 tools After: GET /api/v1/config/endpoints-availability --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
@@ -606,6 +606,12 @@ public class EndpointConfiguration {
|
||||
return endpointGroups.getOrDefault(group, new HashSet<>());
|
||||
}
|
||||
|
||||
public Set<String> getAllEndpoints() {
|
||||
return endpointGroups.values().stream()
|
||||
.flatMap(Set::stream)
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
}
|
||||
|
||||
private boolean isToolGroup(String group) {
|
||||
return "qpdf".equals(group)
|
||||
|| "OCRmyPDF".equals(group)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -11,9 +12,6 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.config.EndpointConfiguration;
|
||||
@@ -320,11 +318,13 @@ public class ConfigController {
|
||||
|
||||
@GetMapping("/endpoints-availability")
|
||||
public ResponseEntity<Map<String, EndpointAvailability>> getEndpointAvailability(
|
||||
@RequestParam(name = "endpoints")
|
||||
@Size(min = 1, max = 100, message = "Must provide between 1 and 100 endpoints")
|
||||
List<@NotBlank String> endpoints) {
|
||||
@RequestParam(name = "endpoints", required = false) List<String> endpoints) {
|
||||
Collection<String> toCheck =
|
||||
(endpoints == null || endpoints.isEmpty())
|
||||
? endpointConfiguration.getAllEndpoints()
|
||||
: endpoints;
|
||||
Map<String, EndpointAvailability> result = new HashMap<>();
|
||||
for (String endpoint : endpoints) {
|
||||
for (String endpoint : toCheck) {
|
||||
String trimmedEndpoint = endpoint.trim();
|
||||
result.put(
|
||||
trimmedEndpoint,
|
||||
|
||||
@@ -38,6 +38,7 @@ spring.devtools.livereload.enabled=true
|
||||
spring.devtools.restart.exclude=stirling.software.proprietary.security/**
|
||||
spring.web.resources.mime-mappings.webmanifest=application/manifest+json
|
||||
spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000}
|
||||
server.tomcat.max-http-header-size=32768
|
||||
|
||||
spring.datasource.url=jdbc:h2:file:./configs/stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL
|
||||
spring.datasource.driver-class-name=org.h2.Driver
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useState, useEffect } from 'react';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
import type { EndpointAvailabilityDetails } from '@app/types/endpointAvailability';
|
||||
|
||||
// Track globally fetched endpoint sets to prevent duplicate fetches across components
|
||||
const globalFetchedSets = new Set<string>();
|
||||
// Track whether we've done the global fetch to prevent duplicate requests
|
||||
let globalFetchDone = false;
|
||||
const globalEndpointCache: Record<string, EndpointAvailabilityDetails> = {};
|
||||
|
||||
/**
|
||||
@@ -72,17 +72,17 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchAllEndpointStatuses = async (force = false) => {
|
||||
const endpointsKey = [...endpoints].sort().join(',');
|
||||
|
||||
// Skip if we already fetched these exact endpoints globally
|
||||
if (!force && globalFetchedSets.has(endpointsKey)) {
|
||||
console.debug('[useEndpointConfig] Already fetched these endpoints globally, using cache');
|
||||
// Skip if already fetched globally and not forced
|
||||
if (!force && globalFetchDone) {
|
||||
console.debug('[useEndpointConfig] Using global cache');
|
||||
const cached = endpoints.reduce(
|
||||
(acc, endpoint) => {
|
||||
const cachedDetails = globalEndpointCache[endpoint];
|
||||
if (cachedDetails) {
|
||||
acc.status[endpoint] = cachedDetails.enabled;
|
||||
acc.details[endpoint] = cachedDetails;
|
||||
} else {
|
||||
acc.status[endpoint] = true;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
@@ -93,6 +93,7 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!endpoints || endpoints.length === 0) {
|
||||
setEndpointStatus({});
|
||||
setEndpointDetails({});
|
||||
@@ -103,45 +104,21 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
console.debug('[useEndpointConfig] Fetching endpoint statuses', { count: endpoints.length, force });
|
||||
console.debug('[useEndpointConfig] Fetching all endpoint statuses from server');
|
||||
|
||||
// Check which endpoints we haven't fetched yet
|
||||
const newEndpoints = endpoints.filter(ep => !(ep in globalEndpointCache));
|
||||
if (newEndpoints.length === 0) {
|
||||
console.debug('[useEndpointConfig] All endpoints already in global cache');
|
||||
const cached = endpoints.reduce(
|
||||
(acc, endpoint) => {
|
||||
const cachedDetails = globalEndpointCache[endpoint];
|
||||
if (cachedDetails) {
|
||||
acc.status[endpoint] = cachedDetails.enabled;
|
||||
acc.details[endpoint] = cachedDetails;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ status: {} as Record<string, boolean>, details: {} as Record<string, EndpointAvailabilityDetails> }
|
||||
);
|
||||
setEndpointStatus(cached.status);
|
||||
setEndpointDetails(prev => ({ ...prev, ...cached.details }));
|
||||
globalFetchedSets.add(endpointsKey);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
// Fetch all endpoints at once - no query params needed
|
||||
const response = await apiClient.get<Record<string, EndpointAvailabilityDetails>>(`/api/v1/config/endpoints-availability`);
|
||||
|
||||
// Use batch API for efficiency - only fetch new endpoints
|
||||
const endpointsParam = newEndpoints.join(',');
|
||||
|
||||
const response = await apiClient.get<Record<string, EndpointAvailabilityDetails>>(`/api/v1/config/endpoints-availability?endpoints=${encodeURIComponent(endpointsParam)}`);
|
||||
const statusMap = response.data;
|
||||
|
||||
// Update global cache with new results
|
||||
Object.entries(statusMap).forEach(([endpoint, details]) => {
|
||||
// Populate global cache with all results
|
||||
Object.entries(response.data).forEach(([endpoint, details]) => {
|
||||
globalEndpointCache[endpoint] = {
|
||||
enabled: details?.enabled ?? true,
|
||||
reason: details?.reason ?? null,
|
||||
};
|
||||
});
|
||||
globalFetchDone = true;
|
||||
|
||||
// Get all requested endpoints from cache (including previously cached ones)
|
||||
// Return status for the requested endpoints
|
||||
const fullStatus = endpoints.reduce(
|
||||
(acc, endpoint) => {
|
||||
const cachedDetails = globalEndpointCache[endpoint];
|
||||
@@ -158,17 +135,17 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
|
||||
setEndpointStatus(fullStatus.status);
|
||||
setEndpointDetails(prev => ({ ...prev, ...fullStatus.details }));
|
||||
globalFetchedSets.add(endpointsKey);
|
||||
} catch (err: any) {
|
||||
// On 401 (auth error), use optimistic fallback instead of disabling
|
||||
if (err.response?.status === 401) {
|
||||
console.warn('[useEndpointConfig] 401 error - using optimistic fallback');
|
||||
endpoints.forEach(endpoint => {
|
||||
globalEndpointCache[endpoint] = { enabled: true, reason: null };
|
||||
});
|
||||
const optimisticStatus = endpoints.reduce(
|
||||
(acc, endpoint) => {
|
||||
const optimisticDetails: EndpointAvailabilityDetails = { enabled: true, reason: null };
|
||||
acc.status[endpoint] = true;
|
||||
acc.details[endpoint] = optimisticDetails;
|
||||
globalEndpointCache[endpoint] = optimisticDetails;
|
||||
acc.details[endpoint] = { enabled: true, reason: null };
|
||||
return acc;
|
||||
},
|
||||
{ status: {} as Record<string, boolean>, details: {} as Record<string, EndpointAvailabilityDetails> }
|
||||
@@ -181,14 +158,13 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
|
||||
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
||||
setError(errorMessage);
|
||||
console.error('[EndpointConfig] Failed to check multiple endpoints:', err);
|
||||
console.error('[EndpointConfig] Failed to check endpoints:', err);
|
||||
|
||||
// Fallback: assume all endpoints are enabled on error (optimistic)
|
||||
const optimisticStatus = endpoints.reduce(
|
||||
(acc, endpoint) => {
|
||||
const optimisticDetails: EndpointAvailabilityDetails = { enabled: true, reason: null };
|
||||
acc.status[endpoint] = true;
|
||||
acc.details[endpoint] = optimisticDetails;
|
||||
acc.details[endpoint] = { enabled: true, reason: null };
|
||||
return acc;
|
||||
},
|
||||
{ status: {} as Record<string, boolean>, details: {} as Record<string, EndpointAvailabilityDetails> }
|
||||
@@ -208,8 +184,7 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
useEffect(() => {
|
||||
const handleJwtAvailable = () => {
|
||||
console.debug('[useEndpointConfig] JWT available event - clearing cache for refetch with auth');
|
||||
// Clear the global cache to allow refetch with JWT
|
||||
globalFetchedSets.clear();
|
||||
globalFetchDone = false;
|
||||
Object.keys(globalEndpointCache).forEach(key => delete globalEndpointCache[key]);
|
||||
fetchAllEndpointStatuses(true);
|
||||
};
|
||||
|
||||
@@ -190,10 +190,8 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
try {
|
||||
setError(null);
|
||||
|
||||
const endpointsParam = endpoints.join(',');
|
||||
|
||||
const response = await apiClient.get<Record<string, EndpointAvailabilityDetails>>(
|
||||
`/api/v1/config/endpoints-availability?endpoints=${encodeURIComponent(endpointsParam)}`,
|
||||
`/api/v1/config/endpoints-availability`,
|
||||
{
|
||||
suppressErrorToast: true,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user