說起這個問題就覺得蛋疼,這個問題可是折騰了我一天一夜的說。。
幸好在知乎上遇到各路高人,在大家的幫助下總算把問題解決了,現在拿出來分享一下~
先描述一下遇到的問題:
學校的信息門戶和教務系統域名分別是portal.uestc.edu.cn和ea.uestc.edu.cn,但是內外網訪問的ip地址不一樣。
信息門戶 portal.uestc.edu.cn out:125.71.228.241 in:222.197.164.72
教務系統 ea.uestc.edu.cn out:125.71.228.243 in:222.197.164.82
當我們身處內網的時候,訪問這兩個網站妥妥的,科大的DNS還是蠻給力的。
但是當我們處於校外的時候,就會發現訪問這兩個網站變得異常的艱難,經常是無法解析域名,或是域名解析成其它IP地址了。(不過發生這種情況還要區分身處哪個省,因為大家的DNS都不一樣。。。)
說了這麼多前提,是時候說正題了。
最近在用python寫一個查成績的客戶端,由於學校特殊的身份驗證方式,需要依次訪問上面的兩個網站。
如果讓python訪問只由域名構成的url,在校內還是無壓力的,但到了校外肯定就是各種403、404,或者直接Connection refused。。
那麼直接用ip去代替url中的域名又行不行呢?答案是否定的,因為這兩個服務器上面都不只是一個網站,而且都不是在主目錄下。
上面就是我所遇到的問題以及我能想到的解決方法(其實沒解決!)
在各種看官方文檔以及各種搜索都無果之後,我決定在知乎上求助,希望有牛人指導一下我 T_T
我在知乎上提出的問題的傳送門:http://www.zhihu.com/question/21054889
感謝知乎上的@狼大人,在他的幫助下解決了上面的問題。下面就來詳細講一下是如何解決的吧~
第一種解決方案是利用http數據包頭部中的“Host”屬性。
在發送HTTP請求的時候,數據包的頭部總是會帶上各種各樣的屬性,比如Data、Referer、Cookie等。(Quick reference to HTTP headers)
其中的Host屬性是指,當前訪問資源對應的主機名和端口號。
假設我們要訪問的url是http://hack0nair.me/wp-admin/ ,需要域名固定解析為 127.0.0.1 這個IP。
使用python中urllib庫的request做法是:
urllib.request(url="http://127.0.0.1/wp-admin/", headers={"Host" : "hack0nair.me"})
訪問會產生如下的數據包:
GET http://127.0.0.1/wp-admin/ HTTP/1.1
Host: hack0nair.me
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:16.0) Gecko/20100101 Firefox/16.0
Accept: */*
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://hack0nair.me/
這樣的訪問會告訴127.0.0.1服務器,當前需要訪問的/wp-admin是hack0nair.me這個域名的資源。
看來問題已經解決了,其實未必。
對於不需要cookie支持的訪問來說,問題確實解決了。但如果是需要多次訪問並要驗證cookie的話,這樣做是不行的。因為這樣訪問產生的cookie對應的作用域是IP而不是域名!於是就有了下面一種更完美的解決方案。
第二種解決方案是直接修改socket的address。
方法來自StackOverFlow的MattH:http://stackoverflow.com/questions/2236498/tell-urllib2-to-use-custom-dns
具體的意思是,建立HTTP連接的時候仍然使用域名,但是通過修改底層socket的address來達到目的。
這裡理解起來需要一點計算機網絡的基礎。在建立HTTP連接的時候,在應用層必然會使用一個socket跟遠方服務器進行通信,而socket建立的時候需要指定服務器的地址,這時候我們只需要填入我們預設的IP地址即可。由於應用層是不會去關心它的底層如何建立連接的,它只知道自己正在對哪個域名進行訪問,這樣就相當於“欺騙”了處於上層的HTTP服務。(我的語言表達能力略拙計的說= =)
大概就是上面這個意思了,那麼要在python中實現上面的想法,首先需要建立一個httplib.HTTPConnection的子類,在裡面定義一個connect方法求修改socket.create_connection的默認參數;然後繼承HTTPHandler這個類,重寫裡面的http_open方法,使得http_open去調用第一步的子類;最後就是把HTTPHandler放到我們的opener裡面。
MattH同時給出了HTTP和HTTPS的修改方案,不過我只要HTTP的修改就夠了。下面是經過我修改的例子:
def MyHost(host):
if host == 'hack0nair.me':
return '127.0.0.1' # 指定的IP地址
else:
return host
class MyHTTPConnection(httplib.HTTPConnection):
def connect(self):
self.sock = socket.create_connection((MyHost(self.host),self.port),self.timeout)
class MyHTTPHandler(urllib2.HTTPHandler):
def http_open(self,req):
return self.do_open(MyHTTPConnection,req)
opener = urllib2.build_opener(MyHTTPHandler)
urllib2.install_opener(opener)
url = 'http://hack0nair.me/wp-admin'
req = request.Request(url)
req = f.read()
上面例子的含義是,當opener遇到域名是hack0nair.me時,MyHTTPHandler會讓所建立的socket的address為指定的IP地址。由於沒有對urllib相關對象進行修改,所以HTTP神馬都不知道~
如果訪問要帶上cookie,那麼還要加上HTTPCookieProcessor這個handler。