[HTTP][HttpComponents之HttpCore基础][四][学习笔记]
1.概述
Apache HttpComponents项目负责创建和维护一个专注于HTTP和相关协议的底层Java组件工具集。
HttpComponents为扩展而设计,同时为基本HTTP协议提供强大的支持,任何构建HTTP客户端和服务器应用程序,(例如浏览器、爬虫、HTTP代理、Web服务传输库或利用或扩展用于分布式通信的系统)的任何人都可能会对HttpComponents感兴趣。
HttpComponents包括两部分,HttpCore和HttpClient。
- HttpCore是一组底层HTTP传输组件,可用于以最小的占用空间构建自定义客户端和服务器端 HTTP服务。HttpCore支持两种I/O模型:基于经典Java I/O的阻塞I/O模型和基于Java NIO的非阻塞、事件驱动I/O模型。
- HttpClient是基于HttpCore的符合HTTP/1.1的HTTP代理实现。它还为客户端身份验证、HTTP状态管理和HTTP连接管理提供可重用组件。
2.HTTP消息
2.1.结构
一个HTTP消息包含header和body。HTTP请求的header包含请求行和其他信息。HTTP响应的header包含状态行和其他信息。所有HTTP消息必须包含协议版本。
HttpCore定义了HTTP消息对象模型以紧密遵循该定义,并提供广泛支持HTTP消息的序列化(格式化)和反序列化(解析)元素。
2.2.基本操作
2.2.1.HTTP请求消息
import org.apache.http.HttpRequest;
import org.apache.http.HttpVersion;
import org.apache.http.message.BasicHttpRequest;
public class HttpRequestMessage {
public static void main(String[] args) {
HttpRequest request = new BasicHttpRequest("GET", "/",
HttpVersion.HTTP_1_1);
System.out.println(request.getRequestLine().getMethod());// GET
System.out.println(request.getRequestLine().getUri());// /
System.out.println(request.getProtocolVersion());// HTTP/1.1
System.out.println(request.getRequestLine().toString());// GET / HTTP/1.1
}
}
2.2.2.HTTP响应消息
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.message.BasicHttpResponse;
public class HttpResponseMessage {
public static void main(String[] args) {
HttpResponse response=new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_NOT_FOUND,"404,找不到该网址");
System.out.println(response.getProtocolVersion());// HTTP/1.1
System.out.println(response.getStatusLine().getStatusCode());// 404
System.out.println(response.getStatusLine().getReasonPhrase());// 404,找不到该网址
System.out.println(response.getStatusLine().toString());// HTTP/1.1 404 404,找不到该网址
}
}
2.2.3.HTTP消息常用属性和方法
HTTP消息可以包含许多描述消息属性的标头,例如内容长度、内容类型等。HttpCore提供检索、添加、删除和枚举此类标头。
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.message.BasicHttpResponse;
public class HttpCommonProperties {
public static void main(String[] args) {
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", "c1=a;path=/;domian=localhost");
response.addHeader("Set-Cookie", "c2=b;path=\"/\",c3=c;domain=\"localhost\"");
Header firstHeader = response.getFirstHeader("Set-Cookie");
System.out.println(firstHeader);// Set-Cookie: c1=a;path=/;domian=localhost
Header lastHeader = response.getLastHeader("Set-Cookie");
System.out.println(lastHeader);// Set-Cookie: c2=b;path="/",c3=c;domain="localhost"
Header[] headers = response.getHeaders("Set-Cookie");
System.out.println(headersw.length);// 2
}
}
获取所有消息头更有效的方式是使用HeaderIterator
接口
import org.apache.http.HeaderIterator;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.message.BasicHttpResponse;
public class HttpCommonIterator {
public static void main(String[] args) {
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 headerIterator = response.headerIterator("Set-Cookie");
while (headerIterator.hasNext()) {
System.out.println(headerIterator.next());
}
/*
* Set-Cookie: c1=a;path=/;domain=localhost
* Set-Cookie: c2=b;path="/",c3=c;domain="localhost"
* */
}
}
还有方便的方法来将HTTP消息转换为独立的头元素
import org.apache.http.*;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicHttpResponse;
public class HttpMessageParse {
public static void main(String[] args) {
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=\"/\";domain=\"localhost\"");
HeaderElementIterator headerElementIterator = new BasicHeaderElementIterator(
response.headerIterator("Set-Cookie")
);
while (headerElementIterator.hasNext()) {
HeaderElement headerElement = headerElementIterator.nextElement();
System.out.println(headerElement.getName() + "=" + headerElement.getValue());
NameValuePair[] parameters = headerElement.getParameters();
for (int i = 0; i < parameters.length; i++) {
System.out.println(" " + parameters[i]);
}
}
/*
* c1=a
path=/
domain=localhost
c2=b
path=/
domain=localhost
*
* */
}
}
2.3.HTTP实体
HTTP消息可以携带与请求和响应关联的实体。实体可在一些请求响应中找到。使用实体的请求称为实体封闭请求。HTTP规范定义了两种实体封装方法:POST和PUT。
HttpCore根据其内容的来源区分三种实体:
- 流式传输:内容是从流中接收或动态生成的。流式实体通常不可重复。
- 自包含:内容在内存中或通过独立于连接或其他实体。自包含实体通常可重复。
- wrapping:内容是从另一个实体获取的。
2.3.1.可重复实体
一个可重复的实体说明内容可以多次被读取。只有自包含实体(例如ByteArrayEntity
或StringEntity
)
2.3.2.使用HTTP实体
实体可以表示二进制和字符内容,在执行带有请求或请求成功时创建响应体将结果发送回客户端。
从实体读取内容,通过HttpEntity#getContent()
方法检索输入流,该方法返回java.io.InputStream
或者可以向HttpEntity#writeTo(OutputStream)
方法提供输出流,将全部返回内容已写入给定流。一些非流式(自包含)实体可能无法有效将其内容表示为java.io.InputStream
。该类实体仅实现HttpEntity#writeTo(OutputStream)
方法并从HttpEntity#getContent()
方法抛出UnsupportedOperationException
。
EntityUtils类公开了几个静态方法来简化从实体中提取内容过程。无需读取java.io.InputStream
,可以检索完整的使用此类中的方法以字符串或字节数组中的内容正文。
当实体接收传入消息时,HttpEntity#getContentType()
和HttpEntity#getContentLength()
方法可以读取Content-Type
和Content-Length
头。当Content-Type
头包含字符编码如text/plain
或text/html
时,使用HttpEntity#getContentEncoding()
方法来读取。如果头部无效,会返回length,内容类型为NULL。如果Content-Type
头可用,返回Header
对象。
用实体创建输出消息,meta data可以用实体来创建,代码如下:
import org.apache.http.Consts;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class HttpEntityDemo1 {
public static void main(String[] args) throws IOException {
StringEntity entity = new StringEntity("important message",
Consts.UTF_8);
System.out.println(entity.getContentType());// Content-Type: text/plain; charset=UTF-8
System.out.println(entity.getContentLength());// 17
System.out.println(EntityUtils.toString(entity));// important message
System.out.println(EntityUtils.toByteArray(entity).length);// 17
}
}
2.3.3.确保释放系统资源
为了确保系统资源释放,必须关闭相应内容流和实体。
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import java.io.IOException;
import java.io.InputStream;
public class HttpEntityDemo2 {
public static void main(String[] args) throws IOException {
HttpResponse response;
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = entity.getContent();
try {
//
}finally {
inputStream.close();
}
}
}
}
2.4.创建实体
HttpCore为创建实体提供以下几种实现:
- BasicHttpEntity
- ByteArrayEntity
- StringEntity
- InputStreamEntity
- FileEntity
- HttpEntityWrapper
- BufferedHttpEntity
2.4.1.BasicHttpEntity
代表底层流。一般用来从HTTP消息接收到的实体。实体有一个空的构造函数。构造后无内容,有负数内容长度。需要设置内容流,以及可选长度。可通过BasicHttpEntity#setContent(InputStream)
和BasicHttpEntity#setContentLength(long)
。
import org.apache.http.entity.BasicHttpEntity;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class HttpEntityBasic {
public static void main(String[] args) throws IOException {
byte[] bytes = new byte[123];
InputStream inputStream = new ByteArrayInputStream(bytes);
BasicHttpEntity entity = new BasicHttpEntity();
entity.setContent(inputStream);// 123
entity.setContentLength(404);// 404
}
}
2.4.2.ByteArrayEntity
是自包含,可重复实体,从给定的字节数组中获取内容。将字节数组提供给构造函数。
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import java.io.IOException;
public class HttpEntityByte {
public static void main(String[] args) throws IOException {
ByteArrayEntity entity = new ByteArrayEntity(new byte[]{1, 2, 3},
ContentType.APPLICATION_OCTET_STREAM);
long contentLength = entity.getContentLength();
System.out.println(contentLength);// 3
}
}
2.4.3.StringEntity
StringEntity
是一个自包含可重复的实体,从java.lang.String
对象获取内容。有三个构造方法。
- 第一个是一个简单构造与给定
java.lang.String
对象。 - 第二个对字符串中的数据进行字符编码。
- 第三个允许指定mime类型。
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import java.io.UnsupportedEncodingException;
import java.util.Map;
public class HttpEntityString {
public static void main(String[] args) throws UnsupportedEncodingException {
StringBuilder stringBuilder = new StringBuilder();
Map<String, String> env = System.getenv();
for (Map.Entry<String, String> evnEntry : env.entrySet()) {
stringBuilder.append(evnEntry.getKey())
.append(": ").append(evnEntry.getValue())
.append("\r\n");
}
//无字符编码构造(默认ISO-8859-1)
HttpEntity entity1 = new StringEntity(stringBuilder.toString());
System.out.println(entity1);//[Content-Type: text/plain; charset=ISO-8859-1,Content-Length: 2957,Chunked: false]
//替换默认编码构造(默认mime类型为"text/plain")
HttpEntity entity2 = new StringEntity(stringBuilder.toString(), Consts.UTF_8);
System.out.println(entity2);//[Content-Type: text/plain; charset=UTF-8,Content-Length: 2957,Chunked: false]
//替换编码和mime类型构造
HttpEntity entity3 = new StringEntity(stringBuilder.toString(),
ContentType.create("text/aline", Consts.ASCII));
System.out.println(entity3);//[Content-Type: text/aline; charset=US-ASCII,Content-Length: 2957,Chunked: false]
}
}
2.4.4.InputStreamEntity
InputStreamEntity
是流式不可重复的实体,从输入流获取内容。通过提供输入流和内容长度来构造。内容长度限制来自java.io.InputStream
读取的数据量。如果输入流上内容长度和长度匹配就发送所有数据。
import org.apache.http.entity.InputStreamEntity;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class HttpEntityInputStream {
public static void main(String[] args) throws IOException {
InputStream inputStream = new ByteArrayInputStream(new byte[]{1,2,3,4,5,5,6,7,8,9,0});
InputStreamEntity entity = new InputStreamEntity(inputStream, 5);
System.out.println(entity.getContentLength());// 5
for (int i = 0; i < entity.getContentLength(); i++) {
System.out.print(entity.getContent().read()+" ");// 1 2 3 4 5
}
}
}
2.4.5.FileEntity
一个可重复自包含、从文件中获取内容的实体。用来流式传输不同类型的大文件,例如,对于XML用application/xml
,zip文件用application/zip
。
HttpEntity entity = new FileEntity(staticFile,
ContentType.create("application/java-archive"));
2.4.6.HttpEntityWrapper
这是创建包装实体的基类。包装实体持有对包装实体的引用实体并将所有调用委托给它。包装实体的实现可以从此类派生,并只需要覆盖那些不应委托给包装实体的方法。
2.4.7.BufferedHttpEntity
BufferedHttpEntity
是HttpEntityWrapper
的子类。通过提供另一个实体构造。从提供的实体读取内容并缓冲到内存中。
这使得从不可重复实体变成可重复实体成为可能。如果提供的实体已经是可重复的,它只是将调用传递到底层实体。
myNonRepeatableEntity.setContent(someInputStream);
BufferedHttpEntity myBufferedEntity = new BufferedHttpEntity(myNonRepeatableEntity);
3.HTTP协议处理器
HTTP协议拦截器是实现HTTP协议特定方面的例程。通常,协议拦截器应作用于传入消息的一个特定标头或一组相关标头,或者使用一个特定标头或一组相关标头填充传出消息头。协议拦截器还可以操纵与消息一起包含的内容实体;内容压缩/解压缩就是一个很好的例子。通常,这是通过使用
"装饰器"模式,其中包装器实体类用于装饰原始实体。多种协议拦截器可以组合成一个逻辑单元
3.1.标准协议拦截器
3.1.1.RequestContent
这是最重要的输出请求拦截器。通过Content-Length
或Transfer-Content
设定内容长度。
3.1.2.ResponseContent
这是最重要的输出响应拦截器。通过Content-Length
或Transfer-Content
设定内容长度。
3.1.3.RequestConnControl
RequestConnControl负责将Connection标头添加到输出请求,这对于管理HTTP/1.0连接的持久性至关重要。
3.1.4.ResponseConnControl
ResponseConnControl负责将Connection标头添加到响应请求,这对于管理HTTP/1.0连接的持久性至关重要。
3.1.5.RequestDate
RequestDate负责将Date标头添加到输出请求。
3.1.6.ResponseDate
ResponseDate负责将Date标头添加到输出响应。
3.1.7.RequestExpectContinue
RequestExpectContinue负责通过添加Expect标头启用’expect-continue握手’。
3.1.8.RequestTargetHost
RequestTargetHost负责添加Host标头。
3.1.9.RequestUserAgent
RequestUserAgent负责添加User-Agent标头。
3.1.10.ResponseServer
ResponseServer负责添加Server标头。
3.2.使用协议处理器
通常,HTTP协议处理器用于在程序逻辑之前对传入消息进行预处理,并对传出消息进行后处理。
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.protocol.*;
import java.io.IOException;
public class HttpEntityProtocol {
public static void main(String[] args) throws HttpException, IOException {
HttpProcessor httpProcessor = HttpProcessorBuilder.create()
.add(new RequestContent())
.add(new RequestTargetHost())
.add(new RequestConnControl())
.add(new RequestUserAgent("MyAgent-HTTP/1.1"))
.add(new RequestExpectContinue(true))
.build();
HttpCoreContext context = HttpCoreContext.create();
HttpRequest request = new BasicHttpRequest("GET", "/");
httpProcessor.process(request,context);
}
}
4.HTTP执行上下文
最初,HTTP被设计为无状态的,面向响应请求的协议。但应用程序通常需要能够通过多个逻辑相关的请求-响应交换来持久保存状态信息。为了能让应用程序保持处理状态,HttpCpre允许在特定的执行上下文内执行HTTP消息。如果在连续请求之间重用相同上下文,则多个逻辑相关的消息可以参与逻辑会话。HTTP上下文的功能类似于java.util.Map<String,Object>。
请注意,HttpContext可以包含任意对象,因此在多个线程之间共享可能是不安全的。必须注意确保 HttpContext 实例一次只能由一个线程访问。
4.1.上下文分享
协议拦截器可以通过HTTP执行上下文共享信息(如处理状态)进行协作。HTTP上下文是一种可用于将属性名称映射到属性值的结构。HTTP上下文实现通常由HashMap支持。HTTP上下文的主要目的是促进各种逻辑相关组件之间的信息共享。HTTP上下文可用于存储一条消息或多条连续消息的处理状态。
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpProcessorBuilder;
import java.io.IOException;
public class HttpEntityContext {
public static void main(String[] args) throws HttpException, IOException {
HttpProcessor httpProcessor = HttpProcessorBuilder.create()
.add(new HttpRequestInterceptor() {
@Override
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
String id = (String) context.getAttribute("session-id");
if (id != null) {
request.addHeader("Session-ID", id);
}
}
}).build();
HttpCoreContext context = HttpCoreContext.create();
HttpRequest request = new BasicHttpRequest("GET", "/");
httpProcessor.process(request,context);
}
}
- 点赞
- 收藏
- 关注作者
评论(0)