ASP.NET Initialization Process Analysis 2

Posted by busin3ss on Wed, 19 Jun 2019 02:53:40 +0200

The previous article talked about the process from creating application domains to creating ISAPIRuntime instances. This article continues with the necessary initialization process for Asp.net to process the first request.

ISAPIRuntime analysis

ISAPIRuntime is implemented in System.Web.Hosting, and its ProcessRequest is our gateway for processing web requests.

    public int ProcessRequest(IntPtr ecb, int iWRType) {
        IntPtr pHttpCompletion = IntPtr.Zero;
        if (iWRType == WORKER_REQUEST_TYPE_IN_PROC_VERSION_2) {
            pHttpCompletion = ecb;
            ecb = UnsafeNativeMethods.GetEcb(pHttpCompletion);
        }
        ISAPIWorkerRequest wr = null;
        try {
            bool useOOP = (iWRType == WORKER_REQUEST_TYPE_OOP);
            wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);
            wr.Initialize();
            String wrPath = wr.GetAppPathTranslated();
            String adPath = HttpRuntime.AppDomainAppPathInternal;            
            if (adPath == null ||StringUtil.EqualsIgnoreCase(wrPath, adPath)) {
                HttpRuntime.ProcessRequestNoDemand(wr);
                return 0;
            }
            else {
                 HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged, SR.GetString(SR.Hosting_Phys_Path_Changed, adPath, wrPath));
                 return 1;
            }
        }
        catch(Exception e) {
            try {
                WebBaseEvent.RaiseRuntimeError(e, this);
            } catch {}
            if (wr != null && wr.Ecb == IntPtr.Zero) {
                if (pHttpCompletion != IntPtr.Zero) {
                    UnsafeNativeMethods.SetDoneWithSessionCalled(pHttpCompletion);
                }
                if (e is ThreadAbortException) {
                    Thread.ResetAbort();
                }                   
                return 0;
             }
            throw;
        }
    }

Note the method's IntPtr type parameter ecb, which is an unmanaged po int er used to pass some necessary data and eventually return the content of the Response to the unmanaged environment ISAPI (asynchronous mode) and present it to the Client user. The method calls the static method CreateWorkerRequest of ISAPI WorkerRequest to create an instance of ISAPI WorkerRequest object with ECB parameters and iWRType parameters representing WorkerRequest type. By judging the specific content of ECB and type types, it decides what type of WorkerRequest to create (the above type of ISPAI WorkerRequest is inherited from HttpWorkerRequest), the above generation. Code can see that different versions of IIS are packaged differently, and some basic information (such as content type, query system ring length, filepath and other related information) is initialized through its Initialize method. Then call HttpRuntime.ProcessRequestNoDemand(wr) to HttpRuntime to process the request, which is finally reflected in the call to ProcessRequestInternational method.

Http Runtime Analysis

Httpruntime is implemented under System.Web. Let's look at its ProcessRequestInternal method for processing requests.

    private void ProcessRequestInternal(HttpWorkerRequest wr) {
        Interlocked.Increment(ref _activeRequestCount);
        if (_disposingHttpRuntime) {
            try {
                wr.SendStatus(503, "Server Too Busy");
                wr.SendKnownResponseHeader(HttpWorkerRequest.HeaderContentType, "text/html; charset=utf-8");
                byte[] body = Encoding.ASCII.GetBytes("<html><body>Server Too Busy</body></html>");
                wr.SendResponseFromMemory(body, body.Length);
                wr.FlushResponse(true);
                wr.EndOfRequest();
            } finally {
                Interlocked.Decrement(ref _activeRequestCount);
            }
            return;
        }
        HttpContext context;
        try {
            context = new HttpContext(wr, false);
        }
        catch {
            try {
                wr.SendStatus(400, "Bad Request");
                wr.SendKnownResponseHeader(HttpWorkerRequest.HeaderContentType, "text/html; charset=utf-8");
                byte[] body = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
                wr.SendResponseFromMemory(body, body.Length);
                wr.FlushResponse(true);
                wr.EndOfRequest();
                return;
            } finally {
                Interlocked.Decrement(ref _activeRequestCount);
            }
        }
        wr.SetEndOfSendNotification(_asyncEndOfSendCallback, context);
        HostingEnvironment.IncrementBusyCount();
        try {
            try {
                EnsureFirstRequestInit(context);
            }
            catch {
                if (!context.Request.IsDebuggingRequest) {
                    throw;
                }
            }
            context.Response.InitResponseWriter();
            IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context);
            if (app == null)
                throw new HttpException(SR.GetString(SR.Unable_create_app_object));
            if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, app.GetType().FullName, "Start");
            if (app is IHttpAsyncHandler) {
                IHttpAsyncHandler asyncHandler = (IHttpAsyncHandler)app;
                context.AsyncAppHandler = asyncHandler;
                asyncHandler.BeginProcessRequest(context, _handlerCompletionCallback, context);
            }
            else {
                app.ProcessRequest(context);
                FinishRequest(context.WorkerRequest, context, null);
            }
        }
        catch (Exception e) {
            context.Response.InitResponseWriter();
            FinishRequest(wr, context, e);
        }
    }

In this method, familiar HttpContext is created, and HttpRequest and HttpResponse are created at the same time.

internal HttpContext(HttpWorkerRequest wr, bool initResponseWriter) {

        _wr = wr;

        Init(new HttpRequest(wr, this), new HttpResponse(wr, this));

if (initResponseWriter)

            _response.InitResponseWriter();

        PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_EXECUTING);

    }

Then through the static method of GetApplication Instance of HttpApplication Factory, we get the familiar instance of HttpApplication object (Note: HttpApplication object inherits IHttpAsyncHandler, and IHttpAsyncHandler inherits IHttpAsyncHandler), and then execute the call to BeginProcessRequest method. So far, it has formally entered the life cycle of the creation of HttpApplication objects and the well-known HttpApplication.

Http Application Factory Analysis

Http Application Factory is implemented under System.Web.

See the GetApplication Instance method used by the Http Application Factory to create the Http application.

    internal static IHttpHandler GetApplicationInstance(HttpContext context) {
        if (_customApplication != null)
            return _customApplication;
        if (context.Request.IsDebuggingRequest)
            return new HttpDebugHandler();
        _theApplicationFactory.EnsureInited();
        _theApplicationFactory.EnsureAppStartCalled(context);
        return _theApplicationFactory.GetNormalApplicationInstance(context);
    }

This method has three steps: first, EnsureInited, which checks whether it has been initialized, and if not, Init method is called to get the full path of the global.asax file, then CompileApplication() is called to compile global.asax. The Init method is as follows.

    private void Init() {
        if (_customApplication != null)
            return;
        try {
            try {
                _appFilename = GetApplicationFile();
                CompileApplication();
             }
            finally {
                SetupChangesMonitor();
            }
        }
        catch {
            throw;
        }
}

Then the EnsureAppStartCalled method calls FireApplicationOnStart if it does not start.

   private void EnsureAppStartCalled(HttpContext context) {
        if (!_appOnStartCalled) {
            lock (this) {
                if (!_appOnStartCalled) {
                    using (new DisposableHttpContextWrapper(context)) {
                        WebBaseEvent.RaiseSystemEvent(this, WebEventCodes.ApplicationStart);
                        FireApplicationOnStart(context);
                        }
                    _appOnStartCalled = true;
                }
            }
        }
    }

    private void FireApplicationOnStart(HttpContext context) {
        if (_onStartMethod != null) {
            HttpApplication app = GetSpecialApplicationInstance();
            app.ProcessSpecialRequest(context, _onStartMethod, _onStartParamCount, this, EventArgs.Empty, null);
            RecycleSpecialApplicationInstance(app);
        }
    }

A specific HttpApplication instance is created here to trigger the Application OnStart event (which executes the Application_Start method in global.asax). It is then reclaimed immediately after the event has been processed, because the system initialization only takes one time.

Finally, GetNormal Application Instance, if there is an idle HTTP Application instance, directly use it, if not create it, then call InitInternational method to initialize the relevant content, and finally return the HttpApplication instance.

    private HttpApplication GetNormalApplicationInstance(HttpContext context) {
        HttpApplication app = null;
        lock (_freeList) {
            if (_numFreeAppInstances > 0) {
                app = (HttpApplication)_freeList.Pop();
                _numFreeAppInstances--;
                if (_numFreeAppInstances < _minFreeAppInstances) {
                    _minFreeAppInstances = _numFreeAppInstances;
                }
            }
        }
        if (app == null) {
            app = (HttpApplication)HttpRuntime.CreateNonPublicInstance(_theApplicationType);
            using (new ApplicationImpersonationContext()) {
                app.InitInternal(context, _state, _eventHandlerMethods);
            }
        }
        ......
        return app;
    }

Http Application Analysis

HttpApplication is implemented under System.Web. First, look at the InitInternational method of HttpApplication, which is used for initialization.

    internal void InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers) {
        Debug.Assert(context != null, "context != null");
        _state = state;
        PerfCounters.IncrementCounter(AppPerfCounter.PIPELINES);
        try {
            try {
                _initContext = context;
                _initContext.ApplicationInstance = this;
                context.ConfigurationPath = context.Request.ApplicationPathObject;
                using (new DisposableHttpContextWrapper(context)) {
                    if (HttpRuntime.UseIntegratedPipeline) {
                        Debug.Assert(_moduleConfigInfo != null, "_moduleConfigInfo != null");
                        Debug.Assert(_moduleConfigInfo.Count >= 0, "_moduleConfigInfo.Count >= 0");
                        try {
                            context.HideRequestResponse = true;
                            _hideRequestResponse = true;
                            InitIntegratedModules();
                        }
                        finally {
                            context.HideRequestResponse = false;
                            _hideRequestResponse = false;
                        }
                    }
                    else {
                        InitModules();
                        Debug.Assert(null == _moduleContainers, "null == _moduleContainers");
                    }
                    if (handlers != null)
                        HookupEventHandlersForApplicationAndModules(handlers);
                    _context = context;
                    if (HttpRuntime.UseIntegratedPipeline && _context != null) {
                        _context.HideRequestResponse = true;
                    }
                    _hideRequestResponse = true;
                    try {
                        Init();
                    }
                    catch (Exception e) {
                        RecordError(e);
                    }
                }
                if (HttpRuntime.UseIntegratedPipeline && _context != null) {
                    _context.HideRequestResponse = false;
                }
                _hideRequestResponse = false;
                _context = null;
                _resumeStepsWaitCallback= new WaitCallback(this.ResumeStepsWaitCallback);
                if (HttpRuntime.UseIntegratedPipeline) {
                    _stepManager = new PipelineStepManager(this);
                }
                else {
                    _stepManager = new ApplicationStepManager(this);
                }
                _stepManager.BuildSteps(_resumeStepsWaitCallback);
            }
            finally {
                _initInternalCompleted = true;
                context.ConfigurationPath = null;
                _initContext.ApplicationInstance = null;
                _initContext = null;
            }
        }
        catch {
            throw;
        }
    }

The code has two main functions, one is to initialize the familiar HttpModules, and the other is to execute multiple life cycle events through BuildSteps. From the above code, we can see that each function has a special judgment to determine whether IIS is the integration mode of IIS7, if it is a special step, if not a general step (the direct difference between the two is that the classical mode initialization HttpModules will be read from the site configuration Modules, the integration mode will preload CLR and a large number of Modules. For example, loading HttpModules set up on the server; in addition, IIS7 integration mode takes its own special process when building Steps.

To sum up, the main functions of the InitInternational method are as follows:

InitModules(): According to the settings of Web.Config, load the corresponding HttpModules.

Init Integrated Modules (): HttpModuels set on the server in IIS7 integration mode and HttpModuels set under system.webserver in Web.config will be loaded.

HookupEventHandlers For Appplication AndModules: Bind the corresponding event handler (defined in Global.asax) in the HttpApplication instance.

Create many instances of classes that implement the IExecutionStep interface and add them to the _execSteps of the current HttpApplication instance (including the periodic event handler defined in HttpModules and special events such as finding matching HttpHandler, executing methods of HttpHandler and filtering output), wait for callback to execute. From here we can see that HttpApplication handles requests asynchronously, and much of the processing of requests is put into _execStep waiting for callbacks to execute.

Events in HttpApplication are defined as follows:

    public event EventHandler BeginRequest {
        add { AddSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }
        remove { RemoveSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }
    }

All events are added by calling the AddSyncEventHookup method, with the first parameter being the value of the Event + event name.

    internal void AddSyncEventHookup(object key, Delegate handler, RequestNotification notification) {
        AddSyncEventHookup(key, handler, notification, false);
    }
private void AddSyncEventHookup(object key, Delegate handler, RequestNotification notification, bool isPostNotification) { ThrowIfEventBindingDisallowed(); Events.AddHandler(key, handler); if (IsContainerInitalizationAllowed) { PipelineModuleStepContainer container = GetModuleContainer(CurrentModuleCollectionKey); if (container != null) { SyncEventExecutionStep step = new SyncEventExecutionStep(this, (EventHandler)handler); container.AddEvent(notification, isPostNotification, step); } } }

In classical mode, when initializing HttpModlue, events are added to the Events collection by calling the Events.AddHandler method, and the key is the Event+event name. In integration mode, these events are added to another place (by wrapping event hanlder into SyncEvent Execution Step type, and then calling container.AddEvent method to add events to another place). In other words, the Events set above if is for classical mode, and the data in the Container below is for integration mode. These events are stored in the Module Containers attribute of HttpApplication. The type of the attribute is Pipeline Module StepContainer [], and the number is the number of HttpModules, that is, the number of HttpModules per HttpApplication Module. The events added above are placed in their respective PipelineModuleStepContainer containers.

    private PipelineModuleStepContainer[] ModuleContainers {
        get {
            if (_moduleContainers == null) {
                Debug.Assert(_moduleIndexMap != null && _moduleIndexMap.Count > 0, "_moduleIndexMap != null && _moduleIndexMap.Count > 0");
                _moduleContainers = new PipelineModuleStepContainer[_moduleIndexMap.Count];
                for (int i = 0; i < _moduleContainers.Length; i++) {
                    _moduleContainers[i] = new PipelineModuleStepContainer();
                }
            }
            return _moduleContainers;
        }
}

StepManager Analysis

Integration mode and classical mode (or IIS6) use different StepManagers. The BuildSteps method of this class is designed to create orderly Execution Step, which includes events and other operations interpolated between time cycles. The most important operations should be known before, such as which cycle can determine which HttpHandler to use, and In which cycle will the BeginProcessRequest method of HttpHandler be executed? The specific implementation classes of StepManager (Application StepManager, Pipeline StepManager) and HttpApplication classes are defined in the same file.

Build Steps Method for Application StepManager (for Classic Patterns)

    internal override void BuildSteps(WaitCallback stepCallback ) {
        ArrayList steps = new ArrayList();
        HttpApplication app = _application;
        bool urlMappingsEnabled = false;
        UrlMappingsSection urlMappings = RuntimeConfig.GetConfig().UrlMappings;
        urlMappingsEnabled = urlMappings.IsEnabled && ( urlMappings.UrlMappings.Count > 0 );
        steps.Add(new ValidateRequestExecutionStep(app));
        steps.Add(new ValidatePathExecutionStep(app));
        if (urlMappingsEnabled)
            steps.Add(new UrlMappingsExecutionStep(app)); // url mappings
        app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps);
        steps.Add(new MapHandlerExecutionStep(app));     // map handler
        app.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps);
        steps.Add(app.CreateImplicitAsyncPreloadExecutionStep());        
        steps.Add(new CallHandlerExecutionStep(app)); // execute handler       
        app.CreateEventExecutionSteps(HttpApplication.EventPostRequestHandlerExecute, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventReleaseRequestState, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPostReleaseRequestState, steps);
        steps.Add(new CallFilterExecutionStep(app)); // filtering
        app.CreateEventExecutionSteps(HttpApplication.EventUpdateRequestCache, steps);
        app.CreateEventExecutionSteps(HttpApplication.EventPostUpdateRequestCache, steps);
        _endRequestStepIndex = steps.Count;
        app.CreateEventExecutionSteps(HttpApplication.EventEndRequest, steps);
        steps.Add(new NoopExecutionStep()); // the last is always there
        _execSteps = new IExecutionStep[steps.Count];
        steps.CopyTo(_execSteps);
        _resumeStepsWaitCallback = stepCallback;
    }

    private void CreateEventExecutionSteps(Object eventIndex, ArrayList steps) {
        AsyncAppEventHandler asyncHandler = AsyncEvents[eventIndex];
        if (asyncHandler != null) {
            asyncHandler.CreateExecutionSteps(this, steps);
        }
        EventHandler handler = (EventHandler)Events[eventIndex];
        if (handler != null) {
            Delegate[] handlers = handler.GetInvocationList();
            for (int i = 0; i < handlers.Length; i++)  {
                steps.Add(new SyncEventExecutionStep(this, (EventHandler)handlers[i]));
            }
         }
    }

The complete functions of this method are summarized as follows:

Validate Request Execution Step for Request Request.

Make security checks on the requested path and prohibit illegal path access (Validate Path Execution Step).  

If you have Url Mappings set up, do RewritePath (Url Mappings Execution Step).

Execute event handling functions, such as BeginRequest, AuthenticateRequest, to executable ExecutionStep when it is formally invoked.

During these multiple event operations, four special Execution Steps are added depending on the timing.

MapHandler Execution Step: Find a matching HttpHandler

CallHandler Execution Step: Execute HttpHandler's Begin Process Request

CallFilterExecutionStep: Call Response.FilterOutput method to filter output

NoopExecution Step: Empty operation, with future extensions

All ExecuteionStep s are stored in the private field _execSteps under the Application StepManager instance, and the BeginProcessRequest method of HttpApplication eventually performs these operations through the ReumeSteps method of the instance.

Build Steps for Pipeline StepManager (for integration mode)

    internal override void BuildSteps(WaitCallback stepCallback) {
        Debug.Trace("PipelineRuntime", "BuildSteps");
        HttpApplication app = _application;
        IExecutionStep materializeStep = new MaterializeHandlerExecutionStep(app);
        app.AddEventMapping(ttpApplication.IMPLICIT_HANDLER,
                    RequestNotification.MapRequestHandler,
                    false, materializeStep);
        app.AddEventMapping(HttpApplication.IMPLICIT_HANDLER,
                    RequestNotification.ExecuteRequestHandler,
                    false, app.CreateImplicitAsyncPreloadExecutionStep());
        IExecutionStep handlerStep = new CallHandlerExecutionStep(app);
        app.AddEventMapping(HttpApplication.IMPLICIT_HANDLER,
                    RequestNotification.ExecuteRequestHandler,
                    false, handlerStep);
        IExecutionStep webSocketsStep = new TransitionToWebSocketsExecutionStep(app);
        app.AddEventMapping(HttpApplication.IMPLICIT_HANDLER,
                    RequestNotification.EndRequest,
                    true, webSocketsStep);
        IExecutionStep filterStep = new CallFilterExecutionStep(app);
        app.AddEventMapping(HttpApplication.IMPLICIT_FILTER_MODULE,
                    RequestNotification.UpdateRequestCache,
                    false, filterStep);
        app.AddEventMapping(HttpApplication.IMPLICIT_FILTER_MODULE,
                    RequestNotification.LogRequest,
                    false, filterStep);
        _resumeStepsWaitCallback = stepCallback;
    }

    private void AddEventMapping(string moduleName,RequestNotification requestNotification, bool isPostNotification,IExecutionStep step) {
        ......
        PipelineModuleStepContainer container = GetModuleContainer(moduleName);
        container.AddEvent(requestNotification, isPostNotification, step);
    }

The above code is different from the classical pattern in two places:

Instead of using MapHandler Execution Step to load Execution Step (that is, to find the corresponding HttpHandler), the integration mode uses the MaterializeHandler Execution Step class to obtain HttpHandler in a different way.

The integration mode adds events to the ModuleContainers container mentioned earlier by adding events through the AddEventMapping method of HttpApplication.

To sum up, in classical mode, Event + event name is used as key to save all events in the Events attribute object of HttpApplication, then assemble them in order in BuildSteps, load four special Execution Steps in the middle, and execute them in a unified way. In integration mode, everything is done by using HttpModule name + RequestNotification enumeration value as key. The file is stored in the ModeleContainers attribute object of HttpApplication, and then loads the four special ExecutionStep by forging the name of HttpModule in BuildSteps. Finally, all HttpModules are traversed in the order of enumeration type to execute these events in order. You can write a custom HTpModuel to execute these events to see how it works.

The following is a summary of the general process of processing the first request.

Topics: ASP.NET Attribute IIS encoding ascii