William Hua的Blog

2009-05-23

利用Apache HTTPClient 4.x访问嘀咕API

Filed under: Android,嵌入式开发 — 标签:, , , , , , — William Hua @ 22:32

HTTPCommons
HttpClient是基于HttpCore实现的一个HTTP/1.1兼容的HTTP客户端,它提供了一系列可重用的客户端身份验证、HTTP状态保持、HTTP连接管理module。功能丰富的HTTPClient同时兼具出色的可扩展性和健壮性,目前已经成为了最为流行的Java HTTP客户端组件,为开发Web浏览器、Web Service客户端提供了很大的便利。

HttpClient(4.x)是Jakarta Commons HttpClient 3.x的继承,当前最新版本HttpClient 4.0-beta2。Android SDK在M5这个Milestone对HttpClient做了重大更新,开始捆绑HttpClient 4.x(当时还处于Alpha阶段)的包,而之前的M3则是捆绑了Jakarta Commons HttpClient 3.x。这一举动虽然在当时看起来太过激进,但是保证了在Android正式发布以后的API一致性。而嘀咕作为国内为博客的后起之秀,以众多好用的插件赢得了不少用户的青睐,这背后所依靠的则是它开放的API。看起来嘀咕API + HttpClient + Android的组合会非常有意思。

当然,实际上Android中的HttpClient在使用上与PC环境下并没有什么区别,我们只要记得在创建Project后给当前的Application设定Internet访问权限即可。

一般情况下,使用 HttpClient 需要以下5个步骤:
1. 创建 HttpClient 的实例
2. 创建某种连接方法的实例,在这里是最常见的是Get和Post
3. 调用第一步中创建好的HttpClient实例的execute方法,得到执行结果
4. 释放连接
5. 对得到后的内容进行处理

下面就结合代码和注释来说明

Get方法

1、创建HttpClient实例,调用DefaultHttpClient的默认构造函数,这里的DefaultHttpClient取代了HttpClient 3.x中的HttpClient,4.x中的HttpClient是作为一个接口存在。

DefaultHttpClient httpclient = new DefaultHttpClient();

2、创建HttpGet实例,把请求的URL地址传进去,比如我们想得到嘀咕的Public_Timeline,这个方法不需要身份验证。

HttpGet httpget = new HttpGet(“http://api.digu.com/statuses/public_timeline.xml?count=10&page=1”);

3、调用HttpClient实例的execute方法,对于执行结果为2XX的情况,BasicResponseHandler会自动把response body以String方式返回,对于3xx的response它会在内部自动重定向,而对于没有response body的情况,他会返回null。

ResponseHandler<String> responseHandler = new BasicResponseHandler();
String response = httpclient.execute(httpget, responseHandler);

对于execute可能抛出的IOException这个可恢复异常,请参考HttpRequestRetryHandler接口来解决

4、最后,释放httpClient资源

httpclient.getConnectionManager().shutdown();

需要身份验证的Get方法

在嘀咕API中,绝大多数是需要身份验证的,接下来我们就来看一看如何通过HttpClient调用对需要身份验证的嘀咕API。

我起初参考了HttpClient Example中的Client authentication方法来调用需要身份验证的嘀咕API,比如friends_timeline,但是无法成功,一直是ClientProtocolException和MalformedChallengeException。从Wireshark抓包的情况来分析,似乎是发出的Get请求包头中并没有包含Authentiction信息。由于网上的HOWTO基本也都是以3.x为主,我花了很多时间也没能找到这个Exmaple的问题在哪里。
No authentiction scheme
好在后来看了Preemptive BASIC authentication,至少有了一个功能正常的解决办法。

与Client authentication中的做法最大的不同就在于给HttpClient设置了一个请求拦截器(Request Interceptor),当Http请求即将被执行的时候,如果发现AuthScheme为null就尝试初始化。

// Add as the first request intercepter
httpclient.addRequestInterceptor(new PreemptiveAuth(), 0);

Post方法
我们以更新一个嘀咕消息为例,Post方法比较特别的地方就在于
1、我们创建的是HttpPost实例,而非HttpGet实例

HttpPost httppost = new HttpPost("http://api.digu.com/statuses/public_timeline.xml");

2、我们把需要向服务端传递的数据封装在一个ArrayList中,这里比如我们想要发送的嘀咕消息内容对应字段为content。接下来,我们通过setEntity方法把经过URLEncode的字串传给httppost。

List<NameValuePair> nvps = new ArrayList<NameValuePair>();
nvps.add(new BasicNameValuePair("content", "test digu api, 测试嘀咕API"));

httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));

3、后面的execute等方法调用就和Get没有什么区别了,并且因为我们是在向服务端提交内容,所以会比较关心请求是否成功执行,用esponse.getStatusLine()就可以得到请求执行的状态。

结语
在尝试用HttpClient调用嘀咕API的过程中,WireShark帮了我很大的忙。另外大家如果想在编码前就测试一下每个API的调用方法和返回结果,curl是个简便的途径,通过-u username:password的方法,它还能够支持需要身份验证的API调用。

得到了XML数据以后,下一步就是解析数据了,关于这一点,我将在下一篇中介绍。

参考资料
HttpClient Example
HttpClient JavaDoc

16 条评论 »

  1. 这个很高级

    评论 by 智康博客 — 2009-05-24 @ 11:42

  2. 还没看到是虾米东东~~~

    评论 by tt — 2009-05-25 @ 15:34

  3. 问下,PHPMYADMIN登录时报错1130,怎么解决?

    评论 by 短歌行 — 2009-05-26 @ 11:09

  4. 我来了,这个太专业了?呼呼
    问个问题,就是你地址链接后的“acccess-digu-api-through-apache-httpclient-4”是怎么实现的,我前段时间摸索了很久,无果……望指教

    评论 by Estoremap — 2009-05-26 @ 12:32

  5. @Estoremap
    首先要打开“永久链接”,即permalink,这个我在你blog里留言提到过。在“设置”–>“永久链接”里面选择启用即可,至于永久链接的格式推荐“月份和名称”,即类似http://www.williamhua.com/2009/05/sample-post/ 这样,不想要月份年份信息也没有关系,这个主要还是看喜好。像我这样加上日期稍微有点多余:P 只是我不想让搜索引擎重新爬了。
    另外,permalink需要服务器启用Apache的mod_rewrite模块,至于IIS怎么搞,我也不太清楚了,不过WP中文论坛里有相关的帖子
    设置完以后,在写blog的时候就会在标题下方设置永久链接

    评论 by William Hua — 2009-05-26 @ 13:23

  6. @短歌行
    是在你自己的NAS上吗?
    基本上你看看这个post就明白了 http://home.mysql.cn/space-55789-do-blog-id-79.html

    评论 by William Hua — 2009-05-26 @ 13:28

  7. 你那个example的图片我还以为是 wordpress代码折叠插件所为,点了半天不见展开。

    评论 by febird — 2009-05-26 @ 16:18

  8. @febird
    哈哈,那个是wireshark的截图

    评论 by William Hua — 2009-05-26 @ 17:55

  9. @William Hua 原来是这样子的,我主要是用WLW发的日志,所以没办法了,那就算了……

    评论 by Estoremap — 2009-05-31 @ 14:45

  10. 嗯Webkit也是可以直接用Curl的认证功能,不用自己再搞个SSL模块

    评论 by Chocolly — 2009-05-31 @ 21:55

  11. @Estoremap
    WLW没有用过,不知道是不是支持发布时改permalink的功能,只要支持就ok

    评论 by William Hua — 2009-05-31 @ 22:08

  12. 您好,我现在正在研究这方面的东西,看到您的这篇博客,很受用。现在我遇到一个问题和您的一样,在使用HttpGet的时候会发出ClientProtocolException和MalformedChallengeException异常,不知道你最后是如何处理的。可不可以看一下你的源代码呢?
    还有就是如果用HttpPost的时候该如何处理呢?
    我的邮箱是:xiaosena@gmail.com
    急切希望收到您的回复,谢谢。

    评论 by xiaosena — 2009-06-01 @ 10:40

  13. @xiaosena
    你好,
    对于ClientProtocolException和MalformedChallengeException异常的问题,我后来参考了Preemptive BASIC authentication这个例子的代码,关键就是添加了一个addRequestInterceptor,代码可以看这里
    http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/httpclient/src/examples/org/apache/http/examples/client/ClientPreemptiveBasicAuthentication.java

    HttpPost你遇到了什么问题? 按照我上面贴的代码,最后调一个httpclient.execute(httppost); 应该就ok了

    不好意思,现在代码不在身边,如果还有问题请留言。

    评论 by William Hua — 2009-06-01 @ 17:11

  14. 我最近想搞个自动顶贴机,哈。
    但是用到这个httpclient的post 方法,代码参照了apache例子的,我简直就是直接copy过去,
    比如登陆163邮箱吧,form里写的方法是post·执行代码没有异常,不过显示操作结果的时候看到了用户名密码错误的提示,什么原因?
    是不是密码的传送不能用basicNameValuePair这个类?

    DefaultHttpClient httpclient = new DefaultHttpClient();

    List nvps = new ArrayList();
    nvps.add(new BasicNameValuePair(“userName”, “用户名”));
    nvps.add(new BasicNameValuePair(“password”, “密码”));
    UrlEncodedFormEntity loginEntity = new UrlEncodedFormEntity(nvps, “UTF-8″);

    HttpPost httpost = new HttpPost(“https://reg.163.com/logins.jsp”);
    httpost.setEntity(loginEntity);

    response = httpclient.execute(httpost);
    entity = response.getEntity();

    System.out.println(“Login form get: ” + response.getStatusLine());
    if (entity != null) {

    // 显示服务器反馈内容

    ResponseHandler responseHandler = new BasicResponseHandler();
    String responseBody = httpclient.execute(httpost, responseHandler);
    System.out.println(responseBody);
    //
    entity.consumeContent();
    }

    评论 by 毛毛 — 2009-07-30 @ 07:13

  15. 您好,我看了您的博客受益匪浅。有一个问题就是用get方法进行身份验证的时候,设置一个请求拦截器,我对这个实在是不太了解,弄了好久都调试成功。不知道楼主能不能分享一下身份验证的代码呢?谢谢。

    评论 by seashell — 2009-11-06 @ 22:33

  16. 晕,你们是不是没有抓过包啊,这可都要设置header信息,还有cookie的啊,我猜那个请求拦截器没必要吧。

    评论 by 卢卡斯 — 2011-05-13 @ 16:13

这篇文章上的评论的 RSS feed TrackBack URL

留下评论

Powered by WordPress