WCF Services beyond HTTP with WAS


                                                                                                                        <<Prev

The IIS 6.0 Architecture and Process Model
The architecture for Windows Server 2003 and IIS 6.0 is divided into two basic parts—a listener process and a set of worker processes. The IIS 6.0 listener process is implemented in the w3svc service, which is a long-running Windows NT service activated by the Windows Service Control Manager (SCM). The listener process waits for messages to arrive via HTTP and then dispatches them to the appropriate worker process (w3wp.exe), which hosts the application code that will finally process the request.
When a request arrives on the network, it is processed by the kernel-mode HTTP stack (http.sys) and delivered to the listener process. The w3svc process then looks at the request URI and uses it to map the request to a specific IIS application living inside a particular IIS application pool that is, in turn, hosted inside an instance of the worker process (w3wp), as you see in Figure 1. The mapping between the request URI and the application is based on configuration information stored in the IIS metabase, which can be found in metabase.xml and mbschema.xml, respectively, both of which are stored in the %windir%\system32\inetsrv folder. Once w3svc has determined the destination application, it can look it up in some internal data structures to resolve the destination worker process and application pool.

Figure 1 IIS 6.0 Basic Architecture

Depending on the state of the targeted IIS server, one of two things can happen at this point. If the worker process and application pool are already running because a previous request has been received for that pool, then no activation is necessary. Therefore, the request is simply dispatched to the waiting worker process. If no worker process exists to handle the current request, the listener process must create a new instance of w3wp before dispatching the request.

The IIS 6.0 worker process is a lightweight executable. When it starts up in response to an activation request, the first thing it does is load a simple unmanaged shim DLL (w3wphost.dll) that enables w3svc to communicate with the worker process. This shim is also responsible for loading aspnet_isapi.dll, which implements the interface between the managed components of ASP.NET and the unmanaged components of IIS.

Aspnet_isapi.dll is responsible for loading the common language runtime (CLR) into the worker process and creating a default application domain, which is where the managed hosting components of ASP.NET will live. These managed hosting components are responsible for creating additional application domains (one per IIS application) on demand and routing requests to them based on the request's URL.

IIS 7.0 and WAS
In IIS 6.0, the w3svc service really did double duty. It acted as the HTTP listener because it registered with http.sys and was the direct recipient of incoming HTTP traffic. It also owned the process activation components responsible for starting new instances of w3wp and dispatching requests appropriately. In IIS 7.0, these two responsibilities have been refactored into separate Windows NT services. The w3svc process retains its role as the HTTP listener, but the components responsible for configuration and process activation have been factored into WAS, which has three parts: the configuration manager, the process manager, and the unmanaged listener adapter interface.

The configuration manager reads application and application pool configuration from applicationhost.config (the IIS 7.0 replacement for the metabase). The process manager maps application pools to existing worker processes and is the one responsible for spawning new instances of w3wp to host new application pools in response to activation requests. The unmanaged listener adapter interface defines how external listeners communicate activation requests they receive to WAS.

The w3svc service owns the communication with kernel-level http.sys and communicates HTTP activation requests to WAS across the listener adapter interface.

WCF uses the listener adapter interface to communicate activation requests that are received over the supported non-HTTP protocols (namely TCP, Named Pipes, and MSMQ). The WCF plumbing that actually receives requests over non-HTTP protocols is hosted inside of SMSvcHost.exe, which hosts the following four long-running Windows NT services: NetTcpPortSharing, NetTcpActivator, NetPipeActivator, and NetMsmqActivator (see Figure 2).

Figure 2 Basic Architecture of IIS 7.0 and WAS

NetTcpPortSharing is the WCF TCP port sharing service. It implements a centralized TCP listener so that multiple processes can listen on the same TCP port. This service is available even if IIS 7.0 is not installed.
NetTcpActivator is the WCF TCP Activation Service. It communicates TCP activation requests to WAS.
NetPipeActivator is the WCF-named pipe activation service which communicates named pipe activation requests to WAS.
NetMsmqActivator is the WCF MSMQ activation service; it communicates MSMQ activation requests to WAS.

Although these services all live in the same binary, they are separate Windows NT services and can be stopped and started individually to reduce both attack surface and overhead. They are all examples of listener services and all behave in a similar manner. For that reason, we will focus on TCP activation, which can also serve as an example for named pipes and MSMQ. The only difference is the particular type of network resource in use in each case.

Configuration and Multi-Protocol Addressing
In order for an application to be activated by WAS, it first needs to be configured. Each application hosted in WAS has a corresponding configuration entry located in %windir%\system32\inetsrv\config\applicationhost.config. This information includes items like the application pool that will host the application and a URL fragment that uniquely identifies it.

Much like IIS 6.0, each application is associated with both an application pool and a site. Each site has a set of address bindings for the network protocols it supports. For example, an administrator might configure the default site to bind to HTTP on port 80 and TCP on port 7777. This site might host two applications (listening on /Foo and /Bar respectively) that live in separate application pools. In this configuration, the /Foo application would listen on http://myserver.com/Foo as well as net.tcp://myserver.com:7777/Foo, while /Bar would listen on http://myserver.com/Bar and net.tcp://myserver.com:7777/Bar. Regardless of how they were received—since each application pool gets its own worker process—all requests for /Foo would go to worker process 1, while everything destined for /Bar would go to worker process 2.

 In general, an application's site governs the set of network addresses associated with the application while its application pool governs the worker process instance that will host it. Note that the one worker process per application pool rule only holds true if you're not using Web gardens. In the Web garden case, each application pool maps to its own set of worker processes, up to the maximum number of worker processes specified in the application pool's configuration.

How Listeners Know to Listen
A listener needs to receive messages. For this, it needs to open a socket (or a pipe handle, or start an MSMQ read, and so on). However, in order to receive the proper messages, it needs to obtain the necessary addressing information from WAS. This is accomplished during listener startup. The protocol's listener adapter calls a function on the WAS listener adapter interface and essentially says, "I am now listening on the net.tcp protocol; please use this set of callback functions I'm handing you to tell me what I need to know." In response, WAS will call back with any configuration it has for applications that are set up to accept messages over the protocol in question. For the example above, the TCP listener would be informed that there were two applications (*:7777/Foo and *:7777/Bar) configured to use TCP. WAS also assigns to each application a unique listener channel ID used for associating requests with their destination applications.

The listener process uses the configuration information provided by WAS to build up a routing table, which it will use to map incoming requests to listener channel IDs as they arrive. The mechanics of this mapping is an implementation detail of the underlying protocol the listener is supporting. The important thing is that each listener service must be able to look at an incoming message, say, "Ah—this is destined for listener channel x," and dispatch the request to WAS accordingly. It happens that WCF uses URIs to indicate the destination of messages that arrive over TCP/MSMQ/named pipes, but other protocols could conceivably implement this mapping in whatever way is appropriate for them.

Once the listener service has connected to WAS and received configuration information, it can open its network resource and begin listening for messages. For TCP, this causes NetTcpActivator to trigger a socket to open and an asynchronous call to Socket.Accept to be made, at which point the listener essentially goes to sleep until a message arrives.

Message-Based Activation over Non-HTTP Protocols
The listener process wakes up and starts reading bytes when they arrive at the waiting socket. At this point, the goal is to read just enough information to determine the eventual destination of the forthcoming message and then pause while the listener calls back to WAS and spawns the worker process. For TCP, the destination address is determined by reading information out of the framing protocol that surrounds the SOAP message. Once the listener has the destination URI, it uses that URI as an index into its internal routing table and resolves the URI into the corresponding listener channel ID assigned to that address by WAS.

The listener service then asks WAS to activate a worker process to handle the pending request by calling the unmanaged WebhostOpenListenerChannelInstance method on the listener adapter interface. In addition to other parameters, WebhostOpenListenerChannelInstance takes a listener channel ID as well as a blob of data (represented as a byte array) that will be used to initialize the receiving side of the plumbing in the activated process. This blob is of no interest to WAS as it's merely a convenience that allows each protocol listener implementation to initialize its newly activated components.

When WAS receives the call to WebhostOpenListenerChannelInstance, the process manager part of WAS spawns a new instance of the worker process. The worker process then needs to do some initialization itself before it can process messages received by the listener. During this time the listener may continue to receive data from the network; however it cannot dispatch the message to the worker until it receives notification that worker process initialization is complete.

Worker Process Initialization
There are several important components that are active inside the worker process, as you can see in Figure 3. The architecture of a worker process is shown in Figure 4.
Figure 3 Important Components in the Worker Process

Component
Description
Process Host
Loads the CLR into the worker process and initializes the default application domain.
Application Manager
Creates unique application domains in response to application-level activation requests and manages their lifetime.
Process Protocol Handlers
Implement protocol-specific process initialization logic.
Application Domain Protocol Handlers
Reside in the activated application domain and perform protocol-specific application domain initialization.

Figure 4 Worker Process Initialization Architecture
Since IIS does not implement any managed code components, the job of loading the CLR into the worker process falls to the ASP.NET process host. WAS creates a process host inside the worker process by calling an API exposed by aspnet_isapi.dll. WAS passes this function a callback interface that the managed process host can use for subsequent communication with WAS. The newly created process host is then returned to WAS, allowing for two-way communication between WAS and the worker process.
WAS uses the instance of the process host it obtained from the worker process to start the protocol-specific initialization routines. The activation system StartProcessProtocolListenerChannel on the process host, passing it a protocol key (such as "net.tcp") and a callback interface that the protocol-specific handlers can use to talk back to WAS.
When the process host receives a request to start a new process protocol listener channel inside the worker process, the first thing it does is look up the protocol key in the configuration. Users of WAS are required to register this prior to starting their listeners. For example, WCF registers both a process protocol handler and an application domain protocol handler for net.tcp. The configuration data shown below can be found in the master web.config file in %windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG.
 Configuration for net.tcp Protocol Handlers

<system.web>
  <protocols>
    <add name="net.tcp"
     processHandlerType=
      "System.ServiceModel.WasHosting.TcpProcessProtocolHandler,
       System.ServiceModel.WasHosting, Version=3.0.0.0, Culture=neutral,
       PublicKeyToken=b77a5c561934e089"
     appDomainHandlerType=
      "System.ServiceModel.WasHosting.TcpAppDomainProtocolHandler,
       System.ServiceModel.WasHosting, Version=3.0.0.0, Culture=neutral,
       PublicKeyToken=b77a5c561934e089" validate="false" />
  </protocols>
</system.web>
Once the process host has resolved the protocol key in configuration, it creates a new instance of the configured process protocol handler type in the default application domain and calls StartListenerChannel on it. This method takes two separate callback interfaces. One allows communication with WAS (it is, in fact, the same WAS callback object as passed to the process host). The other one allows communication with the process host directly (see Figure 5).

Figure 5 Registering Protocol Handlers
A process protocol handler (PPH) has a large degree of control over the hosting model for a particular protocol. For example, the PPH could simply stop at this point in the initialization routine and handle all subsequent requests in the default application domain. However, WCF has chosen to retain the IIS 6.0 application pool model, which activates each application in its own application domain so that each one can be independently monitored and recycled. As such, the WCF PPHs need to collaborate with the ASP.NET application manager, which governs applications and application domain lifetime.
The process host provides the PPH with access to the application manager's functionality through the IAdphManager interface it passes to StartListenerChannel. In order to determine the destination application for the activation request, the PPH calls back to WAS and asks for the data blob that the listener provided when it originally called WebhostOpenListenerChannelInstance. After doing some twiddling on the returned byte array, the WCF PPH has access to the application key that uniquely identifies the destination application (for example, /w3svc/1/Foo) because, by convention, the WCF listener includes the application key in the data blob it sends to the listener channel. WAS places no restrictions on the data passed in the blob—from the WAS perspective, it's merely an opaque byte array. It can then pass the application key, the protocol key, and the listener callback interface to IAdphManager.StartAppDomainProtocolListenerChannel to activate the application's application domain.
In general, the application manager uses the application key as an index into a table of all application domains. When it receives a call to StartAppDomainProtocolListener, it first consults this table to see if the application has already been activated by a previous request that arrived over a different protocol. Finding none, it uses the protocol key to look up the protocol-specific application domain protocol handler type in much the same way as the process host used this key to determine the process protocol handler type. Once the application domain protocol handler type is resolved, the application manager creates a new instance of it in the target application domain. Finally, the application manager calls AppDomainProtocolHandler.StartListenerChannel, again passing the WAS callback interface so the application domain can communicate with WAS if necessary.
Once the application domain protocol handler is instantiated, the internals of the worker process look like Figure 6. At this point, WAS considers the target application to be fully activated and has no further involvement in the process until the application shuts down.

Figure 6 Application Domain Protocol Handler Initialization  
Getting Data from the Listener to the Worker
Activating worker processes and application domains is the first step towards a larger goal: actually delivering the message to the application. WAS, since it is an activation service and not a message delivery service, does not participate in this activity. Instead, each protocol implementation is free to implement communication between the listener and the worker in whatever way it sees fit. This architecture allows for various protocol-specific optimizations and for each implementation to exploit the nature of the underlying protocol. For example, the WCF MSMQ activation service simply passes the name of the MSMQ queue in the data blob, allowing the activated application to read directly from the data source and bypass the listener completely.

                                                                                                                                <<Prev

No comments:

Post a Comment