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

  使用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直接转换报错了。

原文地址:https://www.cnblogs.com/wuxun1997/p/8857560.html