Środowisko uruchomieniowe ASP.NET HTTP*

Published: November 27, 2006 | Updated: November 27, 2006

By Dino Esposito

Streszczenie: Artykuł prezentuje szczegółowy opis komponentów składowych środowiska uruchomieniowego HTTP oraz przedstawia logikę, która steruje przetwarzaniem poszczególnych żądań, skierowanych do aplikacji ASP.NET. Pokazano również zachowanie się procesu roboczego z punktu widzenia modelu „web garden” oraz najnowszego modelu procesów IIS 6, oraz zaprezentowano wszystkie kroki, które prowadzą od żądania HTTP do generacji zwykłego tekstu HTML. (10 stron wydruku)

On This Page

Wprowadzenie Wprowadzenie
Składniki infrastruktury ASP.NET Składniki infrastruktury ASP.NET
Model Web Garden Model Web Garden
Potok HTTP Potok HTTP
Pliki tymczasowe i assembly strony Pliki tymczasowe i assembly strony
Podsumowanie Podsumowanie

Wprowadzenie

Niezawodność i wydajność są kluczowymi wymaganiami, stawianymi wszystkim aplikacjom WWW; niezależnie od platformy, na której są one budowane. Te dwa wymagania, jednakże, stoją we wzajemnej sprzeczności ze sobą. Przykładowo, w celu stworzenia bardziej niezawodnej i odpornej na awarie aplikacji, można rozważyć oddzielenie serwera WWW od fizycznej aplikacji, która będzie pracować w trybie „out-of-process”. Praca aplikacji w kontekście pamięci różnym od kontekstu procesu serwera WWW powoduje jednak samoistne zwolnienie pracy aplikacji. Należy więc zapobiegać takim sytuacjom, aby mieć pewność, że kod uruchomiony w trybie „out-of-process” wykonuje się tak szybko, jak to tylko możliwe.

Architektura środowiska uruchomieniowego Microsoft® ASP.NET została zaprojektowana zgodnie z wytycznymi, które uwypuklają niezawodność i wydajność. Powstały w ten sposób model procesów ASP.NET zawiera dwa elementy — złącze typu „in-process”, żyjące wewnątrz procesu serwera WWW oraz zewnętrzny proces roboczy. Infrastruktura środowiska uruchomieniowego ASP.NET jest dodatkowo wystarczająco skalowalna, aby móc automatycznie użytkować dowolny wybrany procesor na wieloprocesorowej platformie sprzętowej. Taki model, nazywany „web garden”, pozwala wielu procesom roboczym uruchamiać się w tym samym czasie, każdy na osobnym procesorze.

Na wyższym poziomie abstrakcji, środowisko uruchomieniowe ASP.NET posiada trzy cechy:

  • Całkowita niezależność pomiędzy aplikacjami oraz procesem roboczym ASP.NET. Czas życia aplikacji nie jest w żaden sposób uzależniony od czasu życia obsługującego ją procesu roboczego. Innymi słowy, procesy robocze mogą uruchamiać się i kończyć działanie, podczas gdy aplikacja działa i pracuje cały czas.

  • Chociaż aplikacje ASP.NET nigdy nie są uruchamiane w trybie „in-process” wewnątrz serwera WWW, w najbardziej typowych scenariuszach ogólna wydajność jest zbliżona do tej, którą osiągają aplikacje typu „in-process”.

  • Wbudowane i konfigurowalne wsparcie dla architektur typu „web garden”. Poprzez proste odczytanie ustawień z pliku konfiguracyjnego, proces roboczy może klonować siebie samego, w celu wykorzystania wszystkich procesorów, posiadających pokrewieństwo z procesem. Dzięki temu, w większości scenariuszy zbliżamy się do liniowej skalowalności na maszynach wieloprocesorowych. (Więcej na ten temat znajduje się w dalszej części artykułu.)

W tym artykule, zbadamy składowe elementy środowiska uruchomieniowego ASP.NET i przejdziemy, krok po kroku, przez długą i trudną drogę, w czasie której żądanie adresu URL zgłoszone do serwera WWW zamienia się ma zwykły tekst HTML.

Jeżeli jednoznacznie tego nie określono, poniższy opis odnosi się do domyślnego modelu procesów ASP.NET – jedynego dostępnego w Internetowych Usługach Informacyjnych Microsoft® (IIS) 5.x.

 

Składniki infrastruktury ASP.NET

Aplikacje ASP.NET wykonują się pod egidą hostującego je serwera WWW. Na platformie serwerowej z rodziny Microsoft® Windows®, serwer Web jest reprezentowany przez plik wykonywalny IIS nazywający się inetinfo.exe. Jest to wbudowana część systemu operacyjnego Windows 2000 i jego nowszych wersji. Warto jednak zauważyć, że w systemie Microsoft® Windows Server 2003, ani IIS ani ASP.NET nie są instalowane domyślnie. Należy je dodać do systemu, klikając aplet Dodaj/usuń programy w Panelu sterowania.

IIS jest niezarządzanym plikiem wykonywalnym, który zapewnia rozszerzalny model, oparty na rozszerzeniach ISAPI i modułach filtrujących. Pisząc takie moduły, deweloperzy mogą bezpośrednio przejmować żądania dla specyficznych typów zasobów oraz dołączyć się do bieżącego żądania na różnych, predefiniowanych etapach jego przetwarzania. Rozszerzenia i filtry są bibliotekami DLL, które eksportują kilka funkcji z powszechnie znanymi nazwami i sygnaturami. Takie komponenty typu plug-in są zarejestrowane i skonfigurowane w metabazie IIS.

Jedynie kilka typów zasobów, które są żądane przez aplikacje klienckie, jest obsługiwane bezpośrednio przez IIS. Przykładowo, każde nadchodzące żądanie strony HTML, pliku tekstowego, obrazu JPEG i GIF jest przetwarzane przez sam IIS. Żądania plików Active Server Pages (*.asp) są rozwiązywane poprzez wywołanie specyficznego dla ASP modułu rozszerzeń, o nazwie asp.dll. Podobnie żądania dla zasobów ASP.NET (na przykład *.aspx, *.asmx, *.ashx) są przekazywane do rozszerzenia ASP.NET ISAPI. Ten komponent systemowy jest biblioteką Win32 DLL o nazwie aspnet_isapi.dll. Rozszerzenie ASP.NET obsługuje różne typy zasobów, włącznie z usługami web oraz wywołaniami programu obsługi HTTP.

Rozszerzenie ASP.NET ISAPI jest biblioteką Win32 DLL i nie hostuje kodu zarządzanego. Jest to centralna konsola, która otrzymuje i rozdziela żądania do różnorodnych zasobów ASP.NET. Moduł żyje wewnątrz procesu IIS i jest uruchamiany na użytkowniku SYSTEM z uprawnieniami administratora. Konto to nie może być zmienione przez deweloperów i administratorów systemowych. Rozszerzenie ASP.NET ISAPI jest odpowiedzialne za wywoływanie procesu roboczego ASP.NET (aspnet_wp.exe) który kontroluje wykonanie żądania. Poza rutowaniem żądań, ASP.NET ISAPI monitoruje stan procesu roboczego i jest odpowiedzialne za terminowanie go, jeżeli jego wydajność spada poniżej pewnego progu.

Proces roboczy jest niewielkim programem powłoki Win32, który hostuje CLR - Common Language Runtime oraz uruchamia kod zarządzany. Odpowiada on za obsługiwanie żądań dla zasobów ASPX, ASMX, i ASHX. Zasadniczo istnieje tylko jedna instancja tego procesu na danej maszynie. Wszystkie aktywne w danym momencie aplikacje ASP.NET są uruchamiane wewnątrz tego procesu, każda w osobnej AppDomain. Jak wcześniej wspomniano, proces roboczy wspiera również tryb “web garden”, co oznacza, ze identyczne kopie tego procesu są uruchamiane na wszystkich procesorach z pokrewieństwem do procesu. (Więcej na ten temat w rozdziale "Model Web Garden".)

Komunikacja pomiędzy ISAPI a procesem roboczym jest przeprowadzana za pomocą zbioru nazwanych potoków. Nazwany potok jest mechanizmem Win32, służącym do przesyłania danych pomiędzy granicami procesów. Jak sama nazwa mówi, nazwany potok działa jak potok: wprowadza się dane na jednym jego końcu, i te same dane pojawiają się na drugim końcu. Potoki mogą być ustanawiane zarówno do łączenia procesów lokalnych, jak i procesów uruchomionych na zdalnych maszynach. W zakresie komunikacji międzyprocesowej, odbywającej się lokalnie, potoki są najbardziej wydajnym i najbardziej elastycznym narzędziem w systemie Windows.

W celu zagwarantowania optymalnej wydajności, aspnet_isapi wykorzystuje asynchroniczne nazwane potoki do przekazywania żądań do procesu roboczego oraz do odbierania odpowiedzi. Z drugiej strony, proces roboczy wykorzystuje potoki synchroniczne, w celu wykonywania zapytań dotyczących informacji o środowisku IIS (chodzi o zmienne serwerowe). Moduł aspnet_isapi tworzy ustaloną liczbę nazwanych potoków i używa nakładających się operacji, aby równocześnie obsługiwać połączenia z wykorzystaniem niewielkiej puli wątków. Kiedy operacja wymiany danych za pomocą potoków dobiegnie końca, procedura zakończenia rozłącza klienta i ponownie wykorzystuje instancję potoku do obsługi nowego klienta. Pula wątków i nakładających się operacji zapewnia dobry poziom wydajności dla ASP.NET ISAPI. W żadnym przypadku rozszerzenie aspnet_isapi nie przetwarza jednak zapytań HTTP.

Logika leżąca u podstaw przetwarzania każdego żądania ASP.NET może zostać podsumowana za pomocą następujących punktów.

  1. Kiedy pojawia się żądanie, IIS sprawdza typ żądanego zasobu i wywołuje rozszerzenie ASP.NET ISAPI. Jeśli włączono domyślny model procesów, aspnet_isapi kolejkuje żądanie i przypisuje je do procesu roboczego. Dane związane z żądaniem są przesyłane za pośrednictwem asynchronicznego I/O. Jeżeli włączono model procesów IIS 6, żądanie jest automatycznie kolejkowane do procesu roboczego (w3wp.exe), włącznie z obsługą puli aplikacji IIS, do której aplikacja należy. Proces roboczy IIS 6 nie wie nic o ASP.NET i kodzie zarządzanym. Jego działanie jest ograniczone do przetwarzania rozszerzenia *.aspx i ładowania modułu aspnet_isapi. Kiedy ASP.NET ISAPI pracuje pod kontrolą modelu procesów IIS 6, zachowuje się inaczej i po prostu ładuje CLR w kontekście procesu roboczego w3wp.exe.

  2. Po otrzymaniu żądania, proces roboczy ASP.NET powiadamia ASP.NET ISAPI, który będzie go obsługiwał. Notyfikacja odbywa się za pośrednictwem synchronicznego kanału I/O. Wykorzystywany jest model synchroniczny, ze względu na spójność obsługi, proces roboczy nie może rozpocząć obsługi żądania, które nie jest oznaczone jako “wykonywane” w wewnętrznej tabeli żądań ISAPI. Żądanie, które jest przetwarzane przez dany proces roboczy nie może być przypisane do innego procesu, chyba, że pierwotny proces zostanie zakończony.

  3. Żądanie wykonuje się w kontekście procesu roboczego. Mogą istnieć okoliczności, w których proces roboczy musi z powrotem odwołać się do ISAPI, aby ukończyć obsługę żądania, to znaczy, aby wyliczyć zmienne serwerowe. W tym przypadku, proces roboczy wykorzystuje potoki synchroniczne, ponieważ chroni to sekwencję logiki przetwarzania żądania.

  4. Po zakończeniu przetwarzania, odpowiedź jest wysyłana do aspnet_isapi, za pomocą otwarcia asynchronicznego potoku. Stan żądania zmienia się wtedy na „wykonane”; w późniejszym etapie żądanie takie zostanie usunięte z tabeli. Jeżeli proces roboczy się ulegnie awarii, wszystkie żądania, które obsługiwał, pozostaną w stanie „wykonywane”. Kiedy aspnet_isapi wykryje, że proces roboczy nie odpowiada, automatycznie przerywa żądania i zwalnia wszystkie zaalokowane zasoby IIS.

Powyższy opis odnosi się do domyślnego modelu przetwarzania ASP.NET—modelu pracy, zbudowanego w IIS 5.x. Domyślny sposób pracy IIS 6 (dostępnego z Windows Server 2003) wpływa również na model przetwarzania ASP.NET. W przypadku hostowania na IIS 6.0, ASP.NET 1.1 automatycznie adaptuje swój sposób pracy do środowiska hostującego. Proces roboczy aspnet_wp nie jest wykorzystywany, jak również ignorowane są niektóre z parametrów, zdefiniowanych w pliku machine.config. Z perspektywy ASP.NET, ogromna zmiana w IIS 6 polega na tym, że wszystko, co jest związane z żądaniami, jest obsługiwane pod kontrolą aspnet_isapi i w kontekście procesu roboczego w3wp.exe. Konto procesu roboczego jest to konto przydzielone do puli aplikacji, do której należy dana aplikacja WWW. Domyślnie, konto to NETWORKSERVICE; wbudowane, słabe konto funkcjonalnie odpowiadające ASPNET.

Proces roboczy jest przedmiotem działania elementu zwanego recycklingiem procesów. Recykling procesów polega na zdolności aspnet_isapi do automatycznego uruchamiania nowego procesu, kiedy obecnie istniejący konsumuje zbyt wiele pamięci, odpowiada zbyt wolno lub po prostu się zawiesza. Gdy zdarza się taka sytuacja, nowe żądania są obsługiwane przez nową instancję, która staje się nowym, aktywnym procesem. Wszystkie żądania przypisane do starego procesu, pozostają w stanie „oczekujący”. Kiedy stary proces zakończy pracę z oczekującymi żądaniami i przejdzie w stan bezczynności, jest terminowany. Jeżeli proces roboczy przestanie odpowiadać, lub w jakikolwiek inny sposób przestanie obsługiwać żądania, wszystkie oczekujące żądania są przypisywane do nowego procesu.

Chociaż ASP.NET ISAPI i proces roboczy stanowią kluczowe komponenty infrastruktury środowiska uruchomieniowego ASP.NET, inne pliki wykonywalne przyczyniają się do jego pracy. Poniższa tabela prezentuje wszystkie te komponenty.

Tabela 1. Pliki wykonywane, które tworzą środowisko uruchomieniowe ASP.NET

Nazwa

Typ

Konto


aspnet_isapi.dll


Win32 DLL (rozszerzenie ISAPI)


LOCAL SYSTEM


aspnet_wp.exe


Win32 EXE


ASPNET


aspnet_filter.dll


Win32 DLL (filtr ISAPI)


LOCAL SYSTEM


aspnet_state.exe


Usługa Win32 NT


ASPNET

Komponent aspnet_filter.dll jest niewielkim filtrem ISAPI Win32 ISAPI wykorzystywanym do tworzenia kopii zapasowej bezciasteczkowego stanu sesji dla aplikacji ASP.NET. W systemie Windows Server 2003, kiedy model procesów IIS 6 jest włączony, aspnet_filter.dll filtruje również żądania dla zasobów innych niż wykonywalne, które są zlokalizowane w katalogu Bin.

Rola aspnet_state.exe jest bardziej podstawowa dla aplikacji Web, jako że dotyczy zarządzania stanem sesji. Jest to opcjonalna usługa, która może być wykorzystywana do przechowywania stanu sesji poza przestrzenia pamięci aplikacji WWW. Ten plik wykonywalny jest usługą NT i może być uruchomiony zarówno lokalnie, jak i zdalnie. Kiedy usługa jest aktywna, aplikacja ASP.NET może zostać skonfigurowana tak, aby przechowywać dowolną informację o sesji w pamięci tego procesu. Analogiczny schemat jest zapewniony dla bardziej niezawodnego przechowywania danych, które nie podlega recyklingowi procesów i awarii aplikacji ASP.NET. Usługa jest uruchamiania w kontekście lokalnego konta ASPNET, ale szczegółowo można to skonfigurować za pomocą interfejsu menedżera usług.

Istnieje jeszcze jeden plik wykonywalny, o którym warto wspomnieć, chociaż nie jest w zasadzie częścią infrastruktury – jest to aspnet_regiis.exe. Narzędzie to służy do konfiguracji środowiska dla potrzeb uruchamiania obok siebie różnych wersji ASP.NET na pojedynczym komputerze. Narzędzie to jest również pomocne w celu naprawy zepsutej konfiguracji IIS i ASP.NET. Narzędzie działa poprzez aktualizację map skryptów, które są przechowywane w metabazie IIS w węźle głównym i poniżej. Mapa skryptu jest zbiorem asocjacji pomiędzy typami zasobów i modułami ASP.NET. W końcu, narzędzie to może być również wykorzystywane do wyświetlania statusu zainstalowanych wersji ASP.NET oraz do wykonywania innych operacji konfiguracyjnych, takich jak przyznawanie uprawnień NTFS do poszczególnych folderów oraz tworzenie katalogów dla skryptów klienckich.

 

Model Web Garden

Model „web garden” można skonfigurować za pośrednictwem sekcji <processModel> w pliku machine.config. Zauważmy, że sekcja <processModel> jest jedyną sekcją, która nie może zostać umieszczona w pliku web.config, który jest specyficzny dla danej aplikacji WWW. Oznacza to, że model „web garden” stosuje się do wszystkich aplikacji, uruchomionych na danej maszynie. Jednakże, używając węzła <location> w źródle machine.config, można dostosować ustawienia właściwe dla całej maszyny do konkretnej aplikacji.

Dwa atrybuty w sekcji <processModel> wpływają na model „web garden”. Są to webGarden oraz cpuMask. Atrybut webGarden przyjmuje wartość Boolean, która wskazuje, czy ma być wykorzystywane wiele procesów roboczych (jeden na każdy pokrewny CPU). Atrybut jest ustawiony domyślnie na wartość „false”. Atrybut cpuMask przechowuje wartość typu DWORD, której binarna reprezentacja stanowi maskę bitową dla procesorów, które są wybrane do uruchomienia procesu roboczego ASP.NET. Domyślna wartość wynosi -1 (0xFFFFFF), co oznacza, że wszystkie dostępne procesory mogą być wykorzystane. Zawartość atrybutu cpuMask jest ignorowana, jeżeli atrybut webGarden ma wartość „false”. Atrybut cpuMask daje również górne ograniczenie liczby kopii procesu aspnet_wp.exe, które są uruchamiane.

Stare porzekadło “nie wszystko złoto, co się świeci”, jest tutaj bardzo trafne. Model „web garden” pozwala wielu procesom roboczym uruchamiać się w tym samym czasie. Jednakże zauważmy, że wszystkie te procesy będą miały własną kopię stanu aplikacji, wewnątrzprocesowego stanu sesji, cache ASP.NET, danych statycznych i wszystkich innych danych, które są potrzebne do uruchomienia aplikacji. Kiedy tryb „web garden” jest włączony, ASP.NET ISAPI uruchamia tak wiele procesów roboczych, ile jest dostępnych procesorów, z których każdy jest klonem poprzedniego (i każdy spokrewniony z odpowiadającym mu CPU). Aby zbalansować obciążenie, nadchodzące żądania są dzielone pomiędzy uruchomione procesy w sposób cykliczny. Procesy robocze są recyklingowane tak samo, jak działa to w przypadku pojedynczego procesu. Zauważmy również, ze ASP.NET dziedziczy wszystkie restrykcje dotyczące wykorzystania procesora od systemu operacyjnego.

Podsumowując, model „web garden” nie jest koniecznie wielkim zwycięstwem dla wszystkich aplikacji. Im bardziej aplikacja zapamiętuje stan, tym więcej ryzyka, jeżeli chodzi o rzeczywistą wydajność. Dane operacyjne są przechowywane w blokach współdzielonej pamięci, tak, aby zmiany wprowadzone przez jeden proces były natychmiast widoczne dla innych procesów. Jednakże, na czas potrzebny do obsłużenia żądania, dane operacyjne są kopiowane do kontekstu procesu. Każdy proces roboczy będzie więc posiadał własną kopię danych operacyjnych, i im więcej stanu w aplikacji, tym wyższy koszt do zapłacenia w wydajności. W tym kontekście, benchmarking aplikacji jest absolutną koniecznością.

Zmiany wprowadzone do sekcji <processModel> pliku konfiguracyjnego są efektywne dopiero po restarcie IIS.W IIS 6, parametry odpowiadające za tryb „web garden” są przechowywanie w metabazie IIS; atrybuty webGarden i cpuMask są ignorowane.

 

Potok HTTP

Gdy rozszerzenie ASP.NET ISAPI uruchamia proces roboczy, przekazuje do niego kilka parametrów linii poleceń. Proces roboczy wykorzystuje te parametry do wykonania zadań, które muszą zostać zrealizowane zanim zostanie załadowany CLR. Wśród przekazywanych parametrów znajduje się wymagany poziom autentykacji dla bezpieczeństwa COM i DCOM, liczba nazwanych potoków dostępnych do wykorzystania, oraz ID procesu IIS. Nazwy nazwanych potoków są generowane losowo z wykorzystaniem ID procesu IIS i liczby dostępnych potoków. Proces roboczy nie otrzymuje nazw dostępnych potoków, ale otrzymuje informacje, które pozwalają mu je samodzielnie wygenerować.

Co ma wspólnego bezpieczeństwo COM i DCOM z Microsoft® .NET Framework? CLR jest udostępniany jako obiekt COM. Dokładniej rzecz biorąc, CLR sam nie jest napisany jako COM, ale interfejs do CLR jest obiektem COM. Proces roboczy ładuje CLR tak, jakby to był obiekt COM.

Kiedy żądanie ASPX dociera do IIS, serwer WWW przypisuje mu token oparty na wyborze metody autentykacji – anonimowa, Windows, Basic, lub Digest. Token ten jest przekazywany do procesu roboczego, gdy otrzymuje on żądanie do obsłużenia. Żądanie jest podejmowany przez wątek wewnątrz procesu roboczego. Wątek ten dziedziczy token z wątku IIS, który pierwotnie podjął nadchodzące żądanie. W kontekście aspnet_wp.exe, rzeczywiste konto użytkownika, na którym zostanie obsłużone żądanie, zależy od tego, jak jest skonfigurowana impersonacja w tej konkretnej aplikacji ASP.NET. Jeżeli impersonacja jest wyłączona (ustawienie domyślne) wątek uruchamia się w kontekście procesu roboczego. W domyślnym przypadku, tym kontem jest ASPNET w modelu procesów ASP.NET i NETWORKSERVICE w modelu procesów IIS 6. Obydwa są słabymi kontami, które zapewniają limitowany zbiór możliwości i znakomicie odpierają ataki typu „revert-to-self”. (Atak typu „revert-to-self” polega na zamianie tokenu bezpieczeństwa impersonowanego klienta na token procesu rodzica. Poprzez przydzielenie procesowi roboczemu słabego konta taki atak nie udaje się.)

Na wyższym poziomie abstrakcji, proces roboczy ASP.NET realizuje jedno główne zadanie — obsługa żądań przekazywanych poprzez łańcuch zarządzanych obiektów, czyli potok HTTP. Potok HTTP jest aktywowany poprzez stworzenie nowej instancji klasy HttpRuntime i następnie wywołanie jej metody ProcessRequest. Jak wspominano wcześniej, w ASP.NET mamy pojedynczy proces roboczy (poza sytuacją, w której włączony jest tryb „web garden”) który obsługuje wszystkie aplikacje WWW w osobnych AppDomains. Każda AppDomain posiada swoją własną instancję klasy HttpRuntime—punkt wejściowy do potoku. Obiekt HttpRuntime inicjalizuje pewną liczbę wewnętrznych obiektów, które pomagają w obsłużeniu żądania. Pomocnicze obiekty zawierają menedżera pamięci podręcznej (obiekt Cache) oraz wewnętrzny monitor systemu plików, wykorzystywany do wykrywania zmian w plikach źródłowych, tworzących aplikację. HttpRuntime tworzy kontekst dla żądania i wypełnia go wszystkimi informacjami HTTP specyficznymi dla żądania. Kontekst ten jest reprezentowany poprzez instancję klasy HttpContext.

Inny obiekt pomocniczy, który jest tworzony na tak wczesnym etapie konfiguracji środowiska uruchomieniowego HTTP jest generator tekstu—który zawiera tekst odpowiedzi, przekazywany przeglądarce. Generator tekstu jest instancją klasy HttpWriter i jest obiektem, który buforuje każdy tekst, programowo wysyłany do przeglądarki przez kod strony. W momencie, kiedy środowisko uruchomieniowe HTTP zostanie zainicjalizowane, tworzony jest obiekt aplikacji. Obiekt aplikacji jest instancją klasy HttpApplication—klasy pliku global.asax. Plik global.asax jest opcjonalny na poziomie programistycznym, ale jest ściśle wymagane na poziomie infrastruktury. Dlatego, jeżeli żadna klasa nie została stworzona w aplikacji, musi zostać wykorzystany domyślny obiekt. Środowisko uruchomieniowe ASP.NET zawiera kilka pośredniczących fabryk klas, które powinny odnaleźć i zwrócić prawidłowy obiekt programu obsługi do obsłużenia żądania. Pierwsza klasa fabryki, która uczestniczy w grze, to HttpApplicationFactory. Jej głównym zadaniem jest wykorzystanie informacji z URL do odnalezienia powiązania pomiędzy wirtualnym katalogiem URL, a obiektem HttpApplication z puli.

Zachowanie klas fabryki aplikacji można zarysować następująco:

  1. Klasa fabryki utrzymuje pulę obiektów typu HttpApplication i wykorzystuje je do obsługi żądań skierowanych do aplikacji. Pula ma taki sam czas życia, jak czas życia aplikacji.

  2. Kiedy pojawia się pierwsze żądanie skierowane do aplikacji, klasa fabryki wydobywa informacje o typie aplikacji (klasa global.asax), ustanawia monitorowanie plików pod kątem zmian, tworzy stan aplikacji, i uruchamia zdarzenie Application_OnStart.

  3. Fabryka pobiera instancję HttpApplication z puli i ładuje do niej żądanie. Jeżeli nie ma dostępnych żadnych obiektów, tworzony jest nowy obiekt typu HttpApplication. Tworzenie obiektu HttpApplication pociąga za sobą kompilację pliku global.asax w aplikacji.

  4. HttpApplication rozpoczyna przetwarzanie żądania i nie jest dostępna dla nowych żądań dopóty, dopóki żądanie nie zostanie obsłużone. Jeżeli nadejdą nowe żądania dla tego samego zasobu, zostaną obsłużone przez inne obiekty z puli.

  5. Obiekt aplikacji daje wszystkim zarejestrowanym modułom możliwość przetworzenia żądania i ustala, jaki typ programu obsługującego może w najlepszy sposób żądanie obsłużyć. Robi to poprzez sprawdzenie rozszerzenia żądanego URL oraz pobranie informacji z pliku konfiguracyjnego.

Programy obsługujące HTTP są klasami, które implementują interfejs IHttpHandler. .NET Framework zapewnia kilka predefiniowanych programów obsługujących, dla popularnych typów zasobów, w tym stron ASPX i usług WWW. Sekcja <httpHandlers> w pliku machine.config określa nazwę klasy, którą obiekt HttpApplication musi instancjonować, aby obsłużyć żądanie dla konkretnego typu zasobu. Jeżeli klasa pomocnicza jest fabryką programów obsługi, metoda GetHandler w rzeczywistości będzie determinować typ programu obsługi, który należy wykorzystać. W momencie jej wywołania, program obsługi właściwego typu jest pobierany z puli obiektów i konfigurowany do obsługi żądania.

Interfejs IHttpHandler posiada kilka metod: IsReusable i ProcessRequest. Pierwsza z nich zwraca wartość typu Boolean która wskazuje, czy program obsługi może zostać umieszczony w puli. (Większość predefiniowanych programów obsługi jest umieszczanych w puli, ale można zdefiniować własne, które za każdym razem będą potrzebowały nowej instancji.) Metoda ProcessRequest zawiera całą logikę, która jest potrzebna do przetwarzania zasobu konkretnego typu. Przykładowo, program obsługi dla stron ASPX jest oparty na następującym pseudokodzie:

        private void ProcessRequest()
        {
        // ustalenie, czy żądanie przychodzi jako postback
        IsPostBack = DeterminePostBackMode();
        // uruchomienie zdarzenia Page_Init na kodzie źródłowym ASPX
        PageInit();
        // załadowanie viewstate, procesowanie przesłanych wartości
        if (IsPostBack)
        {
        LoadPageViewState();
        ProcessPostData();
        }
        // uruchomienie zdarzenia Page_Load na kodzie źródłowym ASPX
        PageLoad();
        // 1) przetwarzanie przesłanych wartości po raz drugi
        // (na wypadek dynamicznie kreowanych kontrolek)
        // 2) wywołanie zdarzeń po stronie serwera,
        // związanych ze zmianą właściwości,
        // na kontrolkach sterowanych danymi wejsciowymi
        // (np. zmiana stanu checkbox)
        // 3) Wykonanie kodu związanego ze zdarzeniem postback
        if (IsPostBack)
        {
        ProcessPostDataSecondTry();
        RaiseChangedEvents();
        RaisePostBackEvent();
        }
        // uruchomienie zdarzenia Page_PreRender na kodzie źródłowym ASPX
        PreRender();
        // Zapis bieżącego stanu kontrolek do viewstate
        SavePageViewState();
        // Renderowanie zawartości strony do HTML
        RenderControl(CreateHtmlTextWriter(Response.Output));
        }
      

Model działania, oparty na programach obsługi HTTP jest taki sam, niezależnie od typu zasobów, dla których jest wywoływany. Jedyny element, który się zmienia w zależności od typu zasobów, to program obsługi dla konkretnego zasobu. Obiekt HttpApplication jest odpowiedzialny za określenie, który program obsługi powinien zostać użyty do obsłużenia żądania. Obiekt HttpApplication jest również odpowiedzialny za wykrywanie zmian w dynamicznie tworzonych assembly, które reprezentują zasób. Jeżeli jakiekolwiek zmiany zostaną wykryte, obiekt aplikacji sprawdza, czy skompilowano i załadowano aktualne źródła dla żądanych zasobów.

 

Pliki tymczasowe i assembly strony

Na zakończenie przeglądu środowiska uruchomieniowego ASP.NET HTTP, przeanalizujmy, co się stanie na poziomie systemu plików, kiedy pojawia się żądanie strony ASP.NET. Jak za moment zostanie pokazane, pewien zestaw tymczasowych i tworzonych dynamicznie plików jest monitorowany przez obiekty należące do potoku HTTP.

Strony WWW są tworzone i instalowane jako pliki tekstowe z rozszerzeniem .aspx, chociaż można również oddzielnie tworzyć kod źródłowy strony w klasie napisanej w języku C# lub Microsoft® Visual Basic® .NET. Aby strona była widoczna jako URL, plik .aspx musi być dostępny w aplikacji. Bieżąca zawartość pliku .aspx determinuje assembly (jedno lub wiele), które zostaną załadowane przez obiekt aplikacji.

Obiekt HttpApplication szuka klasy nazywającej się tak, jak żądany plik ASPX. Jeżeli strona nazywa się sample.aspx, wtedy odpowiadającą jej klasą do załadowania jest ASP.sample_aspx. Obiekt aplikacji szuka takiej klasy w folderach assembly przeznaczonych dla aplikacji Web—Global Assembly Cache (GAC), podfolderze Bin, oraz w folderze „Pliki tymczasowe ASP.NET”. Jeżeli nie znaleziono odpowiedniej klasy, infrastruktura HTTP analizuje kod źródłowy pliku .aspx file, tworzy klasę C# lub Visual Basic .NET (w zależności od ustawień języka w stronie .aspx), i kompiluje ją w locie. Nowo utworzone assembly ma wygenerowaną losowo nazwę i jest umieszczane w specyficznym dla aplikacji folderze, leżącym na następującej ścieżce: „C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files”.

Podfolder v1.1.4322 jest specyficzny dla ASP.NET 1.1; jeżeli używamy ASP.NET 1.0, podfolder ten ma inną nazwę – w tym przypadku nazywa się v1.0.3705. W czasie następnej próby dostępu do strony, podfolder ten będzie zawierał assembly i nie będzie ono przegenerowywane. Pytanie, w jaki sposób obiekt HttpApplication stwierdza, czy specyficzne dla strony assembly istnieje? Czy naprawdę potrzebuje przeglądać za każdym razem pełną paletę folderów? Niekoniecznie.

Obiekt aplikacji przegląda jedynie zawartość konkretnego podfolderu, zlokalizowanego w folderze „Temporary ASP.NET Files”. Szczegółowa ścieżka do niego – zależna od aplikacji - jest zwracana przez właściwość HttpRuntime.CodegenDir. Jeżeli do pliku aspx odwołujemy się pierwszy raz (a więc żadne assembly strony nie zostało jeszcze stworzone), w folderze tym nie będzie pliku XML, którego nazwa zaczyna się od nazwy żądanej strony ASPX. Przykładowo, dla strony sample.aspx ze stowarzyszonym assemby powinien istnieć plik o takiej nazwie:

        sample.aspx.XXXXX.xml

W miejscu XXXXX pojawia się generowany ciąg znaków. Poprzez odczyt zawartości tego pliku XML, obiekt aplikacji rozpoznaje nazwę assembly do załadowania i klasy do pobrania z niego. Poniższy fragment kodu pokazuje typową zawartość takiego pomocniczego pliku. Nazwa assembly, które zawiera klasę ASP.sample_aspx to mvxvx8xr.

        <preserve assem="mvxvx8xr" type="ASP.sample_aspx">
          <filedep name="c:\inetpub\wwwroot\vdir\sample.aspx" />
        </preserve>
      

Nie trzeba dodawać, że plik ten jest tworzony jedynie wtedy, gdy kod źródłowy pliku filedep jest przeanalizowany w celu generacji dynamicznego assembly. Jakakolwiek zmiana w pliku filedep unieważnia assembly i spowoduje nowy proces kompilacji przy następnym żądaniu. Należy odnotować, ze jest to cecha implementacji, która może się zmienić w przyszłych wersjach ASP.NET framework. Należy być ostrożnym, jeżeli z jakiegoś powodu, decydujemy się na wykorzystanie tego w swoich aplikacjach.

Gdy w efekcie aktualizacji tworzone jest nowe assembly, ASP.NET weryfikuje, czy stare assembly może zostać usunięte. Jeżeli assembly zawiera tylko klasę dla zmodyfikowanej strony, ASP.NET usiłuje usunąć i zastąpić assembly; w przeciwnym wypadku nowe assembly jest tworzone bez dotykania starego assembly.

W czasie procesu usuwania assembly, ASP.NET może wykryć, że plik assembly jest załadowany i zablokowany. W takim przypadku, staremu assembly jest zmieniana nazwa poprzez dodanie rozszerzenia ".DELETE". (Zwróćmy uwagę, że dowolnemu plikowi Windows można zmienić nazwę, nawet jeżeli jest on w danym momencie wykorzystywany). Tymczasowe pliki .DELETE są usuwane po restarcie aplikacji, na przykład w wyniku zmian jednego z plików należących do aplikacji, takich jak web.config lub global.asax. W żadnym wypadku, środowisko uruchomieniowe ASP.NET nie usuwa tych plików w czasie obsługi kolejnego żądania.

Zauważmy że zgodnie z ustawieniami domyślnymi, każda aplikacja ASP.NET może zrekompilować 15 stron, zanim całą aplikacja zostanie zrestartowana, z następującą wtedy utratą danych sesyjnych i aplikacyjnych. Kiedy liczba kompilacji przekroczy próg ustawiony w atrybucie numRecompilesBeforeAppRestart w sekcji <httpRuntime>, AppDomain jest wyładowywana i aplikacja jest restartowana. Zauważmy również, że w .NET Framework, nie można wyładować pojedynczego assembly. Minimalnym blokiem kodu, który może zostać wyładowany z CLR, jest AppDomain.

 

Podsumowanie

Są dwa istotne aspekty w aplikacjach ASP.NET: model procesów i model obiektów strony. ASP.NET antycypuje niektóre z cech IIS 6.0—nowej i rewolucyjnej wersji usług informacyjnych WWW Microsoft która została dostarczona z Windows Server 2003. W szczególności, aplikacje ASP.NET są uruchamiane w osobnym procesie roboczym, dokładnie tak, jak wszystkie aplikacje w IIS 6. Ponadto środowisko uruchomieniowe ASP.NET automatycznie recyklinguje proces roboczy, aby zagwarantować jak najlepszą wydajność, pomimo zachodzących w trakcie pracy anomalii, pojawiających się wycieków pamięci i błędów programistycznych. Ta właściwość stała się właściwością całego systemu w IIS 6.0.

W tym artykule omówiłem domyślny model procesów ASP.NET oraz interakcję pomiędzy kodem na poziomie IIS (rozszerzenia ASP.NET ISAPI) oraz procesem roboczym. Próbowałem również wskazać te właściwości, które są nowe w modelu procesów zgodnym z IIS 6. Nie opisałem szczegółowo modelu obiektów strony, ale jest to dokładnie to, co mam zamiar opisać w przyszłym artykule.

Aby dowiedzieć się więcej na temat ASP.NET, środowiska uruchomieniowego HTTP oraz modelu obiektów strony, zapraszam do lektury mojej nowej książki Programming Microsoft ASP.NET, wydanej przez Microsoft Press, 2003.

O Autorze
Dino Esposito jest trenerem i konsultantem w Rzymie (Włochy). Jak członek zespołu Wintellect, specjalizuje się w ASP.NET i ADO.NET oraz spędza większość czasu ucząc i prowadząc konsultacje w Europie i Stanach Zjednoczonych. W szczególności, Dino zarządza ADO.NET oprogramowaniem szkoleniowym dla Wintellect oraz pisuje do kolumny "Cutting Edge" dla MSDN Magazine. Kontakt: dinoe@wintellect.com.

Artykuł w angielskiej wersji językowej znajduje się na stronie:
https://msdn2.microsoft.com/en-us/library/aa479328.aspx

* Artykuł ten nie był tłumaczony przez pracowników firmy Microsoft. Zarówno artykuł jak i dokładność jego tłumaczenia nie były sprawdzane przez firmę Microsoft. Firma Microsoft nie ponosi odpowiedzialności za poprawność informacji zawartych w artykule. Artykuł zamieszczony jest na zasadzie AS-IS.