原來的測試程序,在Response.Flush()之后桐智,調(diào)用Response.End()稿壁,而出錯(cuò)的程序在Response.Flush()之后,調(diào)用Response.Close()记靡。直接將Close調(diào)用改為End后,問題消失团驱∶停看來問題的根源就在這里了。
MSDN對兩個(gè)方法給出的注釋是:
Close斷開客戶端的連接嚎花。
End結(jié)束當(dāng)前頁面的執(zhí)行寸痢。
看來確有不同。通過查看源碼紊选,Close 是服務(wù)器主動(dòng)斷開連接啼止,然后設(shè)置客戶端斷開連接的標(biāo)志,沒有其他的操作兵罢。而 End 會(huì)再次 Flush 當(dāng)前的內(nèi)容献烦,然后設(shè)置頁面結(jié)束的標(biāo)志,然后引發(fā) EndRequest 事件卖词,并不立即斷開客戶端連接巩那。
正是由于采取了不同的方法,導(dǎo)致最終的結(jié)果不同。Close 采用強(qiáng)硬手段即横,直接斷開客戶端連接噪生,也就使得 xmlhttp 組件不知道內(nèi)容是否已經(jīng)傳完,于是導(dǎo)致取不到內(nèi)容东囚。而 End 采用常規(guī)方法杠园,一步步操作,在 EndRequest 中明確告知后續(xù)操作自己正常結(jié)束舔庶,之后由后續(xù)操作正常斷開客戶端。說到這里陈醒,就不能不說惕橙,這跟.Net中http請求的生命周期相關(guān)。在http請求的生命周期中钉跷,Close 和 End 就像馬拉松的折返點(diǎn)一樣弥鹦,Close 相當(dāng)于到了折返點(diǎn)就直接坐汽車回起點(diǎn)了,而 End 相當(dāng)于到了折返點(diǎn)繼續(xù)跑回去爷辙。那么最終的結(jié)果肯定是不同的彬坏。
到目前為止,還是沒有解釋為什么啟用IIS壓縮功能之前是正常的膝晾,啟用壓縮功能之后就出錯(cuò)了呢栓始?其實(shí)我們已經(jīng)很接近真相了。因?yàn)閱⒂脡嚎s之后血当,服務(wù)器必須明確告訴客戶端幻赚,我傳給你的內(nèi)容是經(jīng)過壓縮的,壓縮方法是GZIP臊旭。這樣客戶端才能正確解析內(nèi)容落恼。而這個(gè)信息是放在http響應(yīng)頭的 Content-Encoding 中的。由于 Close 主動(dòng)斷開了連接离熏,使得IIS沒有機(jī)會(huì)加上這個(gè)響應(yīng)頭信息佳谦。所以就造成了采用Close方式時(shí),xmlhttp 組件獲取不到這個(gè)信息滋戳。由于沒啟用壓縮之前钻蔑,內(nèi)容已經(jīng)被接收完畢,并且xmlhttp可以正常解析胧瓜,所以在啟用壓縮之前矢棚,即使缺少 Content-Encoding 信息,功能也是正常的府喳。啟用壓縮之后蒲肋,xmlhttp 理解不了接收到的數(shù)據(jù),所以就出錯(cuò)了。
?不要使用Response.Close()
?? 1? 因?yàn)?Close()方法會(huì)調(diào)用HttpWorkerRequest.CloseConnection()方法兜粘。終止(Terminate)與客戶端的套接字連接申窘,并使得服務(wù)器,客戶端以及之間設(shè)施上的緩存(buffer)失效孔轴。導(dǎo)致發(fā)送到客戶端的數(shù)據(jù)丟失剃法。
???2?方法Response.End()是為了兼容經(jīng)典ASP程序,在Asp.NET 1.0中引入的路鹰,在調(diào)用后會(huì)拋出ThreadAbortException異常贷洲。成功時(shí)則中止(abort)當(dāng)前的線程,處理管道觸發(fā)EndRequest事件晋柱,不再處理之后的代碼优构。會(huì)以同步的方式將響應(yīng)內(nèi)容發(fā)送(flush)給客戶端。
????? 由于.NET 設(shè)計(jì)原因雁竞,Response.End()在WebForm框架下可以終止代碼執(zhí)行钦椭,不再處理End()之后的代碼。在MVC框架下則只是返回響應(yīng)流碑诉,不會(huì)中止代碼執(zhí)行彪腔。
?3?此外還可以使用HttpApplication.CompleteRequest()方法結(jié)束請求。
????? Response.Flush();
??????this.Context.ApplicationInstance.CompleteRequest();
?? 綜上所述:只有代碼發(fā)生錯(cuò)誤(惡意的攻擊)进栽,希望終止對于客戶端的響應(yīng)/連接時(shí)才可以使用Response.Close()
?? 如果你想結(jié)束請求德挣,并向客戶端發(fā)送請求則應(yīng)該優(yōu)先使用HttpApplication.CompleteRequest()方法。
參考文章:
1?http://weblogs.asp.net/hajan/why-not-to-use-httpresponse-close-and-httpresponse-end
requests是一個(gè)很實(shí)用的Python?HTTP客戶端庫泪幌,編寫爬蟲和測試服務(wù)器響應(yīng)數(shù)據(jù)時(shí)經(jīng)常會(huì)用到盲厌。可以說祸泪,Requests 完全滿足如今網(wǎng)絡(luò)的需求
本文全部來源于官方文檔 http://docs.python-requests.org/en/master/
安裝方式一般采用$ pip install requests吗浩。其它安裝方式參考官方文檔.
import?requests
GET請求
r? = requests.get('http://httpbin.org/get')
傳參
>>>?payload?=?{'key1':?'value1',?'key2':?'value2', 'key3':?None}
>>>?r?=?requests.get('http://httpbin.org/get',?params=payload)
參數(shù)也可以傳遞列表
>>>?payload?=?{'key1':?'value1',?'key2': ['value2',?'value3']}
>>>?r?=?requests.get('http://httpbin.org/get',?params=payload)
>>>?print(r.url)
r.text?返回headers中的編碼解析的結(jié)果,可以通過r.encoding = 'gbk'來變更解碼方式
r.content返回二進(jìn)制結(jié)果
r.json()返回JSON格式没隘,可能拋出異常
r.status_code
r.raw返回原始socket respons懂扼,需要加參數(shù)stream=True
>>>?r?=?requests.get('https://api.github.com/events',?stream=True)
>>>?r.raw
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
>>>?r.raw.read(10)
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
將結(jié)果保存到文件,利用r.iter_content()
with?open(filename,?'wb')?as?fd:
? ??????for?chunk?in?r.iter_content(chunk_size):
????????????????fd.write(chunk)
傳遞headers
>>>?headers?=?{'user-agent':?'my-app/0.0.1'}
>>>?r?=?requests.get(url,?headers=headers)
傳遞cookies
>>>?url?=?'http://httpbin.org/cookies'
>>>?r?=?requests.get(url,?cookies=dict(cookies_are='working'))
>>>?r.text
'{"cookies": {"cookies_are": "working"}}'
POST請求
傳遞表單
r?=?requests.post('http://httpbin.org/post',?data?=?{'key':'value'})
通常右蒲,你想要發(fā)送一些編碼為表單形式的數(shù)據(jù)—非常像一個(gè)HTML表單阀湿。 要實(shí)現(xiàn)這個(gè),只需簡單地傳遞一個(gè)字典給?data?參數(shù)瑰妄。你的數(shù)據(jù)字典 在發(fā)出請求時(shí)會(huì)自動(dòng)編碼為表單形式:
>>>?payload?=?{'key1':?'value1',?'key2':?'value2'}
>>>?r?=?requests.post("http://httpbin.org/post",?data=payload)
>>>?print(r.text)
{? ...? "form": {??? "key2": "value2",??? "key1": "value1"? },? ...}
很多時(shí)候你想要發(fā)送的數(shù)據(jù)并非編碼為表單形式的陷嘴。如果你傳遞一個(gè)?string?而不是一個(gè)dict?,那么數(shù)據(jù)會(huì)被直接發(fā)布出去间坐。
>>>?url?=?'https://api.github.com/some/endpoint'
>>>?payload?=?{'some':?'data'}
>>>?r?=?requests.post(url,?data=json.dumps(payload))
或者
>>>?r?=?requests.post(url,?json=payload)
傳遞文件
url?=?'http://httpbin.org/post'
>>>?files?=?{'file':?open('report.xls',?'rb')}
>>>?r?=?requests.post(url,?files=files)
配置files灾挨,filename, content_type and headers
files?=?{'file': ('report.xls',?open('report.xls',?'rb'),?'application/vnd.ms-excel', {'Expires':?'0'})}
files?=?{'file': ('report.csv',?'some,data,to,send\nanother,row,to,send\n')}
響應(yīng)
r.status_code
r.heards
r.cookies
跳轉(zhuǎn)
>>>?r?=?requests.get('http://httpbin.org/cookies/set?k2=v2&k1=v1')
>>>?r.url
>>>?r.status_code200
>>>?r.history
[<Response [302]>]
r=requests.head('http://httpbin.org/cookies/set?k2=v2&k1=v1',allow_redirects=True)
requests.get('http://github.com',?timeout=0.001)
高級特性
來自?<http://docs.python-requests.org/en/master/user/advanced/#advanced>
session邑退,自動(dòng)保存cookies,可以設(shè)置請求參數(shù)劳澄,下次請求自動(dòng)帶上請求參數(shù)
s?=?requests.Session()
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r?=?s.get('http://httpbin.org/cookies')
print(r.text)
# '{"cookies": {"sessioncookie": "123456789"}}'
session可以用來提供默認(rèn)數(shù)據(jù)地技,函數(shù)參數(shù)級別的數(shù)據(jù)會(huì)和session級別的數(shù)據(jù)合并,如果key重復(fù)秒拔,函數(shù)參數(shù)級別的數(shù)據(jù)將覆蓋session級別的數(shù)據(jù)莫矗。如果想取消session的某個(gè)參數(shù),可以在傳遞一個(gè)相同key砂缩,value為None的dict
s?=?requests.Session()
s.auth?=?('user',?'pass') #權(quán)限認(rèn)證
s.headers.update({'x-test':?'true'})
# both 'x-test' and 'x-test2' are sent
s.get('http://httpbin.org/headers',?headers={'x-test2':?'true'})
函數(shù)參數(shù)中的數(shù)據(jù)只會(huì)使用一次作谚,并不會(huì)保存到session中
如:cookies僅本次有效
r?=?s.get('http://httpbin.org/cookies',?cookies={'from-my':?'browser'})
session也可以自動(dòng)關(guān)閉
with?requests.Session()?as?s:
????????s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
響應(yīng)結(jié)果不僅包含響應(yīng)的全部信息,也包含請求信息
r?=?requests.get('http://en.wikipedia.org/wiki/Monty_Python')
r.headers
r.request.headers
SSL證書驗(yàn)證
Requests可以為HTTPS請求驗(yàn)證SSL證書庵芭,就像web瀏覽器一樣食磕。要想檢查某個(gè)主機(jī)的SSL證書,你可以使用?verify?參數(shù):
>>>?requests.get('https://kennethreitz.com',?verify=True)
requests.exceptions.SSLError: hostname 'kennethreitz.com' doesn't match either of '*.herokuapp.com', 'herokuapp.com'
在該域名上我沒有設(shè)置SSL喳挑,所以失敗了。但Github設(shè)置了SSL:
>>>?requests.get('https://github.com',?verify=True)
<Response [200]>
對于私有證書滔悉,你也可以傳遞一個(gè)CA_BUNDLE文件的路徑給?verify?伊诵。你也可以設(shè)置REQUEST_CA_BUNDLE?環(huán)境變量。
>>>?requests.get('https://github.com',?verify='/path/to/certfile')
如果你將?verify?設(shè)置為False回官,Requests也能忽略對SSL證書的驗(yàn)證曹宴。
>>>?requests.get('https://kennethreitz.com',?verify=False)
<Response [200]>
默認(rèn)情況下,?verify?是設(shè)置為True的歉提。選項(xiàng)?verify?僅應(yīng)用于主機(jī)證書笛坦。
你也可以指定一個(gè)本地證書用作客戶端證書,可以是單個(gè)文件(包含密鑰和證書)或一個(gè)包含兩個(gè)文件路徑的元組:
>>>?requests.get('https://kennethreitz.com',?cert=('/path/server.crt',?'/path/key'))
<Response [200]>
響應(yīng)體內(nèi)容工作流
當(dāng)你進(jìn)行網(wǎng)絡(luò)請求后苔巨,響應(yīng)體會(huì)立即被下載版扩。你可以通過?stream?參數(shù)覆蓋這個(gè)行為,推遲下載響應(yīng)體直到訪問Response.content屬性:
tarball_url?=?'https://github.com/kennethreitz/requests/tarball/master'
r?=?requests.get(tarball_url,?stream=True)
此時(shí)僅有響應(yīng)頭被下載下來了侄泽,連接保持打開狀態(tài)礁芦,因此允許我們根據(jù)條件獲取內(nèi)容:
if? ?int(r.headers['content-length'])?<?TOO_LONG:
content?=?r.content
...
如果設(shè)置stream為True,請求連接不會(huì)被關(guān)閉悼尾,除非讀取所有數(shù)據(jù)或者調(diào)用Response.close柿扣。
可以使用contextlib.closing來自動(dòng)關(guān)閉連接:
import?requests
from?contextlib
import?closing
tarball_url?=?'https://github.com/kennethreitz/requests/tarball/master'
file?=?r'D:\Documents\WorkSpace\Python\Test\Python34Test\test.tar.gz'
with?closing(requests.get(tarball_url,?stream=True))?as?r:
? ? ? ??with?open(file,?'wb')?as?f:
for?data?in?r.iter_content(1024):
f.write(data)
Keep-Alive
?<http://docs.python-requests.org/en/master/user/advanced/>
同一會(huì)話內(nèi)你發(fā)出的任何請求都會(huì)自動(dòng)復(fù)用恰當(dāng)?shù)倪B接!
注意:只有所有的響應(yīng)體數(shù)據(jù)被讀取完畢連接才會(huì)被釋放為連接池闺魏;所以確保將?stream設(shè)置為?False?或讀取?Response?對象的?content?屬性未状。
流式上傳
Requests支持流式上傳,這允許你發(fā)送大的數(shù)據(jù)流或文件而無需先把它們讀入內(nèi)存析桥。要使用流式上傳司草,僅需為你的請求體提供一個(gè)類文件對象即可:
讀取文件請使用字節(jié)的方式艰垂,這樣Requests會(huì)生成正確的Content-Length
with?open('massive-body',?'rb')?as?f:
????????requests.post('http://some.url/streamed',?data=f)
>>>?requests.get('http://pizzabin.org/admin',?auth=PizzaAuth('kenneth'))
<Response [200]>
來自?<http://docs.python-requests.org/en/master/user/advanced/>
流式請求
r?=?requests.get('http://httpbin.org/stream/20',?stream=True)
for?line?in?r.iter_lines():
代理
import?requests
proxies?=?{
'http':?'http://10.10.1.10:3128',
'https':?'http://10.10.1.10:1080',}
requests.get('http://example.org',?proxies=proxies)
proxies?=?{'http':?'http://user:pass@10.10.1.10:3128/'}
超時(shí)
r?=?requests.get('https://github.com',?timeout=5)
r?=?requests.get('https://github.com',?timeout=(3.05,?27))
來自?<http://docs.python-requests.org/en/master/user/advanced/>