This is an automated email from the ASF dual-hosted git repository. tballison pushed a commit to branch TIKA-4809-stage-9 in repository https://gitbox.apache.org/repos/asf/tika.git
commit f236f33b528f84e0e589c2350b46ecd4cd7a95c2 Author: tallison <[email protected]> AuthorDate: Mon Aug 10 19:47:05 2026 -0400 TIKA-4809: Run /detect in a forked pipes worker --- .../ROOT/pages/using-tika/server/index.adoc | 17 +++++++++------ .../apache/tika/server/core/TikaServerProcess.java | 10 ++++----- .../server/core/resource/DetectorResource.java | 25 +++++++++++----------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/modules/ROOT/pages/using-tika/server/index.adoc b/docs/modules/ROOT/pages/using-tika/server/index.adoc index e979e6d926..181750c4b3 100644 --- a/docs/modules/ROOT/pages/using-tika/server/index.adoc +++ b/docs/modules/ROOT/pages/using-tika/server/index.adoc @@ -195,18 +195,21 @@ and parsing); selecting either without it causes the server to refuse to start. is a plain opt-in endpoint — enable it simply by listing it under `endpoints`. See <<_security_configuration,Security Configuration>>. -WARNING: `/language` and `/detect/stream` do their work *in the server's own JVM*, not in a -forked pipes worker. They are therefore outside the process isolation that protects `/tika`, -`/rmeta`, `/meta`, and `/unpack` — a crash or memory exhaustion takes the server with it -rather than one worker. +WARNING: `/language` does its work *in the server's own JVM*, not in a forked pipes worker. +It is therefore outside the process isolation that protects `/tika`, `/rmeta`, `/meta`, +`/detect/stream`, and `/unpack` — a crash or memory exhaustion takes the server with it rather than +one worker. `/language` caps detection at the first 100,000 characters, since accuracy saturates well before that. That bounds the CPU per request, but *not* the memory: the request body is read into the server's heap before the cap applies, and the server has no maximum request size. A caller can still exhaust the heap with a large enough body, or with enough concurrent ones. -Treat these endpoints as available only to trusted callers, the same as the rest of the -server — see xref:security.adoc[the security model]. If you do not need them, omit them from -`endpoints`. +Treat it as available only to trusted callers, the same as the rest of the server — see +xref:security.adoc[the security model]. If you do not need it, omit it from `endpoints`. + +NOTE: `/detect/stream` runs in a forked pipes worker like the parsing endpoints. Detection opens +containers — zip, OPC, POIFS — over caller-supplied bytes, so it gets the same isolation, +timeouts, and worker restart. It does not require `allowPipes`. == Error Responses diff --git a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java index f13773c2af..5cf042ae90 100644 --- a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java +++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java @@ -185,7 +185,7 @@ public class TikaServerProcess { PipesParsingHelper pipesParsingHelper = null; if (needsPipesParsingHelper(tikaServerConfig)) { pipesParsingHelper = initPipesParsingHelper(tikaServerConfig); - LOG.info("Pipes-based parsing enabled for /tika, /rmeta, /unpack, /meta, and /pipes endpoints"); + LOG.info("Pipes-based parsing enabled for /tika, /rmeta, /unpack, /meta, /detect, and /pipes endpoints"); } TikaResource tikaResource = new TikaResource(tikaLoader, serverStatus, pipesParsingHelper, @@ -457,9 +457,9 @@ public class TikaServerProcess { /** * Determines if the shared PipesParser (wrapped in PipesParsingHelper) is needed - * based on configured endpoints. It's needed when /tika, /rmeta, /unpack, /meta, or - * /pipes are enabled (either explicitly or by default) -- all five now share one - * parser. (Note: unlike the others, /pipes also requires allowPipes to actually + * based on configured endpoints. It's needed when /tika, /rmeta, /unpack, /meta, + * /detect, or /pipes are enabled (either explicitly or by default) -- all six now + * share one parser. (Note: unlike the others, /pipes also requires allowPipes to actually * start; if it's listed without allowPipes, loadCoreProviders will refuse to start * regardless of whether this method already triggered building the shared parser.) */ @@ -472,7 +472,7 @@ public class TikaServerProcess { } return endpoints.contains("tika") || endpoints.contains("rmeta") || endpoints.contains("unpack") || endpoints.contains("pipes") - || endpoints.contains("meta"); + || endpoints.contains("meta") || endpoints.contains("detect"); } /** diff --git a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/DetectorResource.java b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/DetectorResource.java index 1960c22323..65b096f276 100644 --- a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/DetectorResource.java +++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/DetectorResource.java @@ -18,6 +18,7 @@ package org.apache.tika.server.core.resource; import java.io.IOException; import java.io.InputStream; +import java.util.List; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.PUT; @@ -29,12 +30,12 @@ import jakarta.ws.rs.core.UriInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.tika.exception.TikaConfigException; import org.apache.tika.io.TikaInputStream; import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.mime.MediaType; import org.apache.tika.parser.ParseContext; +import org.apache.tika.pipes.api.ParseMode; import org.apache.tika.server.core.ServerStatus; @Path("/detect") @@ -62,20 +63,18 @@ public class DetectorResource { long taskId = serverStatus.start(ServerStatus.TASK.DETECT, filename); try (TikaInputStream tis = TikaInputStream.get(is)) { - return tikaResource - .getTikaLoader() - .loadDetectors() - .detect(tis, met, parseContext) - .toString(); - } catch (IOException | TikaConfigException e) { + tis.getPath(); // Spool to temp file for pipes-based parsing + // NO_PARSE: the child detects (and digests, if configured) without parsing. + // Detection still opens containers -- zip, OPC, POIFS -- over caller-supplied + // bytes, so it belongs in the forked worker for the same reason parsing does. + List<Metadata> metadataList = + tikaResource.parseWithPipes(tis, met, parseContext, ParseMode.NO_PARSE); + String detected = metadataList.isEmpty() + ? null : metadataList.get(0).get(Metadata.CONTENT_TYPE); + return detected == null ? MediaType.OCTET_STREAM.toString() : detected; + } catch (IOException e) { LOG.warn("Unable to detect MIME type for file. Reason: {} ({})", e.getMessage(), filename, e); return MediaType.OCTET_STREAM.toString(); - } catch (OutOfMemoryError e) { - LOG.error("OOM while detecting: ({})", filename, e); - throw e; - } catch (Throwable e) { - LOG.error("Exception while detecting: ({})", filename, e); - throw e; } finally { serverStatus.complete(taskId); }
