关于HttpClient的一些知识(Part One)
HttpClient是一个客户端的HTTP通信实现库。HttpClient的目标是发送和接收HTTP报文。
HTTP请求
HttpClient 支持所有定义在HTTP/1.1版本中的HTTP方法:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS。对于每个方法类型都有一个特殊的类:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace和 HttpOptions。HttpClient提供很多工具方法来简化创建和修改执行URI:
- URI也可以编程来拼装:
URI uri = URIUtils.createURI("http", "www.30c.org", -1, "/search", "q=httpclient&btnG=Google+Search&aq=f&oq=", null); HttpGet httpget = new HttpGet(uri); System.out.println(httpget.getURI());
输出内容为:
http://www.30c.org/search?q=httpclient&btnG=Google+Search&aq=f&oq=
- 查询字符串也可以从独立的参数中来生成:
List<NameValuePair> qparams = new ArrayList<NameValuePair>(); qparams.add(new BasicNameValuePair("q", "httpclient")); qparams.add(new BasicNameValuePair("btnG", "Google Search")); qparams.add(new BasicNameValuePair("aq", "f")); qparams.add(new BasicNameValuePair("oq", null)); URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search", URLEncodedUtils.format(qparams, "UTF-8"), null); HttpGet httpget = new HttpGet(uri);
HTTP响应
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());
输出内容为:
HTTP/1.1
200
OK
HTTP/1.1 200 OK
处理报文头部
一个HTTP报文可以包含很多描述如内容长度,内容类型等信息属性的头部信息。HttpClient提供获取,添加,移除和枚举头部信息的方法。
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"); Header h2 = response.getLastHeader("Set-Cookie"); Header[] hs = response.getHeaders("Set-Cookie");
获得给定类型的所有头部信息最有效的方式是使用HeaderIterator接口。
HeaderIterator it = response.headerIterator("Set-Cookie"); while (it.hasNext()) { System.out.println(it.next()); }
HTTP实体
HTTP 报文可以携带和请求或响应相关的内容实体。
HttpClient根据其内容出自何处区分三种类型的实体:
– streamed流式:内容从流中获得,或者在运行中产生。特别是这种分类包含从HTTP响应中获取的实体。流式实体是不可重复生成的。
– 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,HttpEntity#getContentEncoding()方法用来读取这个信息。如果头部信息不可用,那么就返回长度-1。
当完成一个响应实体,那么保证所有实体内容已经被完全消耗是很重要的,所以连接可以安全的放回到连接池中,而且可以通过连接管理器对后续的请求重用连接。处 理这个操作的最方便的方法是调用HttpEntity#consumeContent()方法来消耗流中的任意可用内容。HttpClient探测到内容 流尾部已经到达后,会立即会自动释放低层连接,并放回到连接管理器。HttpEntity#consumeContent()方法调用多次也是安全的。
HttpClient为很多公用的数据容器,比如字符串,字节数组,输入流和文件提供了一些类:StringEntity,ByteArrayEntity,InputStreamEntity和FileEntity。
File file = new File("somefile.txt"); FileEntity entity = new FileEntity(file, "text/plain; charset="UTF-8""); HttpPost httppost = new HttpPost("http://localhost/action.do"); httppost.setEntity(entity);
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, "UTF-8"); HttpPost httppost = new HttpPost("http://localhost/handler.do"); httppost.setEntity(entity);
内容分块
设置 HttpEntity#setChunked()方法为true是通知HttpClient分块编码的首选。请注意HttpClient将会使用标识作为提示。当使用的HTTP协议版本,如HTTP/1.0版本,不支持分块编码时,这个值会被忽略。
StringEntity entity = new StringEntity("important message", "text/plain; charset="UTF-8""); entity.setChunked(true); HttpPost httppost = new HttpPost("http://localhost/acrtion.do"); httppost.setEntity(entity);
响应控制器
控制响应的最简便和最方便的方式是使用ResponseHandler接口。这个放完完全减轻了用户关于连接管理的担心。当使用ResponseHandler时,HttpClient将会自动关注并保证释放连接到连接管理器中去,而不管请求执行是否成功或引发了异常。
HttpClient httpclient = new DefaultHttpClient(); HttpGet httpget = new HttpGet("http://localhost/"); ResponseHandler<byte[]> handler = new ResponseHandler<byte[]>() { public byte[] handleResponse( HttpResponse response) throws ClientProtocolException, IOException { HttpEntity entity = response.getEntity(); if (entity != null) { return EntityUtils.toByteArray(entity); } else { return null; } } }; byte[] response = httpclient.execute(httpget, handler);
HTTP执行的环境
最 初,HTTP是被设计成无状态的,面向请求-响应的协议。然而,应用程序经常需要通过一些逻辑相关的请求-响应交换来持久状态信息。HttpClient允许HTTP请求在一个特定的执行环境中来执行,简称为HTTP上下文。HTTP上下文功能和java.util.Map很相似。 它仅仅是任意命名参数值的集合。
在HTTP请求执行的这一过程中,HttpClient添加了下列属性到执行上下文中:
‘http.connection’:HttpConnection实例代表了连接到目标服务器的真实连接。
‘http.target_host’:HttpHost实例代表了连接目标。
‘http.proxy_host’:如果使用了,HttpHost实例代表了代理连接。
‘http.request’:HttpRequest实例代表了真实的HTTP请求。
‘http.response’:HttpResponse实例代表了真实的HTTP响应。
‘http.request_sent’:java.lang.Boolean对象代表了暗示真实请求是否被完全传送到目标连接的标识。
比如,为了决定最终的重定向目标,在请求执行之后,可以检查http.target_host属性的值:
DefaultHttpClient httpclient = new DefaultHttpClient(); HttpContext localContext = new BasicHttpContext(); HttpGet httpget = new HttpGet("http://www.google.com/"); HttpResponse response = httpclient.execute(httpget, localContext); HttpHost target = (HttpHost) localContext.getAttribute( ExecutionContext.HTTP_TARGET_HOST); System.out.println("Final target: " + target); HttpEntity entity = response.getEntity(); if (entity != null) { entity.consumeContent(); }
幂等的方法
HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等操作对于代理和缓存来说具有“友好性”,因为幂等操作的额外执行不会对二者产生危害性后果(除了带宽浪费)。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。
异常自动恢复
默认情况下,HttpClient会试图自动从I/O异常中恢复。默认的自动恢复机制是受很少一部分已知的异常是安全的这个限制。
HttpClient不会从任意逻辑或HTTP协议错误(那些是从HttpException类中派生出的)中恢复的。
HttpClient将会自动重新执行那么假设是幂等的方法。
HttpClient将会自动重新执行那些由于运输异常失败,而HTTP请求仍然被传送到目标服务器(也就是请求没有完全被送到服务器)失败的方法。
请求重试处理
为了开启自定义异常恢复机制,应该提供一个HttpRequestRetryHandler接口的实现。
DefaultHttpClient httpclient = new DefaultHttpClient(); HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() { public boolean retryRequest(IOException exception, int executionCount,HttpContext context) { if (executionCount >= 5) { // 如果超过最大重试次数,那么就不要继续了 return false; } if (exception instanceof NoHttpResponseException) { // 如果服务器丢掉了连接,那么就重试 return true; } if (exception instanceof SSLHandshakeException) { // 不要重试SSL握手异常 return false; } HttpRequest request = (HttpRequest) context.getAttribute( ExecutionContext.HTTP_REQUEST); boolean idempotent = !(request instanceof HttpEntityEnclosingRequest); if (idempotent) { // 如果请求被认为是幂等的,那么就重试 return true; } return false; } }; httpclient.setHttpRequestRetryHandler(myRetryHandler);
中止请求
被HttpClient执行的HTTP请求可以在执行的任意阶段通过调用HttpUriRequest#abort()方 法而中止。这个方法是线程安全的,而且可以从任意线程中调用。
HTTP参数
在 HTTP请求执行过程中,HttpRequest对象的HttpParams是和用于执行请求的HttpClient实例的HttpParams联系在一起的。这使得设置在HTTP请求级别的参数优先于设置在HTTP客户端级别的HttpParams。推荐的做法是设置普通参数对所有的在HTTP客户端级别的HTTP请求共享,而且可以选择性重写具体在HTTP请求级别的参数。
DefaultHttpClient httpclient = new DefaultHttpClient(); httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION,HttpVersion.HTTP_1_0); httpclient.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET,"UTF-8"); HttpGet httpget = new HttpGet("http://www.google.com/"); httpget.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION,HttpVersion.HTTP_1_1); httpget.getParams().setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE,Boolean.FALSE); httpclient.addRequestInterceptor(new HttpRequestInterceptor() { public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { System.out.println(request.getParams().getParameter( CoreProtocolPNames.PROTOCOL_VERSION)); System.out.println(request.getParams().getParameter( CoreProtocolPNames.HTTP_CONTENT_CHARSET)); System.out.println(request.getParams().getParameter( CoreProtocolPNames.USE_EXPECT_CONTINUE)); System.out.println(request.getParams().getParameter( CoreProtocolPNames.STRICT_TRANSFER_ENCODING)); } });
不少了,后文待续…
分享到: | |
没有评论