protobuf接口调用报错:java.nio.charset.MalformedInputException: Input length = 1

时间:2023-03-08 23:16:10
protobuf接口调用报错:java.nio.charset.MalformedInputException: Input length = 1

  使用protobuf定义的接口api发起http请求报错,日志如下:

[-- ::] DEBUG AbstractPool: - server updated, node=10.211.95.79:, server={ node: 10.211.95.79:, hostname: 10.211.95.79, port: , status: , weight: , capacity: , breaker: { state :CLOSED, working: , delay: , failureThreshold: [/, 0.8], successThreshold: [/, 0.75]}, version:  }
[-- ::] DEBUG EnvironmentInterceptor: - EnvironmentInterceptor.preHandle requesturl:http://127.0.0.1:8080/ms-search-war/ms.search.searchService/getSearchRank
[-- ::] DEBUG EnvironmentInterceptor: - RequestHeads,User-Agent=Apache-HttpClient/4.1. (java 1.5),X-Identity-ID=,X-Auth-Token=,X-Login-Type=
[-- ::] ERROR ServletRequestParser: - IOException: request=/ms-search-war/ms.search.searchService/getSearchRank, ex=java.nio.charset.MalformedInputException: Input length =
[-- ::] DEBUG EnvironmentInterceptor: - EnvironmentInterceptor completed, requesturl= http://127.0.0.1:8080/ms-search-war/ms.search.searchService/getSearchRank and server delayTime = 141

  我们来看ServletRequestParser的报错代码行:

import java.io.IOException;
import java.math.BigDecimal;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
public class ServletRequestParser {
private static final Logger logger = LoggerFactory.getLogger(ServletRequestParser.class); public static Message parse(HttpServletRequest request, Message prototype) {
String requestMethod = request.getMethod();
Descriptors.Descriptor inputType = prototype.getDescriptorForType();
Message.Builder builder = prototype.newBuilderForType();
if (HttpMethod.POST.matches(requestMethod)) {
MediaType contentType = ContentType.BINARY;
try {
contentType = MediaType.valueOf(request.getContentType());
} catch (Exception ex) {
}
try {
if (ContentType.isProtobuf(contentType) || ContentType.isBinary(contentType)) {
return builder.mergeFrom(request.getInputStream()).build();
} else if (ContentType.isJson(contentType)) {
request.setCharacterEncoding("utf-8");
JsonFormat.parser().merge(request.getReader(), builder);
return builder.build();
} else {
logger.error("invalid content-type: {}", contentType);
}
} catch (InvalidProtocolBufferException ex) {
logger.error("InvalidProtocolBuffer: request={}, ex={}", request.getRequestURI(), ex);
} catch (IOException ex) {
logger.error("IOException: request={}, ex={}", request.getRequestURI(), ex);
}
} else if (HttpMethod.GET.matches(requestMethod)) {
for (Descriptors.FieldDescriptor field : inputType.getFields()) {
String[] values = request.getParameterValues(field.getName());
if (null != values && values.length > 0) {
if (!field.isRepeated()) {
Object o = parseFieldValue(field, values[0], builder);
if (null != o) builder.setField(field, o);
} else {
for (String value : values) {
Object o = parseFieldValue(field, value, builder);
if (null != o) builder.addRepeatedField(field, o);
}
}
}
}
return builder.build();
}
return null;
}

  这里调用了com.google.protobuf.util.JsonFormat的内部类Parser的merge方法进行转换时报错,因为异常只在这里抛出,所以只能怀疑获取Reader对象的这个request.getReader()方法,跟进代码:

/**
* Wrapper object for the Coyote request.
*
* @author Remy Maucherat
* @author Craig R. McClanahan
*/
public class Request implements org.apache.catalina.servlet4preview.http.HttpServletRequest {
/**
* Read the Reader wrapping the input stream for this Request. The
* default implementation wraps a <code>BufferedReader</code> around the
* servlet input stream returned by <code>createInputStream()</code>.
*
* @return a buffered reader for the request
* @exception IllegalStateException if <code>getInputStream()</code>
* has already been called for this request
* @exception IOException if an input/output error occurs
*/
@Override
public BufferedReader getReader() throws IOException { if (usingInputStream) {
throw new IllegalStateException
(sm.getString("coyoteRequest.getReader.ise"));
} usingReader = true;
inputBuffer.checkConverter();
if (reader == null) {
reader = new CoyoteReader(inputBuffer);
}
return reader; }

  这里调用的是catalina的Requst对象的getReader方法,返回的是一个BufferedReader对象,这里最终实例化出了一个CoyoteReader对象。

    /**
     * Reader.
     */
    protected CoyoteReader reader = new CoyoteReader(inputBuffer);

  我们来看下CoyoteReader对象:  

/**
* Coyote implementation of the buffered reader.
*
* @author Remy Maucherat
*/
public class CoyoteReader
extends BufferedReader { // -------------------------------------------------------------- Constants private static final char[] LINE_SEP = { '\r', '\n' };
private static final int MAX_LINE_LENGTH = 4096; // ----------------------------------------------------- Instance Variables protected InputBuffer ib; protected char[] lineBuffer = null; // ----------------------------------------------------------- Constructors public CoyoteReader(InputBuffer ib) {
super(ib, 1);
this.ib = ib;
} }

  再看下它里面的InputBuffer对象:

/**
* The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
* OutputBuffer, adapted to handle input instead of output. This allows
* complete recycling of the facade objects (the ServletInputStream and the
* BufferedReader).
*
* @author Remy Maucherat
*/
public class InputBuffer extends Reader
implements ByteChunk.ByteInputChannel, ApplicationBufferHandler { /**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(InputBuffer.class); private static final Log log = LogFactory.getLog(InputBuffer.class); public static final int DEFAULT_BUFFER_SIZE = 8 * 1024; // The buffer can be used for byte[] and char[] reading
// ( this is needed to support ServletInputStream and BufferedReader )
public final int INITIAL_STATE = 0;
public final int CHAR_STATE = 1;
public final int BYTE_STATE = 2; /**
* Encoder cache.
*/
private static final ConcurrentMap<Charset, SynchronizedStack<B2CConverter>> encoders = new ConcurrentHashMap<>(); // ----------------------------------------------------- Instance Variables /**
* The byte buffer.
*/
private ByteBuffer bb; /**
* The char buffer.
*/
private CharBuffer cb; /**
* State of the output buffer.
*/
private int state = 0; /**
* Flag which indicates if the input buffer is closed.
*/
private boolean closed = false; /**
* Encoding to use.
*/
private String enc; /**
* Current byte to char converter.
*/
protected B2CConverter conv; /**
* Associated Coyote request.
*/
private Request coyoteRequest; /**
* Buffer position.
*/
private int markPos = -1; /**
* Char buffer limit.
*/
private int readLimit; /**
* Buffer size.
*/
private final int size; // ----------------------------------------------------------- Constructors /**
* Default constructor. Allocate the buffer with the default buffer size.
*/
public InputBuffer() { this(DEFAULT_BUFFER_SIZE); } /**
* Alternate constructor which allows specifying the initial buffer size.
*
* @param size Buffer size to use
*/
public InputBuffer(int size) { this.size = size;
bb = ByteBuffer.allocate(size);
clear(bb);
cb = CharBuffer.allocate(size);
clear(cb);
readLimit = size; } }

  通过调试发现,当我的请求里没有中文时,CoyoteReader对象的InputBuffer属性的ByteBuffer的hb属性是能取到请求消息体的,而包含了中文则无法获取,protobuf直接转换报错了。