致敬大神:http://blog.csdn.net/column/details/httpclient.htmljavascript
如下只是对大神做品学习以及验证:html
《一》明白HttpClient可以作什么,这个很重要java
超文本传输协议(HTTP)也许是当今互联网上所使用的最重要的协议了。Web services,联网设备和网络计算的发展,都持续扩展了HTTP协议的角色,超越了用户使用的Web浏览器范畴,同时,也增长了须要HTTP协议支持的应用程序的数量。
尽管java.net包提供了经过HTTP访问资源的基本功能,但它缺乏足够的灵活性和其它不少应用程序须要的功能。HttpClient经过提供一个有效的,保持更新的,功能丰富的软件包来实现客户端最新的HTTP标准和建议,来弥补java.net包的在某些技术上的空白。
HttpClient为扩展而设计,同时为基本的HTTP协议提供强大的支持。有一些人会对HttpClient感兴趣,这些人一般是构建 HTTP 客户端应用程序(好比web浏览器,web服务客户端,利用或扩展 HTTP 协议进行来实现的分布式通讯系统)的开发人员。
1.HttpClient 的范围
●基于HttpCore[http://hc.apache.org/httpcomponents-core/index.html]的客户端HTTP通讯库
●基于经典(阻塞) I/O
●内容无关 --我理解为不关注内容
2.HttpClient 所不能作的
●HttpClient 不是一个浏览器。它是一个客户端的 HTTP 通讯实现库。HttpClient 的目标是发送和接收HTTP 报文。HttpClient 不会去处理内容,执行嵌入在 HTML页面中的javascript 代码,猜想内容类型,若是没有明确设置,不然不会从新格式化请求/重定向URI,或其它和HTTP通讯无关的功能。web
------------------------------------------------------------------------------------------------------apache
(1)HttpClient最基本的功能是执行HTTP方法;json
(2)一个 HTTP 方法的执行包含一个或多个 HTTP 请求/HTTP 相应的交换,一般由 HttpClient的内部来处理。数组
(3)使用者被要求提供一个Request对象来执行,HttpClient就会把请求传送给目标服务器并返回一个相对应的response对象,若是执行不成功,将会抛出一个异常。浏览器
--显然,HttpClient API 的主要切入点就是定义描述上述契约的HttpClient接口。缓存
下边就说服务器
1>什么是HTTP请求(Request对象)?
2>什么是HTTP相应的交换
3>HTTP报文
4>Request对象处理
5>Response对象处理
《1》什么是HTTP请求(Request对象)?
全部 HTTP 请求都有一个请求起始行,这个起始行由方法名,请求 URI 和 HTTP 协议版本组成。HttpClient很好地支持了HTTP/1.1规范中全部的HTTP方法:GET,HEAD, POST,PUT, DELETE, TRACE 和 OPTIONS。每一个方法都有一个特别的类:HttpGet,HttpHead, HttpPost,HttpPut,HttpDelete,HttpTrace和HttpOptions。URI是统一资源标识符的缩写,用来标识与请求相符合的资源。HTTP 请求URI包含了一个协议名称,主机名,可选端口,资源路径,可选的参数,可选的片断。
写法以下:
HttpGet httpget = new HttpGet(
"http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
HttpClient提供了URIBuilder工具类来简化建立、修改请求 URI。
因此上边写法还能够改为
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);
2>什么是HTTP相应的交换
HttpClient表明HTTP请求执行的最基本约定。它没有强加限制或具体细节给请求执行过程,它保留了链接管理,状态管理,认证,重定向等处理细节的我的实现。使用额外的功能来装饰这个接口是很是容易的,好比设置响应体缓存。
HttpClient实现做为特殊目的的处理器或策略接口的门面,负责处理HTTP协议特定的方面,好比重定向处理,认证处理,为链接持久化作决定或者保持持续链接。这使得用户使用自定义的、应用程序特定方面的实现来取代那些默认的实现。
这一块涉及不少细节,后边再单独一一列举讲解
3>HTTP报文
http报文专门单列出来说。。
4>Request对象处理(HTTP 响应(Response))
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());
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);
得到全部指定类型首部最有效的方式是使用HeaderIterator接口
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());
}
HttpClient也提供了其余便利的方法吧HTTP报文转化为单个的HTTP元素。
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\"");
HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator("Set-Cookie"));
while (it.hasNext()) {
HeaderElement elem = it.nextElement();
System.out.println(elem.getName() + " = " + elem.getValue());
NameValuePair[] params = elem.getParameters();
for (int i = 0; i < params.length; i++) {
System.out.println(" " + params[i]);
}
}
HTTP实体(HTTP Entity)
HTTP报文可以携带与请求或响应相关联的内容实体。
实体存在于某些请求、响应中,他们是可选的。
使用实体的请求被称为内含实体请求;HTTP规范定义了两种内含实体请求,POST和PUT。
而响应老是内含实体。但有些响应不符合这一规则,好比,对HEAD方法的响应和状态为204 No Content, 304 Not Modified, 205 Reset Content的响应。
依据实体的内容来源,HttpClient区分出三种实体:
流式实体(streamed):内容来源于一个流,或者在运行中产生。特别的,这个类别包括从响应中接收到的实体。流式实体不可重复。
自包含实体(self-contained):在内存中的内容或者经过独立的链接/其余实体得到的内容。自包含实体是可重复的。这类实体大部分是HTTP内含实体请求。
包装实体(wrapping):从另一个实体中获取内容
因为一个实体可以表现为二进制和字符内容,它支持二进制编码(为了支持后者,即字符内容)。
实体将会在一些状况下被建立:当执行一个含有内容的请求时或者当请求成功,响应体做为结果返回给客户端时。为了读取实体的内容,能够经过HttpEntity#getContent() 方法取出输入流,返回一个java.io.InputStream,或者提供一个输出流给HttpEntity#writeTo(OutputStream) 方法,它将会返回写入给定流的全部内容。
当实体内部含有信息时,使用HttpEntity#getContentType()和HttpEntity#getContentLength()方法将会读取一些基本的元数据,好比Content-Type和Content-Length这样的首部(若是他们可用的话),因为Content-Type首部可以包含文本MIME类型(像 text/plain 或text/html),它也包含了与MIME类型相对应的字符编码,HttpEntity#getContentEncoding()方法被用来读取这些字符编码。若是对应的首部不存在,则Content-Length的返回值为-1,Content-Type返回值为NULL。若是Content-Type是可用的,一个Header类的对象将会返回。
当咱们构建一个具备可用信息的实体时,元数据将会被实体构建器提供。
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);
为了确保正确的释放系统资源,你必须关掉与实体与实体相关的的内容流,还必须关掉响应自己。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/";);
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
try {
// do something useful
} finally {
instream.close();
}
}
} finally {
response.close();
}
关闭内容流和关闭响应的不一样点是:前者将会经过消费实体内容保持潜在的链接,然后者迅速的关闭并丢弃链接。
请注意,一旦实体被HttpEntity#writeTo(OutputStream)方法成功地写入时,也须要确保正确地释放系统资源。若是方法得到经过HttpEntity#getContent(),它也须要在一个finally子句中关闭流。
当使用实体时,你可使用EntityUtils#consume(HttpEntity)方法来确保实体内容彻底被消费而且使潜在的流关闭。
某些状况,整个响应内容的仅仅一小部分须要被取出,会使消费其余剩余内容的性能代价和链接可重用性代价过高,这时能够经过关闭响应来终止内容流。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/";);
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int byteOne = instream.read();
int byteTwo = instream.read();
// Do not need the rest
}
} finally {
response.close();
}
这样,链接将不会被重用,它分配的全部级别的资源将会被解除。
为了消费实体内容,推荐的方式是使用HttpEntity#getContent()或者 HttpEntity#writeTo(OutputStream)方法。HttpClient也提供了一个EntityUtils类,它有几个静态方法更容易的从实体中读取内容或信息。取代了直接读取java.io.InputStream,你能够经过这个类的方法取出所有内容体并放入一个String 中或者byte数组中。但是,强烈不建议使用EntityUtils,除非响应实体来自于信任的HTTP服务器而且知道它的长度。
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类包装起来。这将会使原始实体的内容读入一个in-memory缓冲区。全部方式的实体包装都是表明最原始的那个实体。
CloseableHttpResponse response = <...>
HttpEntity entity = response.getEntity();
if (entity != null) {
entity = new BufferedHttpEntity(entity);
}
生产实体内容
HttpClient提供了几个类,用来经过HTTP链接高效地传输内容。
这些类的实例均与内含实体请求有关,好比POST和PUT,它们可以把实体内容封装进友好的HTTP请求中。
对于基本的数据容器String, byte array, input stream, and file,
HttpClient为它们提供了几个对应的类:StringEntity, ByteArrayEntity, InputStreamEntity, and 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);
请注意InputStreamEntity是不可重复的,由于它仅仅可以从数据流中读取一次。
通常建议实现一个定制的HttpEntity类来代替使用通常的InputStreamEntity。
FileEntity将会是一个好的出发点。
HTML表单
许多应用须要模仿一个登录HTML表单的过程,
好比:为了登录一个web应用或者提交输入的数据。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);
UrlEncodedFormEntity实例像上面同样使用URL编码方式来编码参数并生成下面的内容:
param1=value1¶m2=value2
一般,咱们推荐让HttpClient选择基于被传递的HTTP报文属相最合适的传输编码方式。可能地,能够经过设置HttpEntity#setChunked()为true来通知HttpClient你要进行分块编码。注意HttpClient将会使用这个标志做为提示。当使用一些不支持分块编码的HTTP版本(好比HTTP/1.0.)时,这个值将会忽略。
【译者:分块编码是是HTTP1.1协议中定义的Web用户向服务器提交数据的一种方法】
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);
最简单、最方便的方式来处理响应是使用ResponseHandler接口,它包含了一个handleResponse(HttpResponse response)方法。这个方法减轻使用者对于链接管理的担忧。
当你使用ResponseHandler时,不管是请求执行成功亦或出现异常,HttpClient将会自动地确保链接释放回链接管理器中。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/json";);
ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>()
{
public JsonObject handleResponse(final HttpResponse response) throws IOException
{
StatusLine statusLine = response.getStatusLine();
HttpEntity entity = response.getEntity();
if (statusLine.getStatusCode() >= 300)
{
throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
}
if (entity == null)
{
throw new ClientProtocolException("Response contains no content");
}
Gson gson = new GsonBuilder().create();
ContentType contentType = ContentType.getOrDefault(entity);
Charset charset = contentType.getCharset();
Reader reader = new InputStreamReader(entity.getContent(), charset);
return gson.fromJson(reader, MyJsonObject.class);
}
};
MyJsonObject myjson = client.execute(httpget, rh);