mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(llm): avoid stale-connection resets on OpenAI-compatible endpoints
This commit is contained in:
parent
c5805016a3
commit
41a518de93
@ -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.
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>keep-alive timeout</b> — the JDK default is 1200s, far longer than a
|
||||
* typical reverse proxy / API gateway idle window (often 15–75s). A pooled
|
||||
* HTTP/1.1 connection therefore outlives the peer's socket, and the next
|
||||
* request onto that now-closed socket is reset by the peer before any
|
||||
* response byte arrives, surfacing as
|
||||
* {@code "HTTP/1.1 header parser received no bytes"} / {@code Connection reset}.
|
||||
* Capping it to 15s makes the client evict idle connections before most
|
||||
* gateways do, eliminating stale reuse. (curl never hits this because it
|
||||
* opens a fresh connection per invocation.)</li>
|
||||
* <li><b>allow the {@code Connection} request header</b> — {@code Connection}
|
||||
* is a restricted header the JDK client strips by default; allowing it lets
|
||||
* the OpenAI-compatible path send {@code Connection: close} to force a fresh
|
||||
* connection per request against flaky self-hosted gateways.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>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,
|
||||
|
||||
@ -350,6 +350,14 @@ public class OpenAiCompatibleChatModelBuilder implements ChatModelBuilder {
|
||||
private MultiValueMap<String, String> buildOpenAiHeaders(Map<String, Object> kwargs) {
|
||||
LinkedMultiValueMap<String, String> 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) -> {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user