如何使用Jetty HttpClient流式解析JSON与UrlEncoded响应?
Great question! Ditching BufferingResponseListener for streaming parsing is a smart move—especially for high-throughput systems or large payloads—since it eliminates loading entire responses into memory at once. Let’s walk through how to implement this with Jetty’s built-in tools, tailored to your ADM messaging class.
Why Streaming is Better Than Buffering
BufferingResponseListener stores the full response body in a byte array before parsing, which can lead to unnecessary memory overhead. Streaming parsing processes the response incrementally as data arrives, keeping memory usage low and improving scalability.
1. Streaming UrlEncoded Responses (Token Request)
Jetty’s UrlEncoded class has native support for streaming parsing via decodeTo(InputStream, Charset, MultiMap). We can use Response.Listener.Adapter to access the response input stream directly without buffering.
Modified Token Listener
Replace your mTokenListener with this implementation:
private final Response.Listener mTokenListener = new Response.Listener.Adapter() { @Override public void onSuccess(Response response) { try (InputStream inputStream = response.getInputStream()) { MultiMap<String> params = new MultiMap<>(); // Stream the response directly into the MultiMap UrlEncoded.decodeTo(inputStream, StandardCharsets.UTF_8, params); long now = System.currentTimeMillis() / 1000; mExpiresIn = now + Long.parseLong(params.getString("expires_in")); mAccessToken = params.getString("access_token"); } catch (IOException e) { // Handle parsing/stream errors appropriately (log, alert, etc.) e.printStackTrace(); } } @Override public void onFailure(Response response, Throwable failure) { // Handle request failures (network issues, HTTP errors) failure.printStackTrace(); } };
Key benefits here:
- No intermediate byte buffer for the full response
UrlEncoded.decodeToparses key-value pairs as they’re read from the streamtry-with-resourcesensures the input stream is closed properly
2. Streaming JSON Responses (Message Send Results)
Jetty’s JSON class offers parse(InputStream), which reads from a stream instead of a pre-buffered string. Note: While this uses an input stream, Jetty’s underlying JSON parser still loads the entire document into memory to build the map. For true streaming JSON parsing (for very large payloads), we’ll also cover a third-party option with Jackson.
Option A: Jetty’s Built-in JSON Parsing
Replace your mMessageListener with this:
private final Response.Listener mMessageListener = new Response.Listener.Adapter() { @Override public void onSuccess(Response response) { try (InputStream inputStream = response.getInputStream()) { // Parse directly from the response stream Map<String, Object> resp = (Map<String, Object>) JSON.parse(inputStream); String oldRegistrationId = (String) response.getRequest().getAttributes().get("registrationID"); String newRegistrationId = (String) resp.get("registrationID"); if (newRegistrationId != null && !newRegistrationId.equals(oldRegistrationId)) { // Update the changed ADM registration id in the database } } catch (IOException e) { e.printStackTrace(); } } @Override public void onFailure(Response response, Throwable failure) { if (response.getStatus() % 100 == 4) { try (InputStream inputStream = response.getInputStream()) { Map<String, Object> resp = (Map<String, Object>) JSON.parse(inputStream); String reason = (String) resp.get("reason"); if ("AccessTokenExpired".equals(reason)) { postToken(); } else if ("Unregistered".equals(reason)) { // Delete the invalid ADM registration id from the database } } catch (IOException e) { e.printStackTrace(); } } failure.printStackTrace(); } };
Option B: True Streaming JSON with Jackson (For Large Payloads)
If you’re dealing with large JSON responses and want minimal memory usage, use Jackson’s streaming API. First add Jackson’s core dependency to your project, then modify the parsing logic:
// Add these imports: // com.fasterxml.jackson.core.JsonParser; // com.fasterxml.jackson.core.JsonToken; // com.fasterxml.jackson.databind.ObjectMapper; private final Response.Listener mMessageListener = new Response.Listener.Adapter() { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public void onSuccess(Response response) { try (InputStream inputStream = response.getInputStream()) { JsonParser parser = objectMapper.getFactory().createParser(inputStream); String oldRegistrationId = (String) response.getRequest().getAttributes().get("registrationID"); String newRegistrationId = null; // Iterate through JSON tokens incrementally while (parser.nextToken() != JsonToken.END_OBJECT) { String fieldName = parser.getCurrentName(); if ("registrationID".equals(fieldName)) { parser.nextToken(); // Move to the field value newRegistrationId = parser.getText(); break; // No need to parse the rest of the document } parser.skipChildren(); // Skip nested objects/arrays we don't care about } if (newRegistrationId != null && !newRegistrationId.equals(oldRegistrationId)) { // Update the database } } catch (IOException e) { e.printStackTrace(); } } // Implement onFailure similarly with streaming parsing for error responses };
This approach only parses the specific fields you need, avoiding loading the entire JSON document into memory.
Final Notes
- Always use
try-with-resourcesto close input streams and prevent resource leaks. - For your ADM use case, the Jetty-native streaming methods will likely be sufficient unless you’re handling extremely large responses.
- When switching listeners, ensure you update the
send()calls inpostToken()andpostMessage()—they’ll work seamlessly with the newResponse.Listenerimplementations.
内容的提问来源于stack exchange,提问作者Alexander Farber

