tornado平台中使用微信证书退款(https证书退款)( [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed解决)

 python  tornado平台中使用微信证书退款(https证书退款)( [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed解决)已关闭评论
9月 272016
 

使用tornado的httpclient做微信支付开发时,如果需要调用退款等需要证书验证的api时,可以使用下面的函数做证书验证:
(微信提供下载的证书文件有: apiclient_cert.p12, apiclient_cert.pem,apiclient_key.pem,rootca.pem)

from tornado import httpclient 
def get_response_httpclient_withcert(url,body,method, header = None, client_key=None, client_cert=None, ca_certs=None):
    http_client = httpclient.HTTPClient()
    request = httpclient.HTTPRequest(url, headers = header, body=body, method=method, client_key=client_key, client_cert=client_cert, connect_timeout=20.0, request_timeout=20.0)

    #如果出现类似“xxxxxx/tornado/iostream.py[line:1276] WARNING :SSL Error on 14 (‘x.x.x.x.’, 443): [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)”这样的错误,可以将ca_certs的参数也带上(即带上rootca.pem),使用下面的语句: 
    #request = httpclient.HTTPRequest(url, headers = header, body=body, method=method, client_key=client_key,     client_cert=client_cert, ca_certs=ca_certs, connect_timeout=20.0, request_timeout=20.0) 
    response = http_client.fetch(request)
    return response.body

调用举例:
module_path = os.path.dirname(__file__) 
client_cert_path = os.path.abspath(os.path.join(module_path, “../keys/apiclient_cert.pem”))
client_key_path = os.path.abspath(os.path.join(module_path, “../keys/apiclient_key.pem”))
 ca_certs_path = os.path.abspath(os.path.join(module_path, “../keys/rootca_wx.pem”))
resp = webRequrestUtil.get_response_httpclient_withcert(‘https://api.mch.weixin.qq.com/secapi/pay/refund’, “<xml> xxxxxxxxx</xml>”, “POST”, client_key=client_key_path, client_cert=client_cert_path, ca_certs=ca_certs_path)

 

5月 042015
 
 
如果PostMethod提交的是中文字符,需要加上相应的编码格式:  post.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=utf-8");  
如果GetMethod提交的参数有中文字符,需要先转换成utf-8格式:  URLEncoder.encode("中文", "utf-8"); 
测试代码如下:
package httpclient;

import java.io.IOException;
import java.net.URLEncoder;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;

public class HttpClientTest {

	public static void main(String[] args) throws Exception{
		String url = "/xx.jsp/xxxx";
		String host = "www.xxx.cn";
		String param = "city="+URLEncoder.encode("测试", "utf-8")+"&userID=";
		HttpClient httpClient = new HttpClient();
		httpClient.getHostConfiguration().setHost(host, 80, "http");

		HttpMethod method = getMethod(url, param);
		//HttpMethod method = postMethod(url);

		httpClient.executeMethod(method);

		String response = method.getResponseBodyAsString();
		//String response = new String(method.getResponseBodyAsString().getBytes("ISO-8859-1"));
		System.out.println(response);
	}

	private static HttpMethod getMethod(String url,String param) throws IOException{
		GetMethod get = new GetMethod(url+"?"+param);
		get.releaseConnection();
		return get;
	}

	private static HttpMethod postMethod(String url) throws IOException{
		PostMethod post = new PostMethod(url);
		post.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=utf-8"); 
		NameValuePair[] param = { new NameValuePair("city","测试"),
				new NameValuePair("userID","")} ;
    	        post.setRequestBody(param);
    	        post.releaseConnection();
		return post;
	}
}

10月 142014
 

1.1. 请求执行

HttpClient最基本的功能就是执行Http方法。一个Http方法的执行涉及到一个或者多个Http请求/Http响应的交互,通常这个过程都会自动被HttpClient处理,对用户透明。用户只需要提供Http请求对象,HttpClient就会将http请求发送给目标服务器,并且接收服务器的响应,如果http请求执行不成功,httpclient就会抛出异样。

下面是个很简单的http请求执行的例子:

CloseableHttpClient httpclient = HttpClients.createDefault();
    HttpGet httpget = new HttpGet("http://www.yeetrack.com/");
    CloseableHttpResponse response = httpclient.execute(httpget); try {
        <...>
    } finally {
        response.close();
    }

1.1.1. HTTP请求

所有的Http请求都有一个请求列(request line),包括方法名、请求的URI和Http版本号。

HttpClient支持HTTP/1.1这个版本定义的所有Http方法:GET,HEAD,POST,PUT,DELETE,’TRACE和OPTIONS。对于每一种http方法,HttpClient都定义了一个相应的类:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace和HttpOpquertions`。

Request-URI即统一资源定位符,用来标明Http请求中的资源。Http request URIS包含协议名、主机名、主机端口(可选)、资源路径、query(可选)和片段信息(可选)。

HttpGet httpget = new HttpGet(
 "http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");

HttpClient提供URIBuilder工具类来简化URIs的创建和修改过程。

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());

上述代码会在控制台输出:

http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=

1.1.2. HTTP响应

服务器收到客户端的http请求后,就会对其进行解析,然后把响应发给客户端,这个响应就是HTTP response.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

1.1.3. 消息头

一个Http消息可以包含一系列的消息头,用来对http消息进行描述,比如消息长度,消息类型等等。HttpClient提供了API来获取、添加、修改、遍历消息头。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
    response.addHeader("Set-Cookie", "c1=a; path=/; domain=yeetrack.com");
    response.addHeader("Set-Cookie", "c2=b; path="/", c3=c; domain="yeetrack.com"");
    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=yeetrack.com
    Set-Cookie: c2=b; path="/", c3=c; domain="yeetrack.com"
    2

最有效的获取指定类型的消息头的方法还是使用HeaderIterator接口。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
    response.addHeader("Set-Cookie", "c1=a; path=/; domain=yeetrack.com");
    response.addHeader("Set-Cookie", "c2=b; path="/", c3=c; domain="yeetrack.com""); HeaderIterator it = response.headerIterator("Set-Cookie"); while (it.hasNext()) { System.out.println(it.next()); }

上述代码会在控制台输出:

Set-Cookie: c1=a; path=/; domain=yeetrack.com
    Set-Cookie: c2=b; path="/", c3=c; domain="yeetrack.com"

HeaderIterator也提供非常便捷的方式,将Http消息解析成单独的消息头元素。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
    response.addHeader("Set-Cookie", "c1=a; path=/; domain=yeetrack.com");
    response.addHeader("Set-Cookie", "c2=b; path="/", c3=c; domain="yeetrack.com"");

    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=yeetrack.com
    c2 = b
    path=/
    c3 = c
    domain=yeetrack.com

1.1.4. HTTP实体

Http消息可以携带http实体,这个http实体既可以是http请求,也可以是http响应的。Http实体,可以在某些http请求或者响应中发现,但不是必须的。Http规范中定义了两种包含请求的方法:POST和PUT。HTTP响应一般会包含一个内容实体。当然这条规则也有异常情况,如Head方法的响应,204没有内容,304没有修改或者205内容资源重置。

HttpClient根据来源的不同,划分了三种不同的Http实体内容。

  • streamed: Http内容是通过流来接受或者generated on the fly。特别是,streamed这一类包含从http响应中获取的实体内容。一般说来,streamed实体是不可重复的。
  • self-contained: The content is in memory or obtained by means that are independent from a connection or other entity。self-contained类型的实体内容通常是可重复的。这种类型的实体通常用于关闭http请求。
  • wrapping: 这种类型的内容是从另外的http实体中获取的。

当从Http响应中读取内容时,上面的三种区分对于连接管理器来说是非常重要的。请求类的实体通常由应用程序创建,由HttpClient发送给服务器,在请求类的实体中,streamed和self-contained两种类型的区别就不重要了。在这种情况下,一般认为不可重复的实体是streamed类型,可重复的实体时self-contained。

1.1.4.1. 可重复的实体

一个实体是可重复的,也就是说它的包含的内容可以被多次读取。这种多次读取只有self contained(自包含)的实体能做到(比如ByteArrayEntity或者StringEntity)。

1.1.4.2. 使用Http实体

由于一个Http实体既可以表示二进制内容,又可以表示文本内容,所以Http实体要支持字符编码(为了支持后者,即文本内容)。

当需要执行一个完整内容的Http请求或者Http请求已经成功,服务器要发送响应到客户端时,Http实体就会被创建。

如果要从Http实体中读取内容,我们可以利用HttpEntity类的getContent方法来获取实体的输入流(java.io.InputStream),或者利用HttpEntity类的writeTo(OutputStream)方法来获取输出流,这个方法会把所有的内容写入到给定的流中。
当实体类已经被接受后,我们可以利用HttpEntity类的getContentType()和getContentLength()方法来读取Content-Type和Content-Length两个头消息(如果有的话)。由于Content-Type包含mime-types的字符编码,比如text/plain或者text/html,HttpEntity类的getContentEncoding()方法就是读取这个编码的。如果头信息不存在,getContentLength()会返回-1,getContentType()会返回NULL。如果Content-Type信息存在,就会返回一个Header类。

当为发送消息创建Http实体时,需要同时附加meta信息。

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. 确保底层的资源连接被释放

为了确保系统资源被正确地释放,我们要么管理Http实体的内容流、要么关闭Http响应。

CloseableHttpClient httpclient =  HttpClients.createDefault();
    HttpGet httpget = new HttpGet("http://www.yeetrack.com/");
    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();
    }

关闭Http实体内容流和关闭Http响应的区别在于,前者通过消耗掉Http实体内容来保持相关的http连接,然后后者会立即关闭、丢弃http连接。

请注意HttpEntity的writeTo(OutputStream)方法,当Http实体被写入到OutputStream后,也要确保释放系统资源。如果这个方法内调用了HttpEntity的getContent()方法,那么它会有一个java.io.InpputStream的实例,我们需要在finally中关闭这个流。

但是也有这样的情况,我们只需要获取Http响应内容的一小部分,而获取整个内容并、实现连接的可重复性代价太大,这时我们可以通过关闭响应的方式来关闭内容输入、输出流。

CloseableHttpClient httpclient = HttpClients.createDefault();
    HttpGet httpget = new HttpGet("http://www.yeetrack.com/");
    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. 消耗HTTP实体内容

HttpClient推荐使用HttpEntity的getConent()方法或者HttpEntity的writeTo(OutputStream)方法来消耗掉Http实体内容。HttpClient也提供了EntityUtils这个类,这个类提供一些静态方法可以更容易地读取Http实体的内容和信息。和以java.io.InputStream流读取内容的方式相比,EntityUtils提供的方法可以以字符串或者字节数组的形式读取Http实体。但是,强烈不推荐使用EntityUtils这个类,除非目标服务器发出的响应是可信任的,并且http响应实体的长度不会过大。

CloseableHttpClient httpclient = HttpClients.createDefault();
    HttpGet httpget = new HttpGet("http://www.yeetrack.com/");
    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();
    }

有些情况下,我们希望可以重复读取Http实体的内容。这就需要把Http实体内容缓存在内存或者磁盘上。最简单的方法就是把Http Entity转化成BufferedHttpEntity,这样就把原Http实体的内容缓冲到了内存中。后面我们就可以重复读取BufferedHttpEntity中的内容。

CloseableHttpResponse response = <...>
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        entity = new BufferedHttpEntity(entity);
    }

1.1.7. 创建HTTP实体内容

HttpClient提供了一个类,这些类可以通过http连接高效地输出Http实体内容。(原文是HttpClient provides several classes that can be used to efficiently stream out content though HTTP connections.感觉thought应该是throught)HttpClient提供的这几个类涵盖的常见的数据类型,如String,byte数组,输入流,和文件类型: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://www.yeetrack.com/action.do");
    httppost.setEntity(entity);

请注意由于InputStreamEntity只能从下层的数据流中读取一次,所以它是不能重复的。推荐,通过继承HttpEntity这个自包含的类来自定义HttpEntity类,而不是直接使用InputStreamEntity这个类。FileEntity就是一个很好的起点(FileEntity就是继承的HttpEntity)。

1.7.1.1. HTML表单

很多应用程序需要模拟提交Html表单的过程,举个例子,登陆一个网站或者将输入内容提交给服务器。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://www.yeetrack.com/handler.do");
    httppost.setEntity(entity);

UrlEncodedFormEntity实例会使用所谓的Url编码的方式对我们的参数进行编码,产生的结果如下:

param1=value1&param2=value2

1.1.7.2. 内容分块

一般来说,推荐让HttpClient自己根据Http消息传递的特征来选择最合适的传输编码。当然,如果非要手动控制也是可以的,可以通过设置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://www.yeetrack.com/acrtion.do");
    httppost.setEntity(entity);

1.1.8. RESPONSE HANDLERS

最简单也是最方便的处理http响应的方法就是使用ResponseHandler接口,这个接口中有handleResponse(HttpResponse response)方法。使用这个方法,用户完全不用关心http连接管理器。当使用ResponseHandler时,HttpClient会自动地将Http连接释放给Http管理器,即使http请求失败了或者抛出了异常。

CloseableHttpClient httpclient = HttpClients.createDefault();
    HttpGet httpget = new HttpGet("http://www.yeetrack.com/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);
        }
    };
    //设置responseHandler,当执行http方法时,就会返回MyJsonObject对象。
    MyJsonObject myjson = client.execute(httpget, rh);

1.2. HttpClient接口

对于Http请求执行过程来说,HttpClient的接口有着必不可少的作用。HttpClient接口没有对Http请求的过程做特别的限制和详细的规定,连接管理、状态管理、授权信息和重定向处理这些功能都单独实现。这样用户就可以更简单地拓展接口的功能(比如缓存响应内容)。

一般说来,HttpClient实际上就是一系列特殊的handler或者说策略接口的实现,这些handler(测试接口)负责着处理Http协议的某一方面,比如重定向、认证处理、有关连接持久性和keep alive持续时间的决策。这样就允许用户使用自定义的参数来代替默认配置,实现个性化的功能。

ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {

        @Override
        public long getKeepAliveDuration(
            HttpResponse response,
            HttpContext context) {
                long keepAlive = super.getKeepAliveDuration(response, context);
                if (keepAlive == -1) {
                    //如果服务器没有设置keep-alive这个参数,我们就把它设置成5秒
                    keepAlive = 5000;
                }
                return keepAlive;
        }

    };
    //定制我们自己的httpclient
    CloseableHttpClient httpclient = HttpClients.custom()
            .setKeepAliveStrategy(keepAliveStrat)
            .build();

1.2.1.HTTPCLIENT的线程安全性

HttpClient已经实现了线程安全。所以希望用户在实例化HttpClient时,也要支持为多个请求使用。

1.2.2.HTTPCLIENT的内存分配

当一个CloseableHttpClient的实例不再被使用,并且它的作用范围即将失效,和它相关的连接必须被关闭,关闭方法可以调用CloseableHttpClient的close()方法。

CloseableHttpClient httpclient = HttpClients.createDefault();
    try {
        <...>
    } finally {
        //关闭连接
        httpclient.close();
    }

1.3.Http执行上下文

最初,Http被设计成一种无状态的、面向请求-响应的协议。然而,在实际使用中,我们希望能够在一些逻辑相关的请求-响应中,保持状态信息。为了使应用程序可以保持Http的持续状态,HttpClient允许http连接在特定的Http上下文中执行。如果在持续的http请求中使用了同样的上下文,那么这些请求就可以被分配到一个逻辑会话中。HTTP上下文就和一个java.util.Map<String, Object>功能类似。它实际上就是一个任意命名的值的集合。应用程序可以在Http请求执行前填充上下文的值,也可以在请求执行完毕后检查上下文。

HttpContext可以包含任意类型的对象,因此如果在多线程中共享上下文会不安全。推荐每个线程都只包含自己的http上下文。

在Http请求执行的过程中,HttpClient会自动添加下面的属性到Http上下文中:

  • HttpConnection的实例,表示客户端与服务器之间的连接
  • HttpHost的实例,表示要连接的木包服务器
  • HttpRoute的实例,表示全部的连接路由
  • HttpRequest的实例,表示Http请求。在执行上下文中,最终的HttpRequest对象会代表http消息的状态。Http/1.0和Http/1.1都默认使用相对的uri。但是如果使用了非隧道模式的代理服务器,就会使用绝对路径的uri。
  • HttpResponse的实例,表示Http响应
  • java.lang.Boolean对象,表示是否请求被成功的发送给目标服务器
  • RequestConfig对象,表示http request的配置信息
  • java.util.List<Uri>对象,表示Http响应中的所有重定向地址

我们可以使用HttpClientContext这个适配器来简化和上下文交互的过程。

HttpContext context = <...>
    HttpClientContext clientContext = HttpClientContext.adapt(context);
    HttpHost target = clientContext.getTargetHost();
    HttpRequest request = clientContext.getRequest();
    HttpResponse response = clientContext.getResponse();
    RequestConfig config = clientContext.getRequestConfig();

同一个逻辑会话中的多个Http请求,应该使用相同的Http上下文来执行,这样就可以自动地在http请求中传递会话上下文和状态信息。
在下面的例子中,我们在开头设置的参数,会被保存在上下文中,并且会应用到后续的http请求中(源英文中有个拼写错误)。

CloseableHttpClient httpclient = HttpClients.createDefault();
    RequestConfig requestConfig = RequestConfig.custom()
            .setSocketTimeout(1000)
            .setConnectTimeout(1000)
            .build();

    HttpGet httpget1 = new HttpGet("http://www.yeetrack.com/1");
    httpget1.setConfig(requestConfig);
    CloseableHttpResponse response1 = httpclient.execute(httpget1, context);
    try {
        HttpEntity entity1 = response1.getEntity();
    } finally {
        response1.close();
    }
    //httpget2被执行时,也会使用httpget1的上下文
    HttpGet httpget2 = new HttpGet("http://www.yeetrack.com/2");
    CloseableHttpResponse response2 = httpclient.execute(httpget2, context);
    try {
        HttpEntity entity2 = response2.getEntity();
    } finally {
        response2.close();
    }

1.4. 异常处理

HttpClient会被抛出两种类型的异常,一种是java.io.IOException,当遇到I/O异常时抛出(socket超时,或者socket被重置);另一种是HttpException,表示Http失败,如Http协议使用不正确。通常认为,I/O错误时不致命、可修复的,而Http协议错误是致命了,不能自动修复的错误。

1.4.1.HTTP传输安全

Http协议不能满足所有类型的应用场景,我们需要知道这点。Http是个简单的面向协议的请求/响应的协议,当初它被设计用来支持静态或者动态生成的内容检索,之前从来没有人想过让它支持事务性操作。例如,Http服务器成功接收、处理请求后,生成响应消息,并且把状态码发送给客户端,这个过程是Http协议应该保证的。但是,如果客户端由于读取超时、取消请求或者系统崩溃导致接收响应失败,服务器不会回滚这一事务。如果客户端重新发送这个请求,服务器就会重复的解析、执行这个事务。在一些情况下,这会导致应用程序的数据损坏和应用程序的状态不一致。

即使Http当初设计是不支持事务操作,但是它仍旧可以作为传输协议为某些关键程序提供服务。为了保证Http传输层的安全性,系统必须保证应用层上的http方法的幂等性(To ensure HTTP transport layer safety the system must ensure the idempotency of HTTP methods on the application layer)。

1.4.2.方法的幂等性

HTTP/1.1规范中是这样定义幂等方法的,Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request。用其他话来说,应用程序需要正确地处理同一方法多次执行造成的影响。添加一个具有唯一性的id就能避免重复执行同一个逻辑请求,问题解决。

请知晓,这个问题不只是HttpClient才会有,基于浏览器的应用程序也会遇到Http方法不幂等的问题。

HttpClient默认把非实体方法get、head方法看做幂等方法,把实体方法post、put方法看做非幂等方法。

1.4.3.异常自动修复

默认情况下,HttpClient会尝试自动修复I/O异常。这种自动修复仅限于修复几个公认安全的异常。

  • HttpClient不会尝试修复任何逻辑或者http协议错误(即从HttpException衍生出来的异常)。
  • HttpClient会自动再次发送幂等的方法(如果首次执行失败。
  • HttpClient会自动再次发送遇到transport异常的方法,前提是Http请求仍旧保持着连接(例如http请求没有全部发送给目标服务器,HttpClient会再次尝试发送)。

1.4.4.请求重试HANDLER

如果要自定义异常处理机制,我们需要实现HttpRequestRetryHandler接口。

HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {

        public boolean retryRequest(
                IOException exception,
                int executionCount,
                HttpContext context) {
            if (executionCount >= 5) {
                // 如果已经重试了5次,就放弃
                return false;
            }
            if (exception instanceof InterruptedIOException) {
                // 超时
                return false;
            }
            if (exception instanceof UnknownHostException) {
                // 目标服务器不可达
                return false;
            }
            if (exception instanceof ConnectTimeoutException) {
                // 连接被拒绝
                return false;
            }
            if (exception instanceof SSLException) {
                // ssl握手异常
                return false;
            }
            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
            if (idempotent) {
                // 如果请求是幂等的,就再次尝试
                return true;
            }
            return false;
        }

    };
    CloseableHttpClient httpclient = HttpClients.custom()
            .setRetryHandler(myRetryHandler)
            .build();

1.5.终止请求

有时候由于目标服务器负载过高或者客户端目前有太多请求积压,http请求不能在指定时间内执行完毕。这时候终止这个请求,释放阻塞I/O的进程,就显得很必要。通过HttpClient执行的Http请求,在任何状态下都能通过调用HttpUriRequest的abort()方法来终止。这个方法是线程安全的,并且能在任何线程中调用。当Http请求被终止了,本线程(即使现在正在阻塞I/O)也会通过抛出一个InterruptedIOException异常,来释放资源。

1.6. Http协议拦截器

HTTP协议拦截器是一种实现一个特定的方面的HTTP协议的代码程序。通常情况下,协议拦截器会将一个或多个头消息加入到接受或者发送的消息中。协议拦截器也可以操作消息的内容实体—消息内容的压缩/解压缩就是个很好的例子。通常,这是通过使用“装饰”开发模式,一个包装实体类用于装饰原来的实体来实现。一个拦截器可以合并,形成一个逻辑单元。

协议拦截器可以通过共享信息协作——比如处理状态——通过HTTP执行上下文。协议拦截器可以使用Http上下文存储一个或者多个连续请求的处理状态。

通常,只要拦截器不依赖于一个特定状态的http上下文,那么拦截执行的顺序就无所谓。如果协议拦截器有相互依赖关系,必须以特定的顺序执行,那么它们应该按照特定的顺序加入到协议处理器中。

协议处理器必须是线程安全的。类似于servlets,协议拦截器不应该使用变量实体,除非访问这些变量是同步的(线程安全的)。

下面是个例子,讲述了本地的上下文时如何在连续请求中记录处理状态的:

CloseableHttpClient httpclient = HttpClients.custom()
            .addInterceptorLast(new HttpRequestInterceptor() {

                public void process(
                        final HttpRequest request,
                        final HttpContext context) throws HttpException, IOException {
                        //AtomicInteger是个线程安全的整型类
                    AtomicInteger count = (AtomicInteger) context.getAttribute("count");
                    request.addHeader("Count", Integer.toString(count.getAndIncrement()));
                }

            })
            .build();

    AtomicInteger count = new AtomicInteger(1);
    HttpClientContext localContext = HttpClientContext.create();
    localContext.setAttribute("count", count);

    HttpGet httpget = new HttpGet("http://www.yeetrack.com/");
    for (int i = 0; i < 10; i++) {
        CloseableHttpResponse response = httpclient.execute(httpget, localContext);
        try {
            HttpEntity entity = response.getEntity();
        } finally {
            response.close();
        }
    }

上面代码在发送http请求时,会自动添加Count这个header,可以使用wireshark抓包查看。

1.7.1. 重定向处理

HttpClient会自动处理所有类型的重定向,除了那些Http规范明确禁止的重定向。See Other (status code 303) redirects on POST and PUT requests are converted to GET requests as required by the HTTP specification. 我们可以使用自定义的重定向策略来放松Http规范对Post方法重定向的限制。

//LaxRedirectStrategy可以自动重定向所有的HEAD,GET,POST请求,解除了http规范对post请求重定向的限制。
    LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
    CloseableHttpClient httpclient = HttpClients.custom()
            .setRedirectStrategy(redirectStrategy)
            .build();

HttpClient在请求执行过程中,经常需要重写请求的消息。 HTTP/1.0和HTTP/1.1都默认使用相对的uri路径。同样,原始的请求可能会被一次或者多次的重定向。最终结对路径的解释可以使用最初的请求和上下文。URIUtils类的resolve方法可以用于将拦截的绝对路径构建成最终的请求。这个方法包含了最后一个分片标识符或者原始请求。

CloseableHttpClient httpclient = HttpClients.createDefault();
    HttpClientContext context = HttpClientContext.create();
    HttpGet httpget = new HttpGet("http://www.yeetrack.com:8080/");
    CloseableHttpResponse response = httpclient.execute(httpget, context);
    try {
        HttpHost target = context.getTargetHost();
        List<URI> redirectLocations = context.getRedirectLocations();
        URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations);
        System.out.println("Final HTTP location: " + location.toASCIIString());
        // 一般会取得一个绝对路径的uri
    } finally {
        response.close();

}

摘自:http://www.yeetrack.com/?p=773

4月 182014
 

自己使用httpclient3.1同样碰到相同问题,推荐。

Httpclient超时

背景:

网站这边多次因为httpclient调用超时时间没设置好导致关掉,影响非常不好,而且问题重复出现,查看网络,没有比较明确介绍httpclient所有超时相关的设置(大部分只提到连接超时(connectintimeout),读超时(sockettimeout),对连接池超时提到的比较少),因此本文对超时这块最介绍,其他功能性内容,apache官方提供了比较详细的解析,这里不做讨论。具体可见:http://hc.apache.org/httpcomponents-client-a/tutorial/html/index.html

网站这边用的是:

<dependency>

<groupId>commons-httpclient</groupId>

<artifactId>commons-httpclient</artifactId>

<version>3.1</version>

</dependency>

配置:

private final static MultiThreadedHttpConnectionManager manager new MultiThreadedHttpConnectionManager();

private final static HttpClient httpclient new HttpClient(manager);

httpclient.setConnectionTimeout(1000);

httpclient.setTimeout(1000);

现象:

页面无法打开,堆栈信息如下:

Name: trhead-142

State: WAITING onorg.apache.commons.httpclient.MultiThreadedHttpConnectionManager$ConnectionPool@69a4cb

Total blocked: 0 Total waited: 1

Stack trace:

java.lang.Object.wait(Native Method)

org.apache.commons.httpclient.MultiThreadedHttpConnectionManager.doGetConnection(MultiThreadedHttpConnectionManager.java:518)

org.apache.commons.httpclient.MultiThreadedHttpConnectionManager.getConnectionWithTimeout(MultiThreadedHttpConnectionManager.java:416)

org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:153)

org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)

org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323)

com.madding.test.MyRunnable.run(MyTest1.java:53)

java.lang.Thread.run(Thread.java:619)

大部分线程等待在:org.apache.commons.httpclient.MultiThreadedHttpConnectionManager.doGetConnection

分析:

问题:很明显连接池超时没设置,导致请求在线程池中等待,进而引起dubb无法处理其他请求。

解决:加httpclient.setHttpConnectionFactoryTimeout(1000);,设置连接池超时。

说明:废弃方法尽量不要用

进一步分析:

为什么没设置这个会导致请求等待:

MultiThreadedHttpConnectionManager代码在获取连接时去连接池取,而连接池在没设置超时timeToWait0,即一直处于等待状态,如果没有notify不会结束等待。

试用

<dependency>

<groupId>org.apache.httpcomponents</groupId>

<artifactId>httpclient</artifactId>

<version>4.2</version>

</dependency>

发现代码中即使不设置线程池超时,在高并发下也能正常访问,查看代码,发现是因为在默认没设置线程池超时时把连接超时的时间作为线程超时时间,代码如下:

而且httpclient4的代码结构相对3做了很大重构,且实现比较优雅,可以考虑在本地私服把4.x版本添加进来。

完整的超时可参考如下:

static PoolingClientConnectionManager connectionManager null;

static HttpClient httpclient = null;

static{

connectionManager =new PoolingClientConnectionManager();

connectionManager.setMaxTotal(1);

httpclient new DefaultHttpClient(connectionManager);

httpclient.getParams().setParameter(“http.socket.timeout”,1000);

httpclient.getParams().setParameter(“http.connection.timeout”,1000);

httpclient.getParams().setParameter(“http.connection-manager.timeout”,100000000L);

}

具体测试代码如下:

3.1:

package com.madding.test;

import org.apache.commons.httpclient.DefaultMethodRetryHandler;

import org.apache.commons.httpclient.HttpClient;

import org.apache.commons.httpclient.HttpStatus;

import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;

import org.apache.commons.httpclient.methods.GetMethod;

/**

*@author madding.lip

*/

public class MyHttpClientTest3_1 {

private final static MultiThreadedHttpConnectionManager manager new MultiThreadedHttpConnectionManager();

private final static HttpClient httpclient new HttpClient(manager);

public static void  main(String[] args) {

httpclient.getParams().setParameter(“http.socket.timeout”,1000);

httpclient.getParams().setParameter(“http.connection.timeout”,1000);

httpclient.getParams().setParameter(“http.connection-manager.timeout”,60*60L);

// httpclient.setConnectionTimeout(1000);

// httpclient.setTimeout(1000);

// httpclient.setHttpConnectionFactoryTimeout(1000);

for(inti = 0; true;i++) {

new Thread ( new MyRunnable(httpclient),“trhead-“+ i).start();

try{

Thread.sleep(100);

}catch(InterruptedException e) {

}

}

}

}

class MyRunnable implements Runnable {

HttpClient httpclient null;

MyRunnable(HttpClient client){

httpclient= client;

}

public void run() {

GetMethod getMethod =new GetMethod(“http://www.apache.org/”);

try{

DefaultMethodRetryHandler  retryHandler = new DefaultMethodRetryHandler();

retryHandler.setRetryCount(0);

getMethod.setMethodRetryHandler(retryHandler);

int statusCode =httpclient.executeMethod(getMethod);

if(statusCode == HttpStatus.SC_OK){

getMethod.getResponseBodyAsString();

}

}catch(Exception e) {

System.err.println(e);

}finally{

getMethod.releaseConnection();

}

}

}

4.2:

package com.madding.test;

import java.io.IOException;

import org.apache.http.HttpEntity;

import org.apache.http.HttpResponse;

import org.apache.http.client.ClientProtocolException;

import org.apache.http.client.HttpClient;

import org.apache.http.client.methods.HttpGet;

import org.apache.http.client.methods.HttpUriRequest;

import org.apache.http.impl.client.DefaultHttpClient;

import org.apache.http.impl.conn.PoolingClientConnectionManager;

import org.apache.http.util.EntityUtils;

/**

*@author madding.lip

*/

public class MyHttpClientTest4_2 {

static PoolingClientConnectionManager connectionManager null;

static HttpClient httpclient =null;

static{

connectionManager new PoolingClientConnectionManager();

connectionManager.setMaxTotal(1);

httpclient new DefaultHttpClient(connectionManager);

httpclient.getParams().setParameter(“http.socket.timeout”,1000);

httpclient.getParams().setParameter(“http.connection.timeout”,1000);

httpclient.getParams().setParameter(“http.connection-manager.timeout”,100000000L);

}

public static void main(String[] args) {

for(inti = 0; true;i++) {

new Thread(new MyTest(httpclient),“trhead-“+ i).start();

try{

Thread.sleep(100);

}catch(InterruptedException e) {

}

}

}

}

class MyTest implements Runnable {

static HttpClient httpclient =null;

public MyTest(HttpClient hc){

httpclient= hc;

}

public void run() {

HttpUriRequest httpget =new HttpGet(“http://www.apache.org/”);

try{

HttpResponse response =httpclient.execute(httpget);

HttpEntity entity =response.getEntity();

System.out.println(“—————————————-“);

System.out.println(response.getStatusLine());

if(entity !=null){

System.out.println(“Responsecontent length: “ +entity.getContentLength());

EntityUtils.toString(entity);

//System.out.println(EntityUtils.toString(entity));

}

System.out.println(“—————————————-“);

}catch(ClientProtocolException e) {

System.err.println(e);

}catch(IOException e) {

System.err.println(e);

}finally{

if(httpget !=null){

httpget.abort();

}

}

}

}


转自:http://blog.csdn.net/renzy200/article/details/8554534

HttpClient 教程

 apache  HttpClient 教程已关闭评论
5月 272013
 

转自:http://www.cnblogs.com/loveyakamoz/archive/2011/07/21/2112804.html

超文本传输协议(HTTP)也许是当今互联网上使用的最重要的协议了。Web服务,有网络功能的设备和网络计算的发展,都持续扩展了HTTP协议的角色,超越了用户使用的Web浏览器范畴,同时,也增加了需要HTTP协议支持的应用程序的数量。

尽管java.net包提供了基本通过HTTP访问资源的功能,但它没有提供全面的灵活性和其它很多应用程序需要的功能。HttpClient就是寻求弥补这项空白的组件,通过提供一个有效的,保持更新的,功能丰富的软件包来实现客户端最新的HTTP标准和建议。

为扩展而设计,同时为基本的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运输无关的功能。

第一章 基础

1.1 执行请求

HttpClient最重要的功能是执行HTTP方法。一个HTTP方法的执行包含一个或多个HTTP请求/HTTP响应交换,通常由HttpClient的内部来处理。而期望用户提供一个要执行的请求对象,而HttpClient期望传输请求到目标服务器并返回对应的响应对象,或者当执行不成功时抛出异常。

很自然地,HttpClient API的主要切入点就是定义描述上述规约的HttpClient接口。

这里有一个很简单的请求执行过程的示例:

HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int l;
byte[] tmp = new byte[2048];
while ((l = instream.read(tmp)) != -1) {
}
}

1.1.1 HTTP请求

所有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提供很多工具方法来简化创建和修改执行URI。
URI也可以编程来拼装:
URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",
"q=httpclient&btnG=Google+Search&aq=f&oq=", null);
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());

输出内容为:

http://www.google.com/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);
System.out.println(httpget.getURI());

输出内容为:

http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=

1.1.2 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

1.1.3 处理报文头部

一个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");
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"

获得给定类型的所有头部信息最有效的方式是使用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());
}

输出内容为:

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, "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。响应通常期望包含一个内容实体。这个规则也有特例,比如HEAD方法的响应和204 No Content,304 Not Modified和205 Reset Content响应。

HttpClient根据其内容出自何处区分三种类型的实体:

  • streamed流式:内容从流中获得,或者在运行中产生。特别是这种分类包含从HTTP响应中获取的实体。流式实体是不可重复生成的。
  • self-contained自我包含式:内容在内存中或通过独立的连接或其它实体中获得。自我包含式的实体是可以重复生成的。这种类型的实体会经常用于封闭HTTP请求的实体。
  • wrapping包装式:内容从另外一个实体中获得。

当从一个HTTP响应中获取流式内容时,这个区别对于连接管理很重要。对于由应用程序创建而且只使用HttpClient发送的请求实体,流式和自我包含式的不同就不那么重要了。这种情况下,建议考虑如流式这种不能重复的实体,和可以重复的自我包含式实体。

1.1.4.1 重复实体

实体可以重复,意味着它的内容可以被多次读取。这就仅仅是自我包含式的实体了(像ByteArrayEntity或StringEntity)。

1.1.4.2 使用HTTP实体

因为一个实体既可以代表二进制内容又可以代表字符内容,它也支持字符编码(支持后者也就是字符内容)。

实体是当使用封闭内容执行请求,或当请求已经成功执行,或当响应体结果发功到客户端时创建的。

要从实体中读取内容,可以通过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,而对于内容类型返回NULL。如果头部信息Content-Type是可用的,那么就会返回一个Header对象。

当为一个传出报文创建实体时,这个元数据不得不通过实体创建器来提供。

StringEntity myEntity = new StringEntity("important message",
"UTF-8");
System.out.println(myEntity.getContentType());
System.out.println(myEntity.getContentLength());
System.out.println(EntityUtils.getContentCharSet(myEntity));
System.out.println(EntityUtils.toString(myEntity));
System.out.println(EntityUtils.toByteArray(myEntity).length);

输出内容为

Content-Type: text/plain; charset=UTF-8
17
UTF-8
important message
17

1.1.5 确保低级别资源释放

当完成一个响应实体,那么保证所有实体内容已经被完全消耗是很重要的,所以连接可以安全的放回到连接池中,而且可以通过连接管理器对后续的请求重用连接。处理这个操作的最方便的方法是调用HttpEntity#consumeContent()方法来消耗流中的任意可用内容。HttpClient探测到内容流尾部已经到达后,会立即会自动释放低层连接,并放回到连接管理器。HttpEntity#consumeContent()方法调用多次也是安全的。

也可能会有特殊情况,当整个响应内容的一小部分需要获取,消耗剩余内容而损失性能,还有重用连接的代价太高,则可以仅仅通过调用HttpUriRequest#abort()方法来中止请求。

HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int byteOne = instream.read();
int byteTwo = instream.read();
// Do not need the rest
httpget.abort();
}

连接不会被重用,但是由它持有的所有级别的资源将会被正确释放。

1.1.6 消耗实体内容

推荐消耗实体内容的方式是使用它的HttpEntity#getContent()或HttpEntity#writeTo(OutputStream)方法。HttpClient也自带EntityUtils类,这会暴露出一些静态方法,这些方法可以更加容易地从实体中读取内容或信息。代替直接读取java.io.InputStream,也可以使用这个类中的方法以字符串/字节数组的形式获取整个内容体。然而,EntityUtils的使用是强烈不鼓励的,除非响应实体源自可靠的HTTP服务器和已知的长度限制。

HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
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
}
}

在一些情况下可能会不止一次的读取实体。此时实体内容必须以某种方式在内存或磁盘上被缓冲起来。最简单的方法是通过使用BufferedHttpEntity类来包装源实体完成。这会引起源实体内容被读取到内存的缓冲区中。在其它所有方式中,实体包装器将会得到源实体。

HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
entity = new BufferedHttpEntity(entity);
}

1.1.7 生成实体内容

HttpClient提供一些类,它们可以用于生成通过HTTP连接获得内容的有效输出流。为了封闭实体从HTTP请求中获得的输出内容,那些类的实例可以和封闭如POST和PUT请求的实体相关联。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);

请注意InputStreamEntity是不可重复的,因为它仅仅能从低层数据流中读取一次内容。通常来说,我们推荐实现一个定制的HttpEntity类,这是自我包含式的,用来代替使用通用的InputStreamEntity。FileEntity也是一个很好的起点。

1.1.7.1 动态内容实体

通常来说,HTTP实体需要基于特定的执行上下文来动态地生成。通过使用EntityTemplate实体类和ContentProducer接口,HttpClient提供了动态实体的支持。内容生成器是按照需求生成它们内容的对象,将它们写入到一个输出流中。它们是每次被请求时来生成内容。所以用EntityTemplate创建的实体通常是自我包含而且可以重复的。

ContentProducer cp = new ContentProducer() {
public void writeTo(OutputStream outstream) throws IOException {
Writer writer = new OutputStreamWriter(outstream, "UTF-8");
writer.write("<response>");
writer.write(" <content>");
writer.write(" important stuff");
writer.write(" </content>");
writer.write("</response>");
writer.flush();
}
};
HttpEntity entity = new EntityTemplate(cp);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);
1.1.7.2 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);

UrlEncodedFormEntity实例将会使用URL编码来编码参数,生成如下的内容:

param1=value1&param2=value2

1.1.7.3 内容分块

通常,我们推荐让HttpClient选择基于被传递的HTTP报文属性的最适合的编码转换。这是可能的,但是,设置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);

1.1.8 响应控制器

控制响应的最简便和最方便的方式是使用ResponseHandler接口。这个放完完全减轻了用户关于连接管理的担心。当使用ResponseHandler时,HttpClient将会自动关注并保证释放连接到连接管理器中去,而不管请求执行是否成功或引发了异常。

HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://localhost/");
ResponseHandler<byte[]> handler = new ResponseHandler

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);

1.2 HTTP执行的环境

最初,HTTP是被设计成无状态的,面向请求-响应的协议。然而,真实的应用程序经常需要通过一些逻辑相关的请求-响应交换来持久状态信息。为了开启应用程序来维持一个过程状态,HttpClient允许HTTP请求在一个特定的执行环境中来执行,简称为HTTP上下文。如果相同的环境在连续请求之间重用,那么多种逻辑相关的请求可以参与到一个逻辑会话中。HTTP上下文功能和java.util.Map<String,Object>很相似。它仅仅是任意命名参数值的集合。应用程序可以在请求之前或在检查上下文执行完成之后来填充上下文属性。

在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();


}

输出内容为:

Final target: http://www.google.ch

1.3 异常处理

HttpClient能够抛出两种类型的异常:在I/O失败时,如套接字连接超时或被重置的java.io.IOException异常,还有标志HTTP请求失败的信号,如违反HTTP协议的HttpException异常。通常I/O错误被认为是非致命的和可以恢复的,而HTTP协议错误则被认为是致命的而且是不能自动恢复的。

1.3.1 HTTP运输安全

要理解HTTP协议并不是对所有类型的应用程序都适合的,这一点很重要。HTTP是一个简单的面向请求/响应的协议,最初被设计用来支持取回静态或动态生成的内容。它从未向支持事务性操作方向发展。比如,如果成功收到和处理请求,HTTP服务器将会考虑它的其中一部分是否完成,生成一个响应并发送一个状态码到客户端。如果客户端因为读取超时,请求取消或系统崩溃导致接收响应实体失败时,服务器不会试图回滚事务。如果客户端决定重新这个请求,那么服务器将不可避免地不止一次执行这个相同的事务。在一些情况下,这会导致应用数据损坏或者不一致的应用程序状态。

尽管HTTP从来都没有被设计来支持事务性处理,但它也能被用作于一个传输协议对关键的任务应用提供被满足的确定状态。要保证HTTP传输层的安全,系统必须保证HTTP方法在应用层的幂等性。

1.3.2 幂等的方法

HTTP/1.1 明确地定义了幂等的方法,描述如下

[方法也可以有“幂等”属性在那些(除了错误或过期问题)N的副作用>0的相同请求和独立的请求是相同的]

换句话说,应用程序应该保证准备着来处理多个相同方法执行的实现。这是可以达到的,比如,通过提供一个独立的事务ID和其它避免执行相同逻辑操作的方法。

请注意这个问题对于HttpClient是不具体的。基于应用的浏览器特别受和非幂等的HTTP方法相关的相同问题的限制。

HttpClient假设没有实体包含方法,比如GET和HEAD是幂等的,而实体包含方法,比如POST和PUT则不是。

1.3.3 异常自动恢复

默认情况下,HttpClient会试图自动从I/O异常中恢复。默认的自动恢复机制是受很少一部分已知的异常是安全的这个限制。

  • HttpClient不会从任意逻辑或HTTP协议错误(那些是从HttpException类中派生出的)中恢复的。
  • HttpClient将会自动重新执行那么假设是幂等的方法。
  • HttpClient将会自动重新执行那些由于运输异常失败,而HTTP请求仍然被传送到目标服务器(也就是请求没有完全被送到服务器)失败的方法。
  • HttpClient将会自动重新执行那些已经完全被送到服务器,但是服务器使用HTTP状态码(服务器仅仅丢掉连接而不会发回任何东西)响应时失败的方法。在这种情况下,假设请求没有被服务器处理,而应用程序的状态也没有改变。如果这个假设可能对于你应用程序的目标Web服务器来说不正确,那么就强烈建议提供一个自定义的异常处理器。

1.3.4 请求重试处理

为了开启自定义异常恢复机制,应该提供一个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);

1.4 中止请求

在一些情况下,由于目标服务器的高负载或客户端有很多活动的请求,那么HTTP请求执行会在预期的时间框内而失败。这时,就可能不得不过早地中止请求,解除封锁在I/O执行中的线程封锁。被HttpClient执行的HTTP请求可以在执行的任意阶段通过调用HttpUriRequest#abort()方法而中止。这个方法是线程安全的,而且可以从任意线程中调用。当一个HTTP请求被中止时,它的执行线程就封锁在I/O操作中了,而且保证通过抛出InterruptedIOException异常来解锁。

1.5 HTTP协议拦截器

HTTP协议拦截器是一个实现了特定HTPP协议方面的惯例。通常协议拦截器希望作用于一个特定头部信息上,或者一族收到报文的相关头部信息,或使用一个特定的头部或一族相关的头部信息填充发出的报文。协议拦截器也可以操纵包含在报文中的内容实体,透明的内容压缩/解压就是一个很好的示例。通常情况下这是由包装器实体类使用了“装饰者”模式来装饰原始的实体完成的。一些协议拦截器可以从一个逻辑单元中来结合。

协议拦截器也可以通过共享信息来共同合作-比如处理状态-通过HTTP执行上下文。协议拦截器可以使用HTTP内容来为一个或多个连续的请求存储一个处理状态。

通常拦截器执行的顺序不应该和它们基于的特定执行上下文状态有关。如果协议拦截器有相互依存关系,那么它们必须按特定顺序来执行,正如它们希望执行的顺序一样,它们应该在相同的序列中被加到协议处理器。

协议拦截器必须实现为线程安全的。和Servlet相似,协议拦截器不应该使用实例变量,除非访问的那些变量是同步的。

这个示例给出了本地内容在连续的请求中怎么被用于持久一个处理状态的:

DefaultHttpClient httpclient = new DefaultHttpClient();


HttpContext localContext = new BasicHttpContext();


AtomicInteger count = new AtomicInteger(1);


localContext.setAttribute("count", count);


httpclient.addRequestInterceptor(new HttpRequestInterceptor() {


public void process(final HttpRequest request,


final HttpContext context) throws HttpException, IOException {


AtomicInteger count = (AtomicInteger) context.getAttribute("count");


request.addHeader("Count", Integer.toString(count.getAndIncrement()));


}


});


HttpGet httpget = new HttpGet("http://localhost/");


for (int i = 0; i < 10; i++) {


HttpResponse response = httpclient.execute(httpget, localContext);


HttpEntity entity = response.getEntity();


if (entity != null) {


entity.consumeContent();


}


}

1.6 HTTP参数

HttpParams接口代表了定义组件运行时行为的一个不变的值的集合。很多情况下,HttpParams和HttpContext相似。二者之间的主要区别是它们在运行时使用的不同。这两个接口表示了对象的集合,它们被视作为访问对象值的键的Map,但是服务于不同的目的:

  • HttpParams旨在包含简单对象:整型,浮点型,字符串,集合,还有运行时不变的对象。
  • HttpParams希望被用在“一次写入-多处准备”模式下。HttpContext旨在包含很可能在HTTP报文处理这一过程中发生改变的复杂对象
  • HttpParams的目标是定义其它组件的行为。通常每一个复杂的组件都有它自己的HttpParams对象。HttpContext的目标是来表示一个HTTP处理的执行状态。通常相同的执行上下文在很多合作的对象中共享。

1.6.1 参数层次

在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));


}


});

输出内容为:

HTTP/1.1
UTF-8
false
null

1.6.2 HTTP参数bean

HttpParams接口允许在处理组件的配置上很大的灵活性。很重要的是,新的参数可以被引入而不会影响老版本的二进制兼容性。然而,和常规的Java bean相比,HttpParams也有一个缺点:HttpParams不能使用DI框架来组合。为了缓解这个限制,HttpClient包含了一些bean类,它们可以用来按顺序使用标准的Java eban惯例初始化HttpParams对象。

HttpParams params = new BasicHttpParams();
HttpProtocolParamBean paramsBean = new HttpProtocolParamBean(params);
paramsBean.setVersion(HttpVersion.HTTP_1_1);
paramsBean.setContentCharset("UTF-8");
paramsBean.setUseExpectContinue(true);
System.out.println(params.getParameter(
CoreProtocolPNames.PROTOCOL_VERSION));
System.out.println(params.getParameter(
CoreProtocolPNames.HTTP_CONTENT_CHARSET));
System.out.println(params.getParameter(
CoreProtocolPNames.USE_EXPECT_CONTINUE));
System.out.println(params.getParameter(
CoreProtocolPNames.USER_AGENT));

输出内容为:

HTTP/1.1
UTF-8
false
null

1.7 HTTP请求执行参数

这些参数会影响到请求执行的过程:

  • 'http.protocol.version':如果没有在请求对象中设置明确的版本信息,它就定义了使用的HTTP协议版本。这个参数期望得到一个ProtocolVersion类型的值。如果这个参数没有被设置,那么就使用HTTP/1.1。
  • 'http.protocol.element-charset':定义了编码HTTP协议元素的字符集。这个参数期望得到一个java.lang.String类型的值。如果这个参数没有被设置,那么就使用US-ASCII。
  • 'http.protocol.eontent-charset':定义了为每个内容主体编码的默认字符集。这个参数期望得到一个java.lang.String类型的值。如果这个参数没有被设置,那么就使用ISO-8859-1。
  • 'http.useragent':定义了头部信息User-Agent的内容。这个参数期望得到一个java.lang.String类型的值。如果这个参数没有被设置,那么HttpClient将会为它自动生成一个值。
  • 'http.protocol.strict-transfer-encoding':定义了响应头部信息中是否含有一个非法的Transfer-Encoding,都要拒绝掉。
  • 'http.protocol.expect-continue':为包含方法的实体激活Expect: 100-Continue握手。Expect: 100-Continue握手的目的是允许客户端使用请求体发送一个请求信息来决定源服务器是否希望在客户端发送请求体之前得到这个请求(基于请求头部信息)。Expect: 100-Continue握手的使用可以对需要目标服务器认证的包含请求的实体(比如POST和PUT)导致明显的性能改善。Expect: 100-Continue握手应该谨慎使用,因为它和HTTP服务器,不支持HTTP/1.1协议的代理使用会引起问题。这个参数期望得到一个java.lang.Boolean类型的值。如果这个参数没有被设置,那么HttpClient将会试图使用握手。
  • 'http.protocol.wait-for-continue':定义了客户端应该等待100-Continue响应最大的毫秒级时间间隔。这个参数期望得到一个java.lang.Integer类型的值。如果这个参数没有被设置,那么HttpClient将会在恢复请求体传输之前为确认等待3秒。
  •