Java中的HTTP客户端工具——HttpClient

客户端http协议传输类库。HttpClient被用来发送和接受Http消息。HttpClient不会处理Http消息的内容,不会进行Javascript解析,不会关心ContentType,如果没有明确设置,HttpClient也不会对请求进行格式化、重定向url,或者其他任何和http消息传输相关的功能。

HttpClient核心接口

org.apache.http.HttpMessage:HTTP 消息由客户端到服务器的请求和服务器到客户端的响应组成。
org.apache.http.HttpRequest:从客户端到服务器的请求消息在该消息的第一行中包括要应用于资源的方法、资源的标识符和正在使用的协议版本。
org.apache.http.client.HttpClient:这个接口只代表了 HTTP 请求执行的最基本的合约。它对请求执行过程没有施加任何限制或特定细节,并将状态管理、身份验证和重定向处理的细节留给单独的实现。
org.apache.http.HttpResponse:在接收并解释请求消息后,服务器以 HTTP 响应消息进行响应。

核心依赖

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

HttpClient入参

实体入参的请求需要继承HttpEntityEnclosingRequestBase,官方HttpDelete/HttpPatch/HttpPost/HttpPut均支持实体入参

public abstract class HttpEntityEnclosingRequestBase
    extends HttpRequestBase implements HttpEntityEnclosingRequest {

    private HttpEntity entity;

    public HttpEntityEnclosingRequestBase() {
        super();
    }

    @Override
    public HttpEntity getEntity() {
        return this.entity;
    }

    @Override
    public void setEntity(final HttpEntity entity) {
        this.entity = entity;
    }

    @Override
    public boolean expectContinue() {
        final Header expect = getFirstHeader(HTTP.EXPECT_DIRECTIVE);
        return expect != null && HTTP.EXPECT_CONTINUE.equalsIgnoreCase(expect.getValue());
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        final HttpEntityEnclosingRequestBase clone =
            (HttpEntityEnclosingRequestBase) super.clone();
        if (this.entity != null) {
            clone.entity = CloneUtils.cloneObject(this.entity);
        }
        return clone;
    }

}

无需实体入参的请求可以直接继承HttpRequestBase,官方HttpGet/HttpHead/HttpOptions/HttpTrace均不支持实体入参,实在有需要可以自行重写,直接继承HttpEntityEnclosingRequestBase即可

public abstract class HttpRequestBase extends AbstractExecutionAwareRequest
    implements HttpUriRequest, Configurable {

    private ProtocolVersion version;
    private URI uri;
    private RequestConfig config;

    @Override
    public abstract String getMethod();

    /**
     * @since 4.3
     */
    public void setProtocolVersion(final ProtocolVersion version) {
        this.version = version;
    }

    @Override
    public ProtocolVersion getProtocolVersion() {
        return version != null ? version : HttpProtocolParams.getVersion(getParams());
    }

    @Override
    public URI getURI() {
        return this.uri;
    }

    @Override
    public RequestLine getRequestLine() {
        final String method = getMethod();
        final ProtocolVersion ver = getProtocolVersion();
        final URI uriCopy = getURI(); // avoids possible window where URI could be changed
        String uritext = null;
        if (uriCopy != null) {
            uritext = uriCopy.toASCIIString();
        }
        if (uritext == null || uritext.isEmpty()) {
            uritext = "/";
        }
        return new BasicRequestLine(method, uritext, ver);
    }


    @Override
    public RequestConfig getConfig() {
        return config;
    }

    public void setConfig(final RequestConfig config) {
        this.config = config;
    }

    public void setURI(final URI uri) {
        this.uri = uri;
    }

    public void started() {
    }

    public void releaseConnection() {
        reset();
    }

    @Override
    public String toString() {
        return getMethod() + " " + getURI() + " " + getProtocolVersion();
    }

}
HttpEntityEnclosingRequestBase继承关系图

HttpEntity:HTTP 消息发送或接收的实体,HttpEntityEnclosingRequestBase中的属性

public interface HttpEntity {
    //实体是否能够多次生成其数据。可重复实体的 getContent() 和 writeTo(OutputStream) 方法可以多次调用,而不可重复实体则不能。
    boolean isRepeatable();
    //实体的分块编码。HTTP1.0不支持分块。此方法的主要目的是指示在发送实体时是否应使用分块编码。对于接收到的实体,它还可以指示是否使用分块编码接收到实体。
    boolean isChunked();
    //内容的长度
    long getContentLength();
    //获取 Content-Type 标头。这是发送实体时应使用的标头,或者与实体一起接收的标头。它可以包含一个字符集属性。
    Header getContentType();
    //获取 Content-Encoding 标头。这是发送实体时应使用的标头,或者与实体一起接收的标头。修改内容编码的包装实体应相应地调整此标头。
    Header getContentEncoding();
    //实体的内容流。可Repeatable实体应为每次调用此方法创建一个新的InputStream实例,因此可以多次使用。不可repeatable的实体应返回相同的InputStream实例,因此不得多次使用。
    InputStream getContent() throws IOException, UnsupportedOperationException;
    //实体内容写入输出流。重要提示:请注意,所有实体实现必须确保在此方法返回时正确释放所有分配的资源。
    void writeTo(OutputStream outStream) throws IOException;
    //实体是否依赖于基础流。直接从套接字读取数据的流式实体应返回true 。自包含实体应返回false 。包装实体应将此调用委托给被包装实体。
    boolean isStreaming(); // don't expect an exception here
    //自 4.1 版起已弃用。调用该方法表示不再需要该实体的内容。由于此方法调用,所有实体实现都应释放所有分配的资源。
    @Deprecated
    void consumeContent() throws IOException;
}
AbstractHttpEntity:实体的抽象基类。为HttpEntity的流式和自包含实现提供常用属性。
public abstract class AbstractHttpEntity implements HttpEntity {
    protected static final int OUTPUT_BUFFER_SIZE = 4096;
    protected Header contentType;
    protected Header contentEncoding;
    protected boolean chunked;

    protected AbstractHttpEntity() {
        super();
    }

    @Override
    public Header getContentType() {
        return this.contentType;
    }

    @Override
    public Header getContentEncoding() {
        return this.contentEncoding;
    }

    @Override
    public boolean isChunked() {
        return this.chunked;
    }

    public void setContentType(final Header contentType) {
        this.contentType = contentType;
    }

    public void setContentType(final String ctString) {
        Header h = null;
        if (ctString != null) {
            h = new BasicHeader(HTTP.CONTENT_TYPE, ctString);
        }
        setContentType(h);
    }

    public void setContentEncoding(final Header contentEncoding) {
        this.contentEncoding = contentEncoding;
    }

    public void setContentEncoding(final String ceString) {
        Header h = null;
        if (ceString != null) {
            h = new BasicHeader(HTTP.CONTENT_ENCODING, ceString);
        }
        setContentEncoding(h);
    }

    public void setChunked(final boolean b) {
        this.chunked = b;
    }

    @Override
    @Deprecated
    public void consumeContent() throws IOException {
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append('[');
        if (contentType != null) {
            sb.append("Content-Type: ");
            sb.append(contentType.getValue());
            sb.append(',');
        }
        if (contentEncoding != null) {
            sb.append("Content-Encoding: ");
            sb.append(contentEncoding.getValue());
            sb.append(',');
        }
        final long len = getContentLength();
        if (len >= 0) {
            sb.append("Content-Length: ");
            sb.append(len);
            sb.append(',');
        }
        sb.append("Chunked: ");
        sb.append(chunked);
        sb.append(']');
        return sb.toString();
    }

}

AbstractHttpEntity下常用实体
BasicHttpEntity:从InputStream获取其内容的通用流式、不可重复实体
ByteArrayEntity:一个自包含的、可重复的实体,它从字节数组中获取其内容。
EntityTemplate:将内容生成过程委托给ContentProducer的实体。
FileEntity:从文件中获取其内容的自包含、可重复的实体。
InputStreamEntity:从InputStream获取其内容的流式、不可重复的实体。
SerializableEntity:从Serializable获取其内容的流式实体。从Serializable实例获得的内容可以选择缓冲在字节数组中,以使实体自包含和可重复。
StringEntity:一个自包含、可重复的实体,从String获取其内容。
UrlEncodedFormEntity:由 url 编码对列表组成的实体。这在发送 HTTP POST 请求时通常很有用。

HttpEntityWrapper:用于包装实体的基类。保留一个wrappedEntity并将所有调用委托给它。包装实体的实现可以从此类派生,并且只需要覆盖那些不应委托给包装实体的方法。
public class HttpEntityWrapper implements HttpEntity {
    protected HttpEntity wrappedEntity;

    public HttpEntityWrapper(final HttpEntity wrappedEntity) {
        super();
        this.wrappedEntity = Args.notNull(wrappedEntity, "Wrapped entity");
    }

    @Override
    public boolean isRepeatable() {
        return wrappedEntity.isRepeatable();
    }

    @Override
    public boolean isChunked() {
        return wrappedEntity.isChunked();
    }

    @Override
    public long getContentLength() {
        return wrappedEntity.getContentLength();
    }

    @Override
    public Header getContentType() {
        return wrappedEntity.getContentType();
    }

    @Override
    public Header getContentEncoding() {
        return wrappedEntity.getContentEncoding();
    }

    @Override
    public InputStream getContent()
        throws IOException {
        return wrappedEntity.getContent();
    }

    @Override
    public void writeTo(final OutputStream outStream)
        throws IOException {
        wrappedEntity.writeTo(outStream);
    }

    @Override
    public boolean isStreaming() {
        return wrappedEntity.isStreaming();
    }

    @Override
    @Deprecated
    public void consumeContent() throws IOException {
        wrappedEntity.consumeContent();
    }

}

HttpEntityWrapper下常用实体
BufferedHttpEntity:必要时缓冲其内容的包装实体。缓冲实体始终是可重复的。如果被包装的实体本身是可重复的,则调用被传递。如果包装的实体不可重复,则将内容一次读入缓冲区并根据需要从那里提供。
DecompressingEntity:用于解压HttpEntity实现的通用基类,其子类有支持Deflate的DeflateDecompressingEntity和支持GZip的GzipDecompressingEntity
GzipCompressingEntity:writing时压缩内容的包装实体。
ResponseEntityProxy:包含在响应消息中的HttpEntity的包装器类。

HttpClient发起请求

HTTP 请求执行的基本类HttpClient
目前最常用的实现类的是CloseableHttpClient,以前的DefaultHttpClient在自4.3版本之后作废了。

@Contract(threading = ThreadingBehavior.SAFE)
public abstract class CloseableHttpClient implements HttpClient, Closeable {

    private final Log log = LogFactory.getLog(getClass());

    protected abstract CloseableHttpResponse doExecute(HttpHost target, HttpRequest request,
            HttpContext context) throws IOException, ClientProtocolException;
    //使用默认上下文执行 HTTP 请求。
    @Override
    public CloseableHttpResponse execute(
            final HttpHost target,
            final HttpRequest request,
            final HttpContext context) throws IOException, ClientProtocolException {
        return doExecute(target, request, context);
    }

    @Override
    public CloseableHttpResponse execute(
            final HttpUriRequest request,
            final HttpContext context) throws IOException, ClientProtocolException {
        Args.notNull(request, "HTTP request");
        return doExecute(determineTarget(request), request, context);
    }

    private static HttpHost determineTarget(final HttpUriRequest request) throws ClientProtocolException {
        HttpHost target = null;

        final URI requestURI = request.getURI();
        if (requestURI.isAbsolute()) {
            target = URIUtils.extractHost(requestURI);
            if (target == null) {
                throw new ClientProtocolException("URI does not specify a valid host name: "
                        + requestURI);
            }
        }
        return target;
    }

    @Override
    public CloseableHttpResponse execute(
            final HttpUriRequest request) throws IOException, ClientProtocolException {
        return execute(request, (HttpContext) null);
    }

    @Override
    public CloseableHttpResponse execute(
            final HttpHost target,
            final HttpRequest request) throws IOException, ClientProtocolException {
        return doExecute(target, request, null);
    }

    @Override
    public <T> T execute(final HttpUriRequest request,
            final ResponseHandler<? extends T> responseHandler) throws IOException,
            ClientProtocolException {
        return execute(request, responseHandler, null);
    }

    @Override
    public <T> T execute(final HttpUriRequest request,
            final ResponseHandler<? extends T> responseHandler, final HttpContext context)
            throws IOException, ClientProtocolException {
        final HttpHost target = determineTarget(request);
        return execute(target, request, responseHandler, context);
    }

    @Override
    public <T> T execute(final HttpHost target, final HttpRequest request,
            final ResponseHandler<? extends T> responseHandler) throws IOException,
            ClientProtocolException {
        return execute(target, request, responseHandler, null);
    }

    @Override
    public <T> T execute(final HttpHost target, final HttpRequest request,
            final ResponseHandler<? extends T> responseHandler, final HttpContext context)
            throws IOException, ClientProtocolException {
        Args.notNull(responseHandler, "Response handler");

        final CloseableHttpResponse response = execute(target, request, context);
        try {
            final T result = responseHandler.handleResponse(response);
            final HttpEntity entity = response.getEntity();
            EntityUtils.consume(entity);
            return result;
        } catch (final ClientProtocolException t) {
            // Try to salvage the underlying connection in case of a protocol exception
            final HttpEntity entity = response.getEntity();
            try {
                EntityUtils.consume(entity);
            } catch (final Exception t2) {
                // Log this exception. The original exception is more
                // important and will be thrown to the caller.
                this.log.warn("Error consuming content after an exception.", t2);
            }
            throw t;
        } finally {
            response.close();
        }
    }

}

target(HttpHost):请求的目标主机。如果实现仍然可以确定路由,例如到默认目标或通过检查请求,则可以接受null 。
request:要执行的请求,支持HttpRequestHttpUriRequest
responseHandler(ResponseHandler):响应处理程序
context(HttpContext):用于执行的上下文,或null使用默认上下文

HttpClientsCloseableHttpClient实例的工厂方法。
public class HttpClients {

    private HttpClients() {
        super();
    }
    //创建构建器对象以构建自定义CloseableHttpClient实例。
    public static HttpClientBuilder custom() {
        return HttpClientBuilder.create();
    }
    //使用默认配置创建CloseableHttpClient实例。
    public static CloseableHttpClient createDefault() {
        return HttpClientBuilder.create().build();
    }
    //基于系统属性创建具有默认配置的CloseableHttpClient实例。
    public static CloseableHttpClient createSystem() {
        return HttpClientBuilder.create().useSystemProperties().build();
    }
    //创建实现最基本 HTTP 协议支持的CloseableHttpClient实例。
    public static CloseableHttpClient createMinimal() {
        return new MinimalHttpClient(new PoolingHttpClientConnectionManager());
    }
    //创建实现最基本 HTTP 协议支持的CloseableHttpClient实例。
    public static CloseableHttpClient createMinimal(final HttpClientConnectionManager connManager) {
        return new MinimalHttpClient(connManager);
    }

}

HttpClient响应

目前最常用的是CloseableHttpResponse

public interface CloseableHttpResponse extends HttpResponse, Closeable {
}

HttpResponse

public interface HttpResponse extends HttpMessage {
    //获取此响应的状态行。可以使用setStatusLine方法之一设置状态行,也可以在构造函数中对其进行初始化。
    StatusLine getStatusLine();
    //设置此响应的状态行。
    void setStatusLine(StatusLine statusline);
    //设置此响应的状态行。原因短语将根据当前locale确定。
    void setStatusLine(ProtocolVersion ver, int code);
    void setStatusLine(ProtocolVersion ver, int code, String reason);
    //使用新的状态代码更新此响应的状态行。
    void setStatusCode(int code)
        throws IllegalStateException;
    //使用新的原因短语更新此响应的状态行。
    void setReasonPhrase(String reason)
        throws IllegalStateException;
    //获取此响应的消息实体(如果有)。该实体是通过调用setEntity来提供的。
    HttpEntity getEntity();
    //将响应实体与此响应相关联。
    void setEntity(HttpEntity entity);
    //更改此响应的语言环境。
    void setLocale(Locale loc);
}
CloseableHttpResponse继承关系图

HttpClient进行POST请求例子:

    private static String sendPOST(String url) throws IOException {
        String result = "";
        HttpPost post = new HttpPost(url);
        List<NameValuePair> urlParameters = new ArrayList<>();
        urlParameters.add(new BasicNameValuePair("username", "abc"));
        urlParameters.add(new BasicNameValuePair("password", "123"));
        post.setEntity(new UrlEncodedFormEntity(urlParameters));
        try (CloseableHttpClient httpClient = HttpClients.createDefault();
             CloseableHttpResponse response = httpClient.execute(post)){
            result = EntityUtils.toString(response.getEntity());
        }
        return result;
    }
HttpURLConnection相关博客推荐

上传文件:https://lsqingfeng.blog.csdn.net/article/details/90611686
下载文件:https://blog.csdn.net/WxQ92222/article/details/79896489

调用链路源码分析可以参考:
https://blog.csdn.net/u012504392/article/details/109432686

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容