第一章。基础
1.1 请求的执行
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 {
resopnse.close();
}
1.1.1 HTTP请求
所有的HTTP请求都有一个由方法名,请求路径和HTTP协议版本组成的请求行.
HttpClient支持HTTP/1.1定义的所有HTTP方法,具体是;GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS.每个类型的方法分别对应具体的类:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace和HttpOptions.
Request-URI是用于请求唯一的统一资源定位符.HTTP请求路径由协议,主机名,可选的端口号,资源路径,可选的查询参数和可选的段(可以理解为页面元素的ID,可直接跳转).
HttpGet httpget = new HttpGet("http://www.google.com/searche?h1=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
HttpClient提供了URIBuilder工具类可以方便的创建和修改请求路径
URI uri = new URIBuilder().setScheme("http").setHost(".setPath("search").setParameter("q","httpclient").setParameter("btnG","Google Search").setPatameter("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=
1.1.2 HTTP 响应
HTTP响应是由服务器接收和解释请求信息后返回给客户端的信息.消息的第一行由协议及版本后跟着数值的状态code及原因组成.
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
1.1.3 处理消息头部信息
HTTP消息包含多个头部用于描述消息的属性比如:content length,content type等等.HttpClient提供了方法用于检索,添加,移除和枚举头部信息.
HttpResponse response = new BasicHttpResponse(HttpVersioin.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接口.
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,HttpStatus.SC,"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消息单个的头元素
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,HttpStatus.SC,"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
1.1.4 HTTP实体
HTTP消息可以携带一个与请求或响应相关的内容实体.实体是可选的,可以在某些请求或响应中找到.HTTP规范定义的两个包含实体的请求方法:POST和PUT.响应通常包含一个内容实体.某些情况下例外,比如:HEADE方法的响应,204 Not Content,304 Not Modified,205 Reset Content这些响应.
HttpClient根据内容的来源将实体分为三类:
streamed:内容从流中接收或者在运行过程中产生.具体的,这类包含接收自HTTP响应的实体.Streamed实体通常是不可重复的.
self-contained:存储在内存中,独立于HTTP连接或其他的实体.通常是可重复的.这一类的实体常用于HTTP请求封装.
wrapping:内容来自其他的实体
对于连接管理,当从HTTP响应中获得流时这些区别十分重要.
1.1.4.1 可重复的实体
一个实体可重复意味着他的内容可以读多次.仅对于self contained是可能的(比如ByteArrayEntity或StringEntity)
1.1.4.2 使用HTTP实体
由于实体可以代表二进制和字符内容,因此有编码的支持.
实体在执行请求过程中封装内容时创建,或者请求成功,响应体用来向客户端返回结果.
从实体中读取内容,一种通过HttpEntity#getContent()方法获得输入流,该方法返回一个java.io.InputStream,或者为HttpEntity#writeTo(OuputStream)方法提供输出流,该方法将所有内容写入提供的流中.
当实体已经通过传入消息被接收,HttpEntity#getContentType()和HttpEntity#getContentLength()方法可以用来读取普通的元数据比如Content-Type和Content-Length头部(如果它们存在).由于Content-Type头部可以包含字符编码,HttpEntity#getContentEncoding()方法用于读取这部分信息.当该头部不存在时,长度返回-1,内容类型返回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);
输出
Content-Type: text/plain; charset=utf-8
17
important message
17
1.1.5 确认释放底层资源
为了保证适当的释放系统资源,必须关闭实体相关联的流或者响应本身.
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()得到的java.io.InputStream实例,同样应该在finally代码块中关闭该流.
当使用stream实体时,可以通过使用EntityUtils#consume(HttpEntity)方法确保实体内容被完全消耗掉以及底层的流已经被关闭.
有这样一种情况,当仅需要检索响应内容中的一小部分时,消耗剩余内容复用连接代价过高,这种情况可以通过关闭响应来终止流.
CloseableHttpClient httpclient = HttpClient.createDefault();
HttpGet httpget = new HttpGet();
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();
}
该连接将不会被复用,且所有引用的资源都将被正确的释放.
1.1.6 消耗实体内容
消耗实体内容推荐使用HttpEntity#getContent()或者HttpEntity#writeTo(OutputStream)方法.HttpClient还配备了一EntityUtils类,该类提供了几个静态方法能够更容易的读取实体的内容或信息.除了直接读取java.io.InputStream,可以通过该类的方法在字符串或字节数组中检索内容body部分.然而,强烈不鼓励使用EntityUtils,除非响应来自可信任的HTTP服务器且已知是有长度限制的.
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet();
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(EntityUtil.toString(entity));
} else {
// Steam content out
}
}
} finally {
response.close();
}
在某些情况下需要多次读取实体内容.这时必须通过某种方式缓存实体内容,通过内存或磁盘.最简单的方法是使用BufferedHttpEntity类封装原始的实体.这样可以使原始实体的内容被读入到内存缓冲区.
CloseableHttpResponse response = <...>;
HttpEntity entity = response.getEntity();
if(entity != null) {
entity = new BufferedHttpEntity(entity);
}
1.1.7 生产实体内容
HttpClient提供了多个类通过HTTP连接高效的流出内容.这些类的实例可以关联请求内附的实体比如POST和PUT以将实体内容
装入传出HTTP请求.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("");
httppost.setEntity(entity);
请注意InputStreamEntity是不可重复的,因为它只能从底层数据流读取一次.一般情况下建议实现自定义HttpEntity类.
1.1.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();
httppost.setEntity(entity);
UrlEncodedFormEntity实例将调用URL encoding对参数编码并产生以下内容:
param1=value1¶m2=value2
1.1.7.2 内容分块
通常情况下推荐由HttpClient根据传输的HTTP消息的属性选择合适的传输编码.然而也可以通过设置HttpEntity#setChunked()为true告知HttpClient该块优先使用的编码.HttpClient仅将该标记作为提示使用.当使用的HTTP协议版本不支持分块编码时该值将被忽略.比如HTTP/1.0.
StringEntity entity = new StringEntity("important messae",ContentType.create("plain/text",Consts.UTF_8));
entity.setChunked(true);
HttpPost httpPost = new HttpPost();
httppost.setEntity(entity);
1.1.8 响应处理
处理响应最简单和方便的方式是使用ResponseHandler接口,该接口包含handleResponse(HttpRespinse response)方法.该方法不需要用户担心连接管理.当使用ResponseHandler,HttpClient将自动连接的管理和释放无论请求执行成功还是导致异常.
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet();
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);