]> rtime.felk.cvut.cz Git - edu/osp-wiki.git/blob - cviceni/server.mdwn
lectures: minor update of link to LinuxDays 2023 presentation listing.
[edu/osp-wiki.git] / cviceni / server.mdwn
1 [[!meta title="Obsluha mnoha klientů"]]
2
3 [[!toc]]
4
5 Cíl
6 ===
7
8 Hlavním cílem této úlohy je implementovat jednoduchou serverovou
9 aplikaci a optimalizovat její výkon tak, aby zvládala obsluhu velkého
10 množství klientů a požadavků. **Pro implementaci serveru si můžete
11 zvolit jakoukoli platformu** (HW, OS, programovací jazyk, framework,
12 …).
13
14 Vedlejší cíle jsou naučit se pracovat s dokumentací a zdrojovými kódy
15 použitých platforem (zejména těch založených na open source softwaru),
16 aplikace znalostí nabytých na přednáškách a získání přehledu o
17 výkonnosti jednotlivých platforem.
18
19 Zadání úkolu
20 ============
21
22 Vytvořte serverovou aplikaci komunikující protokolem [HTTP][], která
23 bude sloužit k počítání unikátních slov v datech zaslaných klienty. Na
24 server jsou kladeny následující požadavky:
25
26 - Klienti posílají data pomocí [metody POST][POST] s cestou
27   `/osp/myserver/data`. Data jsou ve formátu čistého textu
28   (text/plain) v kódování UTF-8 komprimovaného metodou [gzip][gzip].
29 - Server obsahuje "čítač jedinečných slov". Při spuštění serveru je
30   tento čítač roven 0.
31 - Server si vede evidenci o slovech zaslaných v jednotlivých
32   požadavcích a pro každé nové slovo, které ještě nemá v evidenci
33   zvýší čítač jedinečných slov o jedna.
34 - Na požadavek [GET][] s cestou `/osp/myserver/count` server odpoví
35   aktuální hodnotou čítače jedinečných slov a tento čítač vynuluje.
36   Hodnota se přenáší jako dekadické číslo v UTF-8 kódování.
37 - Server musí zvládnou obsluhovat velké množství (cca 100) současně
38   připojených klientů.
39
40 [HTTP]: http://tools.ietf.org/html/rfc2616
41 [POST]: http://tools.ietf.org/html/rfc2616#section-9.5
42 [GET]: http://tools.ietf.org/html/rfc2616#section-9.3
43 [gzip]: https://tools.ietf.org/html/rfc1952
44
45 Příklad
46 -------
47
48 Předpokládejme, že náš server běží na lokálním počítači (t.j.
49 localhost) na portu 8080. Použijeme program `curl` k posílání
50 požadavků serveru:
51
52     curl localhost:8080/osp/myserver/data --data-binary @<(echo první pokus|gzip)
53     curl localhost:8080/osp/myserver/data --data-binary @<(echo druhý pokus|gzip)
54     curl localhost:8080/osp/myserver/count --output -
55
56 Výstupem posledního příkazu bude řetězec "3". Místo posledního příkazu
57 můžete použít i webový prohlížeč nasměrovaný na adresu
58 <http://localhost:8000/osp/myserver/count>.
59
60 *Poznámka:* Syntaxe `<(příkaz...)` je tzv. *process substitution* v
61 interpreteru `bash`. Jedná se o jeden z možných způsobů předání
62 binárních dat na příkazové řádce.
63
64 Výkon serveru
65 -------------
66
67 Výkon serveru bude měřen testovacím programem, který bude serveru
68 posílat větší množství dat a poté se zeptá na aktuální hodnotu čítače
69 jedinečných slov. Bude se měřit celkový čas od odeslání prvního
70 požadavku typu POST do příjmu hodnoty čítače jedinečných slov.
71
72 Akceptována budou řešení jejichž **celkový čas bude dvakrát menší**
73 než čas naměřený s níže uvedeným ukázkovým serverem bežícím na školním
74 počítači v laboratoři.
75
76 Ukázkový server
77 ===============
78
79 Zde uvádíme jednoduchý server napsaný v jazyce Python, který splňuje
80 výše uvedené požadavky (kromě výkonnosti). Můžete ho použít jako
81 referenci či startovací bod pro vaší implementaci.
82
83 [[!format py """
84 #!/usr/bin/python3
85
86 from http.server import HTTPServer, BaseHTTPRequestHandler
87 import gzip
88
89 words = {}
90
91 class OSPHTTPHandler(BaseHTTPRequestHandler):
92     def do_POST(self):
93         global words
94         if self.path == "/osp/myserver/data":
95             length = int(self.headers.get('Content-Length'))
96             text = gzip.decompress(self.rfile.read(length)).decode("utf-8")
97             for word in text.split():
98                 words[word] = 1;
99             self.send_response(204) # No Content
100             self.end_headers()
101         else:
102             self.send_response(404)
103             self.end_headers()
104
105     def do_GET(self):
106         global words
107         if self.path == "/osp/myserver/count":
108             self.send_response(200)
109             self.send_header("Content-type", "text/plain")
110             self.end_headers()
111             self.wfile.write(str(len(words)).encode())
112             words = {}
113         else:
114             self.send_response(404)
115             self.end_headers()
116
117 class HTTPServerIPv6(HTTPServer):
118     import socket
119     address_family = socket.AF_INET6
120
121 # httpd = HTTPServer(('', 8080), OSPHTTPHandler) # Use if your system uses IPv4 by default
122 httpd = HTTPServerIPv6(('', 8080), OSPHTTPHandler) # Use if your system uses IPv6 by default
123 print("Listening on port", httpd.server_port)
124 httpd.serve_forever()
125 """]]
126
127 Pokud se k tomuto ukázkovému serveru připojí mnoho klientů, bude
128 "přetížen" a čas od času bude klienty odmítat. Takže prvním krokem
129 vašeho řešení může být zjištění příčiny tohoto chování a její
130 napravení.
131
132 Testování a měření výkonu
133 =========================
134
135 Pro testování použijte
136 [odevzdávací systém](https://rtime.felk.cvut.cz/osp/cviceni/server/tester/),
137 kam zadáte informace o vašem serveru a můžete ho nechat otestovat. Pět
138 nejlepších studentů dostane 1 až 5 bonusových bodů.
139
140 **Odevzdávací systém běží momentálně v testovacím provozu.** Je možné,
141 že naměřené hodnoty budou horší, než jaké lze z vašeho serveru
142 "vymáčknout".