Java实现HTTP请求的几种方式-Apache HttpClient(二)

通过Apache Common封装好的HttpClient请求

HttpClient的Get或Post请求方式步骤

  1. 生成一个HttpClient对象并设置相应的参数;
  2. 生成一个GetMethod对象或PostMethod并设置响应的参数;
  3. HttpClient生成的对象来执行GetMethod生成的Get方法/执行PostMethod生成Post方法;
  4. 处理响应状态码;
  5. 若响应正常,处理HTTP响应内容;
  6. 释放连接。

引入jar包

<dependency>
    <groupId>commons-httpclient</groupId>
    <artifactId>commons-httpclient</artifactId>
    <version>3.1</version>
</dependency>
<!--SpringBoot 的webstarter自带,可以不用引入 -->
<dependency>
    <groupId>com.fasterxml</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.3</version>
</dependency>

Get方式请求

请求:

public class HttpClientService {
    private final ObjectMapper objectMapper;
    private final HttpClient httpClient;

    public HttpClientService(ObjectMapper objectMapper){
        //1.生成HttpClient对象并设置参数
        this.objectMapper = objectMapper;
        this.httpClient = new HttpClient();
        //连接超时时间
        this.httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
        //读写超时时间
        this.httpClient.getParams().setParameter(HttpConnectionParams.SO_TIMEOUT,5000);
    }

   /**
     *
     * @param url
     * @param param 这里使用泛型和Object都是一样的,不影响
     * @return
     * @throws IOException
     * @throws IllegalAccessException
     */
    public <R> R doGet(String url,Object param,Class<R> returnType,String token) throws IOException, IllegalAccessException {
        //2.生成GetMethod对象并设置参数
        if(Objects.nonNull(param)){
            List<String> params = new ArrayList<>();
            Class<?> clazz = param.getClass();
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                Object o = declaredField.get(param);
                if (declaredField.getType().equals(String.class)) {
                    //这里拼接的时候注意要使用URL编码
                    String s = (String) declaredField.get(param);
                    s = URLEncoder.encode(s);
                    o = s;
                }
                params.add(declaredField.getName() + "=" + o);
            }
            String paramStr = params.stream().collect(Collectors.joining("&"));
            url = url+"?"+paramStr;
        }
        GetMethod getMethod = new GetMethod(url);
        //设置get请求超时为5秒
        //getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);
        //设置请求重试处理,用的是默认的重试处理:请求三次
        getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
        //设置请求头
        //(可选)设置请求头鉴权信息
        if(StringUtils.isNotBlank(token)){
            getMethod.addRequestHeader("Authorization",token);
        }
        String response = "";
        //3.执行HTTP GET 请求
        try {
            int statusCode = this.httpClient.executeMethod(getMethod);
            //4.判断访问的状态码
            if (statusCode != HttpStatus.SC_OK) {
                log.error("请求出错:" + getMethod.getStatusLine());
                return null;
            }
            //5.处理HTTP响应内容
            //HTTP响应头部信息,这里简单打印
            Header[] headers = getMethod.getResponseHeaders();
            for(Header h : headers) {
                log.info(h.getName() + "---------------" + h.getValue());
            }
            //读取HTTP响应内容,这里简单打印网页内容
            //读取为字节数组
            byte[] responseBody = getMethod.getResponseBody();
            response = new String(responseBody, "UTF-8");
            log.info(response);
            //读取为InputStream,在网页内容数据量大时候推荐使用
            //InputStream response = getMethod.getResponseBodyAsStream();
        }  finally {
            //6.释放连接
            getMethod.releaseConnection();
        }
        /**
         * 如果是List<T>这种带泛型的对象,则需要使用TypeReference<类型> typeRef = new TypeReference...
         * 注意日期的类型,需要事前设置类型转换器
         */
        R jsonResult = objectMapper.readValue(response, returnType);
        return jsonResult;
    }

测试程序:

/**
     * 使用HttpURLConnection发送Post请求
     */
    @SneakyThrows
    public TestHttpAccessResponse sendHttpGetRequestByOldHttpClient() {
        TestHttpAccessRequest request = new TestHttpAccessRequest();
        request.setAge(16);
        request.setName("刘伞");
        request.setAddress("佛山市");

        String httpUrl = "http://localhost:8082/nacos-service-provider/testHttpAccessGet";

        /**
         * 如果是List<T>这种带泛型的对象,则需要使用TypeReference<类型> typeRef = new TypeReference...
         * 注意日期的类型,需要事前设置类型转换器
         */
        String token = httpClientService.getToken();
        JsonResult jsonResult = httpClientService.doGet(httpUrl, request, JsonResult.class,token);
        if (Objects.isNull(jsonResult) || !ReturnCode.SUCCESS.getCode().equals(jsonResult.getCode())) {
            if (Objects.isNull(jsonResult)) {
                throw new BizException(ReturnCode.ERROR);
            } else {
                throw new BizException(jsonResult.getCode(), jsonResult.getMessage());
            }
        }
        /**
         * 由于我做了统一的返回体JsonResult,所以还需要再转一遍
         */
        TestHttpAccessResponse response = objectMapper.convertValue(jsonResult.getData(), TestHttpAccessResponse.class);
        return response;
    }

结果:
无授权

{
    "code": "40026",
    "message": "token不存在或过期,请登录",
    "data": null
}

成功:

{
    "code": "40000",
    "message": "操作成功",
    "data": {
        "name": "刘伞",
        "age": 16,
        "address": "佛山市"
    }
}

Post方式请求

请求方法::

@Slf4j
@Service
public class HttpClientService {
    private final ObjectMapper objectMapper;
    private final HttpClient httpClient;

    public HttpClientService(ObjectMapper objectMapper){
        this.objectMapper = objectMapper;
        this.httpClient = new HttpClient();
        //连接超时时间
        this.httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
        //读写超时时间
        this.httpClient.getParams().setParameter(HttpConnectionParams.SO_TIMEOUT,5000);

    }



    public <R> R doPost(String httpUrl,Object param,Class<R> returnType) throws IOException {
        String json = "";
        if(Objects.nonNull(param)){
            json = objectMapper.writeValueAsString(param);
        }

        PostMethod postMethod = new PostMethod(httpUrl);
        //postMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);

        //这里accept表示的是可接收的返回值类型,一般设置为*/*
        //如果不设置将会使用浏览器的默认值,不过一般都会支持*/*,为了保险还是加上吧
        //如果返回值类型不匹配则会提示No content to map due to end-of-input,可以尝试设置text/html试一下
        postMethod.addRequestHeader("accept", "*/*");

        //HTTP1.1协议默认使用Keep-Alive,如果是1.0的HTTP协议需要设置
        //Keep-Alive发送请求时性能更高
        postMethod.addRequestHeader("connection", "Keep-Alive");
        //告诉服务器请求的媒体类型,目前基本都是用json,所以设置json格式传送
        //即使不设置也是application/json,可能由于下面用到了StringRequestEntity、
        //但是我注释了下面的StringRequestEntity还是application/json,目前搞不懂,所以暂时这么设置了
        postMethod.addRequestHeader("Content-Type", "application/json;charset=UTF-8");

        //设置下面这个Header,没什么用,注释了也能正常访问
        //postMethod.addRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36");

       //添加请求参数
        RequestEntity se = new StringRequestEntity(json, "application/json", "UTF-8");
        postMethod.setRequestEntity(se);

        StringBuilder res = new StringBuilder();
        try {
            //必须要UTF-8,不然中文乱码
            //3. 执行方法
            int code = httpClient.executeMethod(postMethod);
            //4. 处理状态码
            if(code!=200){
                log.error("请求出错:"+postMethod.getStatusLine());
                //这里最好是抛出一个异常
                return null;
            }
            //5. 正常,进行处理
            //方法1:拿到字节数据后使用new String
            byte[] responseBody = postMethod.getResponseBody();
            res.append(new String(responseBody,"UTF-8"));
            log.info(res.toString());

            //方法2:逐行读,在网页内容数据量大时候推荐使用
            /*InputStream responseStream = postMethod.getResponseBodyAsStream();
            InputStreamReader reader = new InputStreamReader(responseStream,"UTF-8");
            BufferedReader bufferedReader = new BufferedReader(reader);
            String line = null;
            while((line = bufferedReader.readLine()) !=null){
                res.append(line).append("\r\n");

            }*/
            log.info(res.toString());
        }finally {
            //6. 释放连接
            postMethod.releaseConnection();
        }
        R r = objectMapper.readValue(res.toString(), returnType);
        return r;

    }
}

请求头设置:
1)accept=*/* 告诉服务器,客户端这边期望接收的媒体类型
2)connection=Keep-Alive : 使用长连接,可以不用频繁的建立/释放连接,节省资源
3)Content-Type=application/json;charset=UTF-8 : 告诉服务器,客户端发送的内容是什么媒体类型

最后一个user-Agent我也不理解是什么意思,注释了对访问没有影响。

测试程序:

  /**
     * 使用HttpURLConnection发送Post请求
     */
    @SneakyThrows
    public TestHttpAccessResponse sendHttpRequestByOldHttpClient() {
        TestHttpAccessRequest request = new TestHttpAccessRequest();
        request.setAge(16);
        request.setName("刘伞");
        request.setAddress("佛山市");

        String httpUrl = "http://localhost:8082/nacos-service-provider/testHttpAccess";
//        String httpUrl = "http://198.168.22.11:8085/nacos-service-provider/testHttpAccess";

        /**
         * 如果是List<T>这种带泛型的对象,则需要使用TypeReference<类型> typeRef = new TypeReference...
         * 注意日期的类型,需要事前设置类型转换器
         */
        String token = httpClientService.getToken();
        JsonResult jsonResult = httpClientService.doPost(httpUrl, request, JsonResult.class,token);
        if (Objects.isNull(jsonResult) || !ReturnCode.SUCCESS.getCode().equals(jsonResult.getCode())) {
            if (Objects.isNull(jsonResult)) {
                throw new BizException(ReturnCode.ERROR);
            } else {
                throw new BizException(jsonResult.getCode(), jsonResult.getMessage());
            }
        }


        /**
         * 由于我做了统一的返回体JsonResult,所以还需要再转一遍
         * 这里不封装进去是因为,除了JsonResult可能还会有其他的返回体
         */
        TestHttpAccessResponse response = objectMapper.convertValue(jsonResult.getData(), TestHttpAccessResponse.class);
        return response;
    }

结果:
无授权

{
    "code": "40026",
    "message": "token不存在或过期,请登录",
    "data": null
}

成功:

{
    "code": "40000",
    "message": "操作成功",
    "data": {
        "name": "刘伞",
        "age": 16,
        "address": "佛山市"
    }
}

关键属性设置

连接超时时间

connectionTimeout: 连接超时的时间,经过查看源码,在执行httpConnection.open()方法的时候使用到,实际上就是new Socket()的超时时间。当遇到一个无法ping通的ip时会出现connectionTimeout的问题,如:

/**
     * 使用HttpURLConnection发送Post请求
     */
    @SneakyThrows
    public TestHttpAccessResponse sendHttpRequestByOldHttpClient() {
        TestHttpAccessRequest request = new TestHttpAccessRequest();
        request.setAge(16);
        request.setName("刘伞");
        request.setAddress("佛山市");
		//这里我把请求的url的ip和port设置成了一个无法ping通的值
		//并且,httpClient设置了5000ms的链接超时
        String httpUrl = "http://198.168.22.11:8085/nacos-service-provider/testHttpAccess";
		
        /**
         * 如果是List<T>这种带泛型的对象,则需要使用TypeReference<类型> typeRef = new TypeReference...
         * 注意日期的类型,需要事前设置类型转换器
         */
        JsonResult jsonResult = httpClientService.doPost(httpUrl, request, JsonResult.class);
       	//......
    }

结果:
大概经过了5s(调试),因为没办法创建连接而提示异常了,且没有重试
在这里插入图片描述
如果不设置的话,大概会有20s左右的超时时间,失败后会重试3次。
在这里插入图片描述

读超时时间(一定要设置)

有两种设置方法

  1. 对连接对象设置读超时时间:this.httpClient.getParams().setParameter(HttpClientParams.SO_TIMEOUT,5000);
    2)对方法对象设置读写超时时间:postMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);(优先级高)

设置的必要性:如果不设置读写超时时间,访问这个URL会一直等待,直到读写完成。我在公司里面某一次定时任务的生产事故,就是因为没有设置读超时时间,结果定时任务一直卡着不动,又查找不出问题。
最后通过打日志才发现卡在请求外部服务的接口里。

如果设置了读超时时间,当服务提供者的接口在指定时间内没有返回,则会提示SocketTimeoutException异常。

各种网络异常

ConnectTimeoutException/ConnectException

没有设置超时时间时:java.net.ConnectException: Connection timed out: connect
设置了超时时间:org.apache.commons.httpclient.ConnectTimeoutException: The host did not accept the connection within timeout of 5000 ms

这种情况可能出现在ip 地址ping不通,或者是服务器(防火墙等)丢弃了该请求报文包,也可能是服务器应答太慢,又或者存在间歇性的问题(这种情况很难从日志文件中排查问题)。
目前我通过一个ping不通的ip地址重现了该错误。

Socket超时:SocketTimeoutException

java.net.SocketTimeoutException: Read timed out

Socket可以设置SoTimeout表示读写的超时时间,如果不设置默认为0,表示没有时间限制;

也可以说是服务端与客户端传输数据包之间的时间间隔,超过这个间隔将抛出 java.net.SocketTimeoutException: Read timed out

相关阅读

Java实现HTTP请求的几种方式-HttpURLConnection(一)
Java实现HTTP请求的几种方式-CloseableHttpClient(三)
Java实现HTTP请求的几种方式-RestTemplate(四)
Java实现HTTP请求的几种方式-OKHttp(五)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值