https(ssl)連接之python實現


今天寫代碼時碰到一個問題,花了幾個小時的時間google,基本上把google搜索的前幾頁內容都一一看了下,問題最終是解決了,不過過程挺曲折的,所以把這個過程記下來以便以后參考之。

原因是以下一段代碼引起的:

import urllib2
urllib2.urlopen('https://xxxx.com')

本來這段代碼很簡單的,就是請求一個https的連接,可是報以下錯誤:

urllib2.URLError: 

第一反應是https證書問題產生的,如是以'python ssl' 為關鍵字google后,看到大家都在用'requests'這個python組件做http請求客戶端,就像java里面的httpclient組件一樣,如果安裝完request包后,改成如下代碼:

import requests
requests.get('https://xxx.com')

 還是報以下錯誤:

requests.exceptions.SSLError: [Errno 1] _ssl.c:504: error:140773E8:SSL routines:SSL23_GET_SERVER_HELLO:reason(1000)

可以看出來,用requests和urllib2報的錯誤信息是一樣,可見它們都是基於相同的底層api操作的,比如基於TLS的socket連接。到這里的時候我懷疑這個問題不是python代碼寫的有問題,可能是操作系統級別的設置錯了。如下直接在shell客戶端運行如下測試腳本:

wget https://xxx.com

果然報如下錯誤:

OpenSSL: error:140773E8:SSL routines:SSL23_GET_SERVER_HELLO:reason(1000)
無法建立 SSL 連接。

到這里我懷疑是openssl安裝有問題,更新到最新版本后還是一樣,然后在瀏覽器里訪問是可以的,所以應該不是openssl有問題。繼續google.......,就發現有人也遇到過這種問題,說是連接SSL服務器時SSL的版本不對,如是用如下代碼測試不同的SSL版本,看是不是這個問題:

curl -1 https://xxx.com

curl -2 https://xxx.com

curl -3 https://xxx.com

分別用上面的三句腳本去測試連接情況,發現第三種可以連接正常(-1,2,3,數字分別代碼tlsv1,sslv2,sslv3三個不同的SSL版本)。說明這個https連接所在的服務器是基於SSLV3版本的。找到的問題,就很容易知道怎么改寫python代碼了。

class MyAdapter(HTTPAdapter):
    def init_poolmanager(self, connections, maxsize):
        self.poolmanager = PoolManager(num_pools=connections,
            maxsize=maxsize,
            ssl_version=ssl.PROTOCOL_SSLv3)

s = requests.Session()
s.mount('https://', MyAdapter())#所有的https連接都用ssl.PROTOCOL_SSLV3去連接
s.get('https://xxx.com')

urllib2實現:

# custom HTTPS opener, banner's oracle 10g server supports SSLv3 only
import httplib, ssl, urllib2, socket
class HTTPSConnectionV3(httplib.HTTPSConnection):
    def __init__(self, *args, **kwargs):
        httplib.HTTPSConnection.__init__(self, *args, **kwargs)
        
    def connect(self):
        sock = socket.create_connection((self.host, self.port), self.timeout)
        if self._tunnel_host:
            self.sock = sock
            self._tunnel()
        try:
            self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv3)
        except ssl.SSLError, e:
            print("Trying SSLv3.")
            self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv23)
            
class HTTPSHandlerV3(urllib2.HTTPSHandler):
    def https_open(self, req):
        return self.do_open(HTTPSConnectionV3, req)
# install opener
urllib2.install_opener(urllib2.build_opener(HTTPSHandlerV3()))

if __name__ == "__main__":
    r = urllib2.urlopen("https://ui2web1.apps.uillinois.edu/BANPROD1/bwskfcls.P_GetCrse")
    print(r.read())

可以看到這兩種方案的原理都是一樣,就是自定義連接處理器,改變連接時ssl的版本號。

參考文章:http://bugs.python.org/issue11220

              https://github.com/kennethreitz/requests/issues/606

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM