Triki z PowerShellem #11 – ĆwierkamyW ramach wyposażania nowozainstalowanego systemu w niezbędne programy, przypomniałem sobie o istnieniu PowerShella. Kiedy jednak chciałem go ściągnąć, spotkała mnie przyjemna niespodzianka: PSh w Windows 7 jest już od razu zainstalowany, więc można go od razu zacząć go używać. Jak sądzę, przyczyni do zwiększenia jego popularności, co jest z pewnością dobrą rzeczą.
Fakt sprawił rzecz jasna, że zaraz zachciało mi się wypróbować go w jakimś nowym zastosowaniu. Padło na wysyłanie update'ów do Twittera, w którym to zresztą niedawno się zarejestrowałem (i wciąż nie wiem, dlaczego ;)). Sprawa na oko nie jest trudna, bo sprowadza się do wykonania jednego żądania HTTP POST. Ale jak wiadomo, diabeł zwykle tkwi w szczegółach. Oto skrypt:
[Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
# Stałe
$LOGIN = "login" # lub e-mail
$PASS = "hasło"
# Pobranie statusu od użytkownika
$tweet = Read-Host -Prompt "Status"
# Złożenie żądania HTTP POST
$uri = [Uri]"http://twitter.com/statuses/update.xml"
$http = [Net.HttpWebRequest]::Create($uri)
$http.Credentials = New-Object Net.NetworkCredential @($LOGIN, $PASS)
$http.Method = [Net.WebRequestMethods+Http]::Post
$http.ServicePoint.Expect100Continue = $false # (*)
# Wysyłanie danych
$data = "status=" + [Web.HttpUtility]::UrlEncode($tweet)
$http.ContentLength = $data.Length
$sw = New-Object IO.StreamWriter @($http.GetRequestStream())
$sw.Write($data)
$sw.Close()
# Wyświetlamy ID nowego statusu
$resp = $http.GetResponse().GetResponseStream()
$sr = New-Object IO.StreamReader @($resp)
$xml = [xml]$sr.ReadToEnd()
"Status updated (ID: " + $xml.status.id + ")" | Out-Host
$sr.Close()
# Obsługa błędów
trap { "Error: " + $_.Exception.Message; return }
Jednym z owych detali było kodowanie statusu algorytmem dla URL-i (zamieniającym spacje na %20 itd.), wykonywane poprzez System.Web.HttpUtility.UrlEncode - stąd konieczność importowania assembly System.Web. Ale to jest w sumie pikuś.
Znacznie większym "trikiem" jest linijka oznaczona gwiazdką (*). Powoduje ona obejście domyślnego zachowania .NET, który do każdego żądania HTTP typu POST dodaje nagłówek:
Powoduje on wysłanie tak naprawdę dwóch requestów: w pierwszym serwer ma tylko sprawdzić poprawność nagłówków (logowania, na przykład) i zwrócić status 100 (Continue). Dopiero w drugim klient wysyła właściwe dane. Mechanizm ten jest w .NET opakowany przezroczyście i ma zapobiegać niepotrzebnemu przesyłaniu dużych ilości danych w żądaniu, które i tak byłoby odrzucone.
API Twittera jednak tego nie obsługuje i jest to właściwe. Trudno przecież nazwać status, mający maks. 160 znaków, "dużą ilością danych". Lepiej więc przesyłać go od razu, a domyślne zachowanie .NET-a obejść. To właśnie robi zaznaczony wiersz.
Przypomnę jeszcze tylko - gdy ktoś zechciał powyższego skryptu używać do przesyłania tweetów - że uruchomienie skryptu PSh z poziomu zwykłej linii poleceń wymaga parametru -Command i kropki:
Do takiej komendy można np. utworzyć skrót i przypisać mu kombinację klawiszy w celu szybkiego uruchamiania.
Przekierowanie z POST-emOprócz ciasteczek (cookies) dwoma podstawowymi sposobami przekazywania parametrów na wejście serwera HTTP są metody znane jako GET i POST. Ta pierwsza wykorzystuje do tego sam adres URL, umieszczając dodatkowe dane po znaku zapytania, np.: /forum/showthread.php?id=12345&filter=none. W przypadku tej drugiej parametry są przekazywane poprzez treść samego żądania HTTP; z punktu widzenia użytkownika są więc one niewidoczne.
Metoda GET zwykle służy do wymiany danych między stronami połączonymi za pomocą zwykłych linków. POST z kolei wykorzystuje się do wysyłania informacji wprowadzanych przez użytkownika w różnego rodzaju formularzach. Przekierowanie następuje wówczas po jego wysłaniu, co zwykle czyni się odpowiednim przyciskiem (submit).
Bywa jednak tak, że chcemy dokonać takiego przekierowania - z ustalonymi parametrami - po zwykłym kliknięciu na link, z pominięciem wypełniania formularza przez użytkownika. Dobrym przykładem jest sytuacja, gdy nasza strona korzysta w jakiś sposób z innego serwisu, do którego przejście wymaga logowania. Jeśli chcielibyśmy, by odbywało się ono automatycznie - po kliknięciu jakiegoś linku - to musimy wysłać odpowiednie żądanie HTTP z parametrami przesłanymi metodą POST. Do tego nie wystarczy niestety sam znacznik <a>.
Rozwiązaniem jest wtedy użycie dodatkowej strony przekierowującej, na której umieścimy już odpowiednio "wypełniony" formularz:
Oczywiście nazwy i wartości parametrów (zapewne generowane dynamicznie po stronie serwera) zależą ściśle od tego, dokąd chcemy nasz wyrób formularzopodobny wysłać. Wszystkie je deklarujemy jednak jako <input type="hidden" />, bo w założeniu użytkownik nie powinien ich (łatwo) zobaczyć. Ponadto, jeśli - tak jak wyżej - mówimy o loginie/haśle czy innych danych, które powinno się chronić przed niepowołanym dostępem, to powinniśmy jeszcze wysyłać razem z naszą stroną nagłówki HTTP zabraniające cache'owania:
W końcu, skoro mamy już gotowy "formularz", to trzeba jeszcze zadbać o to, by wysłał się on sam natychmiast po załadowaniu strony przekierowującej. Do tego już trzeba wykorzystać skrypt uruchamiany w przeglądarce:
Tak przygotowaną stronę możemy podlinkować pod nasz serwis. Jak widać trochę z tym zabawy, ale tak to jest, gdy chcemy zrobić coś niestandardowego :)
Jak ASP.NET naprawia HTTPProtokół HTTP i World Wide Web powstały już bardzo dawno temu, chociaż od tamtego czasu doczekały się całego mnóstwa usprawnień. Pewnie największym była możliwość generowania stron HTML dynamicznie przy użyciu skryptów CGI, PHP, i tak dalej. Dzięki temu strony umieją już całkiem sporo i nie są statycznymi dokumentami. Ich tworzenie zaczęło też bardziej przypominać pisanie normalnych aplikacji.
Pewne ograniczenia HTTP ciężko jest jednak przezwyciężyć. Największym jest chyba to, że serwery każde zgłoszenie (request), które do nich napływa, traktują zupełnie odrębnie i w oderwaniu od wszystkich innych. Działają więc na zasadzie pytanie-odpowiedź. Gdyby poszukać odpowiednika tego modelu w zwyczajnych programach, to byłyby nim narzędzia konsolowe obsługiwane w całości przełącznikami wiersza poleceń. W dobie interfejsów graficznych mieniących się wszystkimi kolorami palety RGB nie wyglądają one zbyt imponująco...
Dlatego też wszelkie platformy służące uruchamianiu aplikacji webowych starają się tę niedogodność jakoś obejść i umożliwić powiązanie poszczególnych żądań oraz przechowywanie danych pomiędzy nimi. Standardem jest mechanizm sesji, ale np. w PHP nigdy nie udało mi się do końca go zrozumieć :)
Ostatnio jednak z konieczności (nietrudno się domyślić, jakiej :)) zajmuję się ASP.NET i muszę przyznać, że tam cała sprawa została potraktowana nieporównywalnie lepiej. Przede wszystkim serwis jest tam rzeczywiście traktowany jako aplikacja, której instancja uruchamia się w momencie pierwszego requestu z danego hosta. Poszczególne strony przypominają też raczej okienka zwykłych programów, na których zamieszcza się kontrolki specyficzne dla ASP (przerabiane na wyjściu na HTML). I to ze wszystkimi konsekwencjami: one rzeczywiście istnieją na serwerze. To sprawia, że możemy np. pobrać dane formularza wypełnionego przed chwilą na poprzedniej stronie, po prostu czytając zawartość jej kontrolek.
Cała ta trwałość nie jest oczywiście aż tak daleko posunięta jak w tradycyjnych programach. Zdarza się (i to dość często), że musimy zwracać uwagę na to, kiedy strona może być przeładowywana. Ale i tak stanowi to spory postęp na przykład w stosunku do wspomnianego mechanizmu sesji (który naturalnie też jest dostępny).
Naturalnie wszystko to są jednak tylko obejścia do starego protokołu, który pierwotnie nie został zaprojektowany do tak złożonych zadań, do których się go obecnie wykorzystuje.