Vaje: podatkovni tipi
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.