HttpClient,DefaultHttpClient使用详解

来源:互联网 发布:安徽软件评测 编辑:程序博客网 时间:2024/06/04 19:43

HttpClient:是一个接口,DefaultHttpClient实现这个接口的子类

首先需要先创建一个DefaultHttpClient的实例

HttpClient httpClient=new DefaultHttpClient();

发送GET请求:

先创建一个HttpGet对象,传入目标的网络地址,然后调用HttpClient的execute()方法即可:

HttpGet HttpGet=new HttpGet(“http://www.baidu.com”);httpClient.execute(httpGet);

发送POST请求:

创建一个HttpPost对象,传入目标的网络地址:

HttpPost httpPost=new HttpPost(“http://www.baidu.com”);

通过一个NameValuePair集合来存放待提交的参数,并将这个参数集合传入到一个UrlEncodedFormEntity中,然后调用HttpPost的setEntity()方法将构建好的UrlEncodedFormEntity传入:

List<NameValuePair>params=newArrayList<NameValuePair>();Params.add(new BasicNameValuePair(“username”,”admin”));Params.add(new BasicNameValuePair(“password”,”123456”));UrlEncodedFormEntity entity=newUrlEncodedFormEntity(params,”utf-8”);httpPost.setEntity(entity);

调用HttpClient的execute()方法,并将HttpPost对象传入即可:

HttpClient.execute(HttpPost);

执行execute()方法之后会返回一个HttpResponse对象,服务器所返回的所有信息就保护在HttpResponse里面.

先取出服务器返回的状态码,如果等于200就说明请求和响应都成功了:

If(httpResponse.getStatusLine().getStatusCode()==200){//请求和响应都成功了HttpEntityentity=HttpResponse.getEntity();//调用getEntity()方法获取到一个HttpEntity实例Stringresponse=EntityUtils.toString(entity,”utf-8”);//用EntityUtils.toString()这个静态方法将HttpEntity转换成字符串,防止服务器返回的数据带有中文,所以在转换的时候将字符集指定成utf-8就可以了    }

HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java.net 包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们再讨论),它不仅是客户端发送Http请求变得容易,而且也方便了开发人员测试接口(基于Http协议的),即提高了开发的效率,也方便提高代码的健壮性。因此熟练掌握HttpClient是很重要的必修内容,掌握HttpClient后,相信对于Http协议的了解会更加深入。

一、简介

HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。HttpClient已经应用在很多的项目中,比如Apache Jakarta上很著名的另外两个开源项目Cactus和HTMLUnit都使用了HttpClient。

下载地址: http://hc.apache.org/downloads.cgi

二、特性

  1. 基于标准、纯净的java语言。实现了Http1.0和Http1.1

  2. 以可扩展的面向对象的结构实现了Http全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE)。

  3. 支持HTTPS协议。

  4. 通过Http代理建立透明的连接。

  5. 利用CONNECT方法通过Http代理建立隧道的https连接。

  6. Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO/Kerberos认证方案。

  7. 插件式的自定义认证方案。

  8. 便携可靠的套接字工厂使它更容易的使用第三方解决方案。

  9. 连接管理器支持多线程应用。支持设置最大连接数,同时支持设置每个主机的最大连接数,发现并关闭过期的连接。

  10. 自动处理Set-Cookie中的Cookie。

  11. 插件式的自定义Cookie策略。

  12. Request的输出流可以避免流中内容直接缓冲到socket服务器。

  13. Response的输入流可以有效的从socket服务器直接读取相应内容。

  14. 在http1.0和http1.1中利用KeepAlive保持持久连接。

  15. 直接获取服务器发送的response code和 headers。

  16. 设置连接超时的能力。

  17. 实验性的支持http1.1 response caching。

  18. 源代码基于Apache License 可免费获取。

三、功能介绍

以下列出的是 HttpClient 提供的主要的功能,要知道更多详细的功能可以参见 HttpClient 的主页。

  • 实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
  • 支持自动转向
  • 支持 HTTPS 协议
  • 支持代理服务器等

HttpClient 基本功能的使用:

(1) GET方法

  使用 HttpClient 需要以下 6 个步骤:

  • 创建 HttpClient 的实例
  • 创建某种连接方法的实例,在这里是 GetMethod。在 GetMethod 的构造函数中传入待连接的地址
  • 调用第一步中创建好的实例的 execute 方法来执行第二步中创建好的 method 实例
  • 读response
  • 释放连接。无论执行方法是否成功,都必须释放连接
  • 对得到后的内容进行处理

根据以上步骤,我们来编写用GET方法来取得某网页内容的代码。创建GET方法的实例。在GET方法的构造函数中传入待连接的地址即可。用GetMethod将会自动处理转发过程,如果想要把自动处理转发过程去掉的话,可以调用方法setFollowRedirects(false)。 GetMethod getMethod = new GetMethod(“…..”);

  调用实例httpClient的executeMethod方法来执行getMethod。由于是执行在网络上的程序,在运行executeMethod方法的时候,需要处理两个异常,分别是HttpException和IOException。引起第一种异常的原因主要可能是在构造getMethod的时候传入的协议不对,比如不小心将”http”写成”htp”,或者服务器端返回的内容不正常等,并且该异常发生是不可恢复的;第二种异常一般是由于网络原因引起的异常,对于这种异常 (IOException),HttpClient会根据你指定的恢复策略自动试着重新执行executeMethod方法。HttpClient的恢复策略可以自定义(通过实现接口HttpMethodRetryHandler来实现)。通过httpClient的方法setParameter设置你实现的恢复策略,本文中使用的是系统提供的默认恢复策略,该策略在碰到第二类异常的时候将自动重试3次。executeMethod返回值是一个整数,表示了执行该方法后服务器返回的状态码,该状态码能表示出该方法执行是否成功、需要认证或者页面发生了跳转(默认状态下GetMethod的实例是自动处理跳转的),设置成了默认的恢复策略,在发生异常时候将自动重试3次,在这里你也可以设置成自定义的恢复策略。

getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,new DefaultHttpMethodRetryHandler());  //执行getMethodint statusCode = client.executeMethod(getMethod);if (statusCode != HttpStatus.SC_OK) {System.err.println("Method failed: " + getMethod.getStatusLine());}

在返回的状态码正确后,即可取得内容。取得目标地址的内容有三种方法:

1.getResponseBody,该方法返回的是目标的二进制的byte流;

2.getResponseBodyAsString,这个方法返回的是String类型,值得注意的是该方法返回的String的编码是根据系统默认的编码方式,所以返回的String值可能编码类型有误,在本文的”字符编码”部分中将对此做详细介绍;

3.getResponseBodyAsStream,这个方法对于目标地址中有大量数据需要传输是最佳的。在这里我们使用了最简单的getResponseBody方法。 byte[] responseBody = method.getResponseBody();

释放连接。无论执行方法是否成功,都必须释放连接。 method.releaseConnection();

处理内容。在这一步中根据你的需要处理内容,在例子中只是简单的将内容打印到控制台。 System.out.println(new String(responseBody));

下面是程序的完整代码:

public class GetSample{     public static void main(String[] args) {         //构造HttpClient的实例         HttpClient httpClient = new HttpClient();         //创建GET方法的实例         GetMethod getMethod = new GetMethod("...");         //使用系统提供的默认的恢复策略         getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,                 new DefaultHttpMethodRetryHandler());         try {             //执行getMethod             int statusCode = httpClient.executeMethod(getMethod);             if (statusCode != HttpStatus.SC_OK) {                 System.err.println("Method failed: "                         + getMethod.getStatusLine());             }             //读取内容              byte[] responseBody = getMethod.getResponseBody();             //处理内容             System.out.println(new String(responseBody));         } catch (HttpException e) {             //发生致命的异常,可能是协议不对或者返回的内容有问题             System.out.println("Please check your provided http address!");             e.printStackTrace();         } catch (IOException e) {             //发生网络异常             e.printStackTrace();         } finally {             //释放连接             getMethod.releaseConnection();         }     } }

(2)POST方法

根据RFC2616,对POST的解释如下:POST方法用来向目的服务器发出请求,要求它接受被附在请求后的实体,并把它当作请求队列(Request-Line)中请求URI所指定资源的附加新子项。POST被设计成用统一的方法实现下列功能:

  • 对现有资源的注释(Annotation of existing resources)
  • 向电子公告栏、新闻组,邮件列表或类似讨论组发送消息
  • 提交数据块,如将表单的结果提交给数据处理过程
  • 通过附加操作来扩展数据库
  • 调用HttpClient中的PostMethod与GetMethod类似,除了设置PostMethod的实例与GetMethod有些不同之外,剩下的步骤都差不多。在下面的例子中,省去了与GetMethod相同的步骤,只说明与上面不同的地方,并以登录清华大学BBS为例子进行说明。
  • 构造PostMethod之前的步骤都相同,与GetMethod一样,构造PostMethod也需要一个URI参数。在创建了PostMethod的实例之后,需要给method实例填充表单的值,在BBS的登录表单中需要有两个域,第一个是用户名(域名叫id),第二个是密码(域名叫passwd)。表单中的域用类NameValuePair来表示,该类的构造函数第一个参数是域名,第二参数是该域的值;将表单所有的值设置到PostMethod中用方法setRequestBody。另外由于BBS登录成功后会转向另外一个页面,但是HttpClient对于要求接受后继服务的请求,比如POST和PUT,不支持自动转发,因此需要自己对页面转向做处理。具体的页面转向处理请参见下面的”自动转向”部分。

代码如下:

String url = "....";     PostMethod postMethod = new PostMethod(url);     // 填入各个表单域的值     NameValuePair[] data = { new NameValuePair("id", "youUserName"),              new NameValuePair("passwd", "yourPwd") };     // 将表单的值放入postMethod中     postMethod.setRequestBody(data);     // 执行postMethod     int statusCode = httpClient.executeMethod(postMethod);     // HttpClient对于要求接受后继服务的请求,象POST和PUT等不能自动处理转发     // 301或者302     if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||              statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {         // 从头中取出转向的地址         Header locationHeader = postMethod.getResponseHeader("location");         String location = null;         if (locationHeader != null) {             location = locationHeader.getValue();             System.out.println("The page was redirected to:" + location);         } else {             System.err.println("Location field value is null.");         }         return;

四、使用方法

使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。

  1. 创建HttpClient对象。

  2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。

  3. 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。

  4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。

  5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。

  6. 释放连接。无论执行方法是否成功,都必须释放连接

五、实例

package com.fico.rma.service;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.net.URL;import org.apache.commons.io.FileUtils;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.HttpStatus;import org.apache.http.client.methods.HttpPost;import org.apache.http.entity.InputStreamEntity;import org.apache.http.impl.client.DefaultHttpClient;import com.fico.rma.RmaConfig;import com.fico.rma.util.RmaConstants;public class ReleaseService {    private final static String SEP=File.separator;    public static void createRelease(String releaseName,String projectName) throws IOException{        String dir=RmaConfig.getString("basepath");        projectName = projectName.toLowerCase();        File destAdbFile=new File(dir+SEP+RmaConstants.RELEASE_DIR+SEP+releaseName+SEP+projectName+".adb");        File srcAdbFile = null;        if("dldsunproject".equals(projectName.toLowerCase().toString().trim())){            srcAdbFile=new File(dir+SEP+RmaConstants.ADB_DIR+SEP+"DldsUnProject"+SEP+"adb"+SEP+"Pingan.server_service_0.adb");        }else {            srcAdbFile=new File(dir+SEP+RmaConstants.ADB_DIR+SEP+"PingAnProject"+SEP+"adb"+SEP+"Pingan.server_service_0.adb");        }        FileUtils.copyFile(srcAdbFile, destAdbFile);    }    public static void pulishRelease(String adbFile,String url,String projectName) throws IOException {        DefaultHttpClient httpclient = new DefaultHttpClient();         File file = new File(adbFile);        InputStreamEntity reqEntity = new InputStreamEntity(new FileInputStream(file), -1);        reqEntity.setContentType("binary/octet-stream");         reqEntity.setChunked(true);        HttpPost httppost = new HttpPost(url);        httppost.setEntity(reqEntity);        httppost.setHeader("projectName", projectName);        HttpResponse response = httpclient.execute(httppost);        //System.out.println("response.getStatusLine().getStatusCode():"+response.getStatusLine().getStatusCode());                if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {            HttpEntity enty = response.getEntity();        }    }}

以上是发送端的代码,下面是接受端的代码:

package com.pingan.res;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.io.PrintWriter;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.blazesoft.server.local.NdLocalServerException;/** * Servlet implementation class ReleaseServlet */@WebServlet("/Release")public class ReleaseServlet extends HttpServlet {    private static final long serialVersionUID = 1L;    /**     * @see HttpServlet#HttpServlet()     */    public ReleaseServlet() {        super();        // TODO Auto-generated constructor stub    }    /**     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse     *      response)     */    protected void doPost(HttpServletRequest request,            HttpServletResponse response) throws ServletException, IOException {        /*         * 功能点:RMA这发布adb包的时候的res调用的入口点。         *       功能1:获取到RMA中具体是哪个项目         *       功能2:根据项目把adb包和对应的.server文件拷贝到不同的目录下(目录名称为RMA的项目名称的小写)         *   功能3:重新加载相对应的adb文件到对应的server中         */        //功能1        String projectName ="";        projectName =request.getHeader("projectName");        projectName = projectName.toLowerCase();        String sep=File.separator;//        String dir=ResConfig.getString("basepath");        String dir = "";        dir=ResConfig.getProperties().getProperty("basepath");        //功能2        String adbFile=dir+sep+"production"+sep+projectName+sep+projectName+".adb";        String serverFile =  dir+sep+"production"+sep+projectName+sep+projectName+".server";        OutputStream out = new FileOutputStream(adbFile);        InputStream in = request.getInputStream();        int length = 0;        byte[] buf = new byte[1024];        while ((length = in.read(buf)) != -1) {            out.write(buf, 0, length);        }        in.close();        out.close();        //功能3:重启server        reloadRuleServer(request,projectName,serverFile);        response.setContentType("text/xml");        PrintWriter pw=response.getWriter();        pw.write("<Response><Result>OK</Result></Response>");        pw.flush();    }    private void reloadRuleServer(HttpServletRequest req,String projectName,String serverFile) throws ServletException{        projectName = projectName.toLowerCase();        String projectDefinition1 = "";        if (!"".equals(projectName)&&projectName.length()>0) {            projectDefinition1 = ResConfig.getProperties().getProperty(projectName+"_df");        }        Server server = null;        ServletContext context=req.getServletContext();        server = (Server)context.getAttribute(projectName+"Server");        try{            if(server==null){                server = (Server)Server.createServer(serverFile);                context.setAttribute(projectName+"Server", server);            }            server.resetService(projectDefinition1);            PriorRunner runner=new PriorRunner(server,100);            runner.run();        } catch (NdLocalServerException e) {            e.printStackTrace();            throw new ServletException(e.getMessage());        }    }}

HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java.net 包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。

六、使用过程中的问题

下面介绍在使用HttpClient过程中常见的一些问题:

1、字符编码

某目标页的编码可能出现在两个地方,第一个地方是服务器返回的http头中,另外一个地方是得到的html/xml页面中。

在http头的Content-Type字段可能会包含字符编码信息。例如可能返回的头会包含这样子的信息:Content-Type: text/html; charset=UTF-8。这个头信息表明该页的编码是UTF-8,但是服务器返回的头信息未必与内容能匹配上。比如对于一些双字节语言国家,可能服务器返回的编码类型是UTF-8,但真正的内容却不是UTF-8编码的,因此需要在另外的地方去得到页面的编码信息;但是如果服务器返回的编码不是UTF-8,而是具体的一些编码,比如gb2312等,那服务器返回的可能是正确的编码信息。通过method对象的getResponseCharSet()方法就可以得到http头中的编码信息。

对于像xml或者html这样的文件,允许作者在页面中直接指定编码类型。比如在html中会有这样的标签;或者在xml中会有

2、自动转向

根据RFC2616中对自动转向的定义,主要有两种:301和302。301表示永久的移走(Moved Permanently),当返回的是301,则表示请求的资源已经被移到一个固定的新地方,任何向该地址发起请求都会被转到新的地址上。302表示暂时的转向,比如在服务器端的servlet程序调用了sendRedirect方法,则在客户端就会得到一个302的代码,这时服务器返回的头信息中location的值就是sendRedirect转向的目标地址。

HttpClient支持自动转向处理,但是象POST和PUT方式这种要求接受后继服务的请求方式,暂时不支持自动转向,因此如果碰到POST方式提交后返回的是301或者302的话需要自己处理。就像刚才在POSTMethod中举的例子:如果想进入登录BBS后的页面,必须重新发起登录的请求,请求的地址可以在头字段location中得到。不过需要注意的是,有时候location返回的可能是相对路径,因此需要对location返回的值做一些处理才可以发起向新地址的请求。

另外除了在头中包含的信息可能使页面发生重定向外,在页面中也有可能会发生页面的重定向。引起页面自动转发的标签是:。如果你想在程序中也处理这种情况的话得自己分析页面来实现转向。需要注意的是,在上面那个标签中url的值也可以是一个相对地址,如果是这样的话,需要对它做一些处理后才可以转发。

3、处理HTTPS协议

HttpClient提供了对SSL的支持,在使用SSL之前必须安装JSSE。在Sun提供的1.4以后的版本中,JSSE已经集成到JDK中,如果你使用的是JDK1.4以前的版本则必须安装JSSE。JSSE不同的厂家有不同的实现。下面介绍怎么使用HttpClient来打开Https连接。这里有两种方法可以打开https连接,第一种就是得到服务器颁发的证书,然后导入到本地的keystore中;另外一种办法就是通过扩展HttpClient的类来实现自动接受证书。

方法1:取得证书,并导入本地的keystore:

安装JSSE (如果你使用的JDK版本是1.4或者1.4以上就可以跳过这一步)。本文以IBM的JSSE为例子说明。先到IBM网站上下载JSSE的安装包。然后解压开之后将ibmjsse.jar包拷贝到\lib\ext\目录下。 
取得并且导入证书。证书可以通过IE来获得:

  1. 用IE打开需要连接的https网址,会弹出如下对话框:

  2. 单击”View Certificate”,在弹出的对话框中选择”Details”,然后再单击”Copy to File”,根据提供的向导生成待访问网页的证书文件

  3. 向导第一步,欢迎界面,直接单击”Next”

  4. 向导第二步,选择导出的文件格式,默认,单击”Next”

  5. 向导第三步,输入导出的文件名,输入后,单击”Next”

  6. 向导第四步,单击”Finish”,完成向导

  7. 最后弹出一个对话框,显示导出成功

用keytool工具把刚才导出的证书倒入本地keystore。Keytool命令在\bin\下,打开命令行窗口,并到\lib\security\目录下,运行下面的命令:

keytool -import -noprompt -keystore cacerts -storepass changeit -alias yourEntry1 -file your.cer

其中参数alias后跟的值是当前证书在keystore中的唯一标识符,但是大小写不区分;参数file后跟的是刚才通过IE导出的证书所在的路径和文件名;如果你想删除刚才导入到keystore的证书,可以用命令: 
keytool -delete -keystore cacerts -storepass changeit -alias yourEntry1

写程序访问https地址。如果想测试是否能连上https,只需要稍改一下GetSample例子,把请求的目标变成一个https地址。

GetMethod getMethod = new GetMethod("your url"); 

   
运行该程序可能出现的问题:

1.抛出异常java.net.SocketException: Algorithm SSL not available。出现这个异常可能是因为没有加JSSEProvider,如果用的是IBM的JSSE Provider,在程序中加入这样的一行:

if(Security.getProvider("com.ibm.jsse.IBMJSSEProvider") == null)Security.addProvider(new IBMJSSEProvider()); 

或者也可以打开\lib\security\java.security,在行

security.provider.1=sun.security.provider.Sunsecurity.provider.2=com.ibm.crypto.provider.IBMJCE

后面加入security.provider.3=com.ibm.jsse.IBMJSSEProvider

2.抛出异常java.net.SocketException: SSL implementation not available。出现这个异常可能是你没有把ibmjsse.jar拷贝到\lib\ext\目录下。

3.抛出异常javax.net.ssl.SSLHandshakeException: unknown certificate。出现这个异常表明你的JSSE应该已经安装正确,但是可能因为你没有把证书导入到当前运行JRE的keystore中,请按照前面介绍的步骤来导入你的证书。

方法2,扩展HttpClient类实现自动接受证书

因为这种方法自动接收所有证书,因此存在一定的安全问题,所以在使用这种方法前请仔细考虑您的系统的安全需求。具体的步骤如下: 
  

  • 提供一个自定义的socket factory(test.MySecureProtocolSocketFactory)。这个自定义的类必须实现接口org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory,在实现接口的类中调用自定义的X509TrustManager(test.MyX509TrustManager),这两个类可以在随本文带的附件中得到
  • 创建一个org.apache.commons.httpclient.protocol.Protocol的实例,指定协议名称和默认的端口号 Protocol myhttps = new Protocol(“https”, new MySecureProtocolSocketFactory (), 443);
  • 注册刚才创建的https协议对象 Protocol.registerProtocol(“https “, myhttps);