Repository navigation
fix: restore response compression for VS Code Gallery API JSON responses - #2073
Merged
Merged
Conversation
…ression working Real VS Code clients rely on the Gallery API's response Content-Type carrying an api-version media type parameter, e.g. application/json;api-version=3.0-preview.1, matching the real VS Code Marketplace. Spring's content negotiation already gets most of the way there on its own: a requested media type with more parameters is considered "more specific" than the plain type declared via produces, so it echoes the client's Accept-header parameter back verbatim. Relying on that echo is fragile in two ways: it depends entirely on whatever the client happened to send (or omitted), and it makes the response Content-Type client-controlled, which breaks compression. Jetty 12.1's new CompressionHandler (pulled in by the Spring Boot 4 migration) matches Content-Type against server.compression.mime-types with an exact string comparison after stripping only the charset parameter - any other parameter left in place, like an arbitrary echoed api-version, fails that match and silently disables compression for these responses. There is no way to enumerate every value a client might send, so compression could never reliably apply to them. Add VSCodeGalleryContentTypeAdvice, a ResponseBodyAdvice scoped to VSCodeAPI that always stamps the server's own, fixed IVSCodeService.GALLERY_API_VERSION onto JSON Gallery responses, regardless of what the client asked for. That keeps the Content-Type a single known literal - application/json;api-version=3.0-preview.1 - which needs to be added to server.compression.mime-types alongside application/json for compression to apply to these responses. Also promotes the previously VSCodeIdService-private API_VERSION constant to IVSCodeService.GALLERY_API_VERSION, shared between the outgoing upstream requests and this server's own Gallery responses. Fixes #2071 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MockMvc-based tests never touch the actual Jetty handler chain that performs response compression, so they can assert the Content-Type header is correct but can't prove compression itself still works. Add VSCodeGalleryCompressionTest, which boots a real embedded Jetty server via JettyServletWebServerFactory - the same class Spring Boot 4 wires up server.compression through - and shows: - with only "application/json" configured (the Spring Boot 3 / Jetty 12.0 setup), a response whose Content-Type is "application/json;api-version=3.0-preview.1" (what VSCodeGalleryContentTypeAdvice now stamps) is NOT compressed; - adding that exact literal to the configured mime types restores compression, and the resulting body is genuinely valid gzip that decodes back to the original JSON. Refs #2071 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…g changes VSCodeGalleryContentTypeAdvice needs application/json;api-version=3.0-preview.1 added to server.compression.mime-types for Jetty 12.1's CompressionHandler to compress those responses (exact string match, only charset stripped). That requires every deployment's config to know about a VS Code Gallery API implementation detail. Add VSCodeGalleryCompressionConfig instead: a WebServerFactoryCustomizer<ConfigurableJettyWebServerFactory> that, once spring-boot-jetty has installed its CompressionHandler from whatever server.compression.* is configured, reads back that installed CompressionConfig and replaces it with an equivalent one that additionally includes the Gallery API's fixed Content-Type - so compression works for it regardless of server.compression.mime-types. If compression is disabled, no CompressionHandler is installed and this is a no-op. This trades a config-only fix for a code-only one: it adds a compile-time dependency on jetty-compression-server (otherwise only a transitive runtime dependency of spring-boot-starter-jetty) and reaches into Jetty-specific classes. Verified end-to-end against a real embedded Jetty server, and against the full application context booted on Jetty (IntegrationTest). Refs #2071 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…path It unconditionally imported Jetty-specific classes (org.eclipse.jetty.compression.server.CompressionHandler/CompressionConfig, org.springframework.boot.jetty.ConfigurableJettyWebServerFactory), so a hypothetical deployment without spring-boot-starter-jetty (Tomcat, Undertow, or a test slice that doesn't pull it in) would fail to start with a NoClassDefFoundError merely loading this configuration class, rather than gracefully doing nothing. Add @ConditionalOnClass on both types this class touches, matching the project's own convention (see JedisClusterBucket4jConfiguration). Add VSCodeGalleryCompressionConfigTest, using ApplicationContextRunner + FilteredClassLoader, to prove the guard actually skips the configuration when Jetty's compression support isn't on the classpath, and still registers it when it is. Refs #2071 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cstamas
approved these changes
Aug 18, 2026
…pressed VSCodeGalleryCompressionTest already proves the compression mechanism works, but against a synthetic servlet, not the real application. Add VSCodeGalleryExtensionQueryCompressionIntegrationTest: boots the full app (real embedded Jetty, real VSCodeGalleryContentTypeAdvice and VSCodeGalleryCompressionConfig wiring, real database-backed search) and sends an actual HTTP request to /vscode/gallery/extensionquery with server.compression.mime-types deliberately left at the bare application/json a Spring Boot 3-era config would have had. Confirms the response comes back Content-Encoding: gzip and genuinely decodes to valid JSON, plus a sanity check that omitting Accept-Encoding correctly gets an uncompressed response. Refs #2071 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fixes #2071: response compression silently stopped applying to VS Code Gallery API JSON responses (e.g.
/vscode/gallery/extensionquery) after the Spring Boot 4 migration.Root cause
produces, so a client sendingAccept: application/json;api-version=3.0-preview.1got that parameter echoed back verbatim as the responseContent-Type. Unchanged behavior between Spring Framework 6.2 and 7 - verified by decompilingAbstractMessageConverterMethodProcessor/MimeTypefrom both versions.GzipHandlerwith a newCompressionHandler/CompressionConfigAPI. The oldGzipHandlerstripped allContent-Typeparameters before matching againstserver.compression.mime-types; the newCompressionHandleronly stripscharset(MimeTypes.getContentTypeWithoutCharset), then does an exact string match. Any other parameter left in place - like an echoedapi-version- breaks the match and compression is silently skipped.api-versionin its Gallery API responseContent-Type, so simply stripping the parameter (my first attempt) would have broken client compatibility.Fix
VSCodeGalleryContentTypeAdvice- aResponseBodyAdvicescoped toVSCodeAPIthat always stamps the server's own fixedIVSCodeService.GALLERY_API_VERSION(3.0-preview.1, promoted out ofVSCodeIdServicewhere it was private) onto Gallery JSON responses, regardless of what the client'sAcceptheader asked for (or omitted). This matches the real Marketplace's behavior and turns theContent-Typeback into a single known literal instead of a client-controlled value.VSCodeGalleryCompressionConfig- rather than requiring every deployment to add that literal toserver.compression.mime-types, this is aWebServerFactoryCustomizer<ConfigurableJettyWebServerFactory>that reads back theCompressionConfigspring-boot-jettyalready installed fromserver.compression.*and replaces it with an equivalent one that additionally includes the Gallery API's fixed Content-Type. Compression works regardless of configuration; a no-op if compression is disabled. Guarded by@ConditionalOnClassso a hypothetical non-Jetty deployment isn't affected.Testing
VSCodeGalleryContentTypeAdviceTest/VSCodeAPITest- unit +MockMvccoverage that the fixedapi-versionis stamped regardless of the client'sAcceptheader.VSCodeGalleryCompressionTest- boots a real embedded Jetty server (MockMvc never touches the actual compression handler chain) and proves: (1) the bareapplication/jsonentry alone no longer compresses the versioned Content-Type (reproducing the bug against real Jetty), (2) adding the literal restores compression, and (3) theVSCodeGalleryCompressionConfigcustomizer alone restores compression with the literal still absent from configured mime types - the body is gunzip-decoded and checked against the original.VSCodeGalleryCompressionConfigTest-ApplicationContextRunner+FilteredClassLoaderproving the@ConditionalOnClassguard actually skips the configuration when Jetty's compression support isn't on the classpath, and registers it when it is.IntegrationTest(real app context, real embedded Jetty, Postgres via Testcontainers) to confirm the new bean wires in cleanly.🤖 Generated with Claude Code