使用protobuf定义的接口api发起http请求报错,日志如下:
[2018-04-16 17:34:58] DEBUG AbstractPool:107 - server updated, node=10.211.95.79:80, server={ node: 10.211.95.79:80, hostname: 10.211.95.79, port: 80, status: 1, weight: 8, capacity: 128, breaker: { state :CLOSED, working: 0, delay: 15000000000, failureThreshold: [80/100, 0.8], successThreshold: [12/16, 0.75]}, version: 2497984700 } [2018-04-16 17:35:16] DEBUG EnvironmentInterceptor:33 - EnvironmentInterceptor.preHandle requesturl:http://127.0.0.1:8080/ms-search-war/ms.search.searchService/getSearchRank [2018-04-16 17:35:16] DEBUG EnvironmentInterceptor:34 - RequestHeads,User-Agent=Apache-HttpClient/4.1.1 (java 1.5),X-Identity-ID=15077870000,X-Auth-Token=123,X-Login-Type=2 [2018-04-16 17:35:16] ERROR ServletRequestParser:46 - IOException: request=/ms-search-war/ms.search.searchService/getSearchRank, ex=java.nio.charset.MalformedInputException: Input length = 1 [2018-04-16 17:35:16] DEBUG EnvironmentInterceptor:55 - 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 = { ' ', ' ' }; 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直接转换报错了。