#include "http_router.h"
#include "http_connection.h"
#include "http_private.h"
+#include "libserver/http_content_negotiation.h"
#include "libutil/regexp.h"
#include "libutil/printf.h"
#include "libserver/logger.h"
if (rspamd_substring_search(encoding->begin, encoding->len,
"gzip", 4) != -1) {
entry->support_gzip = TRUE;
- entry->compression_flags |= (1 << 0); /* RSPAMD_HTTP_COMPRESS_GZIP */
+ entry->compression_flags |= RSPAMD_HTTP_COMPRESS_GZIP;
}
if (rspamd_substring_search(encoding->begin, encoding->len,
"zstd", 4) != -1) {
- entry->compression_flags |= (1 << 1); /* RSPAMD_HTTP_COMPRESS_ZSTD */
+ entry->compression_flags |= RSPAMD_HTTP_COMPRESS_ZSTD;
}
if (rspamd_substring_search(encoding->begin, encoding->len,
"deflate", 7) != -1) {
- entry->compression_flags |= (1 << 2); /* RSPAMD_HTTP_COMPRESS_DEFLATE */
+ entry->compression_flags |= RSPAMD_HTTP_COMPRESS_DEFLATE;
}
}
#include <libutil.h>
#endif
#include "zlib.h"
+#ifdef SYS_ZSTD
+#include "zstd.h"
+#else
+#include "contrib/zstd/zstd.h"
+#endif
#ifdef HAVE_UCONTEXT_H
#include <ucontext.h>
rspamd_controller_maybe_compress(struct rspamd_http_connection_entry *entry,
rspamd_fstring_t *buf, struct rspamd_http_message *msg)
{
- if (entry->support_gzip) {
+ /* Prefer zstd over gzip if client supports it */
+ if (entry->compression_flags & RSPAMD_HTTP_COMPRESS_ZSTD) {
+ gsize compressed_size = ZSTD_compressBound(buf->len);
+ rspamd_fstring_t *compressed = rspamd_fstring_sized_new(compressed_size);
+
+ compressed->len = ZSTD_compress(compressed->str, compressed->allocated,
+ buf->str, buf->len, 1);
+
+ if (!ZSTD_isError(compressed->len) && compressed->len < buf->len) {
+ rspamd_fstring_free(buf);
+ rspamd_http_message_add_header(msg, CONTENT_ENCODING_HEADER, "zstd");
+ return compressed;
+ }
+
+ rspamd_fstring_free(compressed);
+ }
+ else if (entry->support_gzip) {
if (rspamd_fstring_gzip(&buf)) {
rspamd_http_message_add_header(msg, CONTENT_ENCODING_HEADER, "gzip");
}
--- /dev/null
+*** Settings ***
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Test Cases ***
+Metrics Default Content Type
+ [Documentation] Without Accept header, should return OpenMetrics format
+ @{result} = HTTP With Headers GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /metrics
+ Should Be Equal As Integers ${result}[0] 200
+ Should Contain ${result}[2][Content-Type] application/openmetrics-text
+ Should Contain ${result}[1].decode('utf-8') \# EOF
+
+Metrics With OpenMetrics Accept
+ [Documentation] With Accept: application/openmetrics-text, should return OpenMetrics
+ &{headers} = Create Dictionary Accept=application/openmetrics-text
+ @{result} = HTTP With Headers GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /metrics headers=${headers}
+ Should Be Equal As Integers ${result}[0] 200
+ Should Contain ${result}[2][Content-Type] application/openmetrics-text
+ Should Contain ${result}[1].decode('utf-8') \# EOF
+
+Metrics With Text Plain Accept
+ [Documentation] With Accept: text/plain, should return Prometheus 0.0.4 format
+ &{headers} = Create Dictionary Accept=text/plain
+ @{result} = HTTP With Headers GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /metrics headers=${headers}
+ Should Be Equal As Integers ${result}[0] 200
+ Should Contain ${result}[2][Content-Type] text/plain
+ Should Contain ${result}[1].decode('utf-8') \# EOF
+
+Metrics With Wildcard Accept
+ [Documentation] With Accept: */*, should return default (OpenMetrics)
+ &{headers} = Create Dictionary Accept=*/*
+ @{result} = HTTP With Headers GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /metrics headers=${headers}
+ Should Be Equal As Integers ${result}[0] 200
+ Should Contain ${result}[2][Content-Type] application/openmetrics-text
+
+Metrics With Quality Factor
+ [Documentation] Accept header with quality factors should prefer higher quality
+ &{headers} = Create Dictionary Accept=text/plain;q=0.9, application/openmetrics-text;q=1.0
+ @{result} = HTTP With Headers GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /metrics headers=${headers}
+ Should Be Equal As Integers ${result}[0] 200
+ Should Contain ${result}[2][Content-Type] application/openmetrics-text
+
+Metrics Fallback For Unknown Accept
+ [Documentation] With unsupported Accept type, should fallback to text/plain
+ &{headers} = Create Dictionary Accept=application/xml
+ @{result} = HTTP With Headers GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /metrics headers=${headers}
+ Should Be Equal As Integers ${result}[0] 200
+ Should Contain ${result}[2][Content-Type] text/plain
+
+Stat With Msgpack Accept
+ [Documentation] With Accept: application/msgpack, should return msgpack format
+ &{headers} = Create Dictionary Accept=application/msgpack
+ @{result} = HTTP With Headers GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /stat headers=${headers}
+ Should Be Equal As Integers ${result}[0] 200
+ Should Contain ${result}[2][Content-Type] application/msgpack
+
+Stat With JSON Accept
+ [Documentation] With Accept: application/json, should return JSON format
+ &{headers} = Create Dictionary Accept=application/json
+ @{result} = HTTP With Headers GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /stat headers=${headers}
+ Should Be Equal As Integers ${result}[0] 200
+ Should Contain ${result}[2][Content-Type] application/json
+
+Stat With Zstd Encoding
+ [Documentation] With Accept-Encoding: zstd, should return zstd compressed response
+ &{headers} = Create Dictionary Accept-Encoding=zstd
+ @{result} = HTTP With Headers GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /stat headers=${headers}
+ Should Be Equal As Integers ${result}[0] 200
+ Should Contain ${result}[2][Content-Encoding] zstd
+
+Stat With Gzip Encoding
+ [Documentation] With Accept-Encoding: gzip, should return gzip compressed response
+ &{headers} = Create Dictionary Accept-Encoding=gzip
+ @{result} = HTTP With Headers GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /stat headers=${headers}
+ Should Be Equal As Integers ${result}[0] 200
+ Should Contain ${result}[2][Content-Encoding] gzip
+
+Stat With Msgpack And Zstd
+ [Documentation] With Accept: msgpack and Accept-Encoding: zstd, should return compressed msgpack
+ &{headers} = Create Dictionary Accept=application/msgpack Accept-Encoding=zstd
+ @{result} = HTTP With Headers GET ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} /stat headers=${headers}
+ Should Be Equal As Integers ${result}[0] 200
+ Should Contain ${result}[2][Content-Type] application/msgpack
+ Should Contain ${result}[2][Content-Encoding] zstd