目前的工作項目分為前端和后台,雙方事先約定接口,之后獨立開發。后台每天開發完后在測試服務器上部署,前端連接測試服務器進行數據交互。前端和后台分開的好處是代碼不用混在一個工程里一起build,互不干涉。但由此也引發出一個問題,那就是Ajax跨域。目前的項目是一個Single Page App, 基本上所有數據交互都是通過Ajax請求來完成的。為了方便平時前端開發,必須解決跨域問題。
跨域方案有多種,我認為基本上可分為兩大類,一類是需要目標Server配合的,另一類則不需要。前者限制稍多,必須由服務器顯式允許跨域才行,比如返回HTTP頭信息,修改服務器配置,返回JavaScript等。可以用JSONP,iframe等方式實現。后者主動權就掌握在跨域客戶端,服務器不用為此做任何配置。這就是本文要說的Web代理。
作為前端開發,自然希望主動權在自己手里,不用勞煩Sever配合。所以我選擇了使用Web代理方案。該方案的原理其實很簡單,就是將跨域請求轉變為同源請求。具體來說,就是在本地搭建一個Web站點(代理),該站點可以向目標服務器發送HTTP請求,接收響應。它的行為跟瀏覽器類似,因此目標服務器是不用區分對待的。然后將本地的前端站點也部署到這個Web站點中,這樣它們就屬於同一個域了。所有針對目標服務器的Ajax請求,都發送到這個代理,然后由代理負責轉發和接收響應。這樣就避開了跨域之名,卻有跨域之實。
圖有點丑,歡迎拍磚。
剩下的就是實現細節了。由於對Asp.Net比較熟悉,就用它創建一個Web站點。在實現過程中,我覺得有幾點細節需要關注一下。
HTTP Request攔截
由於事先不知道會有哪些請求(就算知道,請求的URL可能也會太多),不可能針對每個URL寫一個轉發規則。因此需要獲取所有Ajax請求,經過統一的處理再轉發到目標服務器。眾所周知,Asp.Net的IHttpHandler接口定義了針對某個具體的HTTP請求的處理方法,如前所述,不可能為每個請求URL寫一個Handler,有沒有一種辦法可以獲取任何請求的信息?答案是HttpModule。HttpModule是先於HttpHandler處理的,所以在這里可以做些手腳。定義一個HttpModule也很簡單,只要實現IHttpModule接口,監聽Request事件就可以了。當然,需要在Web.config
文件里注冊這個HttpModule才能使用。
XDomainProxy.cs
定義:
public class XDomainProxy : IHttpModule
{
public void Dispose() { }
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(Application_BeginRequest);
context.EndRequest += new EventHandler(Application_EndRequest);
}
public void Application_BeginRequest(object sender, EventArgs e)
{
}
public void Application_EndRequest(object sender, EventArgs e)
{
HttpApplication application = sender as HttpApplication;
HttpContext context = application.Context;
HttpResponse response = context.Response;
response.StatusCode = 200;
}
}
在Web.config
中注冊HttpModule
:
<configuration>
<system.web>
<httpHandlers>
...
</httpHandlers>
<httpModules>
<add name="ProxyModule" type="AAProxy.Proxy"/>
</httpModules>
</system.web>
</configuration>
模擬HTTP請求
.Net 已經封裝好了HttpWebRequest
和HttpWebResponse
兩個關鍵的類,非常方便。
//實例化web訪問類
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl);
request.Method = context.Request.HttpMethod;
request.ContentType = context.Request.ContentType;
string postData = context.Request.Form.ToString();
byte[] postdatabytes = Encoding.UTF8.GetBytes(postData);
request.ContentLength = postdatabytes.Length;
request.AllowAutoRedirect = false;
request.CookieContainer = loginCookie;
request.KeepAlive = true;
request.UserAgent = context.Request.UserAgent;
if (context.Request.HttpMethod == "POST")
{
Stream stream;
stream = request.GetRequestStream();
stream.Write(postdatabytes, 0, postdatabytes.Length);
stream.Close();
}
//接收響應
response = (HttpWebResponse)request.GetResponse();
using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
string content = reader.ReadToEnd();
}
這樣就完成了一個簡單的HTTP請求。注意POST請求需要另外發參數。
Cookie
由於所有的Ajax請求都需要用戶登錄才能進行,所以代理程序也必須模擬用戶登錄目標服務器站點。用戶的登錄信息是保存在cookie里的,所以在模擬請求的時候還需要保存cookie。HttpWebRequest
有個CookieContainer
屬性,就是用來裝cookie的。保存好cookie后,之后的每個請求都必須帶上它,這樣才能維持登錄狀態。另外,還需要把這些cookie寫到瀏覽器中。這里需要注意,.Net
封裝了兩個關於cookie的類,HttpCookie
和Cookie
。前者是Asp.Net程序寫回給瀏覽器用的,而后者是向別的服務器發起HTTP請求時用的。所以代理程序收到目標Server返回的Cookie
對象時,要轉換成HttpCookie
對象再返回給瀏覽器。
//response是目標服務器的響應對象,context是返回給瀏覽器的上下文對象
void SetCookie(HttpWebResponse response, HttpContext context)
{
foreach (Cookie cookie in response.Cookies)
{
HttpCookie httpCookie = new HttpCookie(cookie.Name, cookie.Value);
httpCookie.Domain = cookie.Domain;
httpCookie.Expires = cookie.Expires;
httpCookie.Path = cookie.Path;
httpCookie.HttpOnly = cookie.HttpOnly;
httpCookie.Secure = cookie.Secure;
context.Response.SetCookie(httpCookie);
}
}
Https連接
在使用過程中發現,如果目標服務器的數字證書是不受信任的,連接將被拒絕。這在連接Server端開發同事的PC調試時不方便,怎么破?.Net對Http請求有個證書驗證機制,只要讓這個驗證總是通過就好了。(一切為了開發方便)
ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(ValidateRemoteCertificate);
private bool ValidateRemoteCertificate(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
return true;
}
部署Web代理
部署就很簡單了,新建一個IIS站點,根目錄指向前端項目的路徑,設定一個端口號。將Web代理發布到本地的某個目錄下,然后作為一個應用程序添加到之前的IIS站點中即可。
總結
經過以上幾個關鍵步驟,基本上搭建好了Web代理。當然,這個過程中還有一些細節需要關注,比如轉發請求的URL映射處理,Session過期,異常處理等。總的來說,沒有用到什么高深的技術,只是針對各種跨域方案做了一個選擇。