Najpopularniejszym frameworkiem webowym dla aplikacji .NET jest ASP.NET MVC. Pracując w C# sprawdza się on się świetnie, ale bazuje na obiektowym paradygmacie programowania, co nie do końca współgra z funkcyjnym podejściem F#.
Na szczęście jest Suave, który pozwala na proste i funkcyjne pisanie aplikacji w F#.
Wprowadzenie
Pierwszym naszym krokiem będzie pobranie paczki nuget Suave
. Następnie, musimy otworzyć moduł w naszym kodzie:
open Suave
I gdy chcemy uruchomić nasz serwer to użyjemy funkcji:
startWebServer defaultConfig app
Gdzie defaultConfig
jest domyślną konfiguracją HTTP o zasięgu 127.0.0.1
i porcie 8080. A app jest dowolną aplikacją typu WebPart
.
Czym jest WebPart
?
type WebPart = HttpContext -> Async<HttpContext option>
Jest to funkcja, która przyjmuje HttpContext
i zwraca asynchroniczny workflow, który zwraca obiekt typu HttpContext option
, czyli albo Some x
, gdzie x to HttpContext
, albo None
.
Asynchroniczny workflow to coś á la metoda async Task
w C#, która może działać synchronicznie i asynchronicznie.
Zmiana konfiguracji
Zanim zobaczymy jak się taką aplikację definiuje, to zobaczmy jak zmienić konfigurację. Ponieważ jest ona rekordem to wystarczy użyć tej domyślnej jako wzorca i zmienić to co chcemy. Np:
let config =
{ defaultConfig with
bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" 80 ]
}
Gdzie ustalamy, że nadal będziemy używać HTTP, ale nasłuchujemy na 0.0.0.0
na porcie 80.
Wszystkie możliwe opcje znajdziecie tu: Server configuration.
Więc teraz będziemy uruchamiali nasz serwer przez
startWebServer config app
Tworzenie aplikacji
Więc potrzebujemy coś typu WebPart
co będzie określało naszą aplikację. Suave został tak zaprojektowany, że aplikacja może być jedną czynnością, zbiorem czynności, lub zbiorem zbiorów, itd.
Najprostsza czynność to stała czynność, np:
let app = OK "Hello World!"
Funkcja OK
przyjmuje tekst i zwraca WebPart
, który oznacza, że po wejściu na serwer dostaniemy odpowiedź o numerze HTTP 200 OK oraz o zawartości równiej podanemu tekstowi.
Aby mieć dostęp do OK
musimy otworzyć moduł Successful
.
Powiedzmy, że mamy jakiś obiekt, którego stan będzie się zmieniał. Niech będzie to obecny czas serwera. Jeśli zrobilibyśmy tak jak poprzednio
let app = OK (sprintf "Obecnie jest %A" System.DateTime.Now)
to w momencie przejścia aplikacji przez tą linijkę zostałaby wczytana data i zapisana do wartości zwróconej przez OK
jako stała. Natomiast my chcielibyśmy wykonywać to polecenie za każdym requestem.
let app = request (fun httpRequest -> OK sprintf "Obecnie jest %A" System.DateTime.Now)
Funkcja request przyjmuje funkcję HttpRequest -> WebPart
i ta funkcja jest wykonywana za każdym razem kiedy wyślemy zapytanie do serwera.
Wybieranie zachowania
Do tego momentu jakikolwiek url byśmy do naszego serwera nie dopisali, to otrzymamy zawsze to samo. Aby wybrać inną akcję dla danej ścieżki, skorzystamy z funkcji choose
.
let app =
choose [
path "/" >=> OK "To jest strona główna"
path "/blog" >=> OK "To jest strona blog."
path "/date" >=> request (fun httpRequest -> OK sprintf "Obecnie jest %A" System.DateTime.Now)
NOT_FOUND "Nie znaleziono strony."
]
Aby powyższy kawałek kodu się skompilował musi otworzyć więcej modułów
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
open Suave.RequestErrors
Jak możecie się domyślać operator >=>
jest zdefiniowany w module Operators
, a NOT_FOUND
w module RequestErrors
. Natomiast funkcja path
jest w module Filters
.
Więc jak widzimy kiedy przychodzi zapytanie do serwera, odpytujemy nasz app
i widzimy że jest to wybór. Idziemy po kolei i sprawdzamy czy pasujemy do danego filtra. Jeśli tak to wykonujemy WebPart, na który on wskazuje. Warto na koniec dać odpowiedź NOT_FOUND
aby wiadomo było, że ktoś wchodzi w zły url.
W module Filters
są również takie funkcje jak:
pathStarts
, która przyjmuje string i działa tak jak path, ale sprawdza tylko początkową część.pathRegex
, która dopasowywuje ścieżkę na podstawie podanego wyrażenia regularnego.hasFlag
, która przyjmuje string oznaczający flagę w url (po?
).GET
,POST
,PUT
,DELETE
,HEAD
,CONNECT
,PATCH
,TRACE
,OPTIONS
- czyli sprawdzające czasownik HTTP.log*
, zestaw funkcji logujących zapytaniepathScan
, która przyjmuje string w formacie Printf oraz funkcję z argumentów w stringu wWebPart
timeoutWebPart
, która przyjmujeTimeSpan
iWebPart
i określa timeout na wykonanie akcji wWebPart
, po którym zwraca HTTP 408 z wiadomością"Request Timeout"
.
Podsumowanie
Jak widać jest to dość proste i dość niskie API, które bardzo ładnie wpasowywuje się w funkcyjną naturę F#. Naprawdę łatwo można w nim pisać aplikacje dostarczające dane (mamy moduł Json
), ale do tworzenia aplikacji zwracających HTML potrzeba nam jeszcze silnika w stylu Razora/DotLiquid.
Polecam także przeczytać ten artykuł, który pokazuje jeszcze kilka funkcji Suave’a. Oraz projekt F# Snippets - Web site gdzie możecie zobaczyć praktyczne użycie Suave’a do stworzenia strony internetowej.