Opisovanje protokolov in standardov s podatkovnimi tipi

Na teh vajah bomo v jeziku OCaml definirali podatkovni tip za opis odgovora strežnika HTTP, ki je določen v RFC 7231.

Iz ukazne vrstice poženemo interpreter z ocaml. Pri tem lahko podamo datoteko, ki se bo naložila ob zagonu, npr.

ocaml -init http.ml

Datoteko naložimo tudi kasneje z direktivo (prvi # je ukazni poziv, drugega pa napišemo kot del ukaza #use):

# #use "http.ml";;

Za izhod iz interpreterja pritisnemo ctrl+D (Linux) ali ctrl+Z in enter (Windows). Za lažje interaktivno delo lahko uporabimo rlwrap, ki poljubnemu interaktivnemu programu doda možnost urejanja ukazne vrstice:

rlwrap ocaml

Odgovor HTTP

Najprej si oglejmo, kako izgleda glava odgovora strežnika HTTP. V ukazni vrstici poženemo:

$ curl -I https://fri.uni-lj.si/sl

HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Fri, 23 Mar 2018 17:43:15 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Vary: Accept-Encoding
Expires: Sun, 19 Nov 1978 05:00:00 GMT
Cache-Control: no-cache, must-revalidate
X-Content-Type-Options: nosniff
X-Powered-By: HHVM/3.18.1
Content-Language: sl
X-Frame-Options: SAMEORIGIN

Vidimo, da je glava sestavljena iz dveh delov: prva vrstica poda različico protokola in status odgovora, preostale vrstice pa določajo vrednosti posameznih polj.

Podatkovni tip status

Prva vrstica v odgovoru HTTP nam pove različico protokola in status poizvedbe, npr.

HTTP/1.1 200 OK

Mi jo bomo predstavili s podatkovnim tipom status, ki je zapis z dvema poljema:

type status = { version : string; code : int } ;;

Novo vrednost tega tipa naredimo z:

let mystatus = { version = "HTTP/1.1"; code = 200 } ;;

Določeno polje iz zapisa dobimo tako:

mystatus.version ;;
- : string = "HTTP/1.1"

Definirajmo še funkcijo string_of_status, ki za dani status vrne ustrezen niz. V OCamlu združujemo nize z operatorjem ^:

let string_of_status s =
  s.version ^ " " ^
  string_of_int s.code ^ " " ^
  (match s.code with
   | 200 -> "OK"
   | 301 -> "Moved Permanently"
   | _ -> "")

Primer uporabe:

# string_of_status mystatus ;;
- : string = "HTTP/1.1 200 OK"

Naloga: statusna sporočila

Funkcijo string_of_status razširite z vsaj eno statusno kodo iz vsakega razreda.

Podatkovni tip response

Odgovor HTTP je sestavljen iz statusa, glave, ki vsebuje poljubno število polj, in telesa. Telo je lahko niz HTML ali kaj drugega; tu ga bomo obravnavali kot niz. Tip za opis polj (zaenkrat le Server in ContentLength) bomo predstavili z vsoto

type field =
    | Server of string
    | ContentLength of int

Tip response je zapis z glavo, seznamom polj in telesom:

type response = {status: status; headers: field list; body: string}

Zdaj lahko ustvarimo preprosto sporočilo:

let r = {
    status={version="HTTP/1.1"; code=200};
    headers=[Server "nginx/1.6.2"; ContentLength 13];
    body="hello world!\n"
}

Glavo smo predstavili s seznamom elementov tipa field. Nekaj primerov uporabe seznamov v OCamlu:

(* zapišemo seznam tipa "int list" *)
# let stevila = [1; 2; 3; 4] ;;
val stevila : int list = [1; 2; 3; 4]

(* dostopamo do glave in repa seznama *)
# List.hd stevila ;;
- : int = 1
# List.tl stevila ;;
- : int list = [2; 3; 4]

(* na vsakem elementu pokličemo funkcijo string_of_int *)
# let nizi = List.map string_of_int stevila ;;
val nizi : string list = ["1"; "2"; "3"; "4"]

(* kvadriramo vsak element seznama s pomočjo anonimne (λ) funkcije *)
# let nizi = List.map (fun x -> x*x) stevila ;;
val nizi : int list = [1; 4; 9; 16]

Naloga: izpis polja

Definirajte funkcijo string_of_field : field -> string, ki za dano polje vrne ustrezen niz. Primer uporabe:

# string_of_field (Server "nginx/1.6.2") ;;
- : string = "Server: nginx/1.6.2"

# string_of_field (ContentLength 12) ;;
- : string = "Content-Length: 12"

Naloga: izpis odgovora

Definirajte funkcijo string_of_response : response -> string, ki za dan odgovor oblikuje in vrne ustrezen niz.

# print_string (string_of_response r) ;;
HTTP/1.1 200 OK
Server: nginx/1.6.2
Content-Length: 13

hello world!

Pomagate si lahko s funkcijama String.concat in List.map.

Naloga: dodatna polja

Tip field razširite z naslednjimi možnostmi, pri čemer za vrednost polja zaenkrat uporabite kar niz:

  • Content-Type
  • Transfer-Encoding
  • Date
  • Expires
  • Last-Modified
  • Location

Odpravite opozorila, ki jih pri tem začne vračati interpreter.

Naloga: Transfer-Encoding

Popravite tip za polje Transfer-Encoding tako, da vrednost namesto niza predstavite z izbiro med kodiranji chunked, compress, deflate, gzip, identity.

Naloga: datumi

Datumi so v glavi HTTP predstavljeni v taki obliki:

Wed, 21 Mar 2018 07:28:56 GMT

Definirajte podatkovni tip date in funkcijo string_of_date ter ju uporabite za polja Date, Expires in Last-Modified.

Naloga: naslovi URI

Definirajte podatkovni tip uri za povezave in funkcijo string_of_uri ter popravite polje Location tako, da namesto niza uporablja ta tip. Sintaksa naslovov URI je predpisana v RFC 3986. Tip uri naj čim bolj natančno opiše te komponente: nekateri deli so opcijski, ime gostitelja je lahko domena ali naslov (IPv4 ali IPv6) itd.

Zadnja sprememba: sreda, 16. marec 2022, 14.46