ASP.NET Core管道深度剖析


ASP.NET管道

以IIS 6.0為例,在工作進程w3wp.exe中,利用Aspnet_ispai.dll加載.NET運行時(如果.NET運行時尚未加載)。IIS 6引入了應用程序池的概念,一個工作進程對應着一個應用程序池。一個應用程序池可以承載一個或者多個Web應用,每個Web應用映射到一個IIS虛擬目錄。與IIS 5.x一樣,每一個Web應用運行在各自的應用程序域中。

如果HTTP.SYS接收到的HTTP請求是對該Web應用的第一次訪問,當成功加載了運行時后,會通過AppDomainFactory為該Web應用創建一個應用程序域(AppDomain)。隨后,一個特殊的運行時IsapiRuntime被加載。IsapiRuntime定義在程序集System.Web中,對應的命名空間為System.Web.Hosting。IsapiRuntime會接管該HTTP請求。

IsapiRuntime會首先創建一個IsapiWorkerRequest對象,用於封裝當前的HTTP請求,並將該IsapiWorkerRequest對象傳遞給ASP.NET運行時:HttpRuntime,從此時起,HTTP請求正式進入了ASP.NET管道。根據IsapiWorkerRequest對象,HttpRuntime會創建用於表示當前HTTP請求的上下文(Context)對象:HttpContext。

隨着HttpContext被成功創建,HttpRuntime會利用HttpApplicationFactory創建新的或者獲取現有的HttpApplication對象。實際上,ASP.NET維護着一個HttpApplication對象池,HttpApplicationFactory從池中選取可用的HttpApplication用戶處理HTTP請求,處理完畢后將其釋放到對象池中。HttpApplicationFactory負責處理當前的HTTP請求。

在HttpApplication初始化過程中,會根據配置文件加載並初始化相應的HttpModule對象。對於HttpApplication來說,在它處理HTTP請求的不同的階段會觸發不同的事件(Event),而HttpModule的意義在於通過注冊HttpApplication的相應的事件,將所需的操作注入整個HTTP請求的處理流程。ASP.NET的很多功能,比如身份驗證、授權、緩存等,都是通過相應的HttpModule實現的。

而最終完成對HTTP請求的處理實現在另一個重要的對象中:HttpHandler。對於不同的資源類型,具有不同的HttpHandler。比如.aspx頁對應的HttpHandler為System.Web.UI.Page,WCF的.svc文件對應的HttpHandler為System.ServiceModel.Activation.HttpHandler。上面整個處理流程如圖7所示。

 

clip_image016 

圖7 ASP.NET 處理管道

HttpApplication

HttpApplication是整個ASP.NET基礎架構的核心,它負責處理分發給它的HTTP請求。由於一個HttpApplication對象在某個時刻只能處理一個請求,只有完成對某個請求的處理后,HttpApplication才能用於后續的請求的處理。所以,ASP.NET采用對象池的機制來創建或者獲取HttpApplication對象。具體來講,當第一個請求抵達的時候,ASP.NET會一次創建多個HttpApplication對象,並將其置於池中,選擇其中一個對象來處理該請求。當處理完畢,HttpApplication不會被回收,而是釋放到池中。對於后續的請求,空閑的HttpApplication對象會從池中取出,如果池中所有的HttpApplication對象都處於繁忙的狀態,ASP.NET會創建新的HttpApplication對象。

HttpApplication處理請求的整個生命周期是一個相對復雜的過程,在該過程的不同階段會觸發相應的事件。我們可以注冊相應的事件,將我們的處理邏輯注入到HttpApplication處理請求的某個階段。我們接下來介紹的HttpModule就是通過HttpApplication事件注冊的機制實現相應的功能的。表1按照實現的先后順利列出了HttpApplication在處理每一個請求時觸發的事件名稱。

表1

名稱

描述

BeginRequest

HTTP管道開始處理請求時,會觸發BeginRequest事件

AuthenticateRequest,PostAuthenticateRequest

ASP.NET先后觸發這兩個事件,使安全模塊對請求進行身份驗證

AuthorizeRequest,PostAuthorizeRequest

ASP.NET先后觸發這兩個事件,使安全模塊對請求進程授權

ResolveRequestCache,PostResolveRequestCache

ASP.NET先后觸發這兩個事件,以使緩存模塊利用緩存的直接對請求直接進程響應(緩存模塊可以將響應內容進程緩存,對於后續的請求,直接將緩存的內容返回,從而提高響應能力)。

PostMapRequestHandler

對於訪問不同的資源類型,ASP.NET具有不同的HttpHandler對其進程處理。對於每個請求,ASP.NET會通過擴展名選擇匹配相應的HttpHandler類型,成功匹配后,該實現被觸發

AcquireRequestState,PostAcquireRequestState

ASP.NET先后觸發這兩個事件,使狀態管理模塊獲取基於當前請求相應的狀態,比如SessionState

PreRequestHandlerExecute,PostRequestHandlerExecute

ASP.NET最終通過一請求資源類型相對應的HttpHandler實現對請求的處理,在實行HttpHandler前后,這兩個實現被先后觸發

ReleaseRequestState,PostReleaseRequestState

ASP.NET先后觸發這兩個事件,使狀態管理模塊釋放基於當前請求相應的狀態

UpdateRequestCache,PostUpdateRequestCache

ASP.NET先后觸發這兩個事件,以使緩存模塊將HttpHandler處理請求得到的相應保存到輸出緩存中

LogRequest,PostLogRequest

ASP.NET先后觸發這兩個事件為當前請求進程日志記錄

EndRequest

整個請求處理完成后,EndRequest事件被觸發

 

對於一個ASP.NET應用來說,HttpApplication派生於global.asax文件,我們可以通過創建global.asax文件對HttpApplication的請求處理行為進行定制。global.asax采用一種很直接的方式實現了這樣的功能,這種方式既不是我們常用的方法重寫(Method Overriding)或者事件注冊,而是直接采用方法名匹配。在global.asax中,我們按照這樣的方法命名規則進行事件注冊:Application_{Event Name}。比如Application_BeginRequest方法用於處理HttpApplication的BeginRequest事件。如果通過VS創建一個global.asax文件,下面是默認的定義。

   1: <%@ Application Language="C#" %>
   2: <script runat="server">
   3: void Application_Start(object sender, EventArgs e) {}
   4: void Application_End(object sender, EventArgs e) {}
   5: void Application_Error(object sender, EventArgs e) {}
   6: void Session_Start(object sender, EventArgs e) {}
   7: void Session_End(object sender, EventArgs e) {}
   8: </script>

 

HttpModule

ASP.NET為創建各種.NET Web應用提供了強大的平台,它擁有一個具有高度可擴展性的引擎,並且能夠處理對於不同資源類型的請求。那么,是什么成就了ASP.NET的高可擴展性呢? HttpModule功不可沒。

從功能上講,HttpModule之於ASP.NET,就好比ISAPI Filter之於IIS一樣。IIS將接收到的請求分發給相應的ISAPI Extension之前,注冊的ISAPI Filter會先截獲該請求。ISAPI Filter可以獲取甚至修改請求的內容,完成一些額外的功能。與之相似地,當請求轉入ASP.NET管道后,最終負責處理該請求的是與請求資源類型相匹配的HttpHandler對象,但是在Handler正式工作之前,ASP.NET會先加載並初始化所有配置的HttpModule對象。HttpModule在初始化的過程中,會將一些功能注冊到HttpApplication相應的事件中,那么在HttpApplication整個請求處理生命周期中的某個階段,相應的事件會被觸發,通過HttpModule注冊的事件處理程序也得以執行。

所有的HttpModule都實現了IHttpModule接口,下面是IHttpModule的定義。其中Init方法用於實現HttpModule自身的初始化,該方法接受一個HttpApplication對象,有了這個對象,事件注冊就很容易了。

   1: public interface IHttpModule
   2: {
   3:      void Dispose();
   4:     void Init(HttpApplication context);
   5: }

ASP.NET提供的很多基礎構件(Infrastructure)功能都是通過相應的HttpModule實現的,下面類列出了一些典型的HttpModule:

  • OutputCacheModule:實現了輸出緩存(Output Caching)的功能;
  • SessionStateModule:在無狀態的HTTP協議上實現了基於會話(Session)的狀態;
  • WindowsAuthenticationModule + FormsAuthenticationModule + PassportAuthentication- Module:實現了3種典型的身份認證方式:Windows認證、Forms認證和Passport認證;
  • UrlAuthorizationModule + FileAuthorizationModule:實現了基於Uri和文件ACL(Access Control List)的授權。

而另外一個重要的HttpModule與WCF相關,那么就是System.ServiceModel. Activation.HttpModule。HttpModule定義在System.ServiceModel程序集中,在默認的情況下,HttpModule完成了基於IIS的寄宿工作。

除了這些系統定義的HttpModule之外,我們還可以自定義HttpMoudle。通過Web.config,我們可以很容易地將其注冊到我們的Web應用中。

HttpHandler

如果說HttpModule相當於IIS的ISAPI Filter的話,我們可以說HttpHandler則相當於IIS的ISAPI Extension,HttpHandler在ASP.NET中扮演請求的最終處理者的角色。對於不同資源類型的請求,ASP.NET會加載不同的Handler來處理,也就是說.aspx page與.asmx web service對應的Handler是不同的。

所有的HttpHandler都實現了接口IHttpHandler。下面是IHttpHandler的定義,方法ProcessRequest提供了處理請求的實現。

   1: public interface IHttpHandler
   2: {
   3:     void ProcessRequest(HttpContext context);
   4:     bool IsReusable { get; }
   5: }

 

對於某些HttpHandler,具有一個與之相關的HttpHandlerFactory,用於創建或者獲取相應的HttpHandler。HttpHandlerFactory實現接口IHttpHandlerFactory,方法GetHandler用於創建新的HttpHandler,或者獲取已經存在的HttpHandler。

   1: public interface IHttpHandlerFactory
   2: {
   3:     IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
   4:     void ReleaseHandler(IHttpHandler handler);
   5: }

 

HttpHandler和HttpHandlerFactory的類型都可以通過相同的方式配置到Web.config中。下面一段配置包含對3種典型的資源類型的HttpHandler配置:.aspx,.asmx和.svc。可以看到基於WCF Service的HttpHandler類型為:System.ServiceModel.Activation.HttpHandler。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3: <system.web>
   4: <httpHandlers>
   5: <add path="*.svc" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>
   6: <add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="True"/>
   7: <add path="*.asmx" verb="*" type="System.Web.Services.Protocols.WebServiceHandlerFactory, System.Web.Services, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" validate="False"/>
   8: </httpHandlers>
   9: </system.web>
  10: </configuration>

第二部分:轉自 HttpModule的認識

 

HttpModule是向實現類提供模塊初始化和處置事件。當一個HTTP請求到達HttpModule時,整個ASP.NET Framework系統還並沒有對這個HTTP請求做任何處理,也就是說此時對於HTTP請求來講,HttpModule是一個HTTP請求的“必經之路”,所以可以在這個HTTP請求傳遞到真正的請求處理中心(HttpHandler)之前附加一些需要的信息在這個HTTP請求信息之上,或者針對截獲的這個HTTP請求信息作一些額外的工作,或者在某些情況下干脆終止滿足一些條件的HTTP請求,從而可以起到一個Filter過濾器的作用。

 

1、asp.net的HTTP請求處理過程

 

ASPNETHTTP

 

說明: 
(1)、客戶端瀏覽器向服務器發出一個http請求,此請求會被inetinfo.exe進程截獲,然后轉交給aspnet_isapi.dll進程,接着它又通過Http Pipeline的管道,傳送給aspnet_wp.exe這個進程,接下來就到了.net framework的HttpRunTime處理中心,處理完畢后就發送給用戶瀏覽器。 
(2)、當一個http請求被送入到HttpRuntime之后,這個Http請求會繼續被送入到一個被稱之為HttpApplication Factory的一個容器當中,而這個容器會給出一個HttpApplication實例來處理傳遞進來的http請求,而后這個Http請求會依次進入到如下幾個容器中:HttpModule --> HttpHandler Factory --> HttpHandler。當系統內部的HttpHandler的ProcessRequest方法處理完畢之后,整個Http Request就被處理完成了,客戶端也就得到相應的東東了。 
(3)完整的http請求在asp.net framework中的處理流程: 
HttpRequest-->inetinfo.exe->ASPNET_ISAPI.DLL-->Http Pipeline-->ASPNET_WP.EXE-->HttpRuntime-->HttpApplication Factory-->HttpApplication-->HttpModule-->HttpHandler Factory-->HttpHandler-->HttpHandler.ProcessRequest() 

也就是說一個HTTP請求在HttpModule容器的傳遞過程中,會在某一時刻(ResolveRequestCache事件)將這個HTTP請求傳遞給HttpHandler容器。在這個事件之后,HttpModule容器會建立一個HttpHandler的入口實例,但是此時並沒有將HTTP請求控制權交出,而是繼續觸發AcquireRequestState事件以及PreRequestHandlerExcute事件。在PreRequestHandlerExcute事件之后,HttpModule窗口就會將控制權暫時交給HttpHandler容器,以便進行真正的HTTP請求處理工作。

而在HttpHandler容器內部會執行ProcessRequest方法來處理HTTP請求。在容器HttpHandler處理完畢整個HTTP請求之后,會將控制權交還給HttpModule,HttpModule則會繼續對處理完畢的HTTP請求信息流進行層層的轉交動作,直到返回到客戶端為止。 
PS:紅色的HttpApplication實例在HttpModule的Init方法中會用到。


(4)如果想在中途截獲一個httpRequest並做些自己的處理,就應該在HttpRuntime運行時內部來做到這一點,確切的說是在HttpModule這個容器中來實現。

 

2、HttpModule工作原理


     負責監聽HttpRequest,同時對HttpRequest增添或者過濾掉一部分內容。也就是說,當一個HTTP請求到達HttpModule時,整個ASP.NET Framework系統還並沒有對這個HTTP請求做任何處理,也就是說此時對於HTTP請求來講,HttpModule是一個HTTP請求的“必經之路”,所以可以在這個HTTP請求傳遞到真正的請求處理中心(HttpHandler)之前附加一些需要的信息在這個HTTP請求信息之上,或者針對截獲的這個HTTP請求信息作一些額外的工作,或者在某些情況下干脆終止滿足一些條件的HTTP請求,從而可以起到一個Filter過濾器的作用。 
HttpModule實現了接口IHttpModule,我們可以自定義實現該接口的類,從而取代HttpModule。 
asp.net默認的HttpModule如下:

        System.Web.SessionState.SessionStateModule;
        System.Web.Security.WindowsAuthenticationModule;
        System.Web.Security.FormsAuthenticationModule;
        System.Web.Security.PassportAuthenticationModule;
        System.Web.Security.UrlAuthorizationModule;
        System.Web.Security.FileAuthorizationModule;

 

3、編寫自己的HttpModule

要實現HttpModule,必須實現接口IHttpModule。下面是IHttpModule接口分析:

using System;
 
        
namespace System.Web
{
    public interface IHttpModule 
    {
        //   銷毀不再被HttpModule使用的資源
        void Dispose();
 
        
        // 初始化一個Module,為捕獲HttpRequest做准備
        void Init(HttpApplication context);
    }
}

 

下面是自己的HttpModule:

using System;
using System.Web;
 
        
namespace ClassLibrary1
{
    public class MyHttpModule : 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) 
        {
            HttpApplication application = sender as HttpApplication;
            HttpContext context = application.Context;
            HttpResponse response = context.Response;
            response.Write("這是來自自定義HttpModule中有BeginRequest");
        }
 
        
        public void Application_EndRequest(object sender, EventArgs e) 
        {
            HttpApplication application = sender as HttpApplication;
            HttpContext context = application.Context;
            HttpResponse response = context.Response;
            response.Write("這是來自自定義HttpModule中有EndRequest");
        }
 
        
    }
}


web.config

    <httpModules>
      <add name="myHttpModule" type="ClassLibrary1.MyHttpModule,ClassLibrary1"/> 
    </httpModules>

 

default.aspx.cs 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
 
        
public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e) 
    {
        Response.Write("<br/><br/>來自Default.aspx頁面<br/>");
    }
}


2010-03-11_101937

 

4、HttpModule內部事件機制和生命周期


HttpModule對HttpApplication實例進行處理,而HttpApplication有很多事件(對應不同的生命期),這樣就衍生出HttpModule內部事件機制和生命周期。 
(1)、HttpModule的事件

   
BeginRequest 指示請求處理開始
AuthenticateRequest 封裝請求身份驗證過程
AuthorizeRequest 封裝檢查是否能利用以前緩存的輸出頁面處理請求的過程
ResolveRequestCache 從緩存中得到相應時候觸發
AcquireRequestState 加載初始化Session時候觸發
PreRequestHandlerExecute 在Http請求進入HttpHandler之前觸發
PostRequestHandlerExecute 在Http請求進入HttpHandler之后觸發
ReleaseRequestState 存儲Session狀態時候觸發
UpdateRequestCache 更新緩存信息時觸發
EndRequest 在Http請求處理完成的時候觸發
PreSendRequestHenaders 在向客戶端發送Header之前觸發
PreSendRequestConternt 在向客戶端發送內容之前觸發

 

說明: 
a、BenginRequest和EndRequest分別是HttpModule容器最開始的和最后的事件; 
b、EndRequest之后還會觸發PreSendRequestHeaders事件和PreSendRequestContent事件,這不是在HttpModule外的兩個事件,表示HttpModule結束,即將開始向Client發送數據。

(2)、驗證HttpModule生命周期 
與HttpHandler的交互: 
HttpModuleHandler

說明: 
a、HttpModule容器會將HttpRequest傳遞到HttpHandler容器,這個時間點是ResolveRequestCache事件 
b、HttpModule容器會建立HttpHandler實例作為入口——Session從此生效 
c、觸發AcquireRequestState事件以及PreRequestHandlerExecute事件 
d、HttpModule容器便將對HttpRequest的控制權轉讓給HttpHandler容器 
e、HttpHandler容器處理HttpRequest——使用自身的ProcessRequest方法,將對其控制權又還給HttpModule容器——之后Session失效。 

驗證生命周期代碼:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
 
        
namespace MyHttpModule
{
    public class ValidaterHttpModuleEvents : IHttpModule 
    {
 
        
        public void Dispose() 
        { }
 
        
        /// <summary>
        /// 驗證HttpModule事件機制
        /// </summary>
        /// <param name="application"></param>
        public void Init(HttpApplication application) 
        {
            application.BeginRequest += new EventHandler(application_BeginRequest);
            application.EndRequest += new EventHandler(application_EndRequest);
            application.AcquireRequestState += new EventHandler(application_AcquireRequestState);
            application.AuthenticateRequest += new EventHandler(application_AuthenticateRequest);
            application.AuthorizeRequest += new EventHandler(application_AuthorizeRequest);
            application.PreRequestHandlerExecute += new EventHandler(application_PreRequestHandlerExecute);
            application.PostRequestHandlerExecute += new EventHandler(application_PostRequestHandlerExecute);
            application.ReleaseRequestState += new EventHandler(application_ReleaseRequestState);
            application.ResolveRequestCache += new EventHandler(application_ResolveRequestCache);
            application.PreSendRequestHeaders += new EventHandler(application_PreSendRequestHeaders);
            application.PreSendRequestContent += new EventHandler(application_PreSendRequestContent);
        }
 
        
        private void application_BeginRequest(object sender, EventArgs e) 
        {
            HttpApplication application = (HttpApplication)sender;
            application.Context.Response.Write("application_BeginRequest<br/>");
        }
 
        
        private void application_EndRequest(object sender, EventArgs e) 
        {
            HttpApplication application = (HttpApplication)sender;
            application.Context.Response.Write("application_EndRequest<br/>");
        }
 
        
        private void application_PreRequestHandlerExecute(object sender, EventArgs e) 
        {
            HttpApplication application = (HttpApplication)sender;
            application.Context.Response.Write("application_PreRequestHandlerExecute<br/>");
        }
 
        
        private void application_PostRequestHandlerExecute(object sender, EventArgs e) 
        {
            HttpApplication application = (HttpApplication)sender;
            application.Context.Response.Write("application_PostRequestHandlerExecute<br/>");
        }
 
        
        private void application_ReleaseRequestState(object sender, EventArgs e) 
        {
            HttpApplication application = (HttpApplication)sender;
            application.Context.Response.Write("application_ReleaseRequestState<br/>");
        }
 
        
        private void application_AcquireRequestState(object sender, EventArgs e) 
        {
            HttpApplication application = (HttpApplication)sender;
            application.Context.Response.Write("application_AcquireRequestState<br/>");
        }
 
        
        private void application_PreSendRequestContent(object sender, EventArgs e) 
        {
            HttpApplication application = (HttpApplication)sender;
            application.Context.Response.Write("application_PreSendRequestContent<br/>");
        }
 
        
        private void application_PreSendRequestHeaders(object sender, EventArgs e) 
        {
            HttpApplication application = (HttpApplication)sender;
            application.Context.Response.Write("application_PreSendRequestHeaders<br/>");
        }
 
        
        private void application_ResolveRequestCache(object sender, EventArgs e) 
        {
            HttpApplication application = (HttpApplication)sender;
            application.Context.Response.Write("application_ResolveRequestCache<br/>");
        }
 
        
        private void application_AuthorizeRequest(object sender, EventArgs e) 
        {
            HttpApplication application = (HttpApplication)sender;
            application.Context.Response.Write("application_AuthorizeRequest<br/>");
        }
 
        
        private void application_AuthenticateRequest(object sender, EventArgs e) 
        {
            HttpApplication application = (HttpApplication)sender;
            application.Context.Response.Write("application_AuthenticateRequest<br/>");
        }
    }
}

 

<add name="HttpModule1" type="MyHttpModule.HttpModule1,MyHttpModule"/> 
<add name="HttpModule2" type="MyHttpModule.HttpModule2,MyHttpModule"/>

 

HttpModule1和HttpModule2模仿ValidaterHttpModuleEvents編寫(除了類名改變外,事件和方法不變),不貼代碼了。運行結果如下:

HttpModuleCompare

從運行結果可以看到,在web.config文件中引入自定義HttpModule的順序就決定了多個自定義HttpModule在處理一個HTTP請求的接管順序。

 

(3)、利用HttpModule實現終止此次HttpRequest請求

在BeginRequest事件中,使用HttpApplication.CompleteRequest()方法可以實現當滿足一定條件時終止此次HttpRequest請求 

using System;
using System.Web;
 
        
namespace ClassLibrary1
{
    public class MyHttpModule : IHttpModule 
    {
        public void Dispose() { } 
 
        
        public void Init(HttpApplication context) 
        {
            context.BeginRequest += new EventHandler(Application_BeginRequest);
        }
 
        
        public void Application_BeginRequest(object sender, EventArgs e) 
        {
            HttpApplication application = sender as HttpApplication;
            application.CompleteRequest();
            application.Context.Response.Write("請求被終止");
        }
    }
}

2010-03-11_105150

 

說明: 
a、對於一個HttpModule,在BeginRquest中終止,但是仍然會調用EndRequest事件,以及PreSendRequestHeaders事件和PreSendRequestContent事件。也可以說是直接跳轉到EndRequest事件,而不會調用這期間的事件 
b、如果有兩個HttpModule,在第一個HttpModule的BeginRequest中終止,僅僅不會調用第二個HttpModule的BeginRequest,但仍然會調用兩個EndRequest事件,以及PreSendRequestHeaders事件和PreSendRequestContent事件。看下面的圖示:

MultiModule


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM