Crash-fix-2:org.springframework.http.converter.HttpMessageNotReadableException

Keywords: JSON Google REST PHP

Recently, Crash on APP has been started to correspond, and many common problems have been found. The same problem appears in many APPs. Here are some common errors.
crash Log:

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: com.google.gson.stream.MalformedJsonException: Unterminated string at line 1 column 425727 path $.shops[631].lineup; nested exception is com.google.gson.ad: com.google.gson.stream.MalformedJsonException: Unterminated string at line 1 column 425727 path $.shops[631].lineup
    at Package name.k.readInternal(SourceFile:75)
    at org.springframework.http.converter.AbstractHttpMessageConverter.read(SourceFile:147)
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(SourceFile:76)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(SourceFile:655)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(SourceFile:641)
    at org.springframework.web.client.RestTemplate.doExecute(SourceFile:484)
    at org.springframework.web.client.RestTemplate.execute(SourceFile:439)
    at org.springframework.web.client.RestTemplate.exchange(SourceFile:415)

According to the meaning of error log, it should be that the server (php development) returned abnormal json format error information, which caused app crash.

Project Background:
The project was developed using the AA framework, and the Api request was made using Spring RestTemplate, using Gson to transform json to Bean.

To solve Gson's bug on Android 6.0, a Gson Converter is customized and inherited from Gson HttpMessageConverter. log is added in data conversion, the main code is as follows:

 @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        String str = FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), getCharset(inputMessage.getHeaders())));
        LogUtil.d("in =" + str);
        try {
            Type typeOfT = getType();

            if (typeOfT != null) {
                return this.gson.fromJson(str, typeOfT);
            } else {
                return this.gson.fromJson(str, clazz);
            }
        } catch (JsonSyntaxException ex) {
            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        } catch (JsonIOException ex) {
            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        } catch (Exception ex) {
            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        }
    }

Then there's Rest configuration in AA, configuring the custom GsonConverter to Rest.
RestErrorHandler is set up in each request. Simple logs output data without business logic.

mClient.setRestErrorHandler(handler);
  @Override
    public void onRestClientExceptionThrown(RestClientException e) {
        LogUtil.e(e);
    }

According to CrashLog, the location problem is caused by an error in converting the data returned by Api into beans. The code locates the GsonConverter. readInternational method. Usually, the method has declared the error type. Throwing the specified error type according to the business logic should not cause App crash, but should be the method that calls back RestErrorHandler. But according to the actual test and guess is still very different.

Then extract an Api, code as follows:

        ResponseEntity<CheckVersionResponse> entity = apiHelper.checkVersion();

        if (null == entity || !entity.hasBody()) {
            return;
        }

App crashes if an exception is thrown in GsonConverter.readInternal. If you add TryCatch to the above code, you can catch exceptions. This is curious about how to throw an exception directly without calling back the exception handling interface. If so, dozens of interfaces of the whole system need to be modified, which is too heavy and silly.
Solution:
Since throwing exceptions can cause crashes, when the Api conversion error occurs, returning data to null is not enough. Modified code:

 @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        String str = FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), getCharset(inputMessage.getHeaders())));
        LogUtil.d("in =" + str);
        try {
            Type typeOfT = getType();

            if (typeOfT != null) {
                return this.gson.fromJson(str, typeOfT);
            } else {
                return this.gson.fromJson(str, clazz);
            }
        } catch (JsonSyntaxException ex) {
            LogUtil.e("Could not read JSON: " + ex.getMessage(), ex);
//            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        } catch (JsonIOException ex) {
            LogUtil.e("Could not read JSON: " + ex.getMessage(), ex);
//            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        } catch (Exception ex) {
            LogUtil.e("Could not read JSON: " + ex.getMessage(), ex);
//            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        }
        return null;
    }

Reason analysis:
The solution has been found, and the ApiClient source code generated automatically by AA framework has been studied deeply.

    @Override
    public ResponseEntity<CheckVersionResponse> checkVersion(Map<String, Object> params) {
        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<Map<String, Object>>(params);
        try {
            return restTemplate.exchange(rootUrl.concat("/checkVersion/"), HttpMethod.POST, requestEntity, CheckVersionResponse.class);
        } catch (RestClientException e) {
            if (restErrorHandler!= null) {
                restErrorHandler.onRestClientExceptionThrown(e);
                return null;
            } else {
                throw e;
            }
        }
    }

As you can see from this, only RestClientException type calls back the exception callback interface, and other errors are thrown directly.

However, HttpMessageNotReadableException is not RestClientException type, so exceptions are thrown directly, and failure to capture certainly causes APP to crash.

Posted by HERATHEIM on Fri, 14 Dec 2018 14:57:03 -0800