簡介
網絡程序開發者們遇到的最普遍的問題就是如何在無狀態的基於HTTP協議的交互中保持狀態信息有許多聰明的辦法可以解決HTTP協議的無狀態問題例如對每個請求重復發送應用程序數據包使用HTTP認證機制來將請求映射到特定的用戶使用Cookie來存儲一系列請求的狀態等在技術中提供了一個非常有效的方案來保持狀態該方案隱藏了所有高難度的具有挑戰性的工作的細節用戶只需簡單地使用SystemWebSessionStateHttpSessionState類同時你也可以像在程序的Web頁面(aspx)中那樣在Web Service的方法中使用這個類只有一點小小的不同
的Session對象概述
的Session狀態信息是通過兩個機制保持其一是使用Cookie當客戶端發送一個請求到服務器端時服務器將發回一個附加HTTP SetCookie頭的響應信息而Cookie的值就是以鍵/值對的形式保存在該信息裡邊在對同一服務器的所有的同步請求中客戶端在HTTP Cookie頭中發送Cookie鍵/值對然後服務器可以將並發的請求同初始的請求對應起來使用一個保存會話的ID的cookie來保持會話狀態該ID標識被用來為特定的用戶找到與其對應的HttpSessionState類的實例類HttpSessionState僅僅提供了一個通用的數據集你可以在其中保存你需要的任何信息
用來保持狀態的另外一種機制是無須使用Cookie一些浏覽器被用戶設置為禁止使用Cookie或者干脆就不支持Cookie提供了一種機制來解決這個問題它的主要原理是將一個請求重定向到一個包含狀態ID的URL當該請求被接受到時這個嵌在URL中的ID被截取下來服務器通過該ID找到合適的HttpSessionState類的實例這種方式在HTTP協議的使用GET方式的請求中工作的很好但是的XML Web Service代碼中會出現問題
必須指出的是有些時候把信息直接存儲在Cookie中要比存儲在Session中更好避免使用Session可以節省服務器資源而且你也無須考慮一些煩人的問題比如定位一個特定的Session對象Session對象因為請求的長時間的延遲而被移除或者在服務器上沒必要地保留直到過期然而如果你有一些包含你不希望與你提供的服務的使用者共享的執行信息或者有一些你不希望通過未加密的信道傳輸的私有數據或者你認為將這些數據插入HTTP協議頭中是不切實際的那麼你就應該使用中的HttpSessionState它將使你輕松解決這些問題HttpSessionState類返回一個索引鍵用以將一個特定的用戶映射到一個為該用戶保存信息的HttpSessionState類的實例總之無論是的HttpSessionState類還是HTTP的Cookie都可以在 Web Service中使用
為什麼要在XML Web Service中使用基於HTTP的機制來實現狀態保持呢?
在SOAP請求中有許多方法來保持狀態一個切實可行的方法就是在SOAP頭中包含一些像ASP中的會話ID的信息然而問題在於你不得不) 仍然要自己編寫服務器端代碼並且 ) 確信你的客戶會像對待HTTP Cookie一樣對待你的包含會話ID的SOAP頭並且將它附加到每個請求中回傳給你當然有很多時候使用SOAP頭的方法會很方便但是也有很多時候還不如使用基於HTTP協議的方法
很容易在中使用Session來保持狀態信息HttpSessionState類為你封裝了存儲Session狀態的細節問題絕大多數的客戶端已經能夠明白他們必須返回服務器設置的cookie而且HttpSessionState類也支持在SOAP通信中常用的底層傳輸因此很明顯使用的Session機制會是滿足狀態控制要求的明智的選擇
使服務器支持Session
在中對Web方法的狀態支持默認是關閉的你必須為每個要使用Session狀態的Web方法顯式地激活Session支持激活Session支持的方法是添加一個EnableSession選項到你的函數的WebMethod屬性中並且將其值設置為true下面的代碼演示了如何激活Session並且在方法中訪問Session狀態信息
[]
<WebMethod(EnableSession:=True)> _
Public Function IncrementSessionCounterX() As Integer
Dim counter As Integer
If Context
Session(
Counter
) Is Nothing Then
counter =
Else
counter = Context
Session(
Counter
) +
End If
Context
Session(
Counter
) = counter
Return counter
End Function
如你所料如果你為一個Web方法激活了Session支持並不意味著其它的Web方法的Session支持也被激活事實上如果Web方法的EnableSession選項沒有被顯式地設置為true那麼ContextSession屬性的值將是null
假設通過設置nfig文件禁止session那麼即使你在WebMethod屬性中使用了EnableSession選項ContextSession的值也將一直是nullnfig文件中的/configuration/systemweb/sessionState項有一個mode參數它決定了你的程序使用何種方法來保持Session狀態該參數默認設置為InProc這時HttpSessionState對象將簡單地保存在進程的內存區如果被設置為Off那麼程序的Session支持就被關閉了
從服務器端看來的session狀態的有效范圍僅僅是某一個給定的應用程序這就意味著一個HttpSessionState類的實例只能被一個特定用戶向某一個虛擬目錄發出的所有Session被激活的請求所使用也就是說使用同一個會話ID的向其它的虛擬目錄的請求將導致不能找到對應的session對象——因為會話ID不是為該應用程序設定的並不區分對ASPX和ASMX文件的請求直到該請求需要使用Session對象因此理論上你可以在一個Web方法調用和一個普通的ASPX文件之間共享Session狀態信息然而我們將看到也有些客戶端的問題使這個想法變得不那麼容易實現
當設置一個HTTP cookie你可以指定其過期時間過期時間指定在多久的時間內客戶端應該將該cookie回傳給服務器如果一個cookie沒有被設置過期時間那麼它僅僅在該進程處理請求的時間內被回傳例如IE將一直回傳cookie除非你關閉了浏覽器的特定窗口的用來保存會話ID的Cookie沒有過期時間因此如果一台客戶機上的多個進程向你的服務器上發送HTTP請求它們也不會共享同一個HttpSessionState對象甚至兩個進程同時運行也是這樣
如果你要處理來自同一個進程的並發的Web Service調用那麼這些請求將在服務器上被排序從而使得在某一時刻只有一個請求被執行的Web Service不像普通ASPX頁面支持允許多請求的並發進程的對HttpSessionState對象的只讀訪問所有Session被激活的Web方法調用都具有read/write訪問的權限因此必須對之進行排序
客戶端的問題在你的WebService中成功的使用HttpSessionState的功能事實上依賴於對用戶的一些假設
首先
也是最重要的一點
如果你是用默認的HTTP Cookie模式來保存Session狀態
你的客戶端就必須支持cookie
如果你是用無cookie的機制來支持Session
那麼你的客戶端必須能夠並且願意重定向到一個新的URL
該URL由原來的URL中插入會話ID而得到
結果將表明
這並不是一個無足輕重的問題
它關系到你能否成功地部署你的程序
所有工作都依賴於浏覽器如果你是用Microsoft Visual 來開發 Web Service應用程序
那麼默認的調試方法就是打開IE訪問你的
asmx文件
通常
系統將提供一個可以調用你的Web方法的友好的界面
這是一個調試你的Web Service代碼的很好的途徑
如果你已經將Web方法的EnableSession選項設置為true
它被非常漂亮地支持
甚至如果你打開了無cookie的Session支持
客戶端浏覽器也可以完美地完成這項工作
你的Session對象將如你所願地工作
然而
大多數的Web Service請求不是來自浏覽器
而是來自應用程序中的Web引用
我們如何使框架的
添加Web引用
的特性呢?讓我們來看一看
添加Web引用的問題我將使用我們前面看到的代碼段來創建一個簡單的XML Web Service
記起來了吧?這個Web方法被稱作IncrementSessionCounter
它僅僅是簡單地把一個整數存儲在HttpSessionState對象中
然後每次調用則將它加
並且返回當前值
從客戶端浏覽器我們可以看到這個數字的值隨著調用次數的增加而增加
下一步
我創建了一個簡單的WinForm應用程序
並且將上述的Web Service添加到Web引用中
下面就是調用我的Web Service的代碼
這裡並沒有與Session打交道
Private Sub Button
_Click(ByVal sender As System
Object
_
ByVal e As System
EventArgs) Handles Button
Click
Dim proxy As New localhost
Service
()
Dim ret As Integer
ret = proxy
IncrementSessionCounter()
Label
Text =
Result:
& CStr(ret)
End Sub
當我第一次調用Web Service時
一切正常
Web方法返回
這就是那個Session變量的應有的初始值
現在我點擊Button
來再次調用這個Web方法
我希望看到的返回值是
可惜的是
無論我點擊多少次Button
返回值一直都是
你也許會懷疑原因就是我每次都創建了一個新的proxy類的實例去調用Web方法
因此每次我點擊按鈕
都會丟失上一次調用時的cookie
不幸的是
即使你將proxy類的初始化代碼移到窗體的構造函數中
然後對每次Web方法調用使用同一個proxy類的實例
你還是不可能看到返回值有增加的跡象
問題在於cookie
Web Service代碼並未從調用請求中發現有效的會話ID
因此它每次被調用都創建一個全新的HttpSessionState對象
並且返回它的初始值
因為作為客戶端的proxy類是從類System
Web
Service
Protocols
SoapHttpClientProtocol繼承的
它不包含System
Net
CookieContainer類的實例
因此
沒有地方來存放返回的cookie
為了解決這個問題
我對代碼做了如下一些修改
使用了ASP
NET的session
但是並不是無Cookie的session
Private Cookies As System
Net
CookieContainer
Private Sub Button
_Click(ByVal sender As System
Object
_
ByVal e As System
EventArgs) Handles Button
Click
Dim proxy As New localhost
Service
()
Dim ret As Integer
為proxy類設置cookie容器
If Cookies Is Nothing Then
Cookies = New System
Net
CookieContainer()
End If
proxy
CookieContainer = Cookies
ret = proxy
IncrementSessionCounter()
Label
Text =
Result:
& CStr(ret)
End Sub
現在代碼工作正常了!每點擊一次Button我都可以看到返回值增加注意到我並不是在函數中聲明變量Cookies的它是窗體類的一個私有成員因為如果希望每次都返回同一個會話ID給服務器的話就必須在每次請求中使用CookieContainer類的同一個實例這就解釋了為什麼SoapHttpClientProtocol類默認不自動地設置的cookie容器正應為此你可以在多個SoapHttpClientProtocol類的實例中共享一個cookie容器而不是為其每個實例自動地創建一個新的cookie容器
無cookie的Session從Web Service的開發者的角度來看
你可以想到相當多的人在試圖使用你的Web服務時忘記在客戶端代理類中添加Cookie容器
聰明的開發者或許靈光一閃
就會發現無cookie的Session應該可以出色地解決這個問題
如果將nfig文件中sessionState元素的cookieless參數設置為
true
你將會發現
通過浏覽器界面調用Web方法時
session變量工作正常
但是如果你在Visual 中通過
添加Web引用
來調用它時
依然存在著一些問題
為了研究無cookie的session
我決定使用上面已經使用過的代碼
看看它能否在session狀態被設置為cookieless的服務器環境中能否工作正常
我也不想費心去刪除cookie容器的相關代碼
因為我希望得到能在兩種session狀態下都正常工作的代碼
作為一個天生的樂觀主義者
我一個字也不改就直接運行它
令人失望的事發生了——不過也不是完全沒有想到
我不得不面對這個異常
An unhandled exception of type
System
Net
WebException
occurred in system
web
services
dll
Additional information: The request failed with the error message:
<html><head><title>Object moved</title></head><body>
<h
>Object moved to <a /HttpSessionState/(l
z
psnhh
cf
oahmai
p
)/service
asmx
>here</a>
</h
>
</body></html>
發生了什麼呢?原來HTTP請求收到的不是
OK
響應
如果你熟悉HTTP協議
你或許可以從響應中的HTML代碼中發現這是一個
Found
響應
這意味著該請求被重定向到超鏈接中指定的地址
返回HTML代碼是很明智的
這樣如果一個浏覽器因為某些原因不支持重定向的話
它可以把代碼顯示出來
或者在重定向過程中顯示這些代碼直到重定向完成
注意到超鏈接中包含了一個有趣的字符串
(l
z
psnhh
cf
oahmai
p
)
顯然
我們可以推斷這就是的會話ID
它被嵌入了我們要重定向到的位置的URL中
在客戶端代理中
我們需要做的僅僅是重新發送請求到這個新的URL
無須再在Win
WinInet API編程中跋涉
我們可以直接找到proxy類的一個屬性允許自動重定向
用外行人的說法
就是如果我們接收到一個
Found
響應
就直接將請求重新發送到相應中HTTP位置頭所指示的URL
當Visual 的智能提示顯示proxy類的AllowAutoRedirect屬性時
我感到這東西真是機靈得可愛
我馬上就在代碼中加上如下一行
proxy
AllowAutoRedirect = True
我認為這仍然比創建一個CookieContainer類並關聯到proxy類要容易得多
於是我又一次運行程序
很不幸
我遭遇了如下異常(為了簡潔起見有所刪節)
An unhandled exception of type
System
InvalidOperationException
occurred
in system
web
services
dll
Additional information: Client found response content type of
text/html; charset=utf
but expected
text/xml
The request failed with the error message: …
如果你看到錯誤消息的內容
你會發現你所看到的HTML頁面跟你浏覽
ASMX文件的頁面一樣
問題是
為什麼當我傳送XML(以SOAP封裝了的形式)到Web Service服務器時它返回的卻是HTML代碼?結果證實
你並沒有在SOAP封裝中發送HTTP POST請求
而僅僅發送了一個簡單的沒有內容的HTTP GET請求
因此你的Web Service服務端理所當然地假設這個請求來自浏覽器
於是它返回普通的HTML響應
為什麼會這樣呢?
如果你了解HTTP協議
你會發現一個HTTP客戶端在收到
Found
響應時發送HTTP GET請求到響應中指定的地址是合情合理的
即使初始請求是HTTP POST
這種方式下浏覽器工作得很好
因為開始幾乎所有的請求都是HTTP GET類型的
只有當你試圖傳遞數據到一個URL時
才會出現上述失敗的結果
理由是在傳送的數據中可能包含潛在的敏感數據
因此你需要確認是否用戶真的想向新的資源傳送數據
顯然如果你轉向基於重定向設置的新地址
你就沒能確認用戶是否真的允許將他們的數據發送到新的地址
因此數據並沒有被發送
而代之以簡單的HTTP GET請求
我對代碼做了如下修改
捕獲
Found
異常
提示用戶同意重定向他們的請求
然後再次在新的位置調用我的Web方法
同時使用基於Cookie和Cookie的Session
Private Cookies As System
Net
CookieContainer
Private webServiceUrl as Uri
Private Sub Button
_Click(ByVal sender As System
Object
_
ByVal e As System
EventArgs) Handles Button
Click
Dim proxy As New localhost
Service
()
Dim ret As Integer
設置proxy類的Cookie容器
If Cookies Is Nothing Then
Cookies = New System
Net
CookieContainer()
End If
proxy
CookieContainer = Cookies
設置proxy類的URL
If webServiceUrl Is Nothing Then
webServiceUrl = New Uri(proxy
Url)
Else
proxy
Url = webServiceUrl
AbsoluteUri
End If
Try
ret = proxy
IncrementSessionCounter()
Catch we As WebException
如果我們想檢測HTTP狀態碼
那麼就需要一個HttpWebResponse類的實例
If TypeOf we
Response Is HttpWebResponse Then
Dim HttpResponse As HttpWebResponse
HttpResponse = we
Response
If HttpResponse
StatusCode = HttpStatusCode
Found Then
這是一個
Found
響應
提示用戶是否進行重定向
If MsgBox(String
Format(redirectPrompt
_
HttpResponse
Headers(
Location
))
_
MsgBoxStyle
YesNo) = _
MsgBoxResult
Yes Then
用戶選擇Yes
重新嘗試新的URL
webServiceUrl = New Uri(webServiceUrl
_
HttpResponse
Headers(
Location
))
Button
_Click(sender
e)
Return
End If
End If
End If
Throw we
End Try
Label
Text =
Result:
& CStr(ret)
End Sub
現在的Session工作正常了
在你的應用程序中
你可以根據情況自行決定是否提示用戶重定向HTTP POST請求
舉一個例子
如果你正在調用一個Web Service
你也許就不希望出現任何可見的對話框
這樣看來
要使的Session完全正常地工作還真不是很容易
但是
應該意識到上面的代碼所展示的原理在其他情況下一樣的有用
例如
任何平台上的任何Web Service
只要它使用HTTP cookie
都需要一個cookie容器
類似地
也許有很多其他的原因導致當你向一個Web Service服務器發送請求時收到
Found
響應
在一個復雜的應用程序中調用Web Service時
可能有許多特殊的情形需要你去處理
cookie和重定向的問題就是兩種這樣的情形
你應該將之作為你的Web Service調用代碼中最基本的部分
結 論
在你調用Web方法的過程中
對狀態保持是非常有用的
你必須意識到
當你使用手邊的浏覽器界面測試你的Web Service時
你並沒有面對客戶端程序必須處理的問題
幸運的是
這些問題並不是很難解決
From:http://tw.wingwit.com/Article/program/net/201311/12874.html