HttpClient4基础

Httpclient

尽管 java.net 包提供了通过 HTTP 访问资源的基本功能,但它并没有提供许多应用程序所需的全部功能。HttpClient填补了Java.net中在HTTP请求处理方面的一些不足,提供了更丰富、更灵活、更高效的HTTP请求处理能力。
HttpClient 专为扩展而设计,同时为基本 HTTP 协议提供强大的支持,任何构建 HTTP 感知客户端应用程序,例如 Web 浏览器、Web 服务客户端或利用或扩展 HTTP 协议进行分布式通信的系统。

基础

请求执行模板

HttpClient最本质的功能是执行HTTP方法。一个 HTTP 方法的执行涉及一个或多个 HTTP 请求/HTTP 响应交换,通常由 HttpClient 内部处理。用户需要提供一个要执行的请求对象,HttpClient 将请求传输到目标服务器,返回相应的响应对象,如果执行不成功,则抛出异常。
HttpClient API 的主要入口点是定义上述协定的 HttpClient 接口。

以下是最简单形式的请求执行过程的示例:

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
    <...>
} finally {
    response.close();
}

HTTP请求构造

概述

HTTP 请求都有一个请求行,其中包含请求方法、请求URI和HTTP的版本协议
接下来还有请求体和请求头

请求行

HttpClient通过URIBuilder的方式来构造请求

 URI uri = new URIBuilder() 
        .setScheme("http") 
        .setHost("www.google.com") 
        .setPath("/search") 
        .setParameter("q", "httpclient") 
        .setParameter("btnG" , "Google search") 
        .setParameter("aq", "f") 
        .setParameter("oq", "") 
        .build(); 
HttpGet httpget = new HttpGet(uri); 
System.out.println(httpget.getURI());

上述实际发送的请求为http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=
也可以进行Post,Put之类的请求体的构造,其中最重要的是请求体,可以借助HttpEntity的方式来放入数据
HttpEntity是一个核心接口,它定义了HTTP实体的通用行为和属性。HttpEntity接口位于org.apache.http.HttpEntity包中,其主要作用是表示HTTP消息的内容,包括请求和响应的实体内容。

请求体

HttpEntity接口的实现类包括:
BasicHttpEntity: 提供了基本的HttpEntity实现,适用于大多数情况。
BufferedHttpEntity: 是HttpEntityWrapper的一个子类,用于包装HttpEntity,并缓冲实体内容,以便多次读取。
ByteArrayEntity: 将实体内容表示为字节数组。
FileEntity: 将实体内容表示为文件。
StringEntity: 将实体内容表示为字符串。
InputStreamEntity: 将实体内容表示为输入流。
HttpEntity接口和其实现类提供了丰富的方法,可以用于获取和操作HTTP实体的各种属性和内容,例如获取内容长度、获取内容类型、获取内容编码、获取内容流等。这些类和接口的设计使得HttpClient能够灵活处理HTTP请求和响应的实体内容。

例如:

StringEntity myEntity = new StringEntity("important message", 
   ContentType.create("text/plain", "UTF-8"));
System.out.println(myEntity.getContentType());
System.out.println(myEntity.getContentLength());
System.out.println(EntityUtils.toString(myEntity));
System.out.println(EntityUtils.toByteArray(myEntity).length);

输出:

Content-Type: text/plain; charset=utf-8
17
important message
17

将请求数据传入请求体,例如FileEntity

File file = new File("somefile.txt");
FileEntity entity = new FileEntity(file,ContentType.create("text/plain", "UTF-8"));        

HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);
模拟表单提交

有些应用需要表单的方式来提交数据,HttpClient提供了实体类 UrlEncodedFormEntity来进行封装这个过程。

List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("param1", "value1"));
formparams.add(new BasicNameValuePair("param2", "value2"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);
设置分块编码

分块编码是一种用于在HTTP传输中动态生成并发送数据的方法。它允许数据在发送过程中被分成多个块(chunks),每个块的大小可以根据需要而变化。这种方法使得在数据传输过程中不需要提前知道整个数据的大小,而是可以根据实际情况动态生成块并发送,从而提高了数据传输的效率和灵活性。

StringEntity entity = new StringEntity("important message",
        ContentType.create("plain/text", Consts.UTF_8));
entity.setChunked(true);
HttpPost httppost = new HttpPost("http://localhost/acrtion.do");
httppost.setEntity(entity);

HTTP响应

响应基本测试

响应也是类似的

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 
HttpStatus.SC_OK, "OK");

System.out.println(response.getProtocolVersion());
System.out.println(response.getStatusLine().getStatusCode());
System.out.println(response.getStatusLine().getReasonPhrase());
System.out.println(response.getStatusLine().toString

输出为

HTTP/1.1
200
OK
HTTP/1.1 200 OK
获取响应的请求内容

响应 息可以包含许多描述消息属性的标头,例如内容长度、内容类型等。HttpClient 提供了检索、添加、删除和枚举标头的方法。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 
    HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", 
    "c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie", 
    "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
Header h1 = response.getFirstHeader("Set-Cookie");
System.out.println(h1);
Header h2 = response.getLastHeader("Set-Cookie");
System.out.println(h2);
Header[] hs = response.getHeaders("Set-Cookie");
System.out.println(hs.length);

枚举

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 
    HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", 
    "c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie", 
    "c2=b; path=\"/\", c3=c; domain=\"localhost\"");

HeaderIterator it = response.headerIterator("Set-Cookie");

while (it.hasNext()) {
    System.out.println(it.next());
}

响应数据
这个对用户来说是最重要的,官方推荐获取实体内容采用HttpEntity#getContent()或 HttpEntity#writeTo(OutputStream)方法。
同时官方还附带了一个工具类EntityUtils,这个类提供了更方便的从实体类中获取内容和信息的方法

实例

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        long len = entity.getContentLength();
        if (len != -1 && len < 2048) {
            System.out.println(EntityUtils.toString(entity));
        } else {
            // Stream content out
        }
    }
} finally {
    response.close();
}

有时候也需要多次多去实体类,这种情形可以采用缓冲方式,实现这种最简单的是BufferedHttpEntity。这将导致原始实体的内容被读入内存缓冲区

CloseableHttpResponse response = <...>
HttpEntity entity = response.getEntity();
if (entity != null) {
    entity = new BufferedHttpEntity(entity);
}
响应处理封装

如果我们对于响应的处理想要封装一层,直接得到我们想要得到的值,可以考虑采用ResponseHandler的方式,具体使用流程为
1、定义一个类实现ResponseHandler,并设置泛型响应类型
2、实现handleResponse方法,从response返回想要得到的响应类型
3、调用excute,返回定义的泛型类型
实例如下:


import org.apache.http.HttpResponse;
import org.apache.http.client.ResponseHandler;
import org.apache.http.util.EntityUtils;

public class MyResponseHandler<T> implements ResponseHandler<T> {
    @Override
    public T handleResponse(HttpResponse response) throws IOException {
        // 从响应中提取数据并进行处理
        // 例如,将响应内容解析为特定类型的对象
        if (response.getStatusLine().getStatusCode() == 200) {
            // 从响应中提取实体内容并转换为字符串
            String responseBody = EntityUtils.toString(response.getEntity());
            // 进行其他处理,例如将字符串转换为对象
            // 返回处理结果
            return (T) responseBody;
        } else {
            // 处理非200状态码的响应,可以抛出异常或返回默认值等
            return null;
        }
    }
}

import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;

public class Main {
    public static void main(String[] args) throws IOException {
        // 创建HttpClient实例
        HttpClient httpClient = HttpClients.createDefault();
        // 创建HttpGet请求对象
        HttpGet httpGet = new HttpGet("http://example.com/api/resource");
        // 执行HTTP请求并处理响应
        String response = httpClient.execute(httpGet, new MyResponseHandler<>());
        // 输出处理结果
        System.out.println("Response: " + response);
    }
}

上述代码中T为String

释放资源

当不再需要某个实例CloseableHttpClient,必须通过调用该CloseableHttpClient#close() 方法来关闭与其关联的连接管理器。

CloseableHttpClient httpclient = HttpClients.createDefault();
try {
    <...>
} finally {
    httpclient.close();
}

同时input资源也要注意释放

HTTP执行上下文

最初,HTTP 被设计为无状态、面向响应请求的协议。然而,现实世界的应用程序通常需要能够通过几个逻辑相关的请求-响应交换来持久保存状态信息。为了使应用程序能够维持处理状态,HttpClient 允许在特定的执行上下文(称为 HTTP 上下文)内执行 HTTP 请求。如果在连续请求之间重用相同的上下文,则多个逻辑相关的请求可以参与逻辑会话。
HttpContext是一个接口,它提供了在HTTP请求执行期间存储和检索信息的机制。它允许您在多个HTTP请求之间共享状态信息,例如在同一会话中保持会话状态或跟踪身份验证信息。
HTTP执行上下文可以用于在HTTP请求之间传递自定义参数,以便在请求执行期间访问它们。它通常用于在整个请求执行过程中保持状态,并在需要时将其传递给相关的HTTP组件。
在HttpClient中,HTTP执行上下文通常作为方法参数传递给执行HTTP请求的方法,例如execute方法。它是一个接口,允许用户自定义实现以满足特定的需求。
在 HTTP 请求执行过程中,HttpClient 将以下属性添加到执行上下文中:
HttpConnection代表与目标服务器的实际连接的实例。
HttpHost代表连接目标的实例。
HttpRoute代表完整连接路由的实例
HttpRequest代表实际 HTTP 请求的实例。执行上下文中的最终 HttpRequest 对象始终准确地表示消息 发送到目标服务器时的状态。默认情况下,HTTP/1.0 和 HTTP/1.1 使用相对请求 URI。但是,如果请求是通过代理以非隧道模式发送的,则 URI 将是绝对的。
HttpResponse代表实际 HTTP 响应的实例。
java.lang.Boolean表示标志的对象,该标志指示实际请求是否已完全传输到连接目标。
RequestConfig代表实际请求配置的对象。
java.util.List表示请求执行过程中收到的所有重定向位置的集合的对象

HttpContext context = <...>
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpHost target = clientContext.getTargetHost();
HttpRequest request = clientContext.getRequest();
HttpResponse response = clientContext.getResponse();
RequestConfig config = clientContext.getRequestConfig();

在以下示例中,初始请求设置的请求配置将保留在执行上下文中,并传播到共享相同上下文的连续请求。RequestConfig 就是设置的上下文配置

CloseableHttpClient httpclient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom()
        .setSocketTimeout(1000)
        .setConnectTimeout(1000)
        .build();

HttpGet httpget1 = new HttpGet("http://localhost/1");
httpget1.setConfig(requestConfig);
CloseableHttpResponse response1 = httpclient.execute(httpget1, context);
try {
    HttpEntity entity1 = response1.getEntity();
} finally {
    response1.close();
}
HttpGet httpget2 = new HttpGet("http://localhost/2");
CloseableHttpResponse response2 = httpclient.execute(httpget2, context);
try {
    HttpEntity entity2 = response2.getEntity();
} finally {
    response2.close();
}

HTTP拦截器

如果某一个Httpclient需要共用一些代码,可以添加拦截器,在响应的前后执行相关逻辑
您可以通过调用HttpClients.custom()方法获取HttpClientBuilder实例,然后使用addInterceptorLast方法添加一个HttpRequestInterceptor对象作为拦截器,示例如下:

CloseableHttpClient httpclient = HttpClients.custom()
        .addInterceptorLast(new HttpRequestInterceptor() {
            @Override
            public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
                // 在此处添加您的拦截器逻辑
            }
        })
        .build();

在process方法中,可以编写自定义的逻辑来处理HTTP请求。这可以用于在执行请求之前或之后修改请求或响应的内容,添加标头,记录请求和响应等操作。
详细例子

CloseableHttpClient httpclient = HttpClients.custom()
        .addInterceptorLast(new HttpRequestInterceptor() {

            public void process(
                    final HttpRequest request,
                    final HttpContext context) throws HttpException, IOException {
                AtomicInteger count = (AtomicInteger) context.getAttribute("count");
                request.addHeader("Count", Integer.toString(count.getAndIncrement()));
            }

        })
        .build();

AtomicInteger count = new AtomicInteger(1);
HttpClientContext localContext = HttpClientContext.create();
localContext.setAttribute("count", count);

HttpGet httpget = new HttpGet("http://localhost/");
for (int i = 0; i < 10; i++) {
    CloseableHttpResponse response = httpclient.execute(httpget, localContext);
    try {
        HttpEntity entity = response.getEntity();
    } finally {
        response.close();
    }
}

异常处理

HTTP 协议处理器可以抛出两种类型的异常: java.io.IOException发生 I/O 故障(例如套接字超时或套接字重置)以及HttpException发出 HTTP 故障信号(例如违反 HTTP 协议)
通常 I/O 错误被认为是非致命且可恢复的,而 HTTP 协议错误被认为是致命的且无法自动恢复。

幂等性

默认情况下,出于兼容性原因,HttpClient 假定仅非实体封闭方法(例如 GET和HEAD)具有幂等性,而实体封闭方法(例如POST和 )PUT则不是幂等的。

请求重试

官方定义了HttpRequestRetryHandler接口来实现重试代码

HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {

    public boolean retryRequest(
            IOException exception,
            int executionCount,
            HttpContext context) {
        if (executionCount >= 5) {
            // Do not retry if over max retry count
            return false;
        }
        if (exception instanceof InterruptedIOException) {
            // Timeout
            return false;
        }
        if (exception instanceof UnknownHostException) {
            // Unknown host
            return false;
        }
        if (exception instanceof ConnectTimeoutException) {
            // Connection refused
            return false;
        }
        if (exception instanceof SSLException) {
            // SSL handshake exception
            return false;
        }
        HttpClientContext clientContext = HttpClientContext.adapt(context);
        HttpRequest request = clientContext.getRequest();
        boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
        if (idempotent) {
            // Retry if the request is considered idempotent
            return true;
        }
        return false;
    }

};
CloseableHttpClient httpclient = HttpClients.custom()
        .setRetryHandler(myRetryHandler)
        .build();

这段代码创建了一个自定义的HttpRequestRetryHandler对象,并将其设置为CloseableHttpClient的重试处理程序。在这个重试处理程序中,它定义了一系列条件来确定是否应该重试请求。具体来说:
如果执行次数超过了5次,则不再重试。
如果发生了超时异常(InterruptedIOException),则不重试。
如果发生了未知主机异常(UnknownHostException),则不重试。
如果发生了连接超时异常(ConnectTimeoutException),则不重试。
如果发生了SSL握手异常(SSLException),则不重试。
如果请求是幂等的(即不包含实体的请求),则重试。
这样的设置可以根据应用程序的需求灵活调整重试策略,并在遇到特定类型的异常或请求特性时自定义重试行为。

终止请求

在某些情况下,由于目标服务器上的负载过高或客户端发出的并发请求过多,HTTP 请求执行无法在预期时间范围内完成。在这种情况下,可能需要提前终止请求并解除对 I/O 操作中阻塞的执行线程的阻塞。HttpClient 正在执行的 HTTP 请求可以通过调用方法在执行的任何阶段中止 HttpUriRequest#abort()。该方法是线程安全的,可以从任何线程调用。当 HTTP 请求中止时,其执行线程(即使当前在 I/O 操作中被阻塞)也可以通过抛出 InterruptedIOException

重定向处理

HttpClient 自动处理所有类型的重定向,但 HTTP 规范明确禁止的需要用户干预的重定向除外。See Other(状态代码 303)重定向POST并将 PUT请求转换为GETHTTP 规范要求的请求。可以使用自定义重定向策略来放宽 HTTP 规范对 POST 方法自动重定向的限制。

LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy(); 
CloseableHttpClient httpclient = HttpClients.custom() 
        .setRedirectStrategy(redirectStrategy) 
        .build();

HttpClient在执行过程中经常需要重写请求消息。默认情况下,HTTP/1.0 和 HTTP/1.1 通常使用相对请求 URI。同样,原始请求可能会多次从一个位置重定向到另一个位置。最终解释的绝对 HTTP 位置可以使用原始请求和上下文来构建。该实用程序方法URIUtils#resolve可用于构建用于生成最终请求的解释绝对 URI。此方法包括来自重定向请求或原始请求的最后一个片段标识符。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpClientContext context = HttpClientContext.create();
HttpGet httpget = new HttpGet("http://localhost:8080/");
CloseableHttpResponse response = httpclient.execute(httpget, context);
try {
    HttpHost target = context.getTargetHost();
    List<URI> redirectLocations = context.getRedirectLocations();
    URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations);
    System.out.println("Final HTTP location: " + location.toASCIIString());
    // Expected to be an absolute URI
} finally {
    response.close();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值