HttpURLConnection读取全局异常拦截器所返回响应体的一个注意点

背景:
服务端项目中通过全局异常拦截器进行异常处理,但进行单元测试发现用java原生HttpURLConnection方法访问服务端时,无法读取到处理异常后的响应体中的内容,直接报错!在此记录下解决方案!


一、错误场景还原

先看看发生报错时的代码如何写的:

抛出自定义错误的代码:

try {
    service = (MoerService) SpringContextHolder.getBean(serviceImpl);
} catch (Exception e) {
    throw new SystemException(ErrorMessageConstants.NOT_BEAN_ERROR);
}

全局异常拦截器:

/**
 * 全局异常拦截器
 *
 * @Author yuanj
 * @Date: 2018/8/12 15:40
 */
@ControllerAdvice
public class GlobalExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);


    /**
     * 拦截全局异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Result exception(Exception e) {
        logger.error("异常错误:", e);
        return Result.fail(ErrorMessageConstants.ABNORMAL_ERROR);
    }

    /**
     * 拦截系统异常
     */
    @ExceptionHandler(SystemException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Result systemException(SystemException e) {
        logger.error("系统异常:", e);
        return Result.fail(e.getMessage());
    }

    /**
     * 拦截业务异常
     */
    @ExceptionHandler(BussinessException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Result bussinessException(BussinessException e) {
        logger.error("业务异常:", e);
        return Result.fail(e.getMessage());
    }

HttpRequestUtil工具类方法(用来向服务端发起请求):

public static String postJsonParams(String requestUrl, String requestMethod, String params) {
   StringBuilder sb = new StringBuilder();
   try {
      URL url = new URL(requestUrl);// 创建连接
      HttpURLConnection connection = (HttpURLConnection) url
            .openConnection();
      connection.setDoOutput(true);
      connection.setDoInput(true);
      connection.setUseCaches(false);
      connection.setInstanceFollowRedirects(true);
      connection.setRequestMethod(requestMethod); // 设置请求方式
      connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
      connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
      connection.connect();
      OutputStreamWriter out = new OutputStreamWriter(
            connection.getOutputStream(), "UTF-8"); // utf-8编码
      out.append(params);
      out.flush();
      out.close();
      if (connection != null) {
         BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
         String line = null;
         while ((line = br.readLine()) != null) {
            sb.append(line);
            sb.append("\r\n");
         }
         br.close();
      }
      connection.disconnect();
   } catch (IOException e) {
      e.printStackTrace();
   }
   return sb.toString();
}

当服务端抛出 SystemException() 时,会被异常拦截器 GlobalExceptionHandler 所处理,然后return Result.fail(e.getMessage()),返回自定义友好错误提示;

但通过postJsonParams方法发起请求时,却无法获取服务端响应内容,报错信息如下:

java.io.IOException: Server returned HTTP response code: 500 for URL: http://localhost:8885/moerService/access
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1839)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1440)
	at com.moerlong.service_authorize.util.HttpRequest.postJsonParams(HttpRequest.java:94)
	at com.moerlong.service_authorize.controller.AccessController.test(AccessController.java:91)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
......

二、错误定位

由错误信息可以看到:Server returned HTTP response code: 500,说明服务端响应码为500是错误的源头;

我们再看异常拦截器中的 @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 正好为response设置响应码为500,难道是这个问题?

之前通过HttpClient发送请求时没遇到这种问题,难道是HttpURLConnection API不能读取responseCode为500时的响应体内容么?

再看读取响应体的方法:

if (connection != null) {
         BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
         String line = null;
         while ((line = br.readLine()) != null) {
            sb.append(line);
            sb.append("\r\n");
         }
         br.close();
      }

我们是通过connection.getInputStream()方法获取输入流来读取响应体内容的!但这个API只能读取 responseCode=200 时的响应内容!

我们翻阅 HttpURLConnection 的API文档,可以看到十分靠前的一个方法getErrorStream()
HttpURLConnection读取全局异常拦截器所返回响应体的一个注意点_第1张图片

Returns the error stream if the connection failed but the server sent useful data nonetheless.

原来通过这个方法我们才能获取到responseCode不为200(connection failed)的响应内容(useful data)


三、解决方案

修改下HttpRequestUtil发送请求的工具类:

public static String postJsonParams(String requestUrl, String requestMethod, String params) {
   StringBuilder sb = new StringBuilder();
   try {
      URL url = new URL(requestUrl);// 创建连接
      HttpURLConnection connection = (HttpURLConnection) url
            .openConnection();
      connection.setDoOutput(true);
      connection.setDoInput(true);
      connection.setUseCaches(false);
      connection.setInstanceFollowRedirects(true);
      connection.setRequestMethod(requestMethod); // 设置请求方式
      connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
      connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
      connection.connect();
      OutputStreamWriter out = new OutputStreamWriter(
            connection.getOutputStream(), "UTF-8"); // utf-8编码
      out.append(params);
      out.flush();
      out.close();
      if (connection != null) {
         InputStream inputStream = null;
         //根据responseCode来获取输入流,此处错误响应码的响应体内容也要获取(看服务端的返回结果形式决定)
         if (HttpURLConnection.HTTP_OK == connection.getResponseCode()) {
            inputStream = connection.getInputStream();
         } else {
            inputStream = connection.getErrorStream();
         }
         BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
         String line = null;
         while ((line = br.readLine()) != null) {
            sb.append(line);
            sb.append("\r\n");
         }
         br.close();
      }
      connection.disconnect();
   } catch (IOException e) {
      e.printStackTrace();
   }
   return sb.toString();
}

核心:

if (HttpURLConnection.HTTP_OK == connection.getResponseCode()) {
  inputStream = connection.getInputStream();
} else {
   inputStream = connection.getErrorStream();
}

根据 ResponseCode来选择getInputStream/getErrorStream API即可!

你可能感兴趣的:(java开发)