前幾天看到園子里一篇關於 Url 重寫的文章《獲取ISAPI_Rewrite重寫后的URL》 , URL-Rewrite 這項技術早已不是一項新技術了,這個話題也已經被很多人討論過多次。搜索一下URL-Rewrite可以找到很多URL-Rewrite方面的文章和組件,自己以前也多次接觸過這個東東,也來說說吧。 ScottGu 有一篇非常經典的 URL-Rewrite Blog
Tip/Trick: Url Rewriting with ASP.NET http://weblogs.asp.net/scottgu/archive/2007/02/26/tip-trick-url-rewriting-with-asp-net.aspx
為什么要進行URL-Rewrite
ScottGu的blog中給出了兩個重要的原因:
1.保證WebApplication在進行結構調整,移動頁面位置時,用戶收藏的URL不會因此而成為死鏈。
2. SEO優化。
摘引自ScottGu Blog 的原文
---------------------------------------------------------------------------
Why does URL mapping and rewriting matter?
The most common scenarios where developers want greater flexibility with URLs are:
1) Handling cases where you want to restructure the pages within your web application, and you want to ensure that people who have bookmarked old URLs dont break when you move pages around. Url-rewriting enables you to transparently forward requests to the new page location without breaking browsers.
2) Improving the search relevancy of pages on your site with search engines like Google, Yahoo and Live. Specifically, URL Rewriting can often make it easier to embed common keywords into the URLs of the pages on your sites, which can often increase the chance of someone clicking your link. Moving from using querystring arguments to instead use fully qualified URLs can also in some cases increase your priority in search engine results. Using techniques that force referring links to use the same case and URL entrypoint (for example: weblogs.asp.net/scottgu instead of weblogs.asp.net/scottgu/default.aspx) can also avoid diluting your pagerank across multiple URLs, and increase your search results.
In a world where search engines increasingly drive traffic to sites, extracting any little improvement in your page ranking can yield very good ROI to your business. Increasingly this is driving developers to use URL-Rewriting and other SEO (search engine optimization) techniques to optimize sites (note that SEO is a fast moving space, and the recommendations for increasing your search relevancy evolve monthly). For a list of some good search engine optimization suggestions, Id recommend reading the SSW Rules to Better Google Rankings, as well as MarketPositions article on how URLs can affect top search engine ranking.
---------------------------------------------------------------------------
第一點原因中所描述的場景,在Web站點改版中經常碰到。Web站點改版經常會調整一些頁面的位置,QueryString中參數的結構等等。很可能使原來用戶在收藏夾中收藏的鏈接成為死鏈。在這種場景下URL-Rewrite像是軟件架構技術中的一個中間層的概念,URL-Rewrite對外公開的URL是被重寫過的,這個URL被用戶收藏,不會變,當Web站點調整,內部Page的位置改變了,使得內部實際的URL地址也改變了,這時修改內部的重寫規則,讓原來對外公開的URL重寫到新的內部URL上。從而保證了對外URL不變,其實對內已經完成了頁面位置的調整。雖然URL-Rewrite可以做到防止死鏈的產生,但是大多數站點在改版或調整時,不會使用URL-Rewrite來防止死鏈的產生,一般會直接修改404 The page cannot be found 頁面,把404出錯頁面改成一個更加友好的提示頁面,並且會在幾秒鍾之后跳轉到網站首頁。
第二點原因是SEO了,如果您的站點是個內部OA ERP CRM這種站點,只需要自己內部人員來訪問。其實完全可以不用做SEO,因為這種站點根本不需要搜索引擎來收錄,也不需要別人通過搜索引擎找到這個站點,所以這種站點完全沒有必要進行SEO優化。如果您的站點是個商業站點,新聞站點,娛樂站點,越多人訪問越好的站點,SEO優化是非常重要,此時通過URL-Rewrite進行SEO優化也就非常必要了。隨着搜索引擎逐漸成為人們查找信息,索取資源的首選工具,搜索引擎對一個站點的影響也就愈來愈大,下面是 zhangsichu.com 9-1~9-10 這段時間內的第三方來路數據統計。
來路統計是通過記錄httpheader中的Referer,來得知用戶在瀏覽這個頁面之前所在的那個頁面。從而得出用戶是通過那個頁面到達這個頁面的。
在266個獨立IP中,有200個IP是來自搜索引擎的。也就是說,用戶先通過搜索引擎的搜索結果,然后來到zhangsichu.com的用戶有200個。占到了75.2%。一大半的人是通過搜索來的。充分說明了SEO對站點的重要,在這種情況下,就必須做URL-Rewrite進行SEO優化了。
如果您的站點既不需要考慮URL兼容防止死鏈問題,也不需要進行SEO優化,就完全沒有必要進行URL-Rewrite。URL-Rewrite是一個對性能有害的處理過程。
常用的URL-Rewrite方案
URL-Rewrite既可以發生在Web服務器(IIS/Apache)一級,也可以發生在Web應用程序一級(Asp.Net/Jsp/PHP/…)。
1.Web應用程序級別的URL-Rewrite
在Web應用程序級別的URL-Rewrite。有三個比較著名的現成組件。
1) 微軟提供的 URL-Rewrite http://msdn2.microsoft.com/zh-cn/library/ms972974.aspx
2) Open Source的 UrlRewriter.NET http://urlrewriter.net/
3) UrlRewriting http://www.urlrewriting.net/en/Download.aspx
這種組件內部核心的工作原理: 都是在自己的Web Application的web.config中添加httpModule。用這個httpModule來處理重寫。(其實也可繼承System.Web.HttpApplication,在Application_BeginRequest中插入一個自己的方法處理重寫)
其中核心的處理代碼,下面的代碼摘引自UrlRewriter.NET組件。
1)從IHttpModule繼承得到一個自己的HttpModule,這個HttpModule需要在web.config中配置,說明所有的請求都要經過這個HttpModule。
public
sealed
class
RewriterHttpModule : IHttpModule
{
/// <summary>
/// Initialises the module.
/// </summary>
/// <param name="context">The application context.</param>
void
IHttpModule.Init(HttpApplication context)
{
context.BeginRequest +=
new
EventHandler(BeginRequest);
}
…
private
void
BeginRequest(
object
sender, EventArgs e)
{
// Add our PoweredBy header
HttpContext.Current.Response.AddHeader(Constants.HeaderXPoweredBy, Configuration.XPoweredBy);
_rewriter.Rewrite();
}
}
|
2)讀取重寫規則,判斷是否需要重寫,確定如何重寫,進行重寫。
public
void
Rewrite()
{
string
originalUrl = ContextFacade.GetRawUrl().Replace(
"+"
,
" "
);
RawUrl = originalUrl;
// Create the context
RewriteContext context =
new
RewriteContext(
this
, originalUrl,
ContextFacade.GetHttpMethod(), ContextFacade.MapPath,
ContextFacade.GetServerVariables(), ContextFacade.GetHeaders(), ContextFacade.GetCookies());
// Process each rule.
ProcessRules(context);
// Append any headers defined.
AppendHeaders(context);
// Append any cookies defined.
AppendCookies(context);
// Rewrite the path if the location has changed.
ContextFacade.SetStatusCode((
int
)context.StatusCode);
if
((context.Location != originalUrl) && ((
int
)context.StatusCode < 400))
{
if
((
int
)context.StatusCode < 300)
{
// Successful status if less than 300
_configuration.Logger.Info(MessageProvider.FormatString(Message.RewritingXtoY,
ContextFacade.GetRawUrl(), context.Location));
// Verify that the url exists on this server.
HandleDefaultDocument(context);
// VerifyResultExists(context);
ContextFacade.RewritePath(context.Location);
}
else
{
// Redirection
_configuration.Logger.Info(MessageProvider.FormatString(Message.RedirectingXtoY,
ContextFacade.GetRawUrl(), context.Location));
ContextFacade.SetRedirectLocation(context.Location);
}
}
else
if
((
int
)context.StatusCode >= 400)
{
HandleError(context);
}
else
if
(HandleDefaultDocument(context))
{
ContextFacade.RewritePath(context.Location);
}
// Sets the context items.
SetContextItems(context);
}
|
這種重寫是ASP.NET Pipeline級別的重寫,可以重寫一切Asp.net接管的請求。
在這里對/Pd/Book.aspx的請求被重寫到了 /Pd.aspx?Cg=books.
Web應用程序級別的URL-Rewrite只能重寫Web應用程序接管的請求。它沒有辦法處理.js .jpg的重寫。原因是這些請求到達IIS后,IIS根本就沒有把這些請求分發到Asp.Net,所以這些請求就不會發生重寫的處理和操作。在IIS中可以配置,對哪些后綴的請求是被IIS分發到Asp.Net的。
如果您一定要在Asp.Net級別對.js的請求進行重寫,可以在這里指定.js的請求由Asp.Net接管,但是這時您需要自己處理.js的Response。Web服務器級別的URL-Rewrite可以比較好的解決這方面的問題吧。
2. Web服務器級別的URL-Rewrite
Apache服務器
Apache服務器原生支持了URL-Rewrite。在config中打開LoadModule rewrite_module modules/mod_rewrite.so 然后配置重寫的正則表達式。例如:
摘引自Apache2.2中文參考手冊 中文手冊 Apache-UrlRewrite
---------------------------------------------
描述:
這個規則的目的是強制使用特定的主機名以代替其他名字。比如,你想強制使用www.example.com代替example.com,就可以在以下方案的基礎上進行修改:
解決方案:
對運行在非80端口的站點
RewriteCond %{HTTP_HOST} !^fully\.qualified\.domain\.name [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteCond %{SERVER_PORT} !^80$
RewriteRule ^/(.*) http:
//fully.qualified.domain.name:%{SERVER_PORT}/$1 [L,R]
對運行在80端口的站點
RewriteCond %{HTTP_HOST} !^fully\.qualified\.domain\.name [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteRule ^/(.*) http:
//fully.qualified.domain.name/$1 [L,R]
---------------------------------------------------------------------------
|
IIS6/IIS7 Web服務器
IIS7新的“管道模式”其實是把ASP.NET中的某些概念與IIS進行了更加深度的集成。在IIS7 Program Manager: Mike Volodarsky的Blog中有一篇文章分析了這方面的內容:
Breaking Changes for ASP.NET 2.0 applications running in Integrated mode on IIS 7.0
IIS7的“經典模式”與IIS 6基本上是如出一轍的。
在IIS6 + Asp.Net應用程序級的URL-Rewrite,只能在請求被分配到Asp.Net引擎后才能發生重寫操作。在IIS7這一點被改變了。IIS7可以對沒有后綴名的請求進行重寫,Asp.Net和IIS7進行了深度的集成。IIS7可以在 IIS 請求管道的任何地方執行一個HttpModule,下面是一個IIS7下Asp.Net的重寫配置:
摘引自ScottGu的Blog
<?xml version=
"1.0"
encoding=
"UTF-8"
?>
<configuration>
<configSections>
<section name=
"rewriter"
requirePermission=
"false"
type=
"Intelligencia.UrlRewriter.Configuration.RewriterConfigurationSectionHandler, Intelligencia.UrlRewriter"
/>
</configSections>
<system.web>
<httpModules>
<add name=
"UrlRewriter"
type=
"Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter"
/>
</httpModules>
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests=
"true"
>
<add name=
"UrlRewriter"
type=
"Intelligencia.UrlRewriter.RewriterHttpModule"
/>
</modules>
<validation validateIntegratedModeConfiguration=
"false"
/>
</system.webServer>
<rewriter>
<rewrite url=
"~/products/(.+)"
to=
"~/products.aspx?category=$1"
/>
</rewriter>
</configuration>
|
其中:<rewrite url="~/products/(.+)" to="~/products.aspx?category=$1" />這條規則中的~/products/(.+)這條正則表達式。匹配了/products/下的所有鏈接。
IIS6服務器級別下的重寫需要使用ISAPI Filters Rewrite來實現。
ISAPI Filters有兩個非常著名工程:
1)Helicon Techs ISAPI Rewrite: http://www.isapirewrite.com/ 提供一個99美元(可免費試用30天)的ISAPI URL重寫產品完整版,以及一個免費的輕量級版本。
2)Ionics ISAPI Rewrite: http://cheeso.members.winisp.net/IIRF.aspx 全免費開源組件。
在 ISAPI Filter編程重寫URL 中有說明。
服務器級的重寫與應用程序級的重寫最大的區別在於他們發生的時機不同。下圖是在服務器級把/Pd/Book.aspx重寫到/Pd.aspx?Cg=books
請求還沒有到Asp.Net引擎,就被重寫了。
3.Asp.Net級別上重寫的一些小細節問題(部分內容源自ScottGu 的Blog)
如果頁面中存在form且form是runat=server的<form runat="server">,那么這個頁面在重寫后form的action是原始URL,不是重寫后干凈的URL。例如/Pd/Book.aspx重寫到/Pd.aspx?Cg=books這個場景。實際用戶瀏覽器訪問的地址是/Pd/Book.aspx,在服務器級被重寫后請求變成了/Pd.aspx?Cg=books,在這種情況下form的action會被render成/Pd.aspx?Cg=books,其實這時更加想要action被render成/Pd/Book.aspx,讓頁面PostBack到同一位置。在某些情況下action被render成/Pd.aspx?Cg=books是不會對正常工作有影響的,只要/Pd.aspx?Cg=books不被重寫規則匹配上,/Pd.aspx?Cg=books會被正確發回到Asp.Net引擎。但是瀏覽器上的地址欄會變化,暴露出真正的地址。如果這個URL被某個別的規則匹配,那就必須要求form的action被正確的Render成/Pd/Book.aspx,這種統一的重寫后的URL。
解決辦法:
1)自己包裝form控件。把url寫在某個hidden field里同postback一起回來,render時修改action為hidden field里的url.
2)使用JavaScript在form submit前修改action例如 window.document.forms[0].action = window.location;
3)使用ASP.NET 2.0 Control Adapter(源自ScottGu 的Blog)
這種重寫是當在使用Asp.Net應用程序一級的重寫時,使用Context.Request.RawUrl填寫form的action,當使用IIS應用服務器一級的重寫時把干凈的URL記錄在Request.ServerVariables["HTTP_X_REWRITE_URL"]中,使用Request.ServerVariables["HTTP_X_REWRITE_URL"]填寫form的action,填寫form action 的過程都是通過Control Adapter對form Control擴展,override form控件的 WriteAttribute方法,在Render時重新指定form的action。
重寫后路徑兼容問題
在/Pd/Book.aspx重寫到/Pd.aspx?Cg=books的場景中,頁面中如果有相對位置的資源,如某個img的src=”../logo.gif”或src=”logo.gif”。這時瀏覽器請求這些資源基准的位置是/pd/也就是說src=”../logo.gif”請求的路徑是/logo.gif,src=”logo.gif”請求的路徑是/pd/logo.gif。但是其實這些資源的基准位置是 / 因為原始的URL是/Pd.aspx?Cg=books。這時就會發生資源找不到的情況。
1)使用服務器端的img使用 ~ 路徑可以解決這個問題(源自ScottGu的Blog)。
2)使用<base href=" http://xxx/ "></base>標簽,這個標簽需要寫在head里。告訴頁面,頁面中所有相對路徑的基准路徑是http://xxx/ ,從而解決重寫后路徑失效的問題。
base標簽的說明: http://www.w3school.com.cn/tags/tag_base.asp
到這里,URL-Rewrite的問題討論完了。在實際項目中肯定還會遇到各種不同的問題,不過解決的思路,估計會是上面這些技術的組合和擴展,希望通過上面對URL-Rewrite問題的討論,會對遇到的新問題能起到一些幫助的作用。