您的位置:首页 > 理论基础 > 计算机网络

DefaultHttpClient,HttpClient,DefaultHttpClient使用详解

2017-09-19 13:35 357 查看
HttpClient:是一个接口

首先需要先创建一个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协议的重要性相信不用我多说了,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发送请求、接收响应很简单,一般需要如下几步即可。

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. 释放连接。无论执行方法是否成功,都必须释放连接


 


四、实例

1 package com.fico.rma.service;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.IOException;
6 import java.net.URL;
7 import org.apache.commons.io.FileUtils;
8 import org.apache.http.HttpEntity;
9 import org.apache.http.HttpResponse;
10 import org.apache.http.HttpStatus;
11 import org.apache.http.client.methods.HttpPost;
12 import org.apache.http.entity.InputStreamEntity;
13 import org.apache.http.impl.client.DefaultHttpClient;
14 import com.fico.rma.RmaConfig;
15 import com.fico.rma.util.RmaConstants;
16
17 public class ReleaseService {
18
19     private final static String SEP=File.separator;
20
21     public static void createRelease(String releaseName,String projectName) throws IOException{
22         String dir=RmaConfig.getString("basepath");
23         projectName = projectName.toLowerCase();
24         File destAdbFile=new File(dir+SEP+RmaConstants.RELEASE_DIR+SEP+releaseName+SEP+projectName+".adb");
25         File srcAdbFile = null;
26         if("dldsunproject".equals(projectName.toLowerCase().toString().trim())){
27             srcAdbFile=new File(dir+SEP+RmaConstants.ADB_DIR+SEP+"DldsUnProject"+SEP+"adb"+SEP+"Pingan.server_service_0.adb");
28         }else {
29             srcAdbFile=new File(dir+SEP+RmaConstants.ADB_DIR+SEP+"PingAnProject"+SEP+"adb"+SEP+"Pingan.server_service_0.adb");
30         }
31         FileUtils.copyFile(srcAdbFile, destAdbFile);
32     }
33
34     public static void pulishRelease(String adbFile,String url,String projectName) throws IOException {
35
36         DefaultHttpClient httpclient = new DefaultHttpClient();
37
38         File file = new File(adbFile);
39         InputStreamEntity reqEntity = new InputStreamEntity(new FileInputStream(file), -1);
40         reqEntity.setContentType("binary/octet-stream");
41         reqEntity.setChunked(true);
42
43         HttpPost httppost = new HttpPost(url);
44         httppost.setEntity(reqEntity);
45         httppost.setHeader("projectName", projectName);
46
47         HttpResponse response = httpclient.execute(httppost);
48         //System.out.println("response.getStatusLine().getStatusCode():"+response.getStatusLine().getStatusCode());
49
50         if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
51             HttpEntity enty = response.getEntity();
52         }
53     }
54
55
56 }


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

1 package com.pingan.res;
2
3 import java.io.File;
4 import java.io.FileOutputStream;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.io.OutputStream;
8 import java.io.PrintWriter;
9
10 import javax.servlet.ServletContext;
11 import javax.servlet.ServletException;
12 import javax.servlet.annotation.WebServlet;
13 import javax.servlet.http.HttpServlet;
14 import javax.servlet.http.HttpServletRequest;
15 import javax.servlet.http.HttpServletResponse;
16 import com.blazesoft.server.local.NdLocalServerException;
17
18 /**
19  * Servlet implementation class ReleaseServlet
20  */
21 @WebServlet("/Release")
22 public class ReleaseServlet extends HttpServlet {
23     private static final long serialVersionUID = 1L;
24
25     /**
26      * @see HttpServlet#HttpServlet()
27      */
28     public ReleaseServlet() {
29         super();
30         // TODO Auto-generated constructor stub
31     }
32
33     /**
34      * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
35      *      response)
36      */
37     protected void doPost(HttpServletRequest request,
38             HttpServletResponse response) throws ServletException, IOException {
39
40         /*
41          * 功能点:RMA这发布adb包的时候的res调用的入口点。
42          *       功能1:获取到RMA中具体是哪个项目
43          *       功能2:根据项目把adb包和对应的.server文件拷贝到不同的目录下(目录名称为RMA的项目名称的小写)
44          *   功能3:重新加载相对应的adb文件到对应的server中
45          */
46
47         //功能1
48         String projectName ="";
49         projectName =request.getHeader("projectName");
50         projectName = projectName.toLowerCase();
51         String sep=File.separator;
52 //        String dir=ResConfig.getString("basepath");
53         String dir = "";
54         dir=ResConfig.getProperties().getProperty("basepath");
55
56         //功能2
57         String adbFile=dir+sep+"production"+sep+projectName+sep+projectName+".adb";
58         String serverFile =  dir+sep+"production"+sep+projectName+sep+projectName+".server";
59         OutputStream out = new FileOutputStream(adbFile);
60
61         InputStream in = request.getInputStream();
62
63         int length = 0;
64         byte[] buf = new byte[1024];
65
66         while ((length = in.read(buf)) != -1) {
67             out.write(buf, 0, length);
68         }
69
70         in.close();
71         out.close();
72
73         //功能3:重启server
74         reloadRuleServer(request,projectName,serverFile);
75
76
77         response.setContentType("text/xml");
78         PrintWriter pw=response.getWriter();
79
80         pw.write("<Response><Result>OK</Result></Response>");
81
82         pw.flush();
83
84     }
85
86     private void reloadRuleServer(HttpServletRequest req,String projectName,String serverFile) throws ServletException{
87         projectName = projectName.toLowerCase();
88         String projectDefinition1 = "";
89         if (!"".equals(projectName)&&projectName.length()>0) {
90             projectDefinition1 = ResConfig.getProperties().getProperty(projectName+"_df");
91         }
92
93         Server server = null;
94         ServletContext context=req.getServletContext();
95         server = (Server)context.getAttribute(projectName+"Server");
96
97         try{
98             if(server==null){
99                 server = (Server)Server.createServer(serverFile);
100                 context.setAttribute(projectName+"Server", server);
101             }
102             server.resetService(projectDefinition1);
103             PriorRunner runner=new PriorRunner(server,100);
104             runner.run();
105
106
107         } catch (NdLocalServerException e) {
108             e.printStackTrace();
109             throw new ServletException(e.getMessage());
110         }
111
112     }
113
114 }


HttpClient 是 Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。本文首先介绍 HTTPClient,然后根据作者实际工作经验给出了一些常见问题的解决方法。HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过
HTTP 协议来访问网络资源。虽然在 JDK 的 java.net 包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和
HTMLUnit 都使用了 HttpClient。现在HttpClient最新版本为 HttpClient 4.0-beta2 

2.HttpClient 功能介绍 

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

  (1)实现了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)  

  (2)支持自动转向  

  (3)支持 HTTPS 协议  

  (4)支持代理服务器等 

3.HttpClient 基本功能的使用 

  (1) GET方法 

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

  1. 创建 HttpClient 的实例 

  2. 创建某种连接方法的实例,在这里是 GetMethod。在 GetMethod 的构造函数中传入待连接的地址 

  3. 调用第一步中创建好的实例的 execute 方法来执行第二步中创建好的 method 实例 

  4. 读 response 

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

  6. 对得到后的内容进行处理 

  根据以上步骤,我们来编写用GET方法来取得某网页内容的代码。 

  大部分情况下 HttpClient 默认的构造函数已经足够使用。 HttpClient httpClient = new HttpClient(); 

  创建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次,在这里你也可以设置成自定义的恢复策略 

  

1  getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
2   new DefaultHttpMethodRetryHandler());
3   //执行getMethod
4   int statusCode = client.executeMethod(getMethod);
5   if (statusCode != HttpStatus.SC_OK) {
6   System.err.println("Method failed: " + getMethod.getStatusLine());
7   }


 
 

  在返回的状态码正确后,即可取得内容。取得目标地址的内容有三种方法:第一种,getResponseBody,该方法返回的是目标的二进制的byte流;第二种,getResponseBodyAsString,这个方法返回的是String类型,值得注意的是该方法返回的String的编码是根据系统默认的编码方式,所以返回的String值可能编码类型有误,在本文的"字符编码"部分中将对此做详细介绍;第三种,getResponseBodyAsStream,这个方法对于目标地址中有大量数据需要传输是最佳的。在这里我们使用了最简单的getResponseBody方法。
byte[] responseBody = method.getResponseBody(); 

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

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

  下面是程序的完整代码:
    

1 package test;
2 import java.io.IOException;
3 import org.apache.commons.httpclient.*;
4 import org.apache.commons.httpclient.methods.GetMethod;
5 import org.apache.commons.httpclient.params.HttpMethodParams;
6 public class GetSample{
7     public static void main(String[] args) {
8         //构造HttpClient的实例
9         HttpClient httpClient = new HttpClient();
10         //创建GET方法的实例
11         GetMethod getMethod = new GetMethod("...");
12         //使用系统提供的默认的恢复策略
13         getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
14                 new DefaultHttpMethodRetryHandler());
15         try {
16             //执行getMethod
17             int statusCode = httpClient.executeMethod(getMethod);
18             if (statusCode != HttpStatus.SC_OK) {
19                 System.err.println("Method failed: "
20                         + getMethod.getStatusLine());
21             }
22             //读取内容
23             byte[] responseBody = getMethod.getResponseBody();
24             //处理内容
25             System.out.println(new String(responseBody));
26         } catch (HttpException e) {
27             //发生致命的异常,可能是协议不对或者返回的内容有问题
28             System.out.println("Please check your provided http address!");
29             e.printStackTrace();
30         } catch (IOException e) {
31             //发生网络异常
32             e.printStackTrace();
33         } finally {
34             //释放连接
35             getMethod.releaseConnection();
36         }
37     }
38 }


 
(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,不支持自动转发,因此需要自己对页面转向做处理。具体的页面转向处理请参见下面的"自动转向"部分。代码如下: 

    

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


 

 4 使用HttpClient过程中常见的一些问题 

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

  字符编码 

  某目标页的编码可能出现在两个地方,第一个地方是服务器返回的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中会有<meta http-equiv="Content-Type" content="text/html; charset=gb2312"/>这样的标签;或者在xml中会有<?xml version="1.0" encoding="gb2312"?>这样的标签,在这些情况下,可能与http头中返回的编码信息冲突,需要用户自己判断到底那种编码类型应该是真正的编码。  

  自动转向 

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

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

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

  处理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包拷贝到<java-home>\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命令在<java-home>\bin\下,打开命令行窗口,并到<java-home>\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()); 

  或者也可以打开<java-home>\lib\security\java.security,在行 

  security.provider.1=sun.security.provider.Sun 

  security.provider.2=com.ibm.crypto.provider.IBMJCE 

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

  2. 抛出异常java.net.SocketException: SSL implementation not available。出现这个异常可能是你没有把ibmjsse.jar拷贝到<java-home>\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); 

  然后按照普通编程方式打开https的目标地址,代码请参见test.NoCertificationHttpsGetSample 

[编辑本段]5 处理代理服务器 

  HttpClient中使用代理服务器非常简单,调用HttpClient中setProxy方法就可以,方法的第一个参数是代理服务器地址,第二个参数是端口号。另外HttpClient也支持SOCKS代理。 

  httpClient.getHostConfiguration().setProxy(hostName,port); 一般的情况下我们都是使用IE或者Navigator浏览器来访问一个WEB服务器,用来浏览页面查看信息或者提交一些数据等等。所访问的这些页面有的仅仅是一些普通的页面,有的需要用户登录后方可使用,或者需要认证以及是一些通过加密方式传输,例如HTTPS。目前我们使用的浏览器处理这些情况都不会构成问题。不过你可能在某些时候需要通过程序来访问这样的一些页面,比如从别人的网页中“偷”一些数据;利用某些站点提供的页面来完成某种功能,例如说我们想知道某个手机号码的归属地而我们自己又没有这样的数据,因此只好借助其他公司已有的网站来完成这个功能,这个时候我们需要向网页提交手机号码并从返回的页面中解析出我们想要的数据来。如果对方仅仅是一个很简单的页面,那我们的程序会很简单,本文也就没有必要大张旗鼓的在这里浪费口舌。但是考虑到一些服务授权的问题,很多公司提供的页面往往并不是可以通过一个简单的URL就可以访问的,而必须经过注册然后登录后方可使用提供服务的页面,这个时候就涉及到COOKIE问题的处理。我们知道目前流行的动态网页技术例如ASP、JSP无不是通过COOKIE来处理会话信息的。为了使我们的程序能使用别人所提供的服务页面,就要求程序首先登录后再访问服务页面,这过程就需要自行处理cookie,想想当你用java.net.HttpURLConnection来完成这些功能时是多么恐怖的事情啊!况且这仅仅是我们所说的顽固的WEB服务器中的一个很常见的“顽固”!再有如通过HTTP来上传文件呢?不需要头疼,这些问题有了“它”就很容易解决了!  

我们不可能列举所有可能的顽固,我们会针对几种最常见的问题进行处理。当然了,正如前面说到的,如果我们自己使用java.net.HttpURLConnection来搞定这些问题是很恐怖的事情,因此在开始之前我们先要介绍一下一个开放源码的项目,这个项目就是Apache开源组织中的httpclient,它隶属于Jakarta的commons项目,目前的版本是2.0RC2。commons下本来已经有一个net的子项目,但是又把httpclient单独提出来,可见http服务器的访问绝非易事。  

Commons-httpclient项目就是专门设计来简化HTTP客户端与服务器进行各种通讯编程。通过它可以让原来很头疼的事情现在轻松的解决,例如你不再管是HTTP或者HTTPS的通讯方式,告诉它你想使用HTTPS方式,剩下的事情交给httpclient替你完成。本文会针对我们在编写HTTP客户端程序时经常碰到的几个问题进行分别介绍如何使用httpclient来解决它们,为了让读者更快的熟悉这个项目我们最开始先给出一个简单的例子来读取一个网页的内容,然后循序渐进解决掉前进中的所有问题。 

  前一段时间公司项目在利用Apache HttpClient做网络请求的时候,经常拿不到返回结果,试了很多方法都没有结果。尤其是当kt40扫描程序和流水线控制程序同时运作的时候,程序经常收不到后台返回的结果,而后台明明是有值的。后来,通过度娘,最终解决问题。

      Apache HttpClient应该是最常用的Java http组件了。这货有个坑爹的地方,Apache HttpClient如果对方不回应,或者网络原因不返回了,那么HttpClient会一直阻塞。这种情况在公网可能比较容易碰到。在内网时,也有一次因为一台中转的nginx挂掉而导致hessian请求长时间阻塞。

  因为Http Client默认的SO_TIMEOUT是0,即一直等待。

  这个问题,在帮同事查找问题时碰到好几次了,可能是大家潜意识里认为Http请求是即时的,失败的话也很快返回。

  Apache HttpClient的示例也没提到要设置TimeOut,这也是比较坑爹的地方。一个库如果没有默认阻止用户去范错误,那么你也应当在文档,示例代码里提醒用户不要范错误。

  有三个可以设置time out 的参数:  

       

[java] view
plain copy

 print?

  HttpClient httpClient = new DefaultHttpClient(); //或者DefaultHttpClient  httpClient = new DefaultHttpClient();  

  

  

<span style="color:#ff0000;">httpClient.getParams().setIntParameter(CoreConnectionPNames.SO_LINGER, value);</span>  

  

  

httpClient.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 3000);  

  

  

httpClient.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 3000);  

Java中可以使用HttpURLConnection来请求WEB资源。

HttpURLConnection对象不能直接构造,需要通过URL.openConnection()来获得HttpURLConnection对象,示例代码如下:

String szUrl = "http://www.ee2ee.com/";

URL url = new URL(szUrl);

HttpURLConnection urlCon = (HttpURLConnection)url.openConnection();

HttpURLConnection是基于HTTP协议的,其底层通过socket通信实现。如果不设置超时(timeout),在网络异常的情况下,可能会导致程序僵死而不继续往下执行。可以通过以下两个语句来设置相应的超时:

System.setProperty("sun.net.client.defaultConnectTimeout", 超时毫秒数字符串);

System.setProperty("sun.net.client.defaultReadTimeout", 超时毫秒数字符串);

其中: sun.net.client.defaultConnectTimeout:连接主机的超时时间(单位:毫秒)

sun.net.client.defaultReadTimeout:从主机读取数据的超时时间(单位:毫秒)

例如:

System.setProperty("sun.net.client.defaultConnectTimeout", "30000");

System.setProperty("sun.net.client.defaultReadTimeout", "30000");

JDK 1.5以前的版本,只能通过设置这两个系统属性来控制网络超时。在1.5中,还可以使用HttpURLConnection的父类URLConnection的以下两个方法:

setConnectTimeout:设置连接主机超时(单位:毫秒)

setReadTimeout:设置从主机读取数据超时(单位:毫秒)

例如:

HttpURLConnection urlCon = (HttpURLConnection)url.openConnection();

urlCon.setConnectTimeout(30000);

urlCon.setReadTimeout(30000);

需要注意的是,笔者在JDK1.4.2环境下,发现在设置了defaultReadTimeout的情况下,如果发生网络超时,HttpURLConnection会自动重新提交一次请求,出现一次请求调用,请求服务器两次的问题(Trouble)。我认为这是JDK1.4.2的一个bug。在JDK1.5.0中,此问题已得到解决,不存在自动重发现象。
 
例子:

---------------------------------------------------
public static void SendMtToHttp(String sUrl, int comm_id, String userphone, String short_message,String url_addr,String linkid)

{

    try {

           java.net.URL url = new java.net.URL(sUrl);

           java.net.HttpURLConnection con = (HttpURLConnection)url.openConnection();

           con.setRequestMethod("POST");

           System.setProperty("sun.net.client.defaultConnectTimeout", "30000");//jdk1.4换成这个,连接超时

           System.setProperty("sun.net.client.defaultReadTimeout", "30000"); //jdk1.4换成这个,读操作超时

           //con.setConnectTimeout(5000);//jdk 1.5换成这个,连接超时

           // con.setReadTimeout(5000);//jdk 1.5换成这个,读操作超时

           con.setDoOutput(true);

           PrintWriter out = new PrintWriter(con.getOutputStream());

            

           String str = "url=" + url;

           System.out.println("");

           out.println(str);

           out.flush();

           BufferedReader in = null;

           in = new BufferedReader( new InputStreamReader(con.getInputStream()));
           String destStr = "";

           String inputLin="";

           while ((inputLin = in.readLine()) != null)

             {

                destStr += inputLin;

             }

           destStr = URLDecoder.decode(destStr,"gb2312");

           System.out.println(destStr);

           out.close();

         }

        catch(Exception e)

        {

          e.printStackTrace();

        }

   }

DefaultHttpClient:
请求超时
httpclient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 60000); 
读取超时
httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 60000);

HttpClient
HttpClient httpClient=new HttpClient(); 
链接超时
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(60000);  

读取超时

httpClient.getHttpConnectionManager().getParams().setSoTimeout(60000)

    其中HttpClient 是接口,DefaultHttpClient是实现这个接口的子类

    最开始,我只设置SO_TIMEOUT和CONNECTION_TIMEOUT这俩个参数,设置超时,发现无效,后来通过查API,增加了以上红色代码,问题解决。我们都知道,SO_TIMEOUT设置的是socket的超时时间,CONNECTION_TIMEOUT设置的是连接超时的时间,单位都为毫秒。那么SO_LINGER到底是什么鬼,见如下API:

SO_LINGER
static final String SO_LINGER
Sets SO_LINGER with the specified linger time in seconds. The maximum timeout value is platform specific. Value 0 implies that the option is disabled. Value -1 implies that the
JRE default is used. The setting only affects socket close.
This parameter expects a value of type Integer.
大致的意思是,设置socket指定持续的时间,以秒为单位,最大超时的值是特定于平台的。值0意味着选项是禁用的。值1意味着JRE默认使用。设置只影响到套接字关闭。也就是说重新设置套接字关闭的时间。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: