使用 httpclient 4.1.2 连接到 https 会产生 org.apache.http.client.ClientProtocolException

Posted

技术标签:

【中文标题】使用 httpclient 4.1.2 连接到 https 会产生 org.apache.http.client.ClientProtocolException【英文标题】:connection to https with httpclient 4.1.2 gives org.apache.http.client.ClientProtocolException 【发布时间】:2011-11-11 06:34:29 【问题描述】:

我已在 android 中成功使用this solution 发布到接受所有证书的 https 服务器

现在我正在 JAVA 中尝试同样的事情来发布到接受所有证书的 https 服务器。

我从上面的 url 修改了 EasySSLSocketFactory 类,因为在 httpclient 4.1.2 版本中不推荐使用 SocketFactory, LayeredSocketFactory 类。来自上述 url 的 EasyX509TrustManager 类保持不变。这是我修改的EasySSLSocketFactory。我已经标记了我修改的类。

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;

import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.SchemeSocketFactory;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;


public class EasySSLSocketFactory implements SchemeSocketFactory

     private SSLContext sslcontext = null;

     private static SSLContext createEasySSLContext() throws IOException 
             try 
                     SSLContext context = SSLContext.getInstance("TLS");
                     context.init(null, new TrustManager[]  new EasyX509TrustManager(null) , null);
                     return context;
              catch (Exception e) 
                     throw new IOException(e.getMessage());
             
     


    private SSLContext getSSLContext() throws IOException 
             if (this.sslcontext == null) 
                     this.sslcontext = createEasySSLContext();
             
             return this.sslcontext;
     

     /**
      * @see org.apache.http.conn.scheme.SchemeSocketFactory#isSecure(java.net.Socket)
      */
     public boolean isSecure(Socket socket) throws IllegalArgumentException 
             return true;
     

     // -------------------------------------------------------------------
     // javadoc in org.apache.http.conn.scheme.SocketFactory says :
     // Both Object.equals() and Object.hashCode() must be overridden
     // for the correct operation of some connection managers
     // -------------------------------------------------------------------

     public boolean equals(Object obj) 
             return ((obj != null) && obj.getClass().equals(
                             EasySSLSocketFactory.class));
     

     public int hashCode() 
             return EasySSLSocketFactory.class.hashCode();
     

    //this method is modified
    @Override
    public Socket connectSocket(Socket sock, InetSocketAddress remoteAddress,
            InetSocketAddress localAddress, HttpParams params) throws IOException,
            UnknownHostException, ConnectTimeoutException 

        int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
        int soTimeout = HttpConnectionParams.getSoTimeout(params);
        SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket(params));
        if (localAddress != null) 
            // we need to bind explicitly
            sslsock.bind(localAddress);
    

    sslsock.connect(remoteAddress, connTimeout);
    sslsock.setSoTimeout(soTimeout);
    return sslsock;
    

    //this method is modified
    @Override
    public Socket createSocket(HttpParams arg0) throws IOException 
         return getSSLContext().getSocketFactory().createSocket();
    


这里是我的httpclient。这个类也是因为使用httpclient 4.1.2而修改的

public class MyHttpClient extends DefaultHttpClient 

    /** The time it takes for our client to timeout */
    public static final int HTTP_TIMEOUT = 30 * 1000; // milliseconds
    public static final int SOCKET_TIMEOUT = 50 * 1000; // milliseconds


    public MyHttpClient() 
    

    @Override
    protected ClientConnectionManager createClientConnectionManager() 

        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
        // Register for port 443 our SSLSocketFactory to the ConnectionManager
        registry.register(new Scheme("https", 443, new EasySSLSocketFactory()));

        //setting the httpparams
        HttpParams params = new BasicHttpParams();
        //params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 1);
        //params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(1));
        params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
        //HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, "utf8");

        return new SingleClientConnManager(registry);
    


我正在尝试通过以下方式连接

ArrayList<NameValuePair> postParameters = new ArrayList<NameValuePair>();
        postParameters.add(new BasicNameValuePair("userid", userid));
        postParameters.add(new BasicNameValuePair("password", password));
        String newresponse = null;
        BufferedReader in = null;
        try
             DefaultHttpClient client = new MyHttpClient();
             try         
                    HttpPost req = new HttpPost(my https url);
                    UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(postParameters);
                    req.setEntity(formEntity);
                    HttpResponse resp = client.execute(req);
                    in = new BufferedReader(new InputStreamReader(resp.getEntity().getContent()));
                    StringBuffer sb = new StringBuffer("");
                    String line = "";
                    String NL = System.getProperty("line.separator");
                    while ((line = in.readLine()) != null) 
                        sb.append(line + NL);
                    
                    in.close();
                    newresponse = sb.toString();

                    catch(Exception e)
                        LOGGER.error("Exception", e);
                    finally 
                        if (in != null) 
                            try 
                                in.close();
                             catch (IOException e) 
                                LOGGER.error("IOException: ",e);
                            
                        
                    
         catch(Exception e)
            LOGGER.debug("Connection Exception: ",e);
        

当我尝试以上述方式连接到 https 服务器时,我得到了

org.apache.http.client.ClientProtocolException
        at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:822)
        at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:754)
        at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:732)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:164)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:576)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
        at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:563)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:399)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:317)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:204)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:182)
        at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:311)
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
        at java.lang.Thread.run(Thread.java:662)
Caused by: org.apache.http.HttpException: Unable to establish route: planned = HttpRoute[->https://mysiteurlhere]; current = HttpRoute[s->https://mysiteurlhere]
        at org.apache.http.impl.client.DefaultRequestDirector.establishRoute(DefaultRequestDirector.java:774)
        at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:577)
        at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:425)
        at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
        ... 22 more

我尝试访问的服务器正在使用我没有的 verisign 证书。所以我正在尝试接受所有证书。将来我将更改此设置。

任何人都可以让它工作吗? 提前致谢。

【问题讨论】:

【参考方案1】:

如果您希望 LayeredSchemeSocketFactory 支持 SSL 隧道,您的 EasySSLSocketFactory 还必须实现接口。

【讨论】:

@oleg- 感谢您的回复。我会尝试并及时通知您。 @oleg- 我尝试实现它,它就像一个魅力。非常感谢你【参考方案2】:

还需要为 SSLSockerFactory 注册 Scheme:

httpClient.getConnectionManager().getSchemeRegistry().register(
    new Scheme("https", 443, SSLSocketFactory.getSystemSocketFactory())
);

【讨论】:

以上是关于使用 httpclient 4.1.2 连接到 https 会产生 org.apache.http.client.ClientProtocolException的主要内容,如果未能解决你的问题,请参考以下文章

使用 TLS 1.2 从 HttpClient 连接到 Azure FrontDoor 后面的 API

从 HTTPClient 响应中解压 GZip 流

使用 HttpClient POST 到 Form 但在浏览器中获取 SSLPeerUnverifiedException 工作正常

使用 HttpClient 的 HTTP 连接池

无限超时的 HttpClient 抛出超时异常

Ktor 的 HttpClient 使用的正确模式