diff --git a/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java b/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java index dba85b66..46ee0366 100644 --- a/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java +++ b/mateclaw-server/src/main/java/vip/mate/MateClawApplication.java @@ -50,9 +50,45 @@ public class MateClawApplication { private volatile DbType resolvedDbType; public static void main(String[] args) { + configureHttpClientDefaults(); SpringApplication.run(MateClawApplication.class, args); } + /** + * Harden the JDK {@link java.net.http.HttpClient} defaults before any client + * (or the JDK's internal header-allowlist) is initialized. + * + * + * + *

Both are only set when the operator has not already provided an explicit + * {@code -D} override, so deliberate tuning is respected. + */ + private static void configureHttpClientDefaults() { + if (System.getProperty("jdk.httpclient.keepalive.timeout") == null) { + System.setProperty("jdk.httpclient.keepalive.timeout", "15"); + } + String allowRestricted = System.getProperty("jdk.httpclient.allowRestrictedHeaders"); + if (allowRestricted == null) { + System.setProperty("jdk.httpclient.allowRestrictedHeaders", "connection"); + } else if (!allowRestricted.toLowerCase().contains("connection")) { + System.setProperty("jdk.httpclient.allowRestrictedHeaders", allowRestricted + ",connection"); + } + } + /** * Detect the actual database type from the live DataSource so the * {@link PaginationInnerInterceptor} always uses the correct dialect, diff --git a/mateclaw-server/src/main/java/vip/mate/llm/chatmodel/OpenAiCompatibleChatModelBuilder.java b/mateclaw-server/src/main/java/vip/mate/llm/chatmodel/OpenAiCompatibleChatModelBuilder.java index 4700101b..977f26a0 100644 --- a/mateclaw-server/src/main/java/vip/mate/llm/chatmodel/OpenAiCompatibleChatModelBuilder.java +++ b/mateclaw-server/src/main/java/vip/mate/llm/chatmodel/OpenAiCompatibleChatModelBuilder.java @@ -350,6 +350,14 @@ public class OpenAiCompatibleChatModelBuilder implements ChatModelBuilder { private MultiValueMap buildOpenAiHeaders(Map kwargs) { LinkedMultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("User-Agent", "MateClaw/1.0"); + // Force a fresh TCP connection per request (no keep-alive pooling). Self-hosted + // OpenAI-compatible gateways frequently idle-close connections faster than the + // JDK HttpClient evicts them from its pool, so a reused-but-dead socket gets reset + // by the peer before any response byte arrives — "header parser received no bytes". + // This mirrors curl's one-connection-per-call behavior. "Connection" is a JDK + // restricted header, enabled at startup via jdk.httpclient.allowRestrictedHeaders. + // A user-supplied Connection header in kwargs.headers overrides this (set() below). + headers.add("Connection", "close"); Object headerObject = kwargs.get("headers"); if (headerObject instanceof Map headerMap) { headerMap.forEach((key, value) -> {