Spis treści:

Kategoria:C#HTML


Komunikacja przeglądarki z aplikacją lokalną

Komunikacja przeglądarki z aplikacją lokalną - ikona Internetu

Ograniczone uprawnienia stron HTML

Przeglądarka ma swój mały świat. I całe szczęście. Obsługuje sobie znaczniki HTML, potrafi sobie poradzić, lepiej lub gorzej, z różnymi skryptami zapisanymi w języku JavaScript. Nie ma jednak wszystkich możliwości jakie daje nam aplikacja uruchomiona lokalnie. Przeglądarka nie może sama z siebie odczytywać plików, zmieniać ustawień systemu operacyjnego i wielu innych rzeczy. Gdyby mogła, Internet nie miałby prawa bytu. Wyobraźmy sobie, że wchodzimy na jakąś stronę, a ona zmienia nam jakieś pliki bez naszej wiedzy, instaluje jakieś aplikacje, zatrzymuje usługi. Nikt przy zdrowych zmysłach nie odważyłby się wejść na niezaufaną stronę. A to jest chyba główna siła napędowa Internetu - to, że każdy może coś do niego wrzucić, a inni, bez obaw o własny komputer, z tego korzystać. A gdybyśmy tak zechcieli rozszerzyć funkcje naszej strony internetowej o mechanizm wykraczający poza to, co oferuje nam przeglądarka i jej mechanizmy?

Komunikacja z lokalnym odpowiednikiem serwera

Zanim przejdę do pokazania jednego z możliwych rozwiązań postawionego w tytule problemu, krótko przypomnę zasadę działania Internetu z punktu widzenia autora pojedynczej strony. Wiemy, że strona internetowa, czyli jakiś plik HTML, być może pliki CSS i JavaScript, pobierane są z jakiegoś zewnętrznego serwera. Przeglądarka wysyła żądanie: daj mi dane (zawartość strony) znajdującej się pod adresem www.dev.cdur.pl. Serwer po otrzymaniu takiego żądania przygotowuje odpowiedź i odsyła do przeglądarki. Jeżeli przeglądarka wykryje, że na stronie znajduje się odwołanie do innego pliku, powiedzmy /Style.css, ponownie wysyła żądanie: daj mi dane znajdujące się pod adresem www.dev.cdur.pl/Style.css. Bezpieczeństwo minisystemu przeglądarki polega na wyraźnie widocznej izolacji. Przeglądarka wysyła dane i otrzymuje dane. Nie ma bezpośredniej kontroli nad serwerem ani nad komputerem, na którym działa. Ma tylko ograniczone API. Serwer zwraca tylko to, co chce na podstawie teoretycznie bezpiecznego tekstu. Tekst ten jest w praktyce bardziej złożony (protokół HTTP), pozwala między innymi przesyłać pliki, ale nie to jest najważniejsze. Najważniejszy jest podział na dwie niezależne warstwy komunikujące się w ustalony odgórnie sposób.

To, co zamierzam pokazać, to odpowiednik zdalnego serwera. Z tym, że uruchomiony lokalnie i baz całej infrastruktury zwykle stosowanej w przypadku serwerów ogólnodostępnych. Serwer lokalny, a jakże, ma pełny dostęp do lokalnych zasobów. Jego zadaniem będzie odpowiadanie na żądania przeglądarki.

Pobieranie informacji o plikach na pulpicie

Pobranie informacji o plikach na pulpicie nie jest normalnie osiągalne z poziomu przeglądarki i języka JavaScript. Jest osiągalne pośrednio, poprzez lokalnie uruchomioną aplikację pełniącą rolę serwera. Zadaniem tego serwra jest przechwycenie żądania i wygenerowanie odpowiedniej odpowiedzi. W pokazanym przypadku będzie to cała strona, ale nic nie stoi na przeszkodzie, żeby tą odpowiedzią był JSON lub część strony.

Przejdźmy do konkretów.

Klasa HttpListener i jej możliwości

Istnieje przynajmniej kilka sposobów przechwycenia przychodzących żądań. Ja zajmę się najwygodniejszym w obsłudze, dostępnym w systemach od Windows XP SP 2 i Windows Server 2003 wzwyż. Lokalny serwer będzie nasłuchiwał pod adresem http://127.0.0.1:8080/Desktop/ w oddzielnym wątku. Pozwala to zwolnić wątek odpowiedzialny za interfejs użytkownika oraz w miarę prosto przerobić pokazaną aplikację na okienkową. Ja, żeby nie komplikować przykładu, pozwoliłem sobie na rozwiązanie w postaci aplikacji konsolowej, której schemat pokazany jest poniżej:

static void Main()
{
    const string url = "http://127.0.0.1:8080/Desktop/";
    Debug.Assert(HttpListener.IsSupported, "HttpListener is not supported");

    Task.Run(() => ListenerThread(url));
    Console.ReadLine();
}

Po sprawdzeniu czy klasa HttpListener może być wykorzystana następuje uruchomienie wątku. Wątek będzie działał w kontekście procesu aplikacji konsolowej, który zakończy się po naciśnięciu klawisza Enter. Popatrzmy teraz na kod wątku i przeanalizujmy go:

static void ListenerThread(string url)
{
    var listener = new HttpListener();
    listener.Prefixes.Add(url);
    listener.Start();

    Console.WriteLine("Listening from thread {0}", Thread.CurrentThread.ManagedThreadId);
    while (true)
    {
        HttpListenerContext context = listener.GetContext();
        HttpListenerResponse response = context.Response;

        using (response.OutputStream)
        using (var stream = new StreamWriter(response.OutputStream, Encoding.UTF8))
        {
            response.ContentType = "text/html; charset=UTF-8";
            stream.Write(PrepareHtmlResponse());
        }
    }
}

Celowo powycinałem z kodu wszystkie opcjonalne dodatki. Pozostały tylko te, które są kluczowe. Po pierwsze, tworzymy obiekt HttpListener i ustalamy, pod jakim adresem będziemy nasłuchiwać dodając adres do właściwości Prefixes. Właściwość Prefixes może przyjąć kilka adresów, bo jest zwykła kolekcją. Gdyby istniała potrzeba nasłuchiwania żądań pod kilkoma adresami, można ten fragment zmodyfikować. Po uruchomieniu nasłuchu (listener.Start()) odbieramy kolejne żądania. Warto w tym miejscu wspomnieć, że kluczowa jest tu instrukcja listener.GetContext(). Blokuje ona wątek aż do momentu, w którym pojawi się jakieś żądanie. Po takim zdarzeniu należy wpisać do strumienia wyjściowego odpowiedzi to, co chcemy zwrócić. W pokazanym przypadku jest to pełna strona HTML z kodowaniem UTF-8. Strumień wyjściowy nie różni się niczym od innych strumieni wykorzystywanych między innymi do obsługi plików. Dane wstawiamy tak, jakbyśmy przy pomocy strumieni chcieli utworzyć plik tekstowy z zawartością. Z tym, że zamiast do strumienia reprezentującego plik korzystamy ze strumienia odpowiedzi. Sama odpowiedz została wydzielona do metody PrepareHtmlResponse.

Przygotowanie strony HTML

Sklejanie strony HTML w kodzie nie jest ani eleganckie ani czytelne. Postanowiłem stworzyć sobie szablon, którego część będzie podmieniona. Wygląda on mniej więcej tak:

<html>
<head>
    <title>C# Local Server</title>
    <style>
    ...
    </style>
</head>
<body>
    $Content$
</body>
</html>

Sekcja stylów została celowo pominięta, żeby nie zaśmiecać ogólnej struktury. Znacznik $Content$ oznacza treść. Tam wylądują kafelki reprezentujące pliki odczytane z pulpitu. Kod tworzący pełny dokument HTML pokazany jest poniżej:

private static string PrepareHtmlResponse()
{
    var sb = new StringBuilder();
    foreach (var path in new[] {Environment.SpecialFolder.Desktop, Environment.SpecialFolder.CommonDesktopDirectory})
    {
        string desktopPath = Environment.GetFolderPath(path);
        var dir = new DirectoryInfo(desktopPath);

        foreach (var file in dir.GetFiles())
        {
            sb.AppendFormat("<div><i></i><span>{0}</span></div>", file.Name);
        }
    }
    return File.ReadAllText("ResponsePage.html").Replace("$Content$", sb.ToString());
}

Przy okazji zwrócę uwagę na dość wyjątkowy katalog jakim jest pulpit. System Windows traktuje go nieco inaczej niż inne katalogi, bo pliki, które się tam znajdują pochodzą z dwóch źródeł. Istnieje coś takiego jak pulpit dla wszystkich użytkowników i pulpit dla aktualnie zalogowanego użytkownika. Pliki muszą być scalone z tych dwóch źródeł i dla każdego z nich musi być wygenerowany jakiś zbiór znaczników HTML pozwalający jakimś stylem nadać im odpowiedni wygląd. Strona po wyświetleniu w przeglądarce mogłaby wyglądać tak jak poniżej:

desktop.ini
Fields.docx
JavaScript-Stars.zip
Procmon.exe
Sitemap.xml
Stos.txt
Adobe Reader XI.lnk
desktop.ini
Google Chrome.lnk
Intel(R) HD Graphics Control Panel.lnk
Mozilla Firefox.lnk
Mozilla Thunderbird.lnk
Oracle VM VirtualBox.lnk
Skype.lnk
Total Commander 64 bit.lnk

Nasz lokalny serwer potrafi naprawdę dużo. Dużo, jeżeli wziąć pod uwagę rozmiary kodu źródłowego.

Możliwości rozwoju

Projekt ma na celu pokazać możliwość rozszerzenia funkcji przeglądarki o wszystko to, na co pozwalają aplikacje uruchamiane lokalnie. Aplikacja pełniąca funkcję lokalnego serwera może być uruchomiona jako usługa systemu Windows i być uzupełnieniem większego systemu. Można przecież transmitować na żywo lokalny pulpit wysyłając okresowo obrazki JPEGSposób ten nazywany jest Motion JPEG lub MJPEG. Polega na przesyłaniu każdej klatki animacji w postaci skompresowanego pliku graficznego JPEG., można również przekazać czasochłonne operacje do realizacji przez specjalizowaną aplikację, pisaną w bardziej wydajnym języku niż JavaScript, otwiera się również pole dla ciekawych aplikacji rozproszonych. Co ważne - wszystko w przeglądarce.

Kategoria:C#HTML

, 2015-07-22

Brak komentarzy - bądź pierwszy

Dodaj komentarz
Wyślij
Ostatnie komentarze
bardo ciekawe , można dzięki takim wpisom dostrzec wędkę..
Bardzo dziękuję za jasne tłumaczenie z dobrze dobranym przykładem!
Dzieki za te informacje. były kluczowe
Dobrze wyjaśnione, dzięki !