apache Httpclient基于java BIO实现的,也是基于apache HttpCore项目。他最基本的功能是执行HTTP方法。HttpClient的API的主要入口就是HttpClient接口,看看这个示例:
package httpclienttest; 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 T1 { public static void main(String[] args) { HttpGet httpget = new HttpGet("http://www.baidu.com"); try(CloseableHttpClient httpclient = HttpClients.createDefault(); CloseableHttpResponse response = httpclient.execute(httpget);){ System.out.printf("内容类型为:%s",response.getEntity().getContentType()); }catch(Exception e){ e.printStackTrace(); } }
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); } }