Bound HTTP server reads

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
Daniel Garnier-Moiroux 2026-06-23 12:04:17 +02:00 • committed by Daniel Garnier-Moiroux
commit 6a41dedaa0
7 changed files with 619 additions and 38 deletions

View file

@ -681,6 +681,10 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
return Flux.<McpSchema.JSONRPCMessage>error(new McpTransportException(
"Bad Request. Status code:" + statusCode + ", response-event:" + responseEvent));
}
else if (statusCode >= 400 && statusCode < 500) {
return Flux.<McpSchema.JSONRPCMessage>error(
new McpTransportException("Invalid request. Status code: " + statusCode));
}
return Flux.<McpSchema.JSONRPCMessage>error(
new RuntimeException("Failed to send message: " + responseEvent));

View file

@ -4,6 +4,10 @@
package io.modelcontextprotocol.server.transport;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
@ -37,4 +41,32 @@ final class HttpServletRequestUtils {
return headers;
}
/**
* Reads the request body, decoded using the request's character encoding (or UTF-8 if
* not specified), while bounding the number of bytes read.
* @param request The HTTP servlet request
* @param maxSize The maximum number of bytes to read from the request body
* @return The decoded request body
* @throws MaxSizeExceededException If the body exceeds {@code maxSize}
* @throws IOException If an I/O error occurs while reading the request body
*/
static String readBody(HttpServletRequest request, int maxSize) throws MaxSizeExceededException, IOException {
InputStream inputStream = request.getInputStream();
ByteArrayOutputStream bodyBytes = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
int totalBytes = 0;
int readBytes;
while ((readBytes = inputStream.read(buf, 0, buf.length)) != -1) {
totalBytes += readBytes;
if (totalBytes > maxSize) {
throw new MaxSizeExceededException(
"Request body exceeds the maximum allowed size of " + maxSize + " bytes");
}
bodyBytes.write(buf, 0, readBytes);
}
String charset = request.getCharacterEncoding() != null ? request.getCharacterEncoding()
: StandardCharsets.UTF_8.name();
return bodyBytes.toString(charset);
}
}

View file

@ -4,7 +4,6 @@
package io.modelcontextprotocol.server.transport;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Duration;
@ -76,6 +75,11 @@ import reactor.core.publisher.Mono;
@WebServlet(asyncSupported = true)
public class HttpServletSseServerTransportProvider extends HttpServlet implements McpServerTransportProvider {
/**
* Default maximum size of a single request body: 16 MiB (16 * 1024 * 1024 bytes).
*/
private static final int DEFAULT_REQUEST_MAX_SIZE = 16 * 1024 * 1024;
/**
* Logger for this class
*/
@ -111,6 +115,11 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
*/
private final McpJsonMapper jsonMapper;
/**
* Maximum size, in bytes, of a single request body accepted by this transport.
*/
private final int requestMaxSize;
/**
* Base URL for the server transport
*/
@ -166,17 +175,20 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
* keep-alive functionality
* @param contextExtractor The extractor for transport context from the request.
* @param securityValidator The security validator for validating HTTP requests.
* @param requestMaxSize The maximum size, in bytes, of a single request body. Must be
* positive.
*/
private HttpServletSseServerTransportProvider(McpJsonMapper jsonMapper, String baseUrl, String messageEndpoint,
String sseEndpoint, Duration keepAliveInterval,
McpTransportContextExtractor<HttpServletRequest> contextExtractor,
ServerTransportSecurityValidator securityValidator) {
ServerTransportSecurityValidator securityValidator, int requestMaxSize) {
Assert.notNull(jsonMapper, "JsonMapper must not be null");
Assert.notNull(messageEndpoint, "messageEndpoint must not be null");
Assert.notNull(sseEndpoint, "sseEndpoint must not be null");
Assert.notNull(contextExtractor, "Context extractor must not be null");
Assert.notNull(securityValidator, "Security validator must not be null");
Assert.isTrue(requestMaxSize > 0, "requestMaxSize must be positive");
this.jsonMapper = jsonMapper;
this.baseUrl = baseUrl;
@ -184,6 +196,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
this.sseEndpoint = sseEndpoint;
this.contextExtractor = contextExtractor;
this.securityValidator = securityValidator;
this.requestMaxSize = requestMaxSize;
if (keepAliveInterval != null) {
@ -346,6 +359,11 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
return;
}
if (request.getContentLengthLong() > this.requestMaxSize) {
response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
return;
}
String requestURI = request.getRequestURI();
if (!requestURI.endsWith(messageEndpoint)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
@ -392,15 +410,10 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
}
try {
BufferedReader reader = request.getReader();
StringBuilder body = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
body.append(line);
}
String body = HttpServletRequestUtils.readBody(request, this.requestMaxSize);
final McpTransportContext transportContext = this.contextExtractor.extract(request);
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body.toString());
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body);
// Process the message through the session's handle method
// Block for Servlet compatibility
@ -408,6 +421,9 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
response.setStatus(HttpServletResponse.SC_OK);
}
catch (MaxSizeExceededException e) {
response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
}
catch (Exception e) {
logger.error("Error processing message: {}", e.getMessage());
try {
@ -605,6 +621,8 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
private ServerTransportSecurityValidator securityValidator = ServerTransportSecurityValidator.NOOP;
private int requestMaxSize = DEFAULT_REQUEST_MAX_SIZE;
/**
* Sets the JsonMapper implementation to use for serialization/deserialization. If
* not specified, a JacksonJsonMapper will be created from the configured
@ -691,6 +709,19 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
return this;
}
/**
* Sets the maximum size, in bytes, of a single request body accepted by this
* transport. Requests whose body exceeds this size are rejected with a 413
* (Payload Too Large) response. Defaults to 16 MiB if not set.
* @param requestMaxSize The maximum request body size, in bytes. Must be
* positive.
* @return This builder instance
*/
public Builder maxRequestSize(int requestMaxSize) {
this.requestMaxSize = requestMaxSize;
return this;
}
/**
* Builds a new instance of HttpServletSseServerTransportProvider with the
* configured settings.
@ -703,7 +734,7 @@ public class HttpServletSseServerTransportProvider extends HttpServlet implement
}
return new HttpServletSseServerTransportProvider(
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, baseUrl, messageEndpoint,
sseEndpoint, keepAliveInterval, contextExtractor, securityValidator);
sseEndpoint, keepAliveInterval, contextExtractor, securityValidator, requestMaxSize);
}
}

View file

@ -4,7 +4,6 @@
package io.modelcontextprotocol.server.transport;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@ -39,6 +38,11 @@ import reactor.core.publisher.Mono;
@WebServlet(asyncSupported = true)
public class HttpServletStatelessServerTransport extends HttpServlet implements McpStatelessServerTransport {
/**
* Default maximum size of a single request body: 16 MiB (16 * 1024 * 1024 bytes).
*/
private static final int DEFAULT_REQUEST_MAX_SIZE = 16 * 1024 * 1024;
private static final Logger logger = LoggerFactory.getLogger(HttpServletStatelessServerTransport.class);
public static final String UTF_8 = "UTF-8";
@ -66,18 +70,37 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
*/
private final ServerTransportSecurityValidator securityValidator;
/**
* Maximum size, in bytes, of a single request body accepted by this transport.
*/
private final int requestMaxSize;
/**
* Constructs a new HttpServletStatelessServerTransport instance.
* @param jsonMapper The JsonMapper to use for JSON serialization/deserialization of
* messages.
* @param mcpEndpoint The endpoint URI where clients should send their JSON-RPC
* messages.
* @param contextExtractor The extractor for transport context from the request.
* @param securityValidator The security validator for validating HTTP requests.
* @param requestMaxSize The maximum size, in bytes, of a single request body. Must be
* positive.
* @throws IllegalArgumentException if any parameter is null
*/
private HttpServletStatelessServerTransport(McpJsonMapper jsonMapper, String mcpEndpoint,
McpTransportContextExtractor<HttpServletRequest> contextExtractor,
ServerTransportSecurityValidator securityValidator) {
ServerTransportSecurityValidator securityValidator, int requestMaxSize) {
Assert.notNull(jsonMapper, "jsonMapper must not be null");
Assert.notNull(mcpEndpoint, "mcpEndpoint must not be null");
Assert.notNull(contextExtractor, "contextExtractor must not be null");
Assert.notNull(securityValidator, "Security validator must not be null");
Assert.isTrue(requestMaxSize > 0, "requestMaxSize must be positive");
this.jsonMapper = jsonMapper;
this.mcpEndpoint = mcpEndpoint;
this.contextExtractor = contextExtractor;
this.securityValidator = securityValidator;
this.requestMaxSize = requestMaxSize;
}
@Override
@ -133,6 +156,11 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
return;
}
if (request.getContentLengthLong() > this.requestMaxSize) {
response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
return;
}
try {
Map<String, List<String>> headers = HttpServletRequestUtils.extractHeaders(request);
this.securityValidator.validateHeaders(headers);
@ -154,14 +182,9 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
}
try {
BufferedReader reader = request.getReader();
StringBuilder body = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
body.append(line);
}
String body = HttpServletRequestUtils.readBody(request, this.requestMaxSize);
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body.toString());
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body);
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
try {
@ -209,6 +232,9 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
.build());
}
}
catch (MaxSizeExceededException e) {
response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
}
catch (IllegalArgumentException | IOException e) {
logger.error("Failed to deserialize message: {}", e.getMessage());
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
@ -276,6 +302,8 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
private ServerTransportSecurityValidator securityValidator = ServerTransportSecurityValidator.NOOP;
private int requestMaxSize = DEFAULT_REQUEST_MAX_SIZE;
private Builder() {
// used by a static method
}
@ -333,6 +361,19 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
return this;
}
/**
* Sets the maximum size, in bytes, of a single request body accepted by this
* transport. Requests whose body exceeds this size are rejected with a 413
* (Payload Too Large) response. Defaults to 16 MiB if not set.
* @param requestMaxSize The maximum request body size, in bytes. Must be
* positive.
* @return this builder instance
*/
public Builder maxRequestSize(int requestMaxSize) {
this.requestMaxSize = requestMaxSize;
return this;
}
/**
* Builds a new instance of {@link HttpServletStatelessServerTransport} with the
* configured settings.
@ -343,7 +384,7 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements
Assert.notNull(mcpEndpoint, "Message endpoint must be set");
return new HttpServletStatelessServerTransport(
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, mcpEndpoint, contextExtractor,
securityValidator);
securityValidator, requestMaxSize);
}
}

View file

@ -4,7 +4,6 @@
package io.modelcontextprotocol.server.transport;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Duration;
@ -14,12 +13,10 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.json.TypeRef;
import io.modelcontextprotocol.server.McpTransportContextExtractor;
import io.modelcontextprotocol.spec.HttpHeaders;
import io.modelcontextprotocol.spec.McpError;
@ -28,8 +25,6 @@ import io.modelcontextprotocol.spec.McpStreamableServerSession;
import io.modelcontextprotocol.spec.McpStreamableServerTransport;
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
import io.modelcontextprotocol.util.Assert;
import io.modelcontextprotocol.json.McpJsonDefaults;
import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.util.KeepAliveScheduler;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.ServletException;
@ -37,6 +32,8 @@ import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -62,6 +59,11 @@ import reactor.core.publisher.Mono;
public class HttpServletStreamableServerTransportProvider extends HttpServlet
implements McpStreamableServerTransportProvider {
/**
* Default maximum size of a single request body: 16 MiB (16 * 1024 * 1024 bytes).
*/
private static final int DEFAULT_REQUEST_MAX_SIZE = 16 * 1024 * 1024;
private static final Logger logger = LoggerFactory.getLogger(HttpServletStreamableServerTransportProvider.class);
/**
@ -100,6 +102,11 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
private final McpJsonMapper jsonMapper;
/**
* Maximum size, in bytes, of a single request body accepted by this transport.
*/
private final int requestMaxSize;
private McpStreamableServerSession.Factory sessionFactory;
/**
@ -136,21 +143,25 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
* @param keepAliveInterval The interval for keep-alive pings. If null, no keep-alive
* will be scheduled.
* @param securityValidator The security validator for validating HTTP requests.
* @param requestMaxSize The maximum size, in bytes, of a single request body. Must be
* positive.
* @throws IllegalArgumentException if any parameter is null
*/
private HttpServletStreamableServerTransportProvider(McpJsonMapper jsonMapper, String mcpEndpoint,
boolean disallowDelete, McpTransportContextExtractor<HttpServletRequest> contextExtractor,
Duration keepAliveInterval, ServerTransportSecurityValidator securityValidator) {
Duration keepAliveInterval, ServerTransportSecurityValidator securityValidator, int requestMaxSize) {
Assert.notNull(jsonMapper, "JsonMapper must not be null");
Assert.notNull(mcpEndpoint, "MCP endpoint must not be null");
Assert.notNull(contextExtractor, "Context extractor must not be null");
Assert.notNull(securityValidator, "Security validator must not be null");
Assert.isTrue(requestMaxSize > 0, "requestMaxSize must be positive");
this.jsonMapper = jsonMapper;
this.mcpEndpoint = mcpEndpoint;
this.disallowDelete = disallowDelete;
this.contextExtractor = contextExtractor;
this.securityValidator = securityValidator;
this.requestMaxSize = requestMaxSize;
if (keepAliveInterval != null) {
@ -397,6 +408,10 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Server is shutting down");
return;
}
if (request.getContentLengthLong() > this.requestMaxSize) {
response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
return;
}
try {
Map<String, List<String>> headers = HttpServletRequestUtils.extractHeaders(request);
@ -420,14 +435,9 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
McpTransportContext transportContext = this.contextExtractor.extract(request);
try {
BufferedReader reader = request.getReader();
StringBuilder body = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
body.append(line);
}
String body = HttpServletRequestUtils.readBody(request, this.requestMaxSize);
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body.toString());
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body);
// Handle initialization request
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest
@ -535,6 +545,9 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST).message("Unknown message type").build());
}
}
catch (MaxSizeExceededException e) {
response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
}
catch (IllegalArgumentException | IOException e) {
logger.error("Failed to deserialize message: {}", e.getMessage());
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
@ -830,6 +843,8 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
private ServerTransportSecurityValidator securityValidator = ServerTransportSecurityValidator.NOOP;
private int requestMaxSize = DEFAULT_REQUEST_MAX_SIZE;
/**
* Sets the JsonMapper to use for JSON serialization/deserialization of MCP
* messages.
@ -901,6 +916,19 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
return this;
}
/**
* Sets the maximum size, in bytes, of a single request body accepted by this
* transport. Requests whose body exceeds this size are rejected with a 413
* (Payload Too Large) response. Defaults to 16 MiB if not set.
* @param requestMaxSize The maximum request body size, in bytes. Must be
* positive.
* @return this builder instance
*/
public Builder maxRequestSize(int requestMaxSize) {
this.requestMaxSize = requestMaxSize;
return this;
}
/**
* Builds a new instance of {@link HttpServletStreamableServerTransportProvider}
* with the configured settings.
@ -911,7 +939,7 @@ public class HttpServletStreamableServerTransportProvider extends HttpServlet
Assert.notNull(this.mcpEndpoint, "MCP endpoint must be set");
return new HttpServletStreamableServerTransportProvider(
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, mcpEndpoint, disallowDelete,
contextExtractor, keepAliveInterval, securityValidator);
contextExtractor, keepAliveInterval, securityValidator, requestMaxSize);
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright 2026-2026 the original author or authors.
*/
package io.modelcontextprotocol.server.transport;
/**
* Thrown when reading an inbound message would exceed the configured maximum size.
*
* @author Daniel Garnier-Moiroux
*/
class MaxSizeExceededException extends Exception {
MaxSizeExceededException(String message) {
super(message);
}
}

View file

@ -0,0 +1,141 @@
/*
* Copyright 2026-2026 the original author or authors.
*/
package io.modelcontextprotocol.server.transport;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Daniel Garnier-Moiroux
*/
class HttpServletRequestUtilsTests {
@Test
void readsBodyWithinLimit() throws Exception {
HttpServletRequest request = requestWithBody("hello world", null);
String body = HttpServletRequestUtils.readBody(request, 1024);
assertThat(body).isEqualTo("hello world");
}
@Test
void readsEmptyBody() throws Exception {
HttpServletRequest request = requestWithBody("", null);
String body = HttpServletRequestUtils.readBody(request, 1024);
assertThat(body).isEmpty();
}
@Test
void allowsBodyExactlyAtLimit() throws Exception {
HttpServletRequest request = requestWithBody("12345", null);
String body = HttpServletRequestUtils.readBody(request, 5);
assertThat(body).isEqualTo("12345");
}
@Test
void rejectsBodyLargerThanLimit() throws Exception {
HttpServletRequest request = requestWithBody("123456", null);
assertThatThrownBy(() -> HttpServletRequestUtils.readBody(request, 5))
.isInstanceOf(MaxSizeExceededException.class);
}
@Test
void rejectsBodySpanningMultipleReadBuffers() throws Exception {
// larger than the internal 8192-byte read buffer, to exercise multiple loop
// iterations before the limit is exceeded
String largeBody = "a".repeat(9000);
HttpServletRequest request = requestWithBody(largeBody, null);
assertThatThrownBy(() -> HttpServletRequestUtils.readBody(request, 8500))
.isInstanceOf(MaxSizeExceededException.class);
}
@Test
void readsBodySpanningMultipleReadBuffersWithinLimit() throws Exception {
String largeBody = "a".repeat(9000);
HttpServletRequest request = requestWithBody(largeBody, null);
String body = HttpServletRequestUtils.readBody(request, 9000);
assertThat(body).isEqualTo(largeBody);
}
@Test
void defaultsToUtf8WhenCharacterEncodingIsMissing() throws Exception {
HttpServletRequest request = requestWithBody("héllo wörld", null);
String body = HttpServletRequestUtils.readBody(request, 1024);
assertThat(body).isEqualTo("héllo wörld");
}
@Test
void honorsRequestCharacterEncoding() throws Exception {
HttpServletRequest request = mock(HttpServletRequest.class);
byte[] bytes = "café".getBytes(StandardCharsets.ISO_8859_1);
when(request.getInputStream()).thenReturn(servletInputStream(bytes));
when(request.getCharacterEncoding()).thenReturn("ISO-8859-1");
String body = HttpServletRequestUtils.readBody(request, 1024);
assertThat(body).isEqualTo("café");
}
private static HttpServletRequest requestWithBody(String body, String characterEncoding) throws IOException {
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getInputStream()).thenReturn(servletInputStream(body.getBytes(StandardCharsets.UTF_8)));
when(request.getCharacterEncoding()).thenReturn(characterEncoding);
return request;
}
private static ServletInputStream servletInputStream(byte[] data) {
ByteArrayInputStream delegate = new ByteArrayInputStream(data);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return delegate.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() {
return delegate.read();
}
@Override
public int read(byte[] b, int off, int len) {
return delegate.read(b, off, len);
}
};
}
}