Httpclient4.4之原理(请求执行)
Apache Httpclient基于java BIO实现的,也是基于apache HttpCore项目。他最基本的功能是执行HTTP方法。HttpClient的API的主要入口就是HttpClient接口,看看这个示例:
1. HTTP请求
所有的http请求都由:方法名,请求url,HTTP协议组成。HttpClient支持HTTP/1.1支持的所有方法:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS,HttpClient中都有一个特定的类与之对应:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace和HttpOptions。
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);
System.out.println(httpget.getURI())
2. HTTP响应
HTTP响应是HTTP请求发送到服务端处理后响应到客户端的消息。响应第一行是协议与协议版本号,接着是数字状态码和一些文本信息,示例演示一下看看执行结果:
package httpclienttest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.message.BasicHttpResponse;
public class T2 {
public static void main(String[] args) {
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
3. HTTP消息头
HTTP消息头(header)包含多个消息描述的信息,例如:内容长度,内容类型等。HttpClient提供的方法有检索,添加,删除和枚举等操作。 示例:
package httpclienttest;
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 T3 {
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\"");
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);
}
}
输出:
Set-Cookie: c1=a; path=/; domain=localhost
Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
2
更有效率的方法是通过HeaderIterator接口获得所有的header信息,示例:
package httpclienttest;
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 T4 {
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 it = response.headerIterator("Set-Cookie");
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
输出结果:
Set-Cookie: c1=a; path=/; domain=localhost
Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
他也提供了更便利的方法来解析HTTP消息并获得header中一个个独立的header元素,示例:
package httpclienttest;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicHttpResponse;
public class T5 {
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\"");
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]);
}
}
}
}
输出信息:
c1 = a
path=/
domain=localhost
c2 = b
path=/
c3 = c
domain=localhost
4. HTTP Entity
HTTP消息能携带与请求或响应有关的实体内容,它只是可选的,能在有些请求或响应中找到。请求消息中使用实体是针对entity enclosing request的,HTTP规范中定义了两个entity enclosing request方法:POST和PUT。响应通常都是包装一个实体的,当然也有例外!
HttpClient区分三种实体,根据其内容来源:
•streamed(流):内容从流中接收的。流实体是不可重复的。
•self-contained(自包含):内容在内存中或者从连接或其它实体自主获得的。自包含的实体通常都是可重复的。这种类型的实体通常用于entity enclosing request。
•wrapping(包装):内容从其它实体获得。
从HTTP响应中流出内容时,对于连接管理(connection management)这种区别还是很重要的。对于请求实体是通过应用程序创建的并且只是使用HttpClient发送,这种区别对于streamed与self-contained意义不大。这种情况下,建议把不可重复的实体当作streamed,把那些可重复的当作self-contained。
4.1 可重复的实体
一个实体能被重复获取,意味着内容能被读多次。这是唯一可能的自包含实体(如:ByteArrayEntity或StringEntity)。
4.2 使用HTTP实体
因为实体可以代表二进制和字符内容,它是支持字符集的(支持后者,即文字内容)。
从实体中读取内容,可能通过HttpEntity的getContent()方法检索输入流,它返回一个java.io.InputStream,或者可以提供一个输出流作为HttpEntity的writeTo(OutputStream)方法的参数,将返回的所有内容写入给定的流。
当实体收到一个进来的消息时,HttpEntity的getContentType方法和getContentLength方法能被用来读取请求header里的元数据:Content-Type和Content-Length(如果它们可用)。由于header的Content-Type能包含像text/plain或text/html这样的MIME类型的字符集(编码),HttpEntity的getContentEncoding()方法用来读取这个信息。 如果header不可用,长度将返回-1和内容类型为NULL。如果header的Content-Type是可用的,这个Header对象将返回。
创建一个输出消息的实体时,该数据是由实体的创建者提供,示例:
package httpclienttest;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
public class T6 {
public static void main(String[] args) throws Exception{
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
5. 确保底层资源的释放
为了确保系统资源的释放,必须关闭与实体相关的内容流或者response自身:
package httpclienttest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
public class T7 {
public static void main(String[] args) {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://ifeng.com");
//使用java7中的语法,自动调用close()方法,所以这里没有显示调用
try(CloseableHttpResponse response = httpclient.execute(httpget)){
HttpEntity entity = response.getEntity();
try(BufferedReader reader = new BufferedReader(new InputStreamReader(
entity.getContent(),StandardCharsets.UTF_8))){
Stream<String> sm = reader.lines();
sm.forEach(System.out::println);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
关闭内容流与关闭response之间的区别是,前者试图保持消费实体内容的基本连接是活的,而后者关闭连接并丢弃!请注意:一旦实体被完全写出,HttpEntity的writeTo(OutputStream)方法也要确保系统资源释放。如果调用HttpEntity的getContent()方法获得一个java.io.InputStream流的实例,在最后也是要关闭并释放资源的。
当使用流实体工作时,EntityUtils的consume(HttpEntity)方法能确保实体内容完全被消费掉,并自动后台关闭流来释放资源。
有种极少见的情况,请求响应的实体内容只有一小部分需要被检索,剩下的都不需要,而消费剩下的内容又有性能损耗,造成重用连接很高。这种情况下,可以直接关闭response来终止内容流!如下例:
package httpclienttest;
import java.io.InputStream;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
public class T8 {
public static void main(String[] args) {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://ifeng.com");
try (CloseableHttpResponse response = httpclient.execute(httpget)) {
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int byteOne = instream.read();
int byteTwo = instream.read();
System.out.printf("%d,%d",byteOne,byteTwo);
// instream中剩下的内容不需要了!直接关闭response
}
}catch(Exception e){
e.printStackTrace();
}
}
}
这样连接不会被重用,而且所有级别的资源都会被释放!
6. 消费实体内容
消费实体内容的推荐方式是使用HttpEntity的getContent()或HttpEntity的writeTo(OutputStream)方法。HttpClient还配备了EntityUtils类,它提供了几个静态方法让读取实体内容与信息更容易,而不是直接读java.io.InputStream。能通过EntityUtils的这些静态方法检索整个内容体到这符串/字节数组。不管怎样,强烈建议不要使用EntityUtils,除非响应实体产生自一个可信的HTTP服务端并且知道是有限长度的。示例:
package httpclienttest;
import java.nio.charset.StandardCharsets;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class T9 {
public static void main(String[] args) {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://www.ifeng.com/");
try (CloseableHttpResponse response = httpclient.execute(httpget)){
HttpEntity entity = response.getEntity();
if (entity != null) {
System.out.println(EntityUtils.toString(entity, StandardCharsets.UTF_8));
}
}catch(Exception e){
e.printStackTrace();
}
}
}
在某些情况下,可能需要读实体内容不止一次。这种情况下,实体内容在某种程度上必须缓冲,无论在内存还是磁盘。最简单的方法是通过BufferedHttpEntity类来包装原始的实体,这将使原始实体的内容读到内存缓冲区。示例:
package httpclienttest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
public class T10 {
public static void main(String[] args) {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://www.ifeng.com/");
try (CloseableHttpResponse response = httpclient.execute(httpget)){
HttpEntity entity = response.getEntity();
if (entity != null) {
//实体进行缓冲,可重复使用
entity = new BufferedHttpEntity(entity);
try(BufferedReader reader = new BufferedReader(new InputStreamReader(
entity.getContent(),StandardCharsets.UTF_8))){
Stream<String> sm = reader.lines();
sm.forEach(System.out::println);
}
System.out.println("读第二次!");
try(BufferedReader reader = new BufferedReader(new InputStreamReader(
entity.getContent(),StandardCharsets.UTF_8))){
Stream<String> sm = reader.lines();
sm.forEach(System.out::println);
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
7. 创建实体内容
HttpClient提供了几个通过HTTP connection有效地流出内容的类。这些类的实例与POST和PUT这样的entity enclosing requests有关,为了把这些实体内容放进即将发出的请求中。HttpClient提供的这几个类大多数都是数据容器,像字符串,字节数组,输入流和文件对应的:StringEntity,ByteArrayEntity,InputStreamEntity和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是不可重复的,因为它只能从底层数据流读一次。通常推荐使用InputStreamEntity来实现一个自定义的self-contained的HttpEntity类。
7.1 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
7.2 HTTP分块
通常推荐HttpClient选择适当的基于HTTP消息传输特性的传输编码。然而,有个可能是通知HttpEntity优先使用分块编码(chunk coding),通过HttpEntity的setChunked()方法设置为true。请注意,HttpClient使用此标记仅仅是为了提示。当使用HTTP协议不支持分块编码时,这个值将被忽略,如:HTTP/1.0。示例:
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);
8. response处理
处理response最简单,最方便的方式是使用ResponseHandler接口,它包含handleResponse(HttpResponse respnse)方法。这种方法让用户完全不用担心连接的管理。使用ResponseHandler时,HttpClient将自动释放连接并把连接放回连接管理器中,不论请求执行成功还是失败。示例:
package httpclienttest;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
public class T14 {
public static void main(String[] args) {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/json");
ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() {
@Override
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 = httpclient.execute(httpget, rh);
}
}
相关推荐
创建一个 HttpClient 实例,这个实例需要调用 Dispose 方法释放资源,这里使用了 using 语句。接着调用 GetAsync,给它传递要调用的方法的地址,向服务器发送 Get 请求。