Class ChatApiHelper
Extracted common logic from ChatHandler
and ChatStreamHandler so it can be reused
across chat-related handlers without duplication. (Historically these utilities
were also used to mirror the v1 ChatApiManager behavior; the v1
implementation has since been extracted into the fess-webapp-v1-api
plugin.)
This is a DI-managed singleton helper (registered as chatApiHelper); obtain it via
ComponentUtil.getChatApiHelper() and call the instance methods.
Helpers in Fess are singletons accessed through ComponentUtil, not static utility
classes.
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionintgetMaxMessageLength(org.codelibs.fess.mylasta.direction.FessConfig fessConfig) Resolvesrag.chat.message.max.lengthfrom fess_config system properties, defaulting to4000on parse failure.Resolves the effective user identifier for a chat request.String[]Parses theextra_queriesarray (or scalar) from the raw request map.Parses thefieldsobject (nested structure) or legacyfields.labeldotted-key from a raw request map into the map shape accepted by ChatClient.parseRequestBody(Map<String, Object> raw, int maxMessageLength) Parses a raw v2 chat JSON body into a validatedChatRequestBody.resolveChatRateLimitKey(String username, Supplier<String> clientIpSupplier) Resolves a rate-limit key for the chat endpoints that an anonymous caller cannot rotate.toSourceMaps(List<ChatMessage.ChatSource> sources) Converts chat sources to the v2 API response shape.
-
Constructor Details
-
ChatApiHelper
public ChatApiHelper()Default constructor for ChatApiHelper.
-
-
Method Details
-
parseFieldFilters
public Map<String,String[]> parseFieldFilters(Map<String, Object> raw, Map<String, throws IOExceptionList<String>> warnings) Parses thefieldsobject (nested structure) or legacyfields.labeldotted-key from a raw request map into the map shape accepted by ChatClient.The v2 wire format accepts either:
"fields": {"label": "value"}— nested object (preferred v2 form)"fields": {"label": ["v1", "v2"]}— nested object with array
Values that are not present in the
LabelTypeHelperallowlist are dropped silently; rejected values are appended towarningsunder the key"fields.label".- Parameters:
raw- the raw request body mapwarnings- mutable map to collect rejected values (keyed by field name)- Returns:
- a map of validated field filters, or an empty map when none are present
- Throws:
IOException- iffields.labelexceeds the configured maximum array size or per-element length
-
parseExtraQueries
public String[] parseExtraQueries(Map<String, Object> raw, Map<String, throws IOExceptionList<String>> warnings) Parses theextra_queriesarray (or scalar) from the raw request map.Values are validated against the
ViewHelperfacet-query allowlist. Rejected values are appended towarningsunder the key"extra_queries".- Parameters:
raw- the raw request body mapwarnings- mutable map to collect rejected values (keyed by field name)- Returns:
- array of validated extra query strings; empty array when none present
- Throws:
IOException- ifextra_queriesexceeds the configured maximum array size or per-element length
-
parseRequestBody
public ChatRequestBody parseRequestBody(Map<String, Object> raw, int maxMessageLength) throws IOExceptionParses a raw v2 chat JSON body into a validatedChatRequestBody.- Parameters:
raw- the parsed JSON request bodymaxMessageLength- upper bound on themessagelength, in characters- Returns:
- a validated request body
- Throws:
ChatRequestBody.InvalidSessionIdException- ifsession_idexceeds 100 characters or contains invalid charactersChatRequestBody.MessageTooLongException- ifmessageexceedsmaxMessageLengthChatRequestBody.TooManyValuesException- ifextra_queriesorfields.labelexceeds the count or per-element length limitIOException- if validation reports an unrecoverable error
-
toSourceMaps
Converts chat sources to the v2 API response shape.- Parameters:
sources- list of chat sources to convert- Returns:
- list of snake_case maps, one per source
-
getUserId
Resolves the effective user identifier for a chat request.Mirrors the logic in v1
ChatApiManager#getUserId: prefer the authenticated username, fall back to the cookie-bound userCode for guests. This keeps chat usable for anonymous SPA visitors when login is not required.- Returns:
- the user identifier (never null)
-
resolveChatRateLimitKey
Resolves a rate-limit key for the chat endpoints that an anonymous caller cannot rotate.Unlike
getUserId()(which keys chat-session continuity and intentionally falls back to the cookie-bound, client-supplied userCode for guests), this key must be robust against abuse: the guest userCode is only validated by regex/length, so a malicious client could forge and rotate it per request to obtain a fresh throttle bucket and bypass the limit. Authenticated (non-guest) callers are keyed by their server-validated username ("u:"+username); all other callers (guest / anonymous / forged / rotated / blank userCode) are keyed by the proxy-aware client IP ("ip:"+clientIp), which an anonymous attacker cannot freely rotate. The client-IP supplier is evaluated lazily so the IP is only resolved for guest visitors.The returned key is namespaced (
"u:"for usernames,"ip:"for client IPs) so the two key spaces never collide, and is always non-blank so the chat throttle always applies.The client IP is resolved by the caller (the v2 chat handlers, via
RateLimitHelper#getClientIp) rather than here, so this helper carries no servlet-API dependency and stays loadable by non-web Fess processes (e.g. the crawler) whose DI container also instantiates it. Behind a reverse proxy the per-IP key is only as trustworthy as the proxy setup:rate.limit.trusted.proxiesgates which proxies'X-Forwarded-For/X-Real-IPheaders are honored, so the fronting proxy must be trusted and must sanitize any inbound forwarded headers for the per-IP key to resist rotation.- Parameters:
username- the authenticated username (may beConstants.GUEST_USER)clientIpSupplier- supplies the proxy-aware client IP for guest visitors- Returns:
- the namespaced chat rate-limit key (never null/blank)
-
getMaxMessageLength
public int getMaxMessageLength(org.codelibs.fess.mylasta.direction.FessConfig fessConfig) Resolvesrag.chat.message.max.lengthfrom fess_config system properties, defaulting to4000on parse failure.- Parameters:
fessConfig- active Fess config- Returns:
- max chat message length in characters
-